前回は「転移学習」について紹介し、転移学習を使えば少量のデータであっても高い精度を出せるかもしれないという話をしました。今回は転移学習よりもさらに高い精度を出せるかもしれない「ファインチューニング」について解説していきます。
ファインチューニングとは
ファインチューニングを日本語に訳すと「微調整」という意味になります。このファインチューニングという言葉は古くから理論物理学や金融の分野で使用されてきました。
近年、特に機械学習の分野ではファインチューニングは「モデルのパラメータを微調整すること」という意味で用いられています。
しかしながら機械学習分野で「ファインチューニングとは、〇〇というものだ」といった厳密な定義や方法はなく、そのような論文を見つけることも出来ませんでした。
※Fine-Tuningについて解説している論文は発見することが出来ました。CNNについて話を限定していますが、他のケースであってもファインチューニングだと考えて良さそうです。
Fine-Tuning modifies the parameters of an existing CNN to train a new task. The output layer is extended with randomly intialized weights for the new task, and a small learning rate is used to tune all parameters from their original values to minimize the loss on the new task. Sometimes, part of the network is frozen Fine-Tuningは、既存のCNNのパラメータを変更して新しいタスクを学習させる。出力層は新しいタスクのためにランダムに初期化された重みで拡張され、小さな学習率を用いてすべてのパラメータを元の値から調整し、新しいタスクの損失を最小にする。時には,ネットワークの一部が凍結されることもある。 |
この記事では「ファインチューニングとはモデルのパラメーターを微調整すること」として先に進めていきます。
転移学習との違い
前回、転移学習とは「別の問題で得た知識を用いて、問題を解決するために知識を活用すること」であると紹介しました。ピアノを長年やっていた人は別の楽器の習得も早いというケースを例に取り上げました。
自前のデータを使って学習済みのモデルのパラメーターを更新する(再学習させる)ことは上記の意味合いを満たしているので転移学習にあたります。この更新が微調整だった場合には、モデルのパラメーターを微調整していると捉えることが出来るので、ファインチューニングは転移学習の一種であると言えます。
「じゃあ、どこまでがファインチューニングになるのか」と疑問に思いますが、ファインチューニングについては厳密な定義がないため、「ここまでがファインチューニングだ」と判断することが難しいです。
その一方で、実装上では転移学習とファインチューニングは明確に区別がされているので、順に見ていきましょう。
実装方法の違い
転移学習では既存の学習済みのモデルの末端に学習可能な重みを持つ層を新たに追加します。学習をする際は学習済みモデルの全ての重みに対して更新は行わず、新たに追加した末端の層の重みだけを更新します。
それに対して、ファインチューニングでは新たに追加した末端の学習可能な重みを持つ層と合わせて、既存の学習済みモデルの層の重みも更新します。
実装時の2つの注意点
1. 学習済みモデルの全層を更新しない
ファインチューニングでは既存の学習モデルの全ての層を更新するのではなく、先端からいくつかの層までは重みの更新を行わないのが一般的です。
これは深層学習では全結合層(Fully Connected layer)を持つモデルが主流なため、層同士の出力と入力の関係性が非常に重要です。先端から全ての層を学習させてしまうと末端まで非常に多くの更新が必要になり、せっかく学習した内容をモデルが忘れてしまいます。
これでは0からモデルを学習させる状態とほとんど同じなため、ファインチューニングのメリットがなくなってしまいます。
2.学習率を小さな値にする
ファインチューニングを行う際は、過学習を回避するために学習率を小さな値にすることが推奨されています。先端からいくらかの層の重みを更新しないため、学習可能な層の数が少なくなります。その状態で、学習率に大きな値を採用してしまうと、少ない層で大幅に重みの更新がされてしまうため、過学習が発生しやすくなります。
そのため、大胆に学習済みモデルの重みを更新させずに、少しずつ更新させるために学習率を低く設定します。
この後、先端から固定する層の数と学習率を変化させて精度がどれだけ変わるのか、また転移学習を行なった際との精度を比較していきましょう。
ファインチューニングの実装
使用するのは前回の転移学習で使用した kaggle の shoe-dataset です。画像が6種類の靴の何に該当するかを予測するためのデータセットです。詳細については 前回の記事 で紹介しているので、ぜひご覧ください。
DATASET_DIR = "shoeTypeClassifierDataset" TRAIN_DIR = "training" TRAIN_DATASET_PATH = os.path.join(DATASET_DIR, TRAIN_DIR) classes = os.listdir(TRAIN_DATASET_PATH) # ['soccer_shoes', 'sandals', 'boots', 'sneakers', 'loafers', 'flip_flops'] |
今回も前回同様に特別な前処理は行わず、単純なカラーのまま(rgb)、画像サイズを160x160にに変換しました。また、ハイパーパラメーターに関しては学習率以外は転移学習時と同値を採用しました。
- 実装: tensorflow(tensorflow.keras)
- ImageNetを用いた学習済みモデルを使用
- 学習率: 0.00001
- エポック数: 50
- 最適化関数: tf.keras.optimizers.RMSpropを使用
- 損失関数: sparse_categorical_crossentropyを使用
値はGoogleの転移学習チュートリアルを参考に決定しました。
MobileNet V2を使ったファインチューニング
まずはtensorflowの公式のチュートリアルに沿って、全部で164層あるうちの先端から100層までを学習不可としてファインチューニングをします。
inputs = tf.keras.Input(shape=(160, 160, 3)) x = tf.keras.layers.Lambda(tf.keras.applications.mobilenet_v2.preprocess_input)(inputs) base_model = tf.keras.applications.mobilenet_v2.MobileNetV2( weights='imagenet', input_tensor=x, input_shape=(160, 160, 3), include_top=False, pooling='avg' ) |
for文とスライスを用いて先端から100層までを学習不可に設定します。
# モデルを学習可能な状態に変更 & 100層までを学習可能な層に変更 base_model.trainable = True for layer in base_model.layers[:100]: layer.trainable = False |
これで準備は完了です。モデルをコンパイルして学習させます。
model = tf.keras.Sequential([base_model, tf.keras.layers.Dense(len(classes), activation='softmax')]) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.00001), loss='sparse_categorical_crossentropy', metrics=['accuracy'] ) model.fit(train_images, train_labels, epochs=50, validation_split=0.2, batch_size=256) |
結果はこちらです。最終的な精度(バリデーション)は約59%に落ち着きました。
# Fine-Tuning model.evaluate(test_images, test_labels, verbose=0) # [1.0383726358413696, 0.6133333444595337] |
# 転移学習 model.evaluate(test_images, test_labels, verbose=0) # [1.078682780265808, 0.6200000047683716] |
テストデータでの精度は61%とまずまずの結果の精度とですが、転移学習の時とほとんど同じ精度になってしまいました。
学習可能な層の比較
次は先端から固定する層の数を変えて比較してみます。
- 0層: 学習済みモデルで更新される重みはない(転移学習と同じ状態)
- 50層: 0と100の中間のため選抜
- 100層: tensorflowのチュートリアルに従って選抜(※実施済み)
- 164層: MobileNetV2の全層。よって全ての層の重みが更新される
layer_size = 0 # 任意の数に変更 for layer in base_model.layers[:layer_size]: layer.trainable = False |
結果を表で見比べてみます。
最も高い精度を記録したのは50の場合です。逆に最も低い精度を記録したのは全ての層を学習させた164の場合です。まさにこれは上記の注意点で記述したように「先端から全ての層を学習させると学習したことを忘れてしまう」という現象が発生していると判断できます。
なぜ50が良いのかという具体的な根拠は説明が出来ませんが、最終的なloss値を比較してみると、0と50と100でほとんど差がないため、50が一番上手くいったという程度に考えておきます。
次は最も精度がよかった固定する層の数が50の時に学習率を変えて比較してみます。
学習率の比較
ファインチューニングでは名前の通り微調整を行うため、通常の学習時、転移学習時と比べて低い学習率を設定することが推奨されています。
先ほどは0.00001で試してみましたが、他の数値ではどうでしょうか。以下の3つで試してみます。
- 0.0001: 転移学習時と同じ学習率
- 0.00001: tensorflowチュートリアルに従って抜粋(※実施済み)
- 0.000001: 単純に0.00001を0.1倍。もっと微調整
ただし同じエポック数を設定してしまうと学習率が低い分、十分に学習出来ずに終わってしまう可能性があります。なので学習率に合わせて適当にエポック数も変更しています。結果を表で比較してみます。
転移学習の時と同じ学習率0.0001を採用した場合に約80%と最高の精度を記録しました。
最終的なloss値を比べても、0.0001の時に最も値が小さくなっていることが分かります。今回のケースだと多少、大胆に重みを更新させた方が上手く予測することが出来たようです。画像の種類や性質がImageNetと異なっていたのでしょうか。
学習時間はかかってしまいますが、0.000001の場合でも学習している様子がloss値の推移からも分かります。その一方で今回のような少量のデータで0.00001や0.000001と比べて0.0001と大きな学習率を採用しているので過学習が起きやすくなることには注意が必要です。
まとめ
いかがだったでしょうか。今回はファインチューニングとは何かに始まり、ファインチューニングの実装と比較までを行いました。
ファインチューニングとはモデルのパラメーターを微調整することで、実装上では転移学習の場合とは異なり、新たに追加した末端の学習可能な層だけではなく、既存の学習済みモデルの層も合わせて更新します。
この際に全ての層を更新してしまうと、学習した内容を忘れてしまうので先端からいくつかの層は更新させないようにするのが有効です。
転移学習では最終的な精度が約62%となりましたが、今回のファインチューニングでは約72%の精度を得ることが出来ました。ファインチューニングは転移学習と合わせて簡単に行うことが出来ます。もし転移学習で思ったような精度が出なかった時はぜひファインチューニングを試してみてください!