(2017/1/8)のつづき。前回、予告したように下の3つのニューラルネットワークを動かして犬・猫の2クラス分類の精度を比較したい。小さな畳み込みニューラルネットをスクラッチから学習するベースラインとしてVGG16は使わずに小規模な畳み込みニューラルネットワークをスクラッチから学習する。学習データは、犬クラス1000枚、猫クラス1000枚と小規模なデータを使うのであまり大規模なネットワークは学習できない。そこで、畳込みが3層のLeNet相当の小さなモデルを構成した。入力は、150x150ピクセルで3チャンネルのカラー画像とした。青で囲った部分が畳込み層(Convolution2D)+プーリング層(MaxPooling2D)で緑で囲った部分がフル結合層(FC)である。犬猫分類では、出力ユニット数は1であり、0が猫で1が犬とする。(補足)犬クラスと猫クラスの割り当てはImageDataGeneratorが勝手に決める。どっちが0でどっちが1に割り当てられたかはclass_indicesでわかる。0}今回は猫が0で犬が1なのでNNの出力は犬である確率(出力が1に近いほど犬で0に近いほど猫)と解釈できる。0.5を閾値として0.5未満なら猫、0.5以上なら犬と判定すればよい。どのディレクトリがどのクラスに割り当てられるかは表示してみないとわからないみたい。いつもアルファベット順ではなさそうだ。Kerasで実装してみよう。'history_smallcnn.txt'))出力層は0から1なので出力層の活性化関数にはsigmoidを指定。2クラス分類なので損失関数にbinary_crossentropyを指定。最適化アルゴリズムはadamを使った。dataにある画像を直接ロードするためにImageDataGeneratorとflow_from_directory()を使った。ここら辺は前回(2017/1/8)まとめた。学習履歴はあとでグラフ化したいのでsave_history()という自作関数でファイルに保存するようにした。結果はあとで3つまとめて考察したいので次へいこう。VGG16が抽出した特徴を使って多層パーセプトロンを学習する2つめの方法はVGG16を特徴抽出器として使うアプローチである。VGG16は左の図のように畳み込み層のブロックが5つ続いたあとにフル結合層のブロックがくっつく構成になっている。VGG16のフル結合層はImageNetの1000クラスを分類するようになっているためそのままでは2クラスの犬・猫分類には使えない。そこで、この使えないフル結合層(FC)を捨ててしまって直前の畳み込みブロックまでを使うのがこのアプローチである。フラット化すると4x4x512=8192次元ベクトルになる。150x150x3=67500次元ベクトルを8192次元ベクトルに圧縮した特徴量(ボトルネック特徴量)を抽出する。この8192次元ベクトルのボトルネック特徴量を入力として2クラスを出力とする多層パーセプトロン(2016/11/3)を新たに学習する。この程度の小さな多層パーセプトロンの学習なら小さなデータでも十分だろう。以下の実装では、訓練データとバリデーションデータの画像からボトルネック特徴量を抽出するsave_bottleneck_features()と抽出したボトルネック特徴量を使って多層パーセプトロンを学習するtrain_top_model()に分けている。VGG16のinclude_topオプションをFalseにするとFC層を除外したネットワークがロードできる。train_top_model()ここで、save_bottleneck_features()では一切学習を行っていない点に注意。VGG16の学習した重みをそのまま使ってpredict_generator()で出力を求めている(フィードフォワードのみ)。train_top_model()ではランダムな初期値から多層パーセプトロンの重みをfit()で学習している。ここで、optimizerにadamではなく、学習率を小さくしたSGDを使った。実際は、adamでもうまくいくのだが、次のFine-tuningと比較するためにSGDにしている。この方法では、大規模データで学習したVGG16を特徴抽出器として使っている。ニューラルネットのこのような使い方は知ってしまえば当たり前だが初めて知ったときは衝撃を受けた。Learningの強みは青色部分の多段の特徴抽出にあって分類を行うフル結合層はおまけみたいなものなのだ。Machine)を使うケースもあるようだ。VGG16をFine-tuningする2番目の方法では、実質的に重みを学習したのはフル結合層の多層パーセプトロンの部分のみだった。これに対してFine-tuningではフル結合層だけでなく、その1つ前の畳み込み層の重みも学習する。畳込みニューラルネットでは浅い層ほどエッジやブロブなど汎用的な特徴が抽出されているのに対し、深い層ほど学習データに特化した特徴が抽出される傾向がある。そこで、浅い層の汎用的な特徴抽出器はそのまま固定(frozen)し、深い層の重みのみ今回の犬・猫分類に合うように再調整(fine-tuning)してしまう。KerasでFine-tuningをするには下のようなコードを書く。APIを使って結合している。vgg16_modelがフル結合層を除いたVGG16(図の青色と黄色部分)でtop_modelが多層パーセプトロン(図の緑色部分)である。この2つのネットワークを結合する必要がある。vgg16_model.add(top_model)とできれば理想的なのだができなかった。調べるとtop_modelはSequentialクラスであるのに対し、vgg16_modelはkeras.engine.training.Modelという独自クラスでなぜかこのクラスにはadd()がない!そこで、少し面倒だがこの2つの入出力をつなげる下のような新しいモデルを定義する。output=top_model(vgg16_model.output))この式は、新しいモデルへの入力(input)はvgg16_modelの入力と同じで、出力(output)はtop_modelにvgg16_modelの出力を入力した値という意味。ここら辺はベースのTheanoやTensorFlowの書き方に少し近いと思った。今回は、フル結合層(top_model)の重みとして2番目の方法で学習した重みを初期値として用いた。実際はあとで結果に示すようにランダムな初期値から初めても最終的に同じ精度に収束するのでこの行は省いてもよさそう。'bottleneck_fc_model.h5'))同じように黄色部分の畳込み層の重みの初期値はVGG16のものだけどこれをランダム初期値から学習するとどうなるか気になった。結局、時間がかかっても同じ精度まで収束するのかな?ニューラルネットは初期値がかなり重要なファクターだからなあ。重みの固定VGG16の図の青色の部分は重みを固定(frozen)したい。Kerasではlayer.trainableをFalseにすると重みの学習が行われなくなる。layersはInputLayerがlayers[0]でパラメータがないMaxPooling2Dもlayersに含むのでlayers[:15]はちょうど図の青色の部分にあたる。最適化には学習率の小さいSGDを使う最初、最適化にAdamを使っていたのだがうまく学習できなかった。Fine-tuningはあくまで重みの再調整なので学習率を小さくしたSGDを使う方がよさそうだ。以上の3つの学習履歴をプロットすると下のようになった。予想通りの結果で1つめの小さな畳み込みニューラルネット(smallcnn)では75%くらいの精度しか出ていないが、VGG16をFine-tuning(finetuning)すると94%くらいまで精度が跳ね上がる。VGG16を特徴抽出器(extractor)として用いた場合はその中間くらい。random)最終的には学習済み初期値を使った場合と同様に94%近くまで上がることもわかった。Kaggleのスコアを見ると精度94%はトップ100位以内には入っているのでけっこうよい順位だろう。今回は訓練に25000枚のうちたった2000枚しか使っていない。VGG16の学習済みモデルがいかに強力かわかる。訓練データに含まれないテスト画像をいくつか入力して本当に分類できているか確認しておいた。train.zipの画像から適当に選んだけど各クラス最初の1000枚しか学習に使っていないので以下の画像は訓練外データである。出力が0.5未満だと猫、0.5以上だと犬と判定すればよい。