【Python/Keras】VGG16をFine-tunignして100種類の食事画像を認識してみよう!【UECFOOD-100】

Python

みなさん、こんにちは。
シンノユウキ(@y_stadio)です。

以前の記事でVGG16を用いて画像を認識・分類する方法を紹介しました.

【Python】KerasでVGG16を使って画像認識をしてみよう!
今回は学習済みCNNモデル:VGG16を用いて,一般的な画像の分類を行ってみたいと思います.理論などの説明は割愛し,道具としてこれを使えるようになることを目指します.では行きましょう! VGG16とは? VGG16という...

しかし,本ブログのテーマの1つは「栄養×ICT」.やはり食事画像認識をやりたいなー,ということで,VGG16をFine-tuningし,食事画像をしっかりと認識できるようにしたいと思います.なおデータ・セットにはUECFOOD-100を使用しています.

では行きましょう!

 

開発環境を整えよう!

開発環境は「Google Colab」を想定

まずは開発環境を用意しましょう.一応,Google Colabを想定して話を勧めていきます.予め機械学習に必要なライブラリもインストールされていますし,機械学習に必要なGPUも利用できるからです.まだ用意されていないという方は,以下の記事を参考に用意してみてください.

機械学習を体験してみたいなら無料で使える「Google Colab」がオススメ
機械学習,ひいてはディープラーニングを体験してみたい!と思ったときの障壁の1つが環境構築です.これらには様々なライブラリを活用することになるでしょうし,また一般的なノートブック程度のスペックでは処理に何時間もかかってしまいます.とい...

 

必要なライブラリなどをインポートしよう!

次に必要なライブラリなどをインポートしましょう.以下のコードを実行してください:

1-11行目は必要なライブラリだったりパッケージだったりをインポートしています.後ほどこれらを使用していきますので,とりあえず意味を理解せずに実行してください.

13行目ではデータを入れるためのディレクトリを作成しています. mkdir ディレクトリ名 でディレクトリを作成できますので, !mkdir ./UECFOOD100/dataset で作成しちゃいましょう.

14行目では13行目で作成したディレクトリに,1~100までのディレクトリを作成しています.{1..100}とすることで1から100までの連続的な表現として扱ってくれますので,こうかくことで連番のディレクトリを作成できます.

15,16行目では変数にディレクトリのパスを代入しています.このようにすることで後々のファイル操作などが楽になるのでぜひやっておきましょう.

 

画像データを準備しよう!

まずはデータをダウンロードしよう!

VGG16をFine-tuningし,食事画像をしっかりと認識できるようにするためには大量の正解ラベルがついた食事画像が必要です.しかし個人でそれを準備するのはかなり大変.

ということで,今回は電気通信大学の柳井啓司教授の研究室で作成された「UECFOOD-100」を利用していきます.詳細は以下のURLからご確認ください.

HOME

本データセットの利用は非営利の研究目的に限られており,その他での利用の場合は問い合わせが必要となります.私の場合,今回の利用は研究目的ではありませんので,柳井教授に別途使用許可を申請しています.快く許可をいただけましたので,今回は本データセットを使用させていただいております.

本ブログの読者の方で,上記の利用目的外で利用したいという方は,上記URLより別途お問い合わせいただきますようお願いいたします.

データのダウンロードには以下のコードを実行してください:

エクスクラメーションマーク(!)を行頭につけるとLinuxコマンドを使うことができます.

wgetは指定したURLからファイルをダウンロードできるコマンドです.ダウンロードされたファイルはzipファイル形式で保存されていますので,それをunzipコマンドで解凍しましょう.

そうすると,「UECFOOD100」というフォルダが生成されます.ためしに,その中身を表示してみましょう.lsコマンドでディレクトリを指定するとその中身を確かめることができます.以下のような結果が表示されるはずです:

このうち,1-100までのフォルダにそれぞれ食事画像が入力されています.このそれぞれのフォルダのフォルダ名が正解用ラベルとなっています.

たとえば,フォルダ:1に含まれるのは白ごはんの画像です.どの数字がどのラベルに該当するかは,このDirectoryに保存されている「category_ja_utf8.txt」などに格納されていますので確認してみるとよいでしょう.

 

画像をトリミングしよう!

次に,ダウンロードした画像データを適切な形に加工していきましょう.実は,ダウンロードしたままの画像の状態では,複数の食事画像が含まれていたり,画像の端の方に該当の食事が写っていたりして,学習用のデータとしては適切ではありません.

実はこのことを最初知らず,トリミングせずに学習して,全く精度がでないという事態に陥っていました….Twitter上でこのことを知らせてくれた@negi111111さんには感謝です.

では,画像データを適切な形にトリミングしていきましょう.UECFOOD100にはトリミングすべき範囲を示したテキストファイルが含まれていますので,それに従ってトリミングしていきます.以下のコードを実行してください:

食事画像とそのBB情報(バウンディングボックスの情報 トリミングする範囲)は,それぞれのディレクトリごとに格納されていますので,ディレクトリごとに処理を行っていきます.その繰り返し処理を行っているのが for ctg_index in range(1,100):の部分です.

3-8行目でBB情報の格納されたテキストファイルから情報を抜き出しています.取り出した情報の1行目はラベル行で不要ですので bb_info.pop(0) で削除しています.

11-18行目でbb_infoに従って画像ごとにトリミングしています. image.open(ファイルパス) で画像を取得できますのでまずは画像を取得しておきます.次に,bb_infoからトリミングする座標を取得しましょう.行の1-4つめにx1, y1, x2, y2の座標が含まれますので,それを取得しましょう.そしてそれを範囲として格納しています.トリミングするためにはcropメソッドが用いられ,以下のように記述できます. image_crop = image.crop((x1, y1, x2, y2)) とすることで,指定の座標でトリミングされた画像を取得できます.

21行目でトリミングした画像をディレクトリ:datasetにカテゴリごとに保存しなおしています.

 

画像をトレーニング用とテスト用に分類しよう!

次に,画像をトレーニング用とテスト用に分類していきましょう.

Kerasで機械学習する場合,トレーニング用のディレクトリと,テスト用のディレクトリに分けると便利です.ちょうど,以下のような形になると考えればわかりやすいでしょうか.

ですので,まずは以下のコードを実行して,上記のようなディレクトリを作成してください:

では,画像データを実際に分けていきましょう.コードは以下のようになります:

1,2行目でトレーニング用とテスト用のディレクトリのパスを変数に代入しています.

4行目からカテゴリごとに処理を繰り返しています.

5行目はディレクトリの画像ごとに処理を繰り返しています. list_pictures(ディレクトリのパス) でディレクトリの画像を全て取得できますので,その画像をpicに代入しながら繰り返しています.

6-9行目で画像をランダムにトレーニング用とテスト用に分類しています. np.random.rand(1) で0.0以上1.0未満の乱数を生成しています.それが0.2未満の場合はテスト用の,それ以上の場合はトレーニング用のディレクトリにそれぞれ移動させています.移動には shutil.move(移動する画像, 移動先のディレクトリ) を使っています.

これで画像をランダムに振り分けることができました!

 

モデルを構築しよう!

ではモデルを構築していきましょう.今回はVGG16という学習済みのモデルをFine-tuningし,効率的に精度の良いモデルを作成していきます.早速ですが,以下のコードを入力してください:

コードの解説をする前に,VGG16のモデル構造について説明します.VGG16では13の畳み込み層と3つの全結合層から成ります.これらの層のうち,最初の浅い部分では全般的な特徴を学習し,深い部分ではその画像特有の特徴を学習すると言われています.この浅い部分を再利用し,深い部分を調整するのがfine-tuningです.

そのため,Fine-tuningをすることで,従来よりも少ない画像かつ少ない時間で効率的にモデルを構築することができるようになるのです.

では,コードを解説していきます.

3行目でVGG16のモデルをダウンロードしています.

  • weights:重みの初期値を表しています. None でランダムな初期値を, 'imagenet' でImageNetで学習した重みを設定します.今回は weights='imagenet'を設定してください.
  • include_top:モデルに全結合層を含むかどうかを指定します. True だと全結合層を含み, False だと含みません.今回は全結合層を捨てて新しく作り直しますので,Falseを指定してください.従来のVGG16の全結合層は,ImageNetの1000のクラスに分類するように作成されており,今回の食事に特化した100のクラスに分類するようにはできていません.なので,その部分は利用せずに新たに作成します. include_top=False
  • input_shape:入力する画像の大きさや形状を示しています.今回は224×224のRGB配列の画像を入力しますので, input_shape=(image_size, image_size, 3) で問題ありません.

4,5行目ではレイヤーのパラメータを最後の4層を除き固定しています.先でも説明しましたが,今回はVGG16の浅い部分は効率的に再利用します.しかし,デフォルトの設定ではその部分まで再度学習されてしまい,Fine-tuningするメリットを活かすことができません.なので layer.trainable = false とすることで学習されることを防いでいます.

7行目ではモデルのインスタンスを生成しています.

8行目で空のモデルに先ほど作成したvgg_convを追加しています.

10-13行目でモデルに全結合層を新しく追加しています.13行目は出力層で,最終的に100クラスに分類することから100の出力空間を指定しています.また,それらの層にどの程度合致するのか,確率的な表現を行うために活性化関数にsoftmaxを用いています. model.add(Dense(100, activation='softmax'))

最後にモデルの構造を出力しています.以下のようになりましたでしょうか.

 

モデルに画像を学習させていこう!

まずは学習の準備から!

では作成したモデルに,用意した画像を学習させていきましょう.まずは以下のコードを実行して学習するための準備を整えていきましょう:

では,コードを解説していきます.

3-9行目まではImageDataGeneratorを使って画像を水増しし,学習に適切な形に変形しています.

  • rescale:データを整形するために使っています.1.0 / 255 と指定することで画像のピクセル値を0.0~1.0の範囲に正規化しちえます.
  • rotation_range:画像を水増しするために画像を回転させます.その回転の範囲を範囲を指定しています.
  • width_shift_range:画像を水増しするために画像を水平方向にシフトさせる範囲を指定しています.
  • height_shift_range:画像を水増しするために画像を垂直方向にシフトする範囲を指定しています.
  • horizontal_flip:画像を水平方向に回転させるかどうかを指定します. True で回転させ, False だとさせません.

11行目ではテスト用のデータを整形しています.テスト用データを水増しする必要はないのでrescale=1.0/255 のみを引数に指定しています.

13-19行目では学習させるための画像を,ディレクトリから取得するためのバッチを取得しています.

  • directory:最初の引数にはトレーニング用の画像が含まれたディレクトリのファイルパスを指定します.
  • target_size:画像のサイズを指定します.ピクセル値で指定してください.今回は 224 を指定しています.
  • batch_size:一度に取り出して学習させる画像の数を指定しています.今回は 32 ですね.
  • class_mode:どのような型でもって正解用のラベルを返すのかを指定します. 'categorical' とすることでディレクトリ名を正解用のラベル(型は2次元のone-hotベクトル)とすることができます.
  • shuffle:画像をシャッフルするかどうかを指定できます.今回は True を指定しています.

21-27行目ではテスト用データもトレーニング用データと同様の処理を行っています.

29-31行目でモデルをコンパイルしています.

  • loss:損失関数を指定します.今回は交差エントロピー誤差を利用しています.損失関数はディープ・ラーニング時にパラメータ修正の指標となるものです.損失を小さくなる方向にパラメータを修正する際に,この損失関数を用います.一般的に多分類を行う場合は交差エントロピー誤差が利用されるようなので,それを利用しています.
  • optimizer:SGDを指定しています.SDGはStochastic Gradient Descent : 確率的勾配降下法と呼ばれる,基本的なアルゴリズムです.lrはLearning Rata:学習率のことで1回の学習でどれだけ学習するのかを,パラメータを更新するのかを指定しています.momentumには一般的に0.9という値が設定され,これを設定することでより早くパラメータを最適にすることができます.
  • metrics:一般的に 'acuracy' が用いられますのでそちらを指定しています.

 

いよいよ学習!

では実際に学習させていきましょう.なお,今回の学習はGPU環境でもかなりの長い時間が必要なります.Google Colabを利用する場合はランタイムの切断やリセットなどにお気をつけください.では,以下のコードを実行しましょう:

2-8行目でモデルに画像を学習させています.

  • generator:最初の引数にはトレーニング用データのgeneratorを指定します.今回の場合は train_generator ですね.
  • epochs:モデルを訓練させる回数です.多ければ良いというわけではありませんが,少ない場合は十分な精度が得られない場合があります.今回は 100 を指定しています.
  • verbose:訓練の進行状況の表示モードです.1を指定することで訓練ごとにプログレスバーが表示されます.
  • validation_data:テスト用データを指定します.
  • callbacks:訓練の際に呼ばれるコールバックを指定します.今回は訓練の結果をCSV形式でログを残す csv_logger を指定しています.

最後の行では,モデルを保存しています.必要に応じて,こちらのモデルをローカルなどに保存しておきましょう.

これでモデルに学習させることができました!

では,学習の結果を見てみましょう.CSV形式でログを残しましたので,その結果をグラフにしてみました.

75%にも届かないような結果ですが,これである一応のモデルを完成させることができました!では,いよいよ作成したモデルで実際の食事画像を認識させてみましょう.

 

実際に画像を認識してみよう!

ちょっと準備を...

では,実際に画像を認識していきましょう.その前に,例によって準備です.以下のコードを実行してください:

4-8行目は指定されたURLから画像をダウンロードしてくる関数を定義しています.

10-20行目は画像認識した結果から,それがどの食事に該当するのかを返す関数を定義しています.どのカテゴリがどの食事に該当するのかはUECFOOD100/category_ja_utf8.txtにありますので,そこから取得しています.

 

画像の認識・分類結果を表示しよう!

では,画像をダウンロードし,それがどの食事に該当するのかを確認してみましょう.以下のコードを実行してください.

まず1-3行目でURLから画像を取得しています.先ほど定義した関数を使用しています.ちなみに,このURLの画像は以下の画像を拝借しています.

 

引用)http://www.kikkoman.co.jp/homecook/search/recipe/img/00005991.jpg

5-8行目では画像をピクセル配列に変換し,トレーニング画像などと同じように正規化しています.

そして10行目で予測しています.model.predictの引数に画像を指定しています.結果は画像枚数分の配列でかえってきますが,画像は1枚なので1つめだけを指定し取得しています.

12-16行目では,値が上位5つのインデックスを取得しています.

そして,18-20行目でその上位5つのインデックスを先で指定した関数で検索し,プリントしています.この画像の結果は以下のようになりました:

白ごはんは結構特徴的な画像なので,しっかりと予測(約99%)できていますね!

では,次はうな重なんていかがでしょうか.

引用)http://mtsuhan.jp/excludes/shop/unagi/images/title_main_p.jpg

引用元のURLを先ほどのコードのURL部と置き換えて再度実行してみてください.今回は以下のような結果が得られました:

こちらもしっかりと予測できています.では少し難しそうなサンドイッチなんてどうでしょうか.以下の画像を認識してみましょう.

引用)https://kinarino.k-img.com/system/press_images/001/190/330/2cb960f132a3484564ed1a0fe704e9b8f2a8abd9.jpg?1520344124

こちらもしっかりと認識できてますね!こんな感じでしっかりと食事が写っているような画像だといい感じに認識できますね!

 

まとめ

今回はVGG16を食事画像認識用のFine-tuningし,実際に学習→判定までを行ってみました.今巷で流行している食事画像認識AIの一端にふれることができて良かったなーと感じています.でも,さすがに専門外の分野を触るのは難しかったですね笑.