Tensorflowで転移学習をする方法【画像分類/VGG16】

Tensorflowで転移学習をする方法【画像分類/VGG16】

こんにちは、えびかずきです。

今回は学習済みのニューラルネット を転移学習で活用する方法について説明したいと思います。

ディープラーニングは強力な手法ではありますが、いざ活用しようとしても初心者にとっては学習素材集め、構造決定、重み調整と実装のハードルがかなり高いのが実情だと思います。

そんな時に役立つのが転移学習です。

えびかずき
えびかずき
既存の学習済みニューラルネットをうまくカスタマイズして簡単にディープラーニングを活用してみましょう!

転移学習とは

ディープラーニングにおける転移学習とは、

学習済みのニューラルネットモデルの一部を新しい学習素材で訓練することで、コストをかけずに精度の高いモデルを作成する手法のことを言います。

例えば画像認識などで汎用性の高い学習済みニューラルネットモデルの途中までの隠れ層構造とその重みをそのまま使うことで、画像識別としての一般的な特徴量を抽出し、後半で目的に応じた新しい隠れ層構造とその重みを学習させるというようなイメージです。

今回試すこと

今回は画像識別のCNNモデルとして精度の高いVGG16を転移学習でカスタマイズして、性別を識別するCNNモデルを作成したいと思います。

学習素材はケープタウン大学のThe MUCT Face Databaseを活用させていただきました(個人利用のみ可)。

今回は男女300枚ずつの画像を学習素材として、男女100枚ずつの画像をバリデーション素材として、男女混合の8枚をテスト用画像として使用しました。

ちなみに学習素材機械学習をまとめてくださっている親切なサイトがありましたので紹介しておきます。
Md.Lab/機械学習用データセット一覧

開発環境

OS:MacOS Catalina 10.15.2
言語:Python3.5.4
開発ツール:jupyter notebook
使用ライブラリ:
・tensorflow 1.5.0(要install)
・pillow 5.0.0(tensorflowが内部で利用)(要install)
・matplotlib 2.2.2(utilsが内部で利用)(要install)
・numpy 1.17.4(utilsが内部で利用)(要install)
・pandas 0.22.0(utilsが内部で利用)(要install)
・math(標準ライブラリ)
・os(標準ライブラリ)
・json(標準ライブラリ)
・pickle(標準ライブラリ)
補助プログラム:
utils.py(テスト時のデータ入出力に利用)
→githubで公開されている方のものを利用させていただきました。
実行するプログラムと同じディレクトリに保存しておいてください。

転移学習のプログラム作成

転移学習プログラムとして作成したコードを以下に貼り付けました。

細かい説明はコード中に記載しましたが、プログラムのアウトラインとしては以下のようになっています。

①既存モデルの改良(VGG16の出力層を消して新しい隠れ層を追加する)
②学習素材取り込みの準備
③モデルデータの保存準備
④Callbackの設定
⑤学習の実行

#転移学習の実行プログラム(VGG16をベースとして性別を判定するCNN)

from tensorflow.python.keras.applications.vgg16 import VGG16, preprocess_input
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.layers import Dense, Dropout, Flatten
from tensorflow.python.keras.optimizers import SGD
from tensorflow.python.keras.preprocessing.image import ImageDataGenerator
from tensorflow.python.keras.callbacks import ModelCheckpoint, CSVLogger
import os
import json
import pickle
from datetime import datetime
import math
 
#①既存モデルの改良

# `include_top=False`として既存モデルの出力層を消す。
vgg16 = VGG16(include_top=False, input_shape=(224, 224, 3))

# モデルを編集する。
model = Sequential(vgg16.layers)

    # 全19層のうち15層目までは再学習しないようにパラメータを固定する。
for layer in model.layers[:15]:
    layer.trainable = False
    # 出力層の部分を追加
model.add(Flatten())
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(1, activation='sigmoid'))

# 最適化アルゴリズムをSGD(確率的勾配降下法)とし最適化の学習率と修正幅を指定してコンパイルする。
model.compile(
    loss='binary_crossentropy',
    optimizer=SGD(lr=1e-4, momentum=0.9),
    metrics=['accuracy']
)

#②学習素材取り込みの準備

# 学習画像を取り込むジェネレータを作成。それぞれのパラメータを設定
img_gen = ImageDataGenerator(
    rescale=1/255.,
    shear_range=0.1,
    zoom_range=0.1,
    horizontal_flip=True,
    preprocessing_function=preprocess_input
)

# 画像を取り込むためのイテレータを生成(訓練データとバリデーションデータ)
train_itr = img_gen.flow_from_directory(
    'img/male_female/train', 
    target_size=(224, 224),
    batch_size=16,
    class_mode='binary'
)
val_itr = img_gen.flow_from_directory(
    'img/male_female/validation', 
    target_size=(224, 224),
    batch_size=16,
    class_mode='binary'
)

#③モデルデータの保存準備

# モデル保存用のディレクトリを準備
model_dir = os.path.join(
    'models', 
    datetime.now().strftime('%y%m%d_%H%M')
)

os.makedirs(model_dir, exist_ok=True)
dir_weights = os.path.join(model_dir, 'weights')
os.makedirs(dir_weights, exist_ok=True)

# ネットワークの保存
model_json = os.path.join(model_dir, 'model.json')
with open(model_json, 'w') as f:
    json.dump(model.to_json(), f)
# 学習時の正解ラベルの保存
model_classes = os.path.join(model_dir, 'classes.pkl')
with open(model_classes, 'wb') as f:
    pickle.dump(train_itr.class_indices, f)
  
# 1エポックのバッチ数を算出
# ceilは与えられた数以上の最小整数を出力する関数
batch_size = 16
steps_per_epoch = math.ceil(
    train_itr.samples/batch_size
)
validation_steps = math.ceil(
    val_itr.samples/batch_size
)

#④Callbacksの設定
# ここでは重みを5エポックごとに保存する設定
cp_filepath =  os.path.join(dir_weights, 'ep_{epoch:02d}_ls_{loss:.1f}.h5')
cp = ModelCheckpoint(
                     cp_filepath, 
                     monitor='loss', 
                     verbose=0,
                     save_best_only=False, 
                     save_weights_only=True, 
                     mode='auto', 
                     period=5
                     )
csv_filepath =  os.path.join(model_dir, 'loss.csv')
csv = CSVLogger(csv_filepath, append=True)

# ⑤学習の実行
history = model.fit_generator(
    train_itr, 
    steps_per_epoch=steps_per_epoch, 
    epochs=30,  # 学習するエポック数
    validation_data=val_itr, 
    validation_steps=validation_steps,
    callbacks = [cp, csv]
)

学習結果

学習プログラムを実行すると、下図のように学習が進んでいきます。
今回は300epochを指定しましたが、学習が全て終了するまでに約5時間程度かかりました(結構長い!)。

学習結果の曲線を示した結果が以下になります。

# 学習結果曲線の表示
%matplotlib inline
from utils import plot_learningcurve_from_csv

plot_learningcurve_from_csv(csv_filepath)

正確性(acc)、誤差(loss)ともにepochが進むにつれて良化していく様子がみて取れます。
どちらも30epochである程度曲線が十分ねてきているので、epoch数としては適切だったかと思います。

テストの実行と評価結果

さてそれでは、VGG16をカスタマイズして新たに学習させて作ったモデルでテストをしてみましょう。

以下プログラムで未知画像8枚の性別判定を実行してみます。

#テスト用プログラム

from utils import load_random_imgs, show_test_samples

# 評価の実施
test_data_dir = 'img/male_female/test'
x_test, true_labels = load_random_imgs(
    test_data_dir, 
    seed=1
)
x_test_preproc= preprocess_input(x_test.copy())/255.
probs = model.predict(x_test_preproc)

# 評価結果の表示
show_test_samples(
    x_test, probs, 
    train_itr.class_indices, 
    true_labels
)

以下が結果になります。

上段:男100%(正解),女99.9%(正解),女98.5%(正解),男100%(正解)
下段:女99.8%(正解),女99.8%(正解),女100%(正解),女100%(正解)

テスト結果は全て正解でした!

確率の数値を見ても一切迷いなく性別を判定できており、かなり精度の高いモデルが完成していそうです。

まとめ

今回は、転移学習でVGG16をカスタマイズして男女を判定するCNNモデルを作成してみました。

評価結果よりかなり精度の高いモデルを作成できたことが確認できました。

転移学習を使うとコストをかけずに精度の高いモデルを組むことができますので、是非試してみてはいかがでしょうか。

参考資料

今回の記事は、以下の書籍を参考にさせていただきました。

機械学習カテゴリの最新記事