読者です 読者をやめる 読者になる 読者になる

Webデータレポート

株式会社ルーターのデータレポートブログです

モンスター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で検出した特徴点をプロットすると、これが

f:id:webdatareport:20160701094750j:plain

こうなる

f:id:webdatareport:20160701094753j:plain

こんな感じに。
特徴点がモンスター部分に密集しています。
OpenCVには他にも特徴点アルゴリズムがありますが、このFASTが一番モンスター部分に密集してくれました。

この特徴点をヒストグラム化してみます。
X軸方向(横)のヒストグラムがこちら

f:id:webdatareport:20160701094755j:plain

 

山が5個できています。
この山の部分がモンスター部分で、谷が背景部分です。
谷部分で切り取ってあげればモンスター画像が綺麗にトリミングされているのではないか。

山谷部分がわかりやすくなるようグラフを平滑化します。
pandasで移動平均をとってみます。
参考(http://blanktar.jp/blog/2015/12/python-pandas-moving-average.html)

f:id:webdatareport:20160701094757j:plain

青い点は、ピーク解析を行って極小値であると判定された部分です。
SciPyでピーク点を検出することができます。
参考(http://org-technology.com/posts/scipy-peak-finding.html)

これだと両端のモンスター部分は切り取れないので
x座標の先頭と末端から、ピーク解析で得た極小値リストの最小値に一番近い座標をプロットしてみます。

f:id:webdatareport:20160701094759j:plain

うまく切り取れそう。
Y軸方向も同様に

f:id:webdatareport:20160701094800j:plain

試しに、プロットした座標に線を引いてみます。

f:id:webdatareport:20160701094802j:plain

思いのほか綺麗に境界線が引けています。

 

▼分類機作成

いよいよTensorFlowで分類機を作ってみます。
モデル生成~学習まで一通りこちらを参考にしました。
http://kivantium.hateblo.jp/entry/2015/11/18/233834

教師データはこちら(http://monst.appbank.net/monster/archives/1.html)のモンスター図鑑のモンスター画像を使用しました。
また手作業でモンスターの額を取り除いた画像用意して、枠周辺はあんまり気にするなということを教え込みたいなと。

使用した教師画像はこの2パターン。

f:id:webdatareport:20160701094805j:plain

f:id:webdatareport:20160701094806j:plain

それと先程トリミングした際に出た切れ端を負例画像として使用して、モンスターじゃない!ってことを教えます


ということで、使用した教師データは以下の通り。
正例画像:モンスター1981種 × 2枚=3962枚
負例画像:54枚

▼結果

Tensorflowで作った分類機にトリミングしたモンスター画像を入れて分類結果を返す一連の流れを試してみました。

f:id:webdatareport:20160701094750j:plain

トリミングした画像番号,モンスター番号,モンスター名
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割がた当たるのですが、ミッフィーマイメロは区別しにくいようです。単に輪郭を区別しただけという気もしますが、一次元のベクトルから二次元の形を当てるというのもまた一興。

f:id:webdatareport:20151229153030p:plain

ただ、画像認識でやりたいことがはっきりしている場合には、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ができあがります。 f:id:webdatareport:20151229145947p:plain

一番左の列がラベルで、ここにキティかミッフィーマイメロが入ります。それ移行の列は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

2014年夏、こんな状況でした。
 
・センシング技術の発達により多くのデータ計測が可能になり、様々な領域でビッグデータが発生している。web世界でもしかり。
・データ好きな経営者、事業責任者が少数ながら存在する(増えつつある)
データ好きエグゼクティブから社内外のデータを収集分析したいというニーズが発生している
・社内にデータ解析、戦略立案のためにデータサイエンティストという新たな職種を置く企業が増えてきた。
 
といった環境要因がありました。
クロール好きのCTOとエンジニアを要するわが社は昨年、データクローリング技術で
どんな事業ができるだろうと社内ブレストを重ねていました。
企業向けクロールデータ納品ビジネスとコンシューマ向け
スマホアプリビジネスの2軸で検討を続けました。(今も検討、検討です。)
 
企業向けの方はちょっとあたってみようということでwebデータのクロール収集を
ネタに知り合いの会社をいくつか回ってみました。(有名どころの会社です)
そこでデータ好き経営者や事業責任者に接触する機会があり、結構ニーズがある
ことに気付きました。用途は競合分析・定点観測・データベース構築などなど。
しかも社内での収集に後ろ向きで外でやりたいということでした。
これはニーズが結構ありそうだぞ、しかもここ数年ニッチだけど市場大きくなりそう
と考えたのでした。
 
そこで僕たちはwebデータアグリゲ―ション事業を立ち上げることになったのです!
 
つづく

健康食品の「成分ランキング」を作ってみる

私達は日常的にweb上の面白そうなデータを収集、分析しています。なんとなくの想像で思ってることも、実際に集計すると違って見えます。今回は健康食品の「成分ランキング」です。

 

健康食品には成分のブームがあります。コラーゲンとか、プロポリスとか。厳密に成分に分解すると、タンパク質とか脂質とかになるので、消費者がラベルとして認識する粒度の成分のワードがあります。

続きを読む