Path: blob/master/site/ja/guide/keras/transfer_learning.ipynb
25118 views
Copyright 2020 The TensorFlow Authors.
転移学習とファインチューニング
セットアップ
はじめに
ある問題で学習した特徴量を取り入れ、それを新しい類似した問題に利用する方法を転移学習と呼びます。たとえば、アライグマの識別を学習したモデルの特徴量がある場合、それを使用してタヌキの識別を学習するモデルに取り組むことができます。
通常、転移学習はデータセットのデータが少なすぎてフルスケールモデルをゼロからトレーニングできないようなタスクで行われます。
ディープラーニングの文脈では、転移学習は次のワークフローで行われるのが最も一般的です。
以前にトレーニングされたモデルからレイヤーを取得します。
以降のトレーニングラウンドでそれらのレイヤーに含まれる情報が破損しないように凍結します。
凍結したレイヤーの上にトレーニング対象のレイヤーを新たに追加します。これらのレイヤーは古い特徴量を新しいデータセットの予測に変換することを学習します。
データセットで新しいレイヤーをトレーニングします。
最後に任意でファインチューニングを実施できます。ファインチューニングでは、上記で取得したモデル全体(または一部)を解凍し、新しいデータに対して非常に低い学習率で再トレーニングします。これを実施すると、事前トレーニング済みの特徴量を徐々に新しいデータに適応させ、意味のある改善を得られることがあります。
まずは、転移学習とファインチューニングのほとんどのワークフローの基礎である、Keras の trainable
API について詳しく見てみましょう。
次に、一般的なワークフローを説明します。ImageNet データセットで事前にトレーニングされたモデルを取得してそれを Kaggle の「犬と猫」分類データセットで再トレーニングしてみましょう。
これは、Deep Learning with Python および 2016 年のブログ記事「少ないデータで強力な画像分類モデルを構築する」を基にしています。
レイヤーの凍結: trainable
属性を理解する
レイヤーとモデルには 3 つの重み属性があります。
weights
は、レイヤーのすべての重み変数のリストです。trainable_weights
は、トレーニング中の損失を最小限に抑えるために(勾配降下を介して)更新を意図した重みのリストです。non_trainable_weights
は、トレーニングを意図していない重みのリストです。通常は、フォワードパスの間にモデルによって更新されます。
例: Dense
レイヤーに、トレーニング対象の重みが 2 つ(カーネルとバイアス)がある
一般的に、重みはすべてトレーニング対象の重みです。トレーニング対象外の重みレイヤーを持つ組み込みレイヤーは、BatchNormalization
レイヤーしかありません。これはトレーニング対象外の重みを使用して、トレーニング中の入力の平均と分散を追跡します。独自のカスタムレイヤーでトレーニング対象外の重みを使用する方法については、レイヤーの新規作成ガイドをご覧ください。
例: BatchNormalization
レイヤーに 、トレーニング対象の重みとトレーニング対象外の重みが 2 つずつある
レイヤーとモデルには、ブール属性の trainable
もあり、その値を変更することができます。layer.trainable
を False
に設定すると、すべてのレイヤーの重みがトレーニング対象からトレーニング対象外に移動されます。これはレイヤーの「凍結」と呼ばれるもので、凍結されたレイヤーの状態はトレーニング中に更新されません(fit()
でトレーニングする場合、またはtrainable_weights
に依存して勾配の更新を適用するカスタムループでトレーニングする場合)。
例: trainable
を False
に設定する
トレーニング対象の重みがトレーニング対象外の重みになると、その値はトレーニング中に更新されなくなります。
layer.trainable
属性を layer.__call__()
の引数 training
と混同しないようにしてください(後者は、レイヤーがフォワードパスを推論モードで実行するか、トレーニングモードで実行するかを制御します)。詳細については、Keras よくある質問をご覧ください。
trainable
属性を再帰的に設定する
モデルや、サブレイヤーのあるレイヤーで trainable = False
を設定すると、すべての子レイヤーもトレーニング対象外になります。
例:
典型的な転移学習のワークフロー
ここでは、典型的な転移学習のワークフローを Keras に実装する方法を示します。
ベースモデルをインスタンス化し、それに事前トレーニング済みの重みを読み込みます。
trainable = False
を設定して、ベースモデルのすべてのレイヤーを凍結します。ベースモデルの 1 つ以上のレイヤーの出力上に新しいモデルを作成します。
新しいデータセットで新しいモデルをトレーニングします。
これに代わる、より軽量なワークフローとして、以下のようなものも考えられます。
ベースモデルをインスタンス化し、それに事前トレーニング済みの重みを読み込みます。
新しいデータセットを実行して、ベースモデルの 1 つ以上のレイヤーの出力を記録します。特徴量抽出と呼ばれる作業です。
その出力を新しい小さなモデルの入力データとして使用します。
この 2 番目のワークフローには、トレーニングのエポックごとに 1 回ではなく、データに対して 1 回だけベースモデルを実行するため、はるかに高速で安価になるというメリットがあります。
ただし、トレーニング中に新しいモデルの入力データを動的に変更することができないという問題があります。これはデータを拡張する際などに必要なことです。転移学習は通常、フルスケールでモデルを新規にトレーニングするには新しいデータセットのデータ量が少なすぎる場合に使用しますが、そのような場合、データの拡張が非常に重要になります。そこで以降では、1 番目のワークフローに焦点を当てます。
1 番目のワークフローは、Keras では以下のようになります。
まず最初に、事前トレーニング済みの重みを使用してベースモデルをインスタンス化します。
次に、ベースモデルを凍結します。
その上に新しいモデルを作成します。
新しいデータでモデルをトレーニングします。
ファインチューニング
モデルが新しいデータで収束したら、ベースモデルのすべてまたは一部を解凍して、非常に低い学習率でエンドツーエンドでモデル全体を再トレーニングすることができます。
これは任意に行える最後のステップではありますが、段階的な改善を期待することができます。ただし、すぐに過適合になる可能性もあることに注意してください。
このステップは、凍結レイヤーのあるモデルが収束するまでトレーニングされた後にのみ行うことが重要です。ランダムに初期化されたトレーニング対象レイヤーと事前にトレーニングされた特徴量を持つトレーニング対象レイヤーを混ぜると、トレーニング中に、ランダムに初期化されたレイヤーによって非常に大きな勾配の更新が発生し、事前にトレーニングされた特徴量が破損してしまうことになります。
また、この段階では学習率が非常に低いことも重要です。1 回目のトレーニングよりもはるかに大きなモデルを、非常に小さなデータセットでトレーニングするからです。その結果、大量の重みの更新を適用すると、あっという間に過適合が起きてしまう危険性があります。ここでは、事前トレーニング済みの重みを段階的に適応し直します。
ベースモデル全体のファインチューニングを実装するには、以下のようにします。
compile()
および trainable
に関する重要な注意事項
モデルで compile()
を呼び出すと、そのモデルの動作が「凍結」されます。これは、モデルがコンパイルされたときの trainable
属性の値は、compile
が再び呼び出されるまで、そのモデルの寿命が続く限り保持されるということです。したがって、trainable
の値を変更した場合には、その内容が考慮されるように必ずモデルでもう一度 compile()
を呼び出してください。
BatchNormalization
レイヤーに関する重要な注意事項
多くの画像モデルには BatchNormalization
レイヤーが含まれています。このレイヤーは、あらゆる点において特殊なケースです。ここにいくつかの注意点を示します。
BatchNormalization
には、トレーニング中に更新されるトレーニング対象外の重みが 2 つ含まれています。これらは入力の平均と分散を追跡する変数です。bn_layer.trainable = False
を設定すると、BatchNormalization
レイヤーは推論モードで実行されるため、その平均と分散の統計は更新されません。重みのトレーナビリティと推論/トレーニングモードは 2 つの直交する概念であるため、これは一般的には他のレイヤーには当てはまりませんが、BatchNormalization
レイヤーの場合は、この 2 つは関連しています。ファインチューニングを行うために
BatchNormalization
レイヤーを含むモデルを解凍する場合、ベースモデルを呼び出す際にtraining = False
を渡してBatchNormalization
レイヤーを推論モードにしておく必要があります。推論モードになっていない場合、トレーニング対象外の重みに適用された更新によって、モデルが学習したものが突然破壊されてしまいます。
このガイドの最後にあるエンドツーエンドの例で、このパターンの動作を確認することができます。
カスタムトレーニングループで転移学習とファインチューニングをする
fit()
の代わりに独自の低レベルのトレーニングループを使用している場合でも、ワークフローは基本的に同じです。勾配の更新を適用する際には、model.trainable_weights
のリストのみを考慮するように注意する必要があります。
ファインチューニングの場合も同様です。
エンドツーエンドの例: 猫と犬データセットの画像分類モデルのファインチューニング
この概念を固めるために、具体的なエンドツーエンドの転移学習とファインチューニングの例を見てみましょう。ImageNet で事前トレーニングされた Xception モデルを読み込み、Kaggleの 「犬と猫」分類データセットで使用します。
データを取得する
まず、TFDS を使用して犬と猫のデータセットを取得してみましょう。独自のデータセットをお持ちの場合は、tf.keras.preprocessing.image_dataset_from_directory
ユーティリティを使用して、クラス固有のフォルダにファイル作成されたディスク上の画像集合から類似のラベル付きデータセットオブジェクトを生成することもできます。
転移学習は、非常に小さなデータセットを扱う場合に最も有用です。データセットを小さく保つために、元のトレーニングデータ(画像 25,000 枚)の 40 %をトレーニングに、10 %を検証に、10 %をテストに使用します。
ここにトレーニングデータセットの最初の 9 枚の画像があります。ご覧の通り、サイズはバラバラです。
また、ラベル 1 が「犬」、ラベル 0 が「猫」であることもわかります。
データを標準化する
生の画像には様々なサイズがあります。さらに、各ピクセルは 0 ~ 255 の 3 つの整数値(RGB レベル値)で構成されています。これは、ニューラルネットワークへの供給には適しません。次の 2 つを行う必要があります。
標準化して画像サイズを固定します。 150x150 を選択します。
ピクセル値を -1 〜 1 に正規化します。これはモデル自体の一部として
Normalization
レイヤーを使用して行います。
一般的に、すでに処理済みのデータを使用するモデルとは対照的に、入力に生のデータを使用するモデルを開発するのは良い実践です。その理由は、モデルが前処理されたデータを期待していると、モデルをエクスポートして他の場所(ウェブブラウザやモバイルアプリ)で使用する際には、まったく同じ前処理パイプラインを常に再実装する必要が生じるからです。これはすぐに非常に面倒なことになります。だからこそ、モデルを使用する前に可能な限りの前処理を行う必要があるのです。
ここでは、(ディープニューラルネットワークは連続したデータバッチしか処理できないので)データパイプラインで画像のリサイズを行い、モデルを作成する際にモデルの一部として入力値のスケーリングを行います。
画像を 150×150 にリサイズしてみましょう。
さらに、データをバッチ処理して、キャッシング&プリフェッチを使用し、読み込み速度を最適化してみましょう。
ランダムデータ拡張を使用する
大規模な画像データセットを持っていない場合には、ランダムに水平反転や少し回転を加えるなど、ランダムでありながら現実的な変換をトレーニング画像に適用し、サンプルの多様性を人為的に導入するのが良い実践です。これによって、過適合を遅らせると同時にトレーニングデータの異なった側面にモデルを公開することができます。
さまざまなランダム変換の後、最初のバッチの最初の画像がどのように見えるかを可視化してみましょう。
モデルを構築する
では、先ほど説明した青写真に沿ってモデルを構築してみましょう。
注意点:
Rescaling
レイヤーを追加して、入力値(最初の範囲は[0, 255]
)を[-1, 1]
の範囲にスケーリングします。正則化のために、分類レイヤーの前に
Dropout
レイヤーを追加します。ベースモデルを呼び出す際に
training=False
を渡して推論モードで動作するようにし、ファインチューニングを実行するためにベースモデルを解凍した後でも BatchNorm の統計が更新されないようにします。
トップレイヤーをトレーニングする
モデル全体のファインチューニングを行う
最後に、ベースモデルを解凍して、モデル全体のエンドツーエンドを低い学習率でトレーニングしてみましょう。
重要なのは、モデル構築時の呼び出しで training=False
を渡しているため、ベースモデルがトレーニング対象になっても、推論モードで動作しているということです。つまり、内側のバッチ正則化レイヤーのバッチ統計は更新されません。更新してしまうと、それまでにモデルが学習してきた表現が破壊されてしまいます。
10エポック後、ファインチューニングによって有益な改善が得られます。