マルコフ連鎖の文章生成について検証してみた
はじめまして。新入社員の松永です。
文章生成でブログ記事のようなものを自動作成できないか調べたところ、 DeepLearningを使わなくてもマルコフ連鎖でRubyでも手軽に文章生成ができるらしいことがわかったので、 果たしてどの程度の精度で自動生成が可能なのか検証してみました。
簡単な流れとしては、 Mecabで文章を形態素分析して単語単位に分解して辞書を作成。 作成した辞書に基づいて、マルコフ連鎖でランダムに文章を生成しています。
▼実装環境
▼マルコフ連鎖とは
文章を生成するときに、ある形態素に繋げられそうな形態素を選んで徐々に繋げていき文章を作成します。
例えば次の文章。
「マルコフ連鎖 を 使って、文章 を 自動生成 します。」
このとき、「を」という形態素に繋がりそうな形態素は「使って」と「自動生成」ですね。 この繋がりそうな2つの形態素の中から、繋げる形態素を選ぶ作業を繰り返すことで文章を作成していきます。
ブログ記事の共通項目で文章生成
2件のブログの「インフルエンザとは」項目を形態素解析して文章生成してみました。
サンプル1:
インフルエンザとはインフルエンザウイルスが原因で起こる12月から4月に流行る感染症です。 急に起こる38~40℃の高熱、関節痛、頭痛が特徴的です。通常は、薬を使わなくても約1週間で治癒するとされていますが、高齢者や糖尿病などの持病のある方や小さな子どもがインフルエンザに感染すると、肺炎や脳症など合併症を起こすことがあるので注意が必要です。 インフルエンザワクチンの接種が重篤化を防ぐために有効とされています。 インフルエンザは学校保健安全法施行規則(平成24年施行)により出席停止となる伝染病の一つで、「発症後5日かつ解熱後2日(幼児は3日)を経過」するまで出席停止となります。職場などでもそれに倣って自宅待機となることが多いです。
「インフルエンザの症状・原因・治療 Doctors Me(ドクターズミー)」 https://doctors-me.com/doctor/respiratory/13
サンプル2:
インフルエンザは、毎年12月から1月にかけて猛威を振るいます。予防接種をしたり予防・対処法についての知識を得たりと、流行が本格化する前から注意を払っておく必要もあります。今回は、インフルエンザと普通の風邪との違い・検査方法・かかった場合の対処法や治療法について、医師・武井 智昭先生による監修記事で解説していきます。
「インフルエンザの症状~症状、かぜとの違い~|インフル・ニュース」 http://www.influ-news.info/influ/symptoms.html
結果
インフルエンザは、毎年12月から1月にかけて猛威を振るいます。インフルエンザワクチンの接種が重篤化を防ぐために有効とされています。インフルエンザは、薬を使わなくても約1週間で治癒すると、流行が本格化する前から注意を払っておく必要もあります。
まぁまぁそれらしい文章ができています。 しかしよくよく読んでみると文脈が成り立ってない部分があります。
▼Twitterのツイートで自動生成
次に、特定のアカウントの過去のツイートを200件形態素解析して文章生成してみます。
サンプル: 恋愛依存メンヘラ@bot @memememenhel https://twitter.com/memememenhel
結果
①
あなたの手の温もりで有りますように、少しでも、ほんの少しでも。私が手首を切ったの大丈夫だよって証明したくて手首を舐めてくれる気がないの知ってる癖に。離れられない。
②
手首切りたい死にたいが口癖です。
かなりそれっぽい。 文章としては少しおかしい部分もあるのですが、それがかえってポエム感に繋がっています。
▼まとめ
センテンス単位では文章としてまぁまぁそれらしい結果になっています。 しかし、よくみると文脈として成り立っていない場合があるので、長文や論理的な文章にはあまり向いていないのかなという印象を受けました。 多少文章が崩れても問題のないポエムのような文章には向いているかもしれません。
▼ソース
require 'natto' require 'pp' require 'enumerator' $h = {} # ----------------形態素解析 # ----------------辞書的なものの作成 def parse_text(text) mecab = Natto::MeCab.new text = text.strip # 形態素解析したデータを配列に分けて突っ込む # 先頭にBEGIN、最後にENDを追加 data = ["BEGIN","BEGIN"] mecab.parse(text) do |a| if a.surface != nil data << a.surface end end data << "END" # p data data.each_cons(3).each do |a| suffix = a.pop prefix = a $h[prefix] ||= [] $h[prefix] << suffix end end # ----------------マルコフ連鎖 def markov() p "markov" # ランダムインスタンスの生成 random = Random.new # スタートは begin,beginから prefix = ["BEGIN","BEGIN"] ret = "" loop{ n = $h[prefix].length prefix = [prefix[1] , $h[prefix][random.rand(0..n-1)]] ret += prefix[0] if prefix[0] != "BEGIN" if $h[prefix].last == "END" ret += prefix[1] break end } p ret return ret end str = "" file_name = STDIN.gets begin File.open(file_name,"r") do |file| file.each_line do |line| str += line.to_s end parse_text(str) end end #マルコフ連鎖で文章を自動生成 markov()
▼参考
Rubyで形態素解析してマルコフ連鎖で文章生成 http://utsubo21.hatenablog.com/entry/2015/02/08/224155
生活音を機械学習してみた
音声認識というとどうしてもスピーチをテキストにするというソリューションが多いです。しかし用途がユーザーインターフェースに限られる。IOTのシーンではモノとモノが通信し合ってこそ、人間様が楽できるので、生活音を識別することをゴールとしたいと思います。
▼ 概要
今回は5種類のスズの音のサンプルを使って,2種類ずつどちらのスズか判別を行ってみたいと思います。
▼ 実装環境
▼ 用意するもの
Pythonと機械学習ライブラリのscikit-learnを使います。
(参考: http://www.iandprogram.net/entry/2015/09/15/221518 )
UbuntuにPythonをインストール
$ sudo apt-get install python2.7
pipをインストール
$ sudo apt-get install python-pip
Ubuntuにscikit-learnをインストール
$ sudo pip install scikit-learn
他にnumpyもインストールしておく。
▼ 教師データの作成
5種類のスズの音サンプルを用意します。
スズの名前 | スズの音のタイプ |
---|---|
suzu01 | 一般的なスズ |
suzu02 | 喫茶店のドアについてる感じ |
suzu03 | チリーン 風鈴みたいな |
suzu04 | シャン! クリスマスっぽい |
suzu05 | 川の流れる音みたいな |
音のサンプルを載せているWebサイトはたくさんありますが、 スズの音階がわからなかったりや同じスズを使っているけど鳴らし方だけ違ったりするサンプルが多く、教師データを作るのがなかなか難しい(正解ラベルが貼れない)。
人間が聞いて明らかに違うスズだとわかるものをサンプルとして使うことが望ましい。
5種類のスズの音サンプルは実際に聞いてみて明らかに違うものを用意する。今回用意したスズはスペクトログラムを書いてみると、その形状の違いから5つの異なるスズを使っていることがわかります。
サンプルだけではパターンが少ないので、機械学習するには十分ではありません。
そこで、スズの音にノイズ音を加えてデータを量産することに。
ノイズの名前 | ノイズの内容 |
---|---|
noise01 | ハイヒールの足音 |
noise02 | 皮靴の足音 |
noise03 | ドアが閉まる音 |
noise04 | ドアノブをガチャガチャする音 |
noise05 | ドアをノックする音 |
noise06 | ドアが開く音 |
noise07 | 道路の環境音 |
noise08 | 人の笑い声 |
noise09 | 水洗トイレが流れる音 |
noise10 | トイレットペーパーを回す音 |
noise11 | TOTOの音姫 |
5種類のスズの音に11種類のノイズを加えてデータを作成します。
さらに,ノイズの音量を10段階に設定することで更に多くの音声パターンを生成できる(5 × 11 × 10 = 550種類)。
今回はSoX (Sound eXchange) を使って,以下の手順でスズの音とノイズをミックスしていきます。
(参考:http://qiita.com/mountcedar/items/a04ebc4f8c27c226bbff)
ノイズ入りデータ作成手順 1. すべての音声データを一旦.wav形式に変換する 2. 5種類のスズの音を正規化 3. 11種類のノイズの音を正規化 4. 手順1と手順2からできた音声データをスズの音の長さに合わせて合成する。 その際, 新たに合成したノイズ入りのアウトプットファイルをスズの種類ノイズの種類音の段階(0~9).wav として保存する。
例:suzu03のwavファイルとnoise07のwavファイルを合成する。
$ sox -m suzu/suzu03.wav -v 0.2 noise/noise07.wav learning_sample/3_7_9.wav trim 0 suzu03_len
-m
: --combine mix
の略で2つのファイルを結合するためのオプション。
-v
: 後ろの音ファイルの音量を調節をするオプション。(1より大:音量を上げる、1より小:音量を下げる)
trim
: 音声を切り取るオプション。例では0秒目からsuzu03の音の長さ(suzu03_len)に合わせてクリッピングしている。
スペクトログラムと波形図をそれぞれ見てみると、元々のsuzu03の画像と比較して、ノイズが入っていることが確認できます。
▼ MFCC (メル周波数ケプストラム係数)
音のデータから特徴量を抽出する方法としてMFCCを使います。
MFCCは人間の聴覚の特性にあわせて低周波部分は細かく,高周波部分は粗く調べる手法らしい(音声解析ではよく使われている)。
音の専門家じゃないので不明な点がありますが、音声データの一部分を取り出して、指定した次元のベクトルに集約して特徴量を抽出してくれます。
サンプル間で音の長さが違う場合でも、同じ長さのベクトルを用意することができるので、そのまま機械学習にかけられる。
MFCCの抽出手順は
- プリエンファシスフィルタで波形の高域成分を強調する
- 窓関数をかけた後にFFT(高速フーリエ変換)して振幅スペクトルを求める
- 振幅スペクトルにメルフィルタバンクをかけて圧縮する
- 上記の圧縮した数値列を信号とみなして離散コサイン変換する
- 得られたケプストラムの低次成分がMFCC
(参考:http://aidiary.hatenablog.com/entry/20120225/1330179868)
#coding:utf-8 import wave import numpy as np import scipy.signal import scipy.fftpack import scipy.fftpack.realtransforms def wavread(filename): wf = wave.open(filename, "r") fs = wf.getframerate() x = wf.readframes(wf.getnframes()) x = np.frombuffer(x, dtype="int16") / 32768.0 # (-1, 1)に正規化 wf.close() return x, float(fs) def hz2mel(f): """Hzをmelに変換""" return 1127.01048 * np.log(f / 700.0 + 1.0) def mel2hz(m): """melをhzに変換""" return 700.0 * (np.exp(m / 1127.01048) - 1.0) def melFilterBank(fs, nfft, numChannels): """メルフィルタバンクを作成""" # ナイキスト周波数(Hz) fmax = fs / 2 # ナイキスト周波数(mel) melmax = hz2mel(fmax) # 周波数インデックスの最大数 nmax = nfft / 2 # 周波数解像度(周波数インデックス1あたりのHz幅) df = fs / nfft # メル尺度における各フィルタの中心周波数を求める dmel = melmax / (numChannels + 1) melcenters = np.arange(1, numChannels + 1) * dmel # 各フィルタの中心周波数をHzに変換 fcenters = mel2hz(melcenters) # 各フィルタの中心周波数を周波数インデックスに変換 indexcenter = np.round(fcenters / df) # 各フィルタの開始位置のインデックス indexstart = np.hstack(([0], indexcenter[0:numChannels - 1])) # 各フィルタの終了位置のインデックス indexstop = np.hstack((indexcenter[1:numChannels], [nmax])) filterbank = np.zeros((numChannels, nmax)) for c in np.arange(0, numChannels): # 三角フィルタの左の直線の傾きから点を求める increment= 1.0 / (indexcenter[c] - indexstart[c]) for i in np.arange(indexstart[c], indexcenter[c]): i=int(i) filterbank[c, i] = (i - indexstart[c]) * increment # 三角フィルタの右の直線の傾きから点を求める decrement = 1.0 / (indexstop[c] - indexcenter[c]) for i in np.arange(indexcenter[c], indexstop[c]): i=int(i) filterbank[c, i] = 1.0 - ((i - indexcenter[c]) * decrement) return filterbank, fcenters def preEmphasis(signal, p): """プリエンファシスフィルタ""" # 係数 (1.0, -p) のFIRフィルタを作成 return scipy.signal.lfilter([1.0, -p], 1, signal) def mfcc(signal, nfft, fs, nceps): """信号のMFCCパラメータを求める signal: 音声信号 nfft : FFTのサンプル数 nceps : MFCCの次元""" # プリエンファシスフィルタをかける p = 0.97 # プリエンファシス係数 signal = preEmphasis(signal, p) # ハミング窓をかける hammingWindow = np.hamming(len(signal)) signal = signal * hammingWindow # 振幅スペクトルを求める spec = np.abs(np.fft.fft(signal, nfft))[:nfft/2] fscale = np.fft.fftfreq(nfft, d = 1.0 / fs)[:nfft/2] # メルフィルタバンクを作成 numChannels = 20 # メルフィルタバンクのチャネル数 df = fs / nfft # 周波数解像度(周波数インデックス1あたりのHz幅) filterbank, fcenters = melFilterBank(fs, nfft, numChannels) # 定義通りに書いた場合 # 振幅スペクトルに対してフィルタバンクの各フィルタをかけ、振幅の和の対数をとる mspec = np.log10(np.dot(spec, filterbank.T)) # 離散コサイン変換 ceps = scipy.fftpack.realtransforms.dct(mspec, type=2, norm="ortho", axis=-1) # 低次成分からnceps個の係数を返す return ceps[:nceps] #wavファイルと次元数を入れてMFCCを抽出 # nfft:FFTのサンプル数 1024, 2048, 4096 # nceps:MFCCの次元数 大体12次元が多い # ※ fs * cuttime >= nfft/2 を満たす値を与えなければいけない def get_feature(wavfile,nfft,nceps): # 音声をロード wav, fs = wavread(wavfile) t = np.arange(0.0, len(wav) / fs, 1/fs) # 音声波形の中心部分を切り出す center = len(wav) / 2 # 中心のサンプル番号 cuttime = 0.8 # 切り出す長さ [s] wavdata = wav[int(center - cuttime/2*fs) : int(center + cuttime/2*fs)] ceps = mfcc(wavdata, nfft, fs, nceps) return ceps.tolist()
先ほどミックスしたsuzu03とnoise07 (ノイズの音量レベル9) の3_7_9.wavをMFCCにかけてみる。
上記のコードに以下のコードを付け加えて、実際にMFCCを実行する。
if __name__ == "__main__": wavfile="learning_sample/3_11_9.wav" nfft=2048 nceps=12 tmp=get_feature(wavfile,nfft,nceps) print tmp
実行すると、
[-2.1340185367571975, -1.2788164503621682, 0.2980729758980935, -0.7116624981453882, 0.33237397666134955, 0.22739636812287548, 0.7001966634450906, -0.12020691819143244, -0.10969326122265541, -0.45330444421058963, 0.2237907950505698, 0.2439421042510435]
このように、音声のwavファイルから12次のベクトルを生成することができました。
▼ SVMで判別
いよいよ、スズの音を判別してみます。
5種類のスズ (suzu01~05) を以下すべての組み合わせで判別します。
(ただし、すべてノイズ入りです。)
スズの組み合わせ | スズの音番号のペアNo |
---|---|
suzu01, suzu02 | (1, 2) |
suzu01, suzu03 | (1, 3) |
suzu01, suzu04 | (1, 4) |
suzu01, suzu05 | (1, 5) |
suzu02, suzu03 | (2, 3) |
suzu02, suzu04 | (2, 4) |
suzu02, suzu05 | (2, 5) |
suzu03, suzu04 | (3, 4) |
suzu03, suzu05 | (3, 5) |
suzu04, suzu05 | (4, 5) |
全組み合わせにおいて、ノイズ11種類中10種類を学習データに、残り1種類を評価用データに使用するようにして、
学習時にないノイズであっても、スズの音を判別できるかを試しました。
学習用データ | 200個 |
評価用データ | 20個 |
今回は機械学習ではメジャーなSVM (Support Vector Machine) を使用します。
(参考:http://may46onez.hatenablog.com/entry/2016/02/19/152532)
#coding:utf-8 from sklearn import svm from sklearn.metrics import classification_report, accuracy_score import sys from mfcc import * import glob import csv import random import itertools import numpy as np if __name__ == "__main__": bell_nums = range(1,6) pairs = list(itertools.combinations(bell_nums,2)) for pair in pairs: bell_num1=int(pair[0]) bell_num2=int(pair[1]) train_data = np.empty((0,12),float) train_label = np.array([]) test_data = np.empty((0,12),float) test_label = np.array([]) noise_nums = range(1,12) level_nums = range(0,10) random.shuffle(noise_nums) nfft = 2048 # FFTのサンプル数 nceps = 12 # MFCCの次元数 #鈴の音1 for noise_num in noise_nums[0:10]: random.shuffle(level_nums) #学習用データを作成 for level_num in level_nums[0:10]: files_name = glob.glob("learning_sample/%d_%d_%d.wav" % (bell_num1,noise_num,level_num)) for file_name in files_name: feature = get_feature(file_name,nfft,nceps) if len(train_data) == 0: train_data=feature else: train_data=np.vstack((train_data,feature)) train_label=np.append(train_label,bell_num1) #テストデータを作成 file_name = "learning_sample/%d_%d_%d.wav" % (bell_num1,noise_num,level_nums[8]) feature = get_feature(file_name,nfft,nceps) if len(test_data) == 0: test_data=feature else: test_data=np.vstack((test_data,feature)) test_label=np.append(test_label,bell_num1) #鈴の音2 for noise_num in noise_nums[0:10]: random.shuffle(level_nums) #学習用データを作成 for level_num in level_nums[0:10]: files_name = glob.glob("learning_sample/%d_%d_%d.wav" % (bell_num2,noise_num,level_num)) for file_name in files_name: feature = get_feature(file_name,nfft,nceps) if len(train_data) == 0: train_data=feature else: train_data=np.vstack((train_data,feature)) train_label=np.append(train_label,bell_num2) #テストデータを作成 file_name = "learning_sample/%d_%d_%d.wav" % (bell_num2,noise_num,level_nums[8]) feature = get_feature(file_name,nfft,nceps) if len(test_data) == 0: test_data=feature else: test_data=np.vstack((test_data,feature)) test_label=np.append(test_label,bell_num2) #特徴データをテキストに出力 feature_train_data=np.hstack((train_label.reshape(len(train_label),1),train_data)) feature_test_data=np.hstack((test_label.reshape(len(test_label),1),test_data)) with open("feature_data/train_data.txt","w") as f: writer=csv.writer(f) writer.writerows(feature_train_data) with open("feature_data/test_data.txt","w") as f: writer=csv.writer(f) writer.writerows(feature_test_data) #識別機学習 clf = svm.SVC() clf.fit(train_data,train_label) #推定 test_pred = clf.predict(test_data) #print np.hstack((test_label.reshape(len(test_label),1),(test_pred.reshape(len(test_pred),1)))) #結果算出 score=accuracy_score(test_label, test_pred) print pair,score
svm.pyの中でmfcc.pyをインポートして特徴量抽出ができるように関数を呼び出しています。
train_data
とtest_data
は1列目にスズの音のラベル、2列目以降にMFCCで抽出した12次元の特徴量ベクトルが入っており、これをSVMの学習器に入れます。
▼ 結果
(1, 2) 1.0 (1, 3) 1.0 (1, 4) 1.0 (1, 5) 1.0 (2, 3) 1.0 (2, 4) 1.0 (2, 5) 1.0 (3, 4) 1.0 (3, 5) 1.0 (4, 5) 1.0
1列目が判定したスズの音番号のペアNoで2列目が正答率になっています。
どのスズの組み合わせも100%の精度で判別することができました。
▼ まとめ
今回,SVM自体のチューニングは特にしていません。
前処理のMFCCによる特徴量抽出の時点でかなり上手く分類できてしまっているのだと思います。
モンスターBOXのモンスター名をTensorFlowで特定してみた
▼モンスター画像がたくさん転がってる国、日本
機械学習で何かと進化したのが画像認識。人間の顔の認識は古くから研究されつくされてる感もあるので、それとは別にソーシャルメディア上にアップされている画像を特定してみたいと思います。
特に日本だと、Twitterにゲームのキャプチャをアップしている人が多いから、ゲームのキャプチャを解析すれば、その人のレベル感が分かるし、誰と何を交換すればいいかなどの特定がしやすくなどいろいろ可能性は広がりそうです。
▼実装環境
ubuntu 14.04
python 2.7.6
opencv 3.1.0
▼モンスターBOXからモンスターを取り出す
機械学習ではいつも「この屏風からトラを取り出していただければ退治してみせましょう」というシーンに出くわします。今回もモンスターBOXのたくさん並んでいるモンスターを直接学習させても、パターンが膨大すぎる。だからモンスターアイコンだけを取り出したい。
今回はOpenCVの特徴点を取り出す方法を採用します。特徴点をピックアップするとその画像の中で「ごちゃごちゃしているところ」が特定できます。
特徴点を取り出すにもいろいろアルゴリズムがありますが、今回はFASTというアルゴリズムを使いました。
FASTの詳しい説明(http://www.vision.cs.chubu.ac.jp/CV-R/jpdf/RostenECCV2006.pdf)
FASTで検出した特徴点をプロットすると、これが
こうなる
こんな感じに。
特徴点がモンスター部分に密集しています。
OpenCVには他にも特徴点アルゴリズムがありますが、このFASTが一番モンスター部分に密集してくれました。
この特徴点をヒストグラム化してみます。
X軸方向(横)のヒストグラムがこちら
山が5個できています。
この山の部分がモンスター部分で、谷が背景部分です。
谷部分で切り取ってあげればモンスター画像が綺麗にトリミングされているのではないか。
山谷部分がわかりやすくなるようグラフを平滑化します。
pandasで移動平均をとってみます。
参考(http://blanktar.jp/blog/2015/12/python-pandas-moving-average.html)
青い点は、ピーク解析を行って極小値であると判定された部分です。
SciPyでピーク点を検出することができます。
参考(http://org-technology.com/posts/scipy-peak-finding.html)
これだと両端のモンスター部分は切り取れないので
x座標の先頭と末端から、ピーク解析で得た極小値リストの最小値に一番近い座標をプロットしてみます。
うまく切り取れそう。
Y軸方向も同様に
試しに、プロットした座標に線を引いてみます。
思いのほか綺麗に境界線が引けています。
▼分類機作成
いよいよTensorFlowで分類機を作ってみます。
モデル生成~学習まで一通りこちらを参考にしました。
http://kivantium.hateblo.jp/entry/2015/11/18/233834
教師データはこちら(http://monst.appbank.net/monster/archives/1.html)のモンスター図鑑のモンスター画像を使用しました。
また手作業でモンスターの額を取り除いた画像用意して、枠周辺はあんまり気にするなということを教え込みたいなと。
使用した教師画像はこの2パターン。
それと先程トリミングした際に出た切れ端を負例画像として使用して、モンスターじゃない!ってことを教えます
ということで、使用した教師データは以下の通り。
正例画像:モンスター1981種 × 2枚=3962枚
負例画像:54枚
▼結果
Tensorflowで作った分類機にトリミングしたモンスター画像を入れて分類結果を返す一連の流れを試してみました。
トリミングした画像番号,モンスター番号,モンスター名
000,344,マチルダⅡ
001,1538,宇宙の伝達者 ナスカ
002,1226,通天大聖 孫悟空
003,1040,張飛 益徳
004,844,ヴィシャス
005,335,エール・ソレイユ
006,1739,黄泉津大神 イザナミ零
007,1523,ハートの女王
008,1202,ロックガール ガーゴイル
009,1038,非天 阿修羅
010,736,レルネーの主 ヒュドラ
011,915,タランチュラス・ウェポン
012,1739,黄泉津大神 イザナミ零
013,1472,ちゃす
014,1181,ヒーロー ドラえもん
015,1038,非天 阿修羅
016,563,黄泉津大神 イザナミ
017,1981,水瓶座の黄金聖闘士 カミュ
018,1617,リリム
019,1464,灼夏の巨人 スルト
020,1139,護法善神 羅刹
021,1036,除夜を射る将軍 徳川吉宗
022,563,黄泉津大神 イザナミ
023,1965,白鳥星座の青銅聖闘士 氷河
024,1584,情熱の爆音シンガー エナ
025,1266,輝石の美将 雲母大佐
026,1040,張飛 益徳
027,900,MDT チェリーウィンガー
028,460,炎狼の赤ずきん ノンノ
029,1881,白馬の騎士 のび太
30枚中28枚が正解。教師データのバリエーションは大してないのですが、文字などのノイズが含まれた画像を分類機に入れても結構な精度で分類できていることがわかります。これだけ雑な学習でもまぁまぁ特定できてる。
▼まとめ
TensorFlow自体は、特にこれといったチューニングめいたものは使っておらず、ポイントは前処理。そして前処理に使ってるのは、OpenCVの特徴点抽出のみってところです。
RとH2Oを使って Deep Learning でキャラクター判定をやらせてみた
何かとDeepLearningがブームになっています。うちでもやってみました。
学習させること
画像認識させてみたいと思います。手始めに、ミッフィー、キティ、マイメロのキャラクターを判別させてみます。人間が見てもおばあちゃんくらいになると厳しいこの3者が区別できるのか?
結論
3つのキャラのうちどれ?って判定をさせると、6割くらいまでの精度にはなりました。普通は背景を取り除いて、キャラの顔だけを切り抜いてから分類させるとは思うのですが、背景込でキャラ判別させた割にはまぁまぁの精度かなと思います。キャラの顔だけをくり抜くことをさせるには、OpenCVとかを使うはずですが、OpenCVをいじりまくるのはまた今度ということで。
以下の図は、活性化関数の種類別に、横軸が繰り返し回数、縦軸が正答率です。20回のRectifierが一番正答率が高いという感じですね。キティは9割がた当たるのですが、ミッフィーとマイメロは区別しにくいようです。単に輪郭を区別しただけという気もしますが、一次元のベクトルから二次元の形を当てるというのもまた一興。
ただ、画像認識でやりたいことがはっきりしている場合には、OpenCVで行けるところまでいくのが定石。
以下は、手順のメモです。
用意するもの
DeepLearning専用のフレームワークを使わず、ここは手軽にRとH2Oを使ってみます。Rであれば、統計な人たちにも馴染み深いですね。
通常はRはRstudioで手元のPCで使うことが多いかと思いますが、機械学習には時間がかかるので、Rをクラウドサーバに入れたいと思います。クラウドサーバだと、特定の時間だけ、サーバースペックをあげることができるので便利ですね。
centOSにRを入れる
wget "http://cran.ism.ac.jp/src/base/R-3/R-3.2.2.tar.gz" tar xvzf R-3.2.2.tar.gz cd R-3.2.2 yum install gcc-gfortran libgfortran ./configure --with-readline=no --with-x=no yum install java-1.7.0-openjdk-devel make make install
centOSの RにH2Oを入れる
> yum install libcurl-devel #パッケージインストールするのに必要 >R >>install.packages("h2o") 接続先を選択する画面 HTTPS CRAN mirror 1: 0-Cloud [https] 2: Austria [https] 3: China (Beijing 4) [https] 4: China (Hefei) [https] 5: Colombia (Cali) [https] 6: France (Lyon 2) [https] 7: Iceland [https] 8: Russia (Moscow 1) [https] 9: Switzerland [https] 10: UK (Bristol) [https] 11: UK (Cambridge) [https] 12: USA (CA 1) [https] 13: USA (KS) [https] 14: USA (MI 1) [https] 15: USA (TN) [https] 16: USA (TX) [https] 17: USA (WA) [https] 18: (HTTP mirrors) ←これ選択 新たに選択肢が増えるので48:Japan(Tokyo)を選択
ダウンロード完了
用意する画像
画像検索を使って、ミッフィー、キティ、マイメロの画像をそれぞれ落としてきます。イラストのこともあれば、グッズの写真ってこともあります。そういうのも一旦お構いなしに、インプットとしてみます。
実際に学習するのは、一次元のベクトル
画像を学習させるとは言うものの、機械学習にかけるのは、1次元のベクトルです。画像をグレースケールにして(この時点でマイメロの色がなくなり輪郭がミッフィーとおなじになる)、100×100のピクセルを1万の一次元の要素にしちゃいます。ホントは色付きでもっとたくさんのピクセル数でやりたいところですが、めちゃくちゃ時間がかかるので、この辺で許さないと厳しかったです。
こんなcsvができあがります。
一番左の列がラベルで、ここにキティかミッフィーかマイメロが入ります。それ移行の列は1万個のピクセルの値ですね。0~255までのグレースケールの値が入ります。
このデータを、学習用と判定用の両方作ります。
h2oのパラメータ
以下のパラメータを指定しました。
パラメータ | 説明 | 今回のモデル |
---|---|---|
x | モデルの文字列変数を含むベクトル 100*100ピクセルだったら2列目から1001列目 |
2:10001 |
y | モデル内の応答変数の名前。ラベルが付いている列(たぶん) | 1 |
training_frame | 学習用データフレーム | 略 |
activation | 活性化関数を選択 Tanh TanhWithDropout Rectifier RectifierWithDropout Maxout MaxoutWithDropout |
Tanh TanhWithDropout Rectifier RectifierWithDropout Maxout MaxoutWithDropout|Tanh TanhWithDropout Rectifier RectifierWithDropout |
hidden | 隠れレイヤーのユニット数をベクトルで並べる 入力次元の1/10 or 1/100ユニットが普通 例:rep(20,5) 20ユニット×5層 |
rep(1000,4) |
epochs | 繰り返し回数 デフォルトだと1回 |
10~40 |
グレースケールに変換してベクトルに変換するRスクリプト
ImageMagicとRubyとかの組み合わせでもできちゃうけど、Rでもここまでできるってことで。
library(raster) #グレースケール変換関数 as.grayscale.array<-function(file,flag){ image<-brick(file) image.rgb<-getValues(image) rgbcol<-ncol(image.rgb) #元画像がカラーであればRGB値をグレースケールに変換 if (rgbcol>1){ image.bw<-image.rgb[,1]*0.21+image.rgb[,2]*0.72+image.rgb[,3]*0.07 return (c(flag,image.bw)) #元画像が白黒であればそのまま }else{ return (c(flag,image.rgb)) } } #各画像のパスをキャラごとに格納 kity <- list.files("C:/rooter/pic_resize100/",pattern = "kity",full.names=T) miffy <- list.files("C:/rooter/pic_resize100/",pattern = "miffy",full.names=T) mymelo <- list.files("C:/rooter/pic_resize100/",pattern = "mymelo",full.names=T) #評価用データ pre_kity <- list.files("C:/rooter/pic_eva/",pattern="kity",full.names=T) pre_miffy <- list.files("C:/rooter/pic_eva/",pattern="miffy",full.names=T) pre_mymelo <- list.files("C:/rooter/pic_eva/",pattern="mymelo",full.names=T) #3つのキャラクターを1つのベクトルに結合 res.dl<-rbind(t(sapply(kity, as.grayscale.array , "kity")), t(sapply(miffy, as.grayscale.array , "miffy")), t(sapply(mymelo, as.grayscale.array , "mymelo")) ) #評価用データのベクトル生成 pred.dl<-rbind(t(sapply(pre_kity, as.grayscale.array , "kity")), t(sapply(pre_miffy, as.grayscale.array , "miffy")), t(sapply(pre_mymelo, as.grayscale.array , "mymelo")) ) #学習用・評価用データをデータフレーム化 res.dl<-data.frame(res.dl) pred.dl<-data.frame(pred.dl) #出力 write.csv(res.dl,"C:/rooter/training_data.csv",row.names=FALSE, quote=TRUE) write.csv(pred.dl,"C:/rooter/prediction_data.csv",row.names=FALSE, quote=TRUE)
学習と評価のRスクリプト
library(h2o) #あらかじめJava SE 7をダウンロードしておく localH2O <- h2o.init(ip="localhost" , nthreads = -1) #データファイルをH2Oクラスに変換 #学習用 target<-read.csv("training_data.csv") target<-data.frame(target) act<-"TanhWithDropout" epo<-40 start<-proc.time() #学習 res.dl<-h2o.deeplearning(x = 2:10001, y = 1, training_frame = as.h2o(target), activation = act, hidden = rep(1000,4), epochs = epo, ) end<-proc.time() #評価用 pred.dt<-read.csv("prediction_data.csv") #予測 prediction<-h2o.predict(object = res.dl , newdata = as.h2o(pred.dt)) #精度を算出 pred<-as.data.frame(prediction) result.ar<-t(rbind(pred.dt[,1],pred[,1])) #正答率を計算 kity.num<-0 miffy.num<-0 mymelo.num<-0 num<-0 for(i in 1:nrow(result.ar)){ if(result.ar[i,1]==result.ar[i,2]){ if(result.ar[i,1]==1){ kity.num<-kity.num+1 } if(result.ar[i,1]==2){ miffy.num<-miffy.num+1 } if(result.ar[i,1]==3){ mymelo.num<-mymelo.num+1 } num<-num+1 } } kity.res<-format(kity.num/sum(result.ar[,1]==1), nsmall=5) miffy.res<-format(miffy.num/sum(result.ar[,1]==2), nsmall=5) mymelo.res<-format(mymelo.num/sum(result.ar[,1]==3), nsmall=5) res<-format(num/nrow(result.ar),nsmall=5) #出力 pred.ar<-data.frame(chara=c("kity","miffy","mymelo","all","time"), pre=c(kity.res,miffy.res,mymelo.res,res,as.character(end-start)[3])) write.table(pred.ar,paste("log/",act,"/epo",epo,".txt",sep="")) write.csv(pred,paste("log/",act,"/epo",epo,".csv",sep=""))
僕たちはなぜwebデータアグリゲ―ション事業をやっているのか?その① text by maeda
健康食品の「成分ランキング」を作ってみる
私達は日常的にweb上の面白そうなデータを収集、分析しています。なんとなくの想像で思ってることも、実際に集計すると違って見えます。今回は健康食品の「成分ランキング」です。
健康食品には成分のブームがあります。コラーゲンとか、プロポリスとか。厳密に成分に分解すると、タンパク質とか脂質とかになるので、消費者がラベルとして認識する粒度の成分のワードがあります。
続きを読む