目次
VGG16モデルを使ったFine-tuning
こんにちは。
AI coordinatorの清水秀樹です。
近々、人を認識させる学習モデルを真面目に開発する機会が発生したため、以前からチャレンジしてみたかったFine-tuningに挑戦してみました。
筆者のように趣味でDeepLearningをやっている人にとって、大量の学習データを集めることは非常に困難です。
そんな時に使えるのが、Fine-tuningと言う学習方法です。
Fine-tuningとは既存の学習モデルを流用する学習方法で、少ない画像データからでも精度が高い学習モデルを作成することができるようになります。
大量の学習データを集められない時に重宝する手法ですね。
今回は1000クラス学習されているVGG16モデルを流用します。
VGG16モデルは2014年のILSVRCで提案された畳み込み層13層とフル結合層3層から成るCNNモデルです。
しかも、このVGG16モデルはKerasのモジュールにも対応しているため、簡単に流用して学習することができます。
これにより、1から学習モデルを作成させる必要がなくなり、今まで以上に効率よく学習モデルを作成することが可能となります。
今回はこのVGG16を流用したFine-tuningについて紹介したいと思います。
参考にさせて頂いたサイトの紹介
情報提供ありがとうございます。
開発環境
macOS Sierra
Anaconda3-4.2.0-MacOSX-x86_64
python 3.5.2
opencv3 3.1.0
tensorflow 1.1.0
まずはVGG16を試してみる
まずは、高機能学習モデルVGG16を試してみました。
冒頭でも説明しましたがVGG16はKerasのモジュールとしても対応しているため、事前に学習モデルをダウンロードせずとも、ソースを実行するだけでダウンロードが始まります。
初回実行時はダウンロードに時間がかかりますが、2回目以降は瞬時に動きます。
以下、ソースコードです。
mport ssl ssl._create_default_https_context = ssl._create_unverified_context from keras.applications.vgg16 import VGG16, preprocess_input, decode_predictions from keras.preprocessing import image import numpy as np import sys filename = "4.png" # 学習済みのVGG16をロード model = VGG16(weights='imagenet') # model.summary() # 引数で指定した画像ファイルを読み込む img = image.load_img(filename, target_size=(224, 224)) # 読み込んだPIL形式の画像をarrayに変換 x = image.img_to_array(img) x = np.expand_dims(x, axis=0) # Top-5のクラスを予測する # VGG16の1000クラスはdecode_predictions()で文字列に変換される preds = model.predict(preprocess_input(x)) results = decode_predictions(preds, top=5)[0] for result in results: print(result)
以下、使用した画像です。
実行結果は・・・
('n02085620', 'Chihuahua', 0.64485019) ('n02087046', 'toy_terrier', 0.15428923) ('n02094433', 'Yorkshire_terrier', 0.040288612) ('n02096294', 'Australian_terrier', 0.02443241) ('n02086910', 'papillon', 0.022342021)
素晴らしいです。
チワワと犬の種類まで判別してくれました。
噂通りの性能ですね。
このモデルそのままでも色々なことに使えそうですね。
VGG16を流用したFine-tuningに挑戦
それでは本題です。
VGG16の性能の高さを知ったところで、早速このVGG16を流用したFine-tuningにチャレンジしたいと思います。
参考元サイトのソースコードをほとんど流用させて頂きました。
今回は人物画像を学習させたいと考えています。
ただし、悲しいことにVGG16は人物を学習させたモデルではありません。
Fine-tuningの注意点として元々学習で使われた画像データと、今回新たに分類しようとしている画像が似ていない場合は、その特徴モデルがそのまま使えない可能性があります。
これメッチャ重要です。
Fine-tuningは似たような画像を準備しなければ、あまり精度が出ないと言うことですね。
そのため人物画像の場合、VGG16を使ったFine-tuningでどこまで精度が出るかわかりませんが、今回はFine-tuningのやり方を学ぶことを目的としているので精度が低くても良しとします。
以下ソースコードです。
画像を水増しするImageDataGeneratorも組み込んであります。
import ssl ssl._create_default_https_context = ssl._create_unverified_context import os from keras.applications.vgg16 import VGG16 from keras.preprocessing.image import ImageDataGenerator from keras.models import Sequential, Model from keras.layers import Input, Activation, Dropout, Flatten, Dense from keras.preprocessing.image import ImageDataGenerator from keras import optimizers import numpy as np #from smallcnn import save_history from keras.utils.visualize_util import plot import matplotlib.pyplot as plt # https://gist.github.com/fchollet/7eb39b44eb9e16e59632d25fb3119975 img_width, img_height = 150, 150 train_data_dir = 'data/train' validation_data_dir = 'data/validation' nb_train_samples = 3476 nb_validation_samples = 147 nb_epoch = 20 result_dir = 'results' def plot_history(history): # 精度の履歴をプロット plt.plot(history.history['acc'],"o-",label="accuracy") plt.plot(history.history['val_acc'],"o-",label="val_acc") plt.title('model accuracy') plt.xlabel('epoch') plt.ylabel('accuracy') plt.legend(loc="lower right") plt.show() # 損失の履歴をプロット plt.plot(history.history['loss'],"o-",label="loss",) plt.plot(history.history['val_loss'],"o-",label="val_loss") plt.title('model loss') plt.xlabel('epoch') plt.ylabel('loss') plt.legend(loc='lower right') plt.show() if __name__ == '__main__': # VGG16モデルと学習済み重みをロード # Fully-connected層(FC)はいらないのでinclude_top=False) # input_tensorを指定しておかないとoutput_shapeがNoneになってエラーになるので注意 # https://keras.io/applications/#inceptionv3 input_tensor = Input(shape=(img_height, img_width, 3)) vgg16_model = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor) # vgg16_model.summary() # FC層を構築 # Flattenへの入力指定はバッチ数を除く top_model = Sequential() top_model.add(Flatten(input_shape=vgg16_model.output_shape[1:])) top_model.add(Dense(256, activation='relu')) top_model.add(Dropout(0.5)) top_model.add(Dense(3, activation='softmax')) # 学習済みのFC層の重みをロード # TODO: ランダムな重みでどうなるか試す #top_model.load_weights(os.path.join(result_dir, 'bottleneck_fc_model.h5')) # vgg16_modelはkeras.engine.training.Model # top_modelはSequentialとなっている # ModelはSequentialでないためadd()がない # そのためFunctional APIで二つのモデルを結合する # https://github.com/fchollet/keras/issues/4040 model = Model(input=vgg16_model.input, output=top_model(vgg16_model.output)) print('vgg16_model:', vgg16_model) print('top_model:', top_model) print('model:', model) # Total params: 16,812,353 # Trainable params: 16,812,353 # Non-trainable params: 0 model.summary() # layerを表示 for i in range(len(model.layers)): print(i, model.layers[i]) # 最後のconv層の直前までの層をfreeze for layer in model.layers[:15]: layer.trainable = False # Total params: 16,812,353 # Trainable params: 9,177,089 # Non-trainable params: 7,635,264 model.summary() # TODO: ここでAdamを使うとうまくいかない # Fine-tuningのときは学習率を小さくしたSGDの方がよい? model.compile(loss='categorical_crossentropy', optimizer=optimizers.SGD(lr=1e-4, momentum=0.9), metrics=['accuracy']) train_datagen = ImageDataGenerator( rescale=1.0 / 255, shear_range=0.2, zoom_range=0.2, horizontal_flip=True) test_datagen = ImageDataGenerator(rescale=1.0 / 255) train_generator = train_datagen.flow_from_directory( train_data_dir, target_size=(img_height, img_width), batch_size=32, class_mode='categorical') validation_generator = test_datagen.flow_from_directory( validation_data_dir, target_size=(img_height, img_width), batch_size=32, class_mode='categorical') # Fine-tuning history = model.fit_generator( train_generator, samples_per_epoch=nb_train_samples, nb_epoch=nb_epoch, validation_data=validation_generator, nb_val_samples=nb_validation_samples) model.save_weights(os.path.join(result_dir, 'finetuning.h5')) #save_history(history, os.path.join(result_dir, 'history_finetuning.txt')) # modelに学習させた時の変化の様子をplot plot_history(history)
今回は、子供、成人男性、成人女性、の3クラスでFine-tuningしています。
それぞれの画像枚数は約1000枚です。
以下のように学習データとテストデータの枚数を指定する必要があります。
nb_train_samples = 3476 nb_validation_samples = 147
頑張ってデータを集めましょう。
畳み込み層は15層目まではそのまま固定し、以降の畳み込み層を学習するように設定します。
for layer in model.layers[:15]: layer.trainable = False
以下、学習結果です。
Using TensorFlow backend. vgg16_model: <keras.engine.training.Model object at 0x1169f2cc0> top_model: <keras.models.Sequential object at 0x116a01860> model: <keras.engine.training.Model object at 0x116ae22b0> ____________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ==================================================================================================== input_1 (InputLayer) (None, 150, 150, 3) 0 ____________________________________________________________________________________________________ block1_conv1 (Convolution2D) (None, 150, 150, 64) 1792 input_1[0][0] ____________________________________________________________________________________________________ block1_conv2 (Convolution2D) (None, 150, 150, 64) 36928 block1_conv1[0][0] ____________________________________________________________________________________________________ block1_pool (MaxPooling2D) (None, 75, 75, 64) 0 block1_conv2[0][0] ____________________________________________________________________________________________________ block2_conv1 (Convolution2D) (None, 75, 75, 128) 73856 block1_pool[0][0] ____________________________________________________________________________________________________ block2_conv2 (Convolution2D) (None, 75, 75, 128) 147584 block2_conv1[0][0] ____________________________________________________________________________________________________ block2_pool (MaxPooling2D) (None, 37, 37, 128) 0 block2_conv2[0][0] ____________________________________________________________________________________________________ block3_conv1 (Convolution2D) (None, 37, 37, 256) 295168 block2_pool[0][0] ____________________________________________________________________________________________________ block3_conv2 (Convolution2D) (None, 37, 37, 256) 590080 block3_conv1[0][0] ____________________________________________________________________________________________________ block3_conv3 (Convolution2D) (None, 37, 37, 256) 590080 block3_conv2[0][0] ____________________________________________________________________________________________________ block3_pool (MaxPooling2D) (None, 18, 18, 256) 0 block3_conv3[0][0] ____________________________________________________________________________________________________ block4_conv1 (Convolution2D) (None, 18, 18, 512) 1180160 block3_pool[0][0] ____________________________________________________________________________________________________ block4_conv2 (Convolution2D) (None, 18, 18, 512) 2359808 block4_conv1[0][0] ____________________________________________________________________________________________________ block4_conv3 (Convolution2D) (None, 18, 18, 512) 2359808 block4_conv2[0][0] ____________________________________________________________________________________________________ block4_pool (MaxPooling2D) (None, 9, 9, 512) 0 block4_conv3[0][0] ____________________________________________________________________________________________________ block5_conv1 (Convolution2D) (None, 9, 9, 512) 2359808 block4_pool[0][0] ____________________________________________________________________________________________________ block5_conv2 (Convolution2D) (None, 9, 9, 512) 2359808 block5_conv1[0][0] ____________________________________________________________________________________________________ block5_conv3 (Convolution2D) (None, 9, 9, 512) 2359808 block5_conv2[0][0] ____________________________________________________________________________________________________ block5_pool (MaxPooling2D) (None, 4, 4, 512) 0 block5_conv3[0][0] ____________________________________________________________________________________________________ sequential_1 (Sequential) (None, 3) 2098179 block5_pool[0][0] ==================================================================================================== Total params: 16,812,867 Trainable params: 16,812,867 Non-trainable params: 0 ____________________________________________________________________________________________________ 0 <keras.engine.topology.InputLayer object at 0x1047597b8> 1 <keras.layers.convolutional.Convolution2D object at 0x116710940> 2 <keras.layers.convolutional.Convolution2D object at 0x116732860> 3 <keras.layers.pooling.MaxPooling2D object at 0x1165c5a58> 4 <keras.layers.convolutional.Convolution2D object at 0x1167c1f28> 5 <keras.layers.convolutional.Convolution2D object at 0x1167d8ac8> 6 <keras.layers.pooling.MaxPooling2D object at 0x1167e7ba8> 7 <keras.layers.convolutional.Convolution2D object at 0x11690aac8> 8 <keras.layers.convolutional.Convolution2D object at 0x116915c18> 9 <keras.layers.convolutional.Convolution2D object at 0x116931a20> 10 <keras.layers.pooling.MaxPooling2D object at 0x11694c710> 11 <keras.layers.convolutional.Convolution2D object at 0x11695a748> 12 <keras.layers.convolutional.Convolution2D object at 0x116967780> 13 <keras.layers.convolutional.Convolution2D object at 0x116982470> 14 <keras.layers.pooling.MaxPooling2D object at 0x11699feb8> 15 <keras.layers.convolutional.Convolution2D object at 0x11699ff98> 16 <keras.layers.convolutional.Convolution2D object at 0x1169b9f98> 17 <keras.layers.convolutional.Convolution2D object at 0x1169c75c0> 18 <keras.layers.pooling.MaxPooling2D object at 0x1169e4e10> 19 <keras.models.Sequential object at 0x116a01860> ____________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ==================================================================================================== input_1 (InputLayer) (None, 150, 150, 3) 0 ____________________________________________________________________________________________________ block1_conv1 (Convolution2D) (None, 150, 150, 64) 1792 input_1[0][0] ____________________________________________________________________________________________________ block1_conv2 (Convolution2D) (None, 150, 150, 64) 36928 block1_conv1[0][0] ____________________________________________________________________________________________________ block1_pool (MaxPooling2D) (None, 75, 75, 64) 0 block1_conv2[0][0] ____________________________________________________________________________________________________ block2_conv1 (Convolution2D) (None, 75, 75, 128) 73856 block1_pool[0][0] ____________________________________________________________________________________________________ block2_conv2 (Convolution2D) (None, 75, 75, 128) 147584 block2_conv1[0][0] ____________________________________________________________________________________________________ block2_pool (MaxPooling2D) (None, 37, 37, 128) 0 block2_conv2[0][0] ____________________________________________________________________________________________________ block3_conv1 (Convolution2D) (None, 37, 37, 256) 295168 block2_pool[0][0] ____________________________________________________________________________________________________ block3_conv2 (Convolution2D) (None, 37, 37, 256) 590080 block3_conv1[0][0] ____________________________________________________________________________________________________ block3_conv3 (Convolution2D) (None, 37, 37, 256) 590080 block3_conv2[0][0] ____________________________________________________________________________________________________ block3_pool (MaxPooling2D) (None, 18, 18, 256) 0 block3_conv3[0][0] ____________________________________________________________________________________________________ block4_conv1 (Convolution2D) (None, 18, 18, 512) 1180160 block3_pool[0][0] ____________________________________________________________________________________________________ block4_conv2 (Convolution2D) (None, 18, 18, 512) 2359808 block4_conv1[0][0] ____________________________________________________________________________________________________ block4_conv3 (Convolution2D) (None, 18, 18, 512) 2359808 block4_conv2[0][0] ____________________________________________________________________________________________________ block4_pool (MaxPooling2D) (None, 9, 9, 512) 0 block4_conv3[0][0] ____________________________________________________________________________________________________ block5_conv1 (Convolution2D) (None, 9, 9, 512) 2359808 block4_pool[0][0] ____________________________________________________________________________________________________ block5_conv2 (Convolution2D) (None, 9, 9, 512) 2359808 block5_conv1[0][0] ____________________________________________________________________________________________________ block5_conv3 (Convolution2D) (None, 9, 9, 512) 2359808 block5_conv2[0][0] ____________________________________________________________________________________________________ block5_pool (MaxPooling2D) (None, 4, 4, 512) 0 block5_conv3[0][0] ____________________________________________________________________________________________________ sequential_1 (Sequential) (None, 3) 2098179 block5_pool[0][0] ==================================================================================================== Total params: 16,812,867 Trainable params: 9,177,603 Non-trainable params: 7,635,264 ____________________________________________________________________________________________________ Found 3476 images belonging to 3 classes. Found 147 images belonging to 3 classes. Epoch 1/20 3476/3476 [==============================] - 828s - loss: 1.1295 - acc: 0.4249 - val_loss: 0.9231 - val_acc: 0.6122 Epoch 2/20 3476/3476 [==============================] - 826s - loss: 0.9067 - acc: 0.5636 - val_loss: 0.7475 - val_acc: 0.7007 Epoch 3/20 3476/3476 [==============================] - 822s - loss: 0.7920 - acc: 0.6375 - val_loss: 0.6294 - val_acc: 0.7619 Epoch 4/20 3476/3476 [==============================] - 821s - loss: 0.7179 - acc: 0.6916 - val_loss: 0.5655 - val_acc: 0.7823 Epoch 5/20 3476/3476 [==============================] - 825s - loss: 0.6546 - acc: 0.7204 - val_loss: 0.4980 - val_acc: 0.7959 Epoch 6/20 3476/3476 [==============================] - 821s - loss: 0.6079 - acc: 0.7494 - val_loss: 0.4355 - val_acc: 0.8503 Epoch 7/20 3476/3476 [==============================] - 838s - loss: 0.5556 - acc: 0.7693 - val_loss: 0.4190 - val_acc: 0.8163 Epoch 8/20 3476/3476 [==============================] - 827s - loss: 0.5349 - acc: 0.7759 - val_loss: 0.4019 - val_acc: 0.8435 Epoch 9/20 3476/3476 [==============================] - 825s - loss: 0.5188 - acc: 0.7934 - val_loss: 0.3639 - val_acc: 0.8571 Epoch 10/20 3476/3476 [==============================] - 828s - loss: 0.5052 - acc: 0.7957 - val_loss: 0.3453 - val_acc: 0.8912 Epoch 11/20 3476/3476 [==============================] - 828s - loss: 0.4668 - acc: 0.8139 - val_loss: 0.3387 - val_acc: 0.9048 Epoch 12/20 3476/3476 [==============================] - 823s - loss: 0.4556 - acc: 0.8150 - val_loss: 0.3594 - val_acc: 0.8571 Epoch 13/20 3476/3476 [==============================] - 826s - loss: 0.4353 - acc: 0.8228 - val_loss: 0.3140 - val_acc: 0.8844 Epoch 14/20 3476/3476 [==============================] - 823s - loss: 0.4274 - acc: 0.8303 - val_loss: 0.3082 - val_acc: 0.8912 Epoch 15/20 3476/3476 [==============================] - 827s - loss: 0.4013 - acc: 0.8458 - val_loss: 0.2989 - val_acc: 0.8707 Epoch 16/20 3476/3476 [==============================] - 824s - loss: 0.3875 - acc: 0.8484 - val_loss: 0.2921 - val_acc: 0.8776 Epoch 17/20 3476/3476 [==============================] - 822s - loss: 0.3767 - acc: 0.8518 - val_loss: 0.3029 - val_acc: 0.8707 Epoch 18/20 3476/3476 [==============================] - 826s - loss: 0.3847 - acc: 0.8403 - val_loss: 0.2841 - val_acc: 0.8844 Epoch 19/20 3476/3476 [==============================] - 822s - loss: 0.3552 - acc: 0.8662 - val_loss: 0.2877 - val_acc: 0.8707 Epoch 20/20 3476/3476 [==============================] - 822s - loss: 0.3508 - acc: 0.8570 - val_loss: 0.3060 - val_acc: 0.8707
epoch = 20で止めました。
それ以上はval_lossが上昇し過学習となるので・・・
それでもVal_loss = 0.3060は1から学習モデルを作成することを考えると、中々の精度かと思います。
Fine-tuningの結果は
以下の画像でテストしてみました。
('Woman', 0.49978063) ('Man', 0.30947286) ('Child', 0.19074652)
('Woman', 0.72878003) ('Child', 0.16285408) ('Man', 0.10836589)
('Woman', 0.68285507) ('Man', 0.24562132) ('Child', 0.071523607)
う〜ん、微妙。
やはりVGG16モデルで、人物画像のFine-tuningは精度が高くならない感じですね。
それともやり方が悪いのか。
とはいえ、Fine-tuningがどんなものなのか知ることができたので良しとします。
次回は別の画像でチャレンジしてみたいと思います。
GPU欲しいっす。
それではまた。
その他の物体検出記事はこちらから
この記事へのコメントはありません。