今回の記事の目的
前回までの記事では、実際に学習済みモデルを用いて人検知を行いました。そしてSmooth gradを用いることで、CNNからはどのように見えているか、そしてこれを分析することでモデルのどこが悪いかということを特定することが重要であるということについて説明しました。
今回はFine tuningという手法で学習済みモデルを再構築する方法について説明します。
Fine tuningとは…?
Fine tuningとは、 今まで用いていたような学習済みモデルに対して、いくつかの学習データを使って改めて学習を行うことにより、モデルを再構築する手法になります。
人検知の場合、今までは学習済みモデルを用いて人検知などを行ってきましたが、実際に用いる場合、例えば人検知のみで使用するという場合、人以外は検知する必要がなく、むしろ人以外のものを検知するくらいなら人の検知精度をあげたほうが良いという風に思うわけです。
しかし、一から学習するとなると、その分の画像データを集めなくてはならない上に、学習を行う上でのコストがかかり過ぎてしまうという難点があります。
そのような時に、Fine tuningを用いて学習済みモデルに対して人検知用に再度学習を行うことによって人検知に特化したモデルを作成できるということになります。
Fine tuningの概要
そこでまず、概念的にはどのようなことを行っているか説明したいと思います。
CNNはで基本的に、畳み込み層、プーリング層で画像の特徴量を抽出、その結果、その特徴量がどのクラスに合致するかを全結合層、識別層でクラス分類するという方式が一般になります。
Fine tuningでは、学習済みモデルのうち、畳み込み層、プーリング層はそのまま、すなわちすでに学習済みの特徴量をそのまま用いて、最後の全結合層、識別層を別途こちらで用意したモデルに置き換えて再度全結合層、識別層のみ学習することによって、例えば上記の例であれば人検知に特化したモデルが作成できるというものになります。
Fine tuningの特徴
Fine tuningの特徴としては冒頭にも少し述べたように、学習済みモデルをベースにモデルを再構築するという手法になるため、一からCNNを作り直すよりは少ない学習データでモデルの構築が行えるということがあげられます。そのため、学習時間も少ない時間で十分であるということも同時にメリットとしてあげられます。
Fine tuningの実装
人検知の前のステップとして、まずVGG-16の学習済みデータを用いて,簡単なFine tuningを行い、「人」と「枝豆」の画像認識を行ってみます。
実行環境
学習は比較的時間がかかるため、GPUを使う必要があります。GPUを使う環境の1つにGoogle Colaboratoryというのがあり、今回はこのGoogle Colaboratory上で実行しました。ここでの実行はjupyter notebookと同様のような形で実行することが可能です。
Google ColaboratoryのGPUの設定
まず、Google Colaboratoryにアクセスすると,「Colaboratoryにようこそ」という画面が表示されるはずです。そうしたら左上の「ファイル」から「Python3の新しいノートブック」を選択します。するとUntitled0.ipynbというファイルが作成されます。ここの上でコードを実行していきます。
ちなみに.pyファイルを作成してから実行したいという場合、左にある小さな矢印(>)から「ファイル」→「アップロード」でアップロードできますので、アップロードしたら「run ファイル名」で実行が可能です。
次にGPUの設定ですが、上部の「編集」→「ノートブックの設定」で「ハードウェア アクセラレータ」をGPUにします。
学習データ
学習データは本来はもっとデータが必要だと思うのですが、学習用の画像を各クラス60、合計120、評価用の画像を各クラス30、合計60用意しました。人の画像はCOCOの画像から、枝豆の画像はFood-101任意の画像を選択しました。
今回、これらの画像を以下のようなファイルにいれて実行しました。
- 学習用画像データ
- 人:dataset/train/person/
- 枝豆:dataset/train/edamame/
- 評価用画像データ
- 人:dataset/validation/person/
- 枝豆:dataset/validation/edamame/
コード
変数宣言
# 分類クラス classes = ['person','edamame'] nb_classes = len(classes) img_width, img_height = 224,224 # 学習用と評価用の画像格納先 train_data_dir = 'dataset/train/' validation_data_dir = 'dataset/validation/' # 学習画像数と評価画像数 nb_train_samples = 120 nb_validation_samples = 60 # バッチサイズ:サブセットに含まれる数 batch_size = 10
まず、classesでpersonを指定、そして今回用意したデータは学習データ250、評価データ50用意したので、それをnb_train_samplesとnb_validation_samplesに指定してます。
そしてバッチサイズを指定しています。
バッチサイズとは、学習中データセットはサブセットとして切り分けられて実行されるのですが、そのサブセットの大きさを指します。任意の値で問題ないですが、学習データ数で割り切れる数の方が良いため、10を指定しました。
学習データの水増し
少ない学習データのアスペクト比(画像の縦横の割合)を変更したり反転させたりすることで、学習データを水増しし、様々なデータに対応できるようにします。
# 学習用,評価用の画像の水増し train_datagen = ImageDataGenerator( rescale = 1.0/255, zoom_range=0.2, horizontal_flip=True ) validation_datagen = ImageDataGenerator(rescale=1.0 / 255) # 水増し対象の画像データを指定 train_generator = train_datagen.flow_from_directory( train_data_dir, target_size=(img_width, img_height), color_mode='rgb', classes=classes, class_mode='categorical', batch_size=batch_size, shuffle=True) validation_generator = validation_datagen.flow_from_directory( validation_data_dir, target_size=(img_width, img_height), color_mode='rgb', classes=classes, class_mode='categorical', batch_size=batch_size, shuffle=True)
上のコードではまず、ImageDataGeneratorで学習データ、評価データ共に画像の水増しの設定を行なっています。そして、それぞれに対して、flow_from_directory関数で、ファイルのパスを指定し、実際に水増しを実行しています。
Fine tuning
VGG16にFine tuningを行う処理です。Fine tuningでは前述の通り、全結合層以下を取り除いてこちらの用意したものを結合します。
VGG-16の準備
kerasではImageNetの学習済みモデルを用意できるため、以下のコードでImageNetで学習させたVGG-16の学習済みモデルを変数として格納します。
# VGG16のロード # 全結合層は不要→→include_top=False input_tensor = Input(shape=(img_width, img_height, 3)) vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)
Inputで入力画像を指定しています。今回はSSD300に対応させたいため、300にしました。
VGG16関数で,VGG-16の学習済みモデルを変数定義する。Fine tuningでは全結合層を指定しないため、include_topにはFalseを指定します。
全結合層の作成
上記で作成したVGG-16の全結合層抜きのモデルにこちらで定義した全結合層を結合させます。
# 全結合層の作成 top_model = Sequential() top_model.add(Flatten(input_shape=vgg16.output_shape[1:])) top_model.add(Dense(4, activation='relu')) top_model.add(Dropout(0.5)) top_model.add(Dense(nb_classes, activation='softmax')) # VGG16と全結合層を結合してモデルを作成 vgg_model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output)) vgg_model.summary() print(vgg_model)
- top_modelに全結合を表すSequential関数を代入して、Flattenという入力を平滑化するモジュールやDenceという全結合層のレイヤーモジュールを使って全結合層を実装。
- そして最後にvgg_modelに前節でのVGG-16モデルと作成したtop_modelを結合。
モデルの構築
# VGG16の図の青色の部分は重みを固定 for layer in vgg_model.layers[:13]: layer.trainable = False # 2クラス分類を指定 vgg_model.compile(loss='categorical_crossentropy', optimizer=optimizers.SGD(lr=1e-3, momentum=0.9), metrics=['accuracy']) history = vgg_model.fit_generator( train_generator, steps_per_epoch=nb_train_samples, epochs=nb_train_samples/batch_size, validation_data=validation_generator, validation_steps=nb_validation_samples/batch_size) vgg_model.save_weights(os.path.join('.', 'finetuning.h5'))
特徴量抽出部分は学習しないようにlayer.trainableをfalseに指定。
compileでモデル構築する際は人/人じゃないかなので2クラス分類のための損失関数、2値誤差エントロピー(binary_crossentropy)を指定。
fit_generatorで学習を開始。
最後にsave_weightsでモデルを保存。
枝豆か人かの認識
VGG16の重みを学習しました。この重みを用いて枝豆と人の認識を以下の画像で行います。
パラメータの設定とVGG16のモデルの作成
まず、パラメータの設定とVGG16を作成します。
パラメータとしては、人、枝豆のクラスを設定。画像の大きさは224×224として設定します。
そして学習時と同じようにVGGのモデルを作成して、最後に重みをロードします。
#事前に設定するパラメータ classes = ['person','edamame'] nb_classes = len(classes) img_width, img_height = 224,224 # VGG16のロード。FC層は不要なので include_top=False input_tensor = Input(shape=(img_width, img_height, 3)) vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor) # FC層の作成 top_model = Sequential() top_model.add(Flatten(input_shape=vgg16.output_shape[1:])) top_model.add(Dense(4, activation='relu')) top_model.add(Dropout(0.5)) top_model.add(Dense(nb_classes, activation='softmax')) # VGG16とFC層を結合してモデルを作成 vgg_model = Model(input=vgg16.input, output=top_model(vgg16.output)) vgg_model.load_weights('./finetuning.h5')
画像の読み込み
以下のように画像を読み込んで、クラスを予測する関数を作成します。
# 画像を読み込んで予測する def img_predict(filename): # 画像を読み込んで4次元テンソルへ変換 img = image.load_img(filename, target_size=(img_height, img_width)) x = image.img_to_array(img) x = np.expand_dims(x, axis=0) # 学習時にImageDataGeneratorのrescaleで正規化したので同じ処理が必要 # これを忘れると結果がおかしくなるので注意 x = x / 255.0 #表示 #plt.imshow(img) #plt.show() # 指数表記を禁止にする np.set_printoptions(suppress=True) #画像の人物を予測 pred = vgg_model.predict(x)[0] #結果を表示する print('[person, edamame]') print(pred*100)
そして最後にこの関数を実行します。
画像は./testの下に学習では用いていない画像を入力として入れました。
test = glob.glob('./test/*') for t in test: img_predict(t)
結果
結果は以下のようになりました。
結果を見る限り、比較的ちゃんと認識できているように感じます。 次回はこれの応用として、SSDをFine tuningしてみたいと思います。
[参考サイト]
学習用の全体のコード
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 import time # 分類クラス:人or枝豆 classes = ['person','edamame'] nb_classes = len(classes) img_width, img_height = 224,224 # 学習用と評価用の画像格納先 train_data_dir = 'dataset/train/' validation_data_dir = 'dataset/validation/' # 学習画像数と評価画像数 nb_train_samples = 120 nb_validation_samples = 60 # バッチサイズ:サブセットに含まれる数 batch_size = 10 # 学習用,評価用の画像の水増し train_datagen = ImageDataGenerator( rescale = 1.0/255, zoom_range=0.2, horizontal_flip=True ) validation_datagen = ImageDataGenerator(rescale=1.0 / 255) # 水増し対象の画像データを指定 train_generator = train_datagen.flow_from_directory( train_data_dir, target_size=(img_width, img_height), color_mode='rgb', classes=classes, class_mode='categorical', batch_size=batch_size, shuffle=True) validation_generator = validation_datagen.flow_from_directory( validation_data_dir, target_size=(img_width, img_height), color_mode='rgb', classes=classes, class_mode='categorical', batch_size=batch_size, shuffle=True) # VGG16のロード。 # 全結合層は不要→→include_top=False input_tensor = Input(shape=(img_width, img_height, 3)) vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor) # 全結合層の作成 top_model = Sequential() top_model.add(Flatten(input_shape=vgg16.output_shape[1:])) top_model.add(Dense(4, activation='relu')) top_model.add(Dropout(0.5)) top_model.add(Dense(nb_classes, activation='softmax')) # VGG16と全結合層を結合してモデルを作成 vgg_model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output)) vgg_model.summary() print(vgg_model) # VGG16の図の青色の部分は重みを固定 for layer in vgg_model.layers[:13]: layer.trainable = False # 2クラス分類を指定 vgg_model.compile(loss='categorical_crossentropy', optimizer=optimizers.SGD(lr=1e-3, momentum=0.9), metrics=['accuracy']) history = vgg_model.fit_generator( train_generator, steps_per_epoch=nb_train_samples, epochs=nb_train_samples/batch_size, validation_data=validation_generator, validation_steps=nb_validation_samples/batch_size) vgg_model.save_weights(os.path.join('.', 'finetuning.h5'))