Fine-tuningを使って少ない画像データから効率よく学習モデルを作成する方法

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欲しいっす。

 

それではまた。

 


その他の物体検出記事はこちらから

あなたにオススメの記事

コメント

  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

PAGE TOP