ResNetで料理の材料をあてていく:food2stuff
モチベーション
- ResNetを使ってみたい
- 強い転移学習方法を教わったのでやってみたい
- 料理から何の材料でできているかわかれば、img2cal(画像からカロリーを当てるタスク)などに転用できるかもと思った
food2stuffって?
- stuffって材料って意味らしいです
- 画像からなんの構成品目でできており、なんの食材でできているかベクトル表現で出力します
- 2048次元の出力に対応しておりメジャーどころの食材はだいたい抑えています
- 将来的には自分が食べたものをinstagramなどで写真を取るだけで栄養素の管理とかできたらいいよね
学習用のデータ・セット集め
- Cookpadさんのサイトから95万件のレシピと投稿写真をあつめさせていただきました
レシピは個人の自由な書き方が許さており、林檎という表現一つとってもリンゴ、りんご、林檎と3つぐらい激しく揺らぐのですが、ここで小細工を入れるということを昔はしていましたが、一応の王道はデータ量で押し切ることです
それでどうにもこうにも行かなくなったら、いろいろな仕組みの導入を検討すべきというのが最近の私のやり方です
スクレイパーは自作のものを用いました。 - 頻出するレシピの単語を上位2048個以外削る
出力次元数をどこまでも高次元にするのはGPUのメモリに乗らなくなるなど、現実的でないので、2048個に限定します
学習するネットワークの選定
- ディープであるのは決定なのですが、Inception, ResNet, VGGなどいろいろあり、ResNetを今回は使おうと思いました。
TensorFlowなどではinception系の資料が豊富で学習のやりかたも幾つか確立されており、マニュアルに従うだけでかんたんに学習できます
VGGなども軽くていいのですが、画像識別で強い方を見ているとResNetが良さそうだと思いました。
ResNet転移学習ハックを適応する
- この前のCookpadの画像識別コンテストで好成績を収めていた方が使っていた手法が、BatchNormalizationレイヤーを再トレーニングするという転移学習の方法でした
この方法を採用して、BatchNormalizationレイヤのフリーズはしません
Python3 + keras2で行いました
model = Model(inputs=resnet_model.input, outputs=result)
for layer in model.layers[:139]: # default 179
if 'BatchNormalization' in str(layer):
...
else:
layer.trainable = False
model.compile(loss='binary_crossentropy', optimizer='adam')
return model
- Stochastic Depthを適応する
Stochastic Depthは今回、twitterでResNetの転移学習でいい方法ないかなと聞いたところ、だんごさんからアドバイスいただいた方法です
具体的にはResNetは何層もの直列のアンサンブルにしたもので、層をminiBatchごとに確率的にLayerをスキップします
私の実装では、なかなか安定しなく、安定した実装ができたら公開したいと思います
図1. Stochasit Depth
集めた画像をResNetの入力値にサイズにリサイズ
めんどくさいですが、正しくやる必要があります。
PILを利用して、縮小して、四角形の何もない画像に貼り付けました。
def cookpad():
target_size = (224,224)
dir_path = "../kotlin-phantomjs-selenium-jsoup-parser/dataset/*.jpg"
max_size = len(glob.glob(dir_path))
for i, name in enumerate(glob.glob(dir_path)):
if i%10 == 0:
print(i, max_size, name)
save_name = name.split("/")[-1]
if Path("cookpad/imgs/{save_name}.minify".format(save_name=save_name)).is_file():
continue
try:
img = Image.open(name)
except OSError as e:
continue
w, h = img.size
if w > h :
blank = Image.new('RGB', (w, w))
if w <= h :
blank = Image.new('RGB', (h, h))
try:
blank.paste(img, (0, 0) )
except OSError as e:
continue
blank = blank.resize( target_size )
blank.save("cookpad/imgs/{save_name}.mini.jpeg".format(save_name=save_name), "jpeg" )
学習
いよいよ学習です。自宅のゲーム用に買ったマシンをLinuxとWindowsのデュアルブートにしてあるものがあるのですが、Windowsはここ半年ぐらい起動してません(何故買ったし)
GTX 1070でおこない、100epoch回すのに、20時間ほど必要でした。
$ python3 deep_food.py --train
過学習の取り扱い
明確に正解を定義する必要があるので、その分が実装大変になるのですが、epochごとのmodelをダンプしてモデルに未知の画像を投入して、一番良いepochを定性的に決めるということをしました。
-> 90epoch前後のモデルが良いと判断しました。
学習済みモデルのダウンロード
dropboxにおいておきました
$ wget https://www.dropbox.com/s/tgeosjt4i5dg79b/model00099.model
$ mv model00099.model models/
予想タスク
ネットを徘徊して訓練に用いたデータであるCookpad以外のサイトから幾つか集めました。
そして評価した結果です。
$ python3 deep_food.py --pred
特徴が少なく、突飛でない料理に関しては良好な結果です。
図1. プリン
図2. パスタ
図3. クッキー
図4. チャーハン
図5. カレー
図6. サラダ
図7. GOGOカレー(失敗例:揚げ物と勘違いしている)
今回の実装
非商用・研究目的の場合自由にご利用ください
GitHub
参考文献
[1] Deep Networks with Stochastic Depth
[2] Stochastic Depth元論文