TensorFlow 2.4 : ガイド : Keras :- 転移学習と再調整 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 01/22/2021
* 本ページは、TensorFlow org サイトの Guide – Keras の以下のページを翻訳した上で
適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
- Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
人工知能研究開発支援 | 人工知能研修サービス | テレワーク & オンライン授業を支援 |
PoC(概念実証)を失敗させないための支援 (本支援はセミナーに参加しアンケートに回答した方を対象としています。 |
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション |
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/ |
Facebook: https://www.facebook.com/ClassCatJP/ |
ガイド : Keras :- 転移学習と再調整
セットアップ
import numpy as np import tensorflow as tf from tensorflow import keras
イントロダクション
転移学習 は 1 つの問題上で学習された特徴を取り、そしてそれらを新しい、類似の問題上で活用することから成ります。例えば、アライグマ (= raccoon) を識別するために学習したモデルからの特徴はタヌキを識別することを意図したモデルを始動するために有用かもしれません。
転移学習はスクラッチから本格的な (= full-scale) モデルを訓練するには貴方のデータセットが少な過ぎるデータを持つようなタスクのために通常は行なわれます。
深層学習のコンテキストで転移学習の最も一般的な具現化は以下のワークフローです :
- 前に訓練されたモデルから層を取ります。
- 今後の訓練ラウンドの間にそれらが含む任意の情報を壊すことを避けるために、それらを凍結します。
- 凍結された層の上に幾つかの新しい、訓練可能な層を追加します。それらは古い特徴を新しいデータセット上の予測に変えるために学習します。
- 貴方のデータセット上で新しい層を訓練します。
最後の、オプションのステップは 再調整 で、これは上で得たモデル全体 (あるいはその一部) を解凍して、非常に低い学習率で新しいデータ上でそれを再訓練することから成ります。事前訓練された特徴を新しいデータに漸増的事前に適応させることによりこれは潜在的には意味のある改良を獲得できます。
最初に、Keras 訓練可能 API を詳細に調べます、これは殆どの転移学習 & 再調整ワークフソーの基礎となります。
そして、ImageNet データセット上で事前訓練されたモデルを取り、そしてそれを Kaggle “猫 vs 犬” 分類データセット上で再訓練することにより典型的なワークフローを実演します。
これは Deep Learning with Python と 2016 ブログ投稿 “building powerful image classification models using very little data” から採用されました。
層を凍結する : 訓練可能な属性を理解する
層 & モデルは 3 つの重み属性を持ちます :
- weights は層の総ての重み変数のリストです。
- trainable_weights は訓練の間に損失を最小化するために (勾配降下を通して) 更新されることを意図したそれらのリストです。
- non_trainable_weights は訓練されることを意図していないそれらのリストです。典型的にはそれらは forward パスの間にモデルにより更新されます。
サンプル: Dense 層は 2 つの訓練可能な重み (カーネル & バイアス) を持ちます。
layer = keras.layers.Dense(3) layer.build((None, 4)) # Create the weights print("weights:", len(layer.weights)) print("trainable_weights:", len(layer.trainable_weights)) print("non_trainable_weights:", len(layer.non_trainable_weights))
weights: 2 trainable_weights: 2 non_trainable_weights: 0
一般に、総ての重みは訓練可能な重みです。非訓練可能な重みを持つ唯一の組込み層は BatchNormalization 層 です。訓練の間 その入力の平均と分散を追跡するために非訓練可能な重みを利用します。貴方自身のカスタム層で非訓練可能な重みをどのように使用するかを学習するためには、writing new layers from scratch へのガイド を見てください。
サンプル: BatchNormalization 層は 2 つの訓練可能な重みと 2 つの非訓練可能な重みを持つ。
layer = keras.layers.BatchNormalization() layer.build((None, 4)) # Create the weights print("weights:", len(layer.weights)) print("trainable_weights:", len(layer.trainable_weights)) print("non_trainable_weights:", len(layer.non_trainable_weights))
weights: 4 trainable_weights: 2 non_trainable_weights: 2
層 & モデルはまたブーリアン属性 trainable も持ちます。その値は変更可能です。layer.trainable を False を設定すると総ての層の重みを訓練可能から非訓練可能に移します。これは層を「凍結する (= freezing)」と呼称されます : (fit() で訓練するとき、あるいは勾配更新を適用するために trainable_weights に依拠する任意のカスタムループで訓練するときのいずれかの) 訓練の間に凍結された層の状態は更新されません。
サンプル: trainable を False に設定します
layer = keras.layers.Dense(3) layer.build((None, 4)) # Create the weights layer.trainable = False # Freeze the layer print("weights:", len(layer.weights)) print("trainable_weights:", len(layer.trainable_weights)) print("non_trainable_weights:", len(layer.non_trainable_weights))
weights: 2 trainable_weights: 0 non_trainable_weights: 2
訓練可能な重みが非訓練可能になるとき、その値は訓練の間にもはや更新されません。
# Make a model with 2 layers layer1 = keras.layers.Dense(3, activation="relu") layer2 = keras.layers.Dense(3, activation="sigmoid") model = keras.Sequential([keras.Input(shape=(3,)), layer1, layer2]) # Freeze the first layer layer1.trainable = False # Keep a copy of the weights of layer1 for later reference initial_layer1_weights_values = layer1.get_weights() # Train the model model.compile(optimizer="adam", loss="mse") model.fit(np.random.random((2, 3)), np.random.random((2, 3))) # Check that the weights of layer1 have not changed during training final_layer1_weights_values = layer1.get_weights() np.testing.assert_allclose( initial_layer1_weights_values[0], final_layer1_weights_values[0] ) np.testing.assert_allclose( initial_layer1_weights_values[1], final_layer1_weights_values[1] )
1/1 [==============================] - 1s 565ms/step - loss: 0.1359
layer.trainable 属性を layer.__call__() の引数 training と混同しないでください (これは層がその forward パスを推論モードか訓練モードのいずれで実行するべきかを制御します)。より多くの情報については、Keras FAQ を見てください。
trainable 属性の再帰的な設定
モデルか副層 (= sublayer) を持つ任意の層上で trainable = False を設定する場合、総ての子層 (= children layers) もまた非訓練になります。
サンプル:
inner_model = keras.Sequential( [ keras.Input(shape=(3,)), keras.layers.Dense(3, activation="relu"), keras.layers.Dense(3, activation="relu"), ] ) model = keras.Sequential( [keras.Input(shape=(3,)), inner_model, keras.layers.Dense(3, activation="sigmoid"),] ) model.trainable = False # Freeze the outer model assert inner_model.trainable == False # All layers in `model` are now frozen assert inner_model.layers[0].trainable == False # `trainable` is propagated recursively
典型的な転移学習ワークフロー
これは典型的な転移学習ワークフローが Keras でどのように実装できるかに導いてくれます :
- 基底モデルをインスタンス化して事前訓練された重みをその中にロードします。
- trainable = False を設定して基底モデルの総ての層を凍結します。
- 基底モデルからの一つ (or 幾つか) の層の出力の上に新しいモデルを作成します。
- 貴方の新しいデータセット上で新しいモデルを訓練します。
代替の、より軽量なワークフローはまた次のようなものであり得ることに注意してください :
- 基底モデルをインスタンス化して事前訓練された重みをその中にロードします。
- それを通して新しいデータセットを実行してそして基底モデルからの一つ (or 幾つか) の層の出力を記録します。これは 特徴抽出 と呼ばれます。
- その出力を新しい、より小さなモデルのための入力データとして利用します。
2 番目のワークフローの主要な優位点は基底モデルを (訓練のエポック毎に 1 度ではなく) 貴方のデータ上 1 度実行するだけであることです。従ってそれは遥かに高速で & 安価です。
けれども、その 2 番目のワークフローに伴う問題は、それは訓練の間に貴方の新しいモデルの入力データを動的に変更することを可能にしないことです、これは例えば、データ増強を行なうときに必要です。転移学習は典型的には貴方の新しいデータセットが完全な (= full-scale) モデルをスクラッチから訓練すためには非常に少ないデータを持つときタスクのために使用され、そしてそのようなシナリオではデータ増強は非常に重要です。そのため以下では、最初のワークフローにフォーカスします。
ここに最初のワークフローが Keras でどのようなものかがあります :
最初に、事前訓練された重みで基底モデルをインスタンス化します。
base_model = keras.applications.Xception( weights='imagenet', # Load weights pre-trained on ImageNet. input_shape=(150, 150, 3), include_top=False) # Do not include the ImageNet classifier at the top.
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/xception/xception_weights_tf_dim_ordering_tf_kernels_notop.h5 83689472/83683744 [==============================] - 1s 0us/step
それから、基底モデルを凍結します。
base_model.trainable = False
上に新しいモデルを作成します。
inputs = keras.Input(shape=(150, 150, 3)) # We make sure that the base_model is running in inference mode here, # by passing `training=False`. This is important for fine-tuning, as you will # learn in a few paragraphs. x = base_model(inputs, training=False) # Convert features of shape `base_model.output_shape[1:]` to vectors x = keras.layers.GlobalAveragePooling2D()(x) # A Dense classifier with a single unit (binary classification) outputs = keras.layers.Dense(1)(x) model = keras.Model(inputs, outputs)
新しいデータでモデルを訓練します。
model.compile(optimizer=keras.optimizers.Adam(), loss=keras.losses.BinaryCrossentropy(from_logits=True), metrics=[keras.metrics.BinaryAccuracy()]) model.fit(new_dataset, epochs=20, callbacks=..., validation_data=...)
再調整
貴方のモデルが新しいデータ上でひとたび収束したのであれば、基底モデルの総てまたは一部を解凍してそして非常に小さい学習率でモデル全体を end-to-end に再訓練することを試すができます。
これは増加的な (= incremental) 改良を潜在的に与えられるオプションの最後のステップです。それはまた潜在的に素早い overfitting に繋がる可能性があります — それに留意してください。
凍結された層を持つモデルが収束まで訓練された後だけにこのステップを行なうことが不可欠です。もしランダムに初期化された訓練可能な層を事前訓練された特徴を保持する訓練可能な層と混在させれば、ランダムに初期化された層は訓練の間に非常に大きな勾配更新を引き起こすでしょう、これは事前訓練された特徴を破壊します。
この段階で非常に低い学習率を利用することも重要です、何故ならば典型的には非常に小さいデータセット上で、訓練の最初のラウンドにおけるよりも遥かに大規模なモデルを訓練しているからです。その結果、大きな重み更新を適用する場合非常に迅速に overffitting する危険性があります。ここでは、漸増的な方法で事前訓練された重みを再適応させることを望むだけです。
これが基底モデル全体の再調整をどのように実装するかです :
# Unfreeze the base model base_model.trainable = True # It's important to recompile your model after you make any changes # to the `trainable` attribute of any inner layer, so that your changes # are take into account model.compile(optimizer=keras.optimizers.Adam(1e-5), # Very low learning rate loss=keras.losses.BinaryCrossentropy(from_logits=True), metrics=[keras.metrics.BinaryAccuracy()]) # Train end-to-end. Be careful to stop before you overfit! model.fit(new_dataset, epochs=10, callbacks=..., validation_data=...)
compile() と trainable についての重要なノート
モデル上での compile() の呼び出しはそのモデルの動作を「凍結する」ことを意図しています。これはモデルがコンパイルされた時の訓練可能な属性値は再度 compile が呼び出されるまでそのモデルのライフタイムを通して保存されるべきであることを含蓄します。こうして、任意の訓練可能な値を変更する場合、貴方の変更が考慮ためにモデル上で compile() を再度呼び出すことを確実にしてください。
BatchNormalization 層についての重要なノート
多くの画像モデルは BatchNormalization 層を含みます。その層は考えうる総てにおいて特別なケースです。ここに留意すべき幾つかのことがあります。
- BatchNormalization は訓練の間に更新されるべき 2 つの非訓練可能な重みを含みます。これらは入力の平均と分散を追跡する変数です。
- bn_layer.trainable = False を設定するとき、BatchNormalization 層は推論モードで実行され、そしてその平均 & 分散統計値を更新しません。これは一般には他の層には当てはまりません、というのは 重みの訓練可能性 & 推論/訓練モードは 2 つの直交する概念だからです。しかし 2 つは BatchNormalization 層の場合には結び付けられます。
- 再調整を行なうために BatchNormalization 層を含むモデルを解凍するとき、基底モデルを呼び出すとき training=False を渡して BatchNormalization 層を推論モードに保持するべきです。そうでないと非訓練可能な重みに適用される更新がモデルが学習したものを突然に破壊します。
このガイドの最後の end-to-end サンプルでこのパターンを実際に見ます。
カスタム訓練ループで転移学習 & 再調整
fit() の代わりに、貴方自身の低位訓練ループを使用している場合、ワークフローは本質的には同じままです。勾配更新を適用するときリスト model.trainable_weights を考慮することだけに注意すべきです :
# Create base model base_model = keras.applications.Xception( weights='imagenet', input_shape=(150, 150, 3), include_top=False) # Freeze base model base_model.trainable = False # Create new model on top. inputs = keras.Input(shape=(150, 150, 3)) x = base_model(inputs, training=False) x = keras.layers.GlobalAveragePooling2D()(x) outputs = keras.layers.Dense(1)(x) model = keras.Model(inputs, outputs) loss_fn = keras.losses.BinaryCrossentropy(from_logits=True) optimizer = keras.optimizers.Adam() # Iterate over the batches of a dataset. for inputs, targets in new_dataset: # Open a GradientTape. with tf.GradientTape() as tape: # Forward pass. predictions = model(inputs) # Compute the loss value for this batch. loss_value = loss_fn(targets, predictions) # Get gradients of loss wrt the *trainable* weights. gradients = tape.gradient(loss_value, model.trainable_weights) # Update the weights of the model. optimizer.apply_gradients(zip(gradients, model.trainable_weights))
Likewise for fine-tuning.
end-to-end サンプル:
猫 vs. 犬上で画像分類モデルを再調整します。
データセット
これらのコンセプトを固めるために、具体的な end-to-end 転移学習 & 再調整サンプルを通り抜けましょう。ImageNet 上で事前訓練された、Xception モデルをロードし、そしてそれを Kaggle “猫 vs. 犬” 分類データセット上で利用します。
データを得る
最初に、TFDS を使用して猫 vs. 犬データセットを取得しましょう。貴方自身のデータセットを持つ場合、クラス専用フォルダーにファイリングされた画像のセットから同様のラベル付けられたデータセット・オブジェクトを生成するために貴方は多分ユティリティ tf.keras.preprocessing.image_dataset_from_directory を使用することを望むでしょう。
転移学習は非常に小さいデータセットで作業するとき最も有用です。データセットを小さく保持するために、訓練のために元の訓練データ (25,000 画像) の 40%、検証のために 10%、そしてテストのために 10% を利用します。
import tensorflow_datasets as tfds tfds.disable_progress_bar() train_ds, validation_ds, test_ds = tfds.load( "cats_vs_dogs", # Reserve 10% for validation and 10% for test split=["train[:40%]", "train[40%:50%]", "train[50%:60%]"], as_supervised=True, # Include labels ) print("Number of training samples: %d" % tf.data.experimental.cardinality(train_ds)) print( "Number of validation samples: %d" % tf.data.experimental.cardinality(validation_ds) ) print("Number of test samples: %d" % tf.data.experimental.cardinality(test_ds))
Number of training samples: 9305 Number of validation samples: 2326 Number of test samples: 2326
これらは訓練データセットの最初の 9 画像です — 見れるように、それらは総て異なるサイズです。
import matplotlib.pyplot as plt plt.figure(figsize=(10, 10)) for i, (image, label) in enumerate(train_ds.take(9)): ax = plt.subplot(3, 3, i + 1) plt.imshow(image) plt.title(int(label)) plt.axis("off")
ラベル 1 が「犬」でラベル 0 が「猫」であることも見れます。
データを標準化する
raw 画像は様々なサイズを持ちます。加えて、各ピクセルは 0 と 255 の間の 3 つの整数値 (RGB レベル値) から成ります。これはニューラルネットワークに供給するために素晴らしい適合ではありません。私達は 2 つのことを行なう必要があります :
- 固定画像サイズに標準化する。150×150 を選択します。
- ピクセル値を -1 と 1 の間に正規化する。モデル自身の一部として Normalization を使用してこれを行ないます。
一般に、既に前処理されたデータを取るモデルに対して、入力として raw データを取るモデルを開発することは良い実践です。その理由は、モデルが前処理されたデータを想定する場合、他で (web ブラウザ、モバイル app で) それを使用するためにモデルをエクスポートするたびに、正確に同じ前処理パイプラインを再実装する必要があるためです。それは非常に素早く非常に技巧的になります。そのためモデルに達する前に最小限の量の前処理を行なうべきです。
ここでは、データパイプラインで画像リサイズを行ない (何故ならば深層ニューラルネットワークはデータの連続的なバッチを処理するだけだからです)、そしてモデルの一部として入力値スケーリングを行ないます、それを作成するときに。
画像を 150×150 にリサイズしましょう :
size = (150, 150) train_ds = train_ds.map(lambda x, y: (tf.image.resize(x, size), y)) validation_ds = validation_ds.map(lambda x, y: (tf.image.resize(x, size), y)) test_ds = test_ds.map(lambda x, y: (tf.image.resize(x, size), y))
更に、データをバッチ化してローディング・スピードを最適化するためにキャッシングと先取り (= prefetching) を利用しましょう。
batch_size = 32 train_ds = train_ds.cache().batch(batch_size).prefetch(buffer_size=10) validation_ds = validation_ds.cache().batch(batch_size).prefetch(buffer_size=10) test_ds = test_ds.cache().batch(batch_size).prefetch(buffer_size=10)
ランダムデータ増強を使用する
大規模な画像データセットを持たないとき、ランダムな水平反転や小さなランダム回転のような、サンプルの多様性を人工的に導入することは良い実践です。これは overfitting をスローダウンさせる一方で訓練データの様々な様相にモデルを晒す役に立ちます。
from tensorflow import keras from tensorflow.keras import layers data_augmentation = keras.Sequential( [ layers.experimental.preprocessing.RandomFlip("horizontal"), layers.experimental.preprocessing.RandomRotation(0.1), ] )
様々なランダム変換の後に最初のバッチの最初の画像がどのように見えるか可視化しましょう :
import numpy as np for images, labels in train_ds.take(1): plt.figure(figsize=(10, 10)) first_image = images[0] for i in range(9): ax = plt.subplot(3, 3, i + 1) augmented_image = data_augmentation( tf.expand_dims(first_image, 0), training=True ) plt.imshow(augmented_image[0].numpy().astype("int32")) plt.title(int(labels[i])) plt.axis("off")
モデルを構築する
今は先に説明したブループリントに従ってモデルを構築しましょう。
以下に注意してください :
- (初期化時に [0, 255] 範囲内の) 入力値を [-1, 1] 範囲にスケールするために Normalization 層を追加します。
- 正則化のために、分類層の前に Dropout 層を追加します。
- 基底モデルを呼び出すときに、それが推論モードで動作するように training=False を渡すことを確実にします、その結果 batchnorm 統計値は再調整のために基底モデルを解凍した後でさえ更新されえません。
base_model = keras.applications.Xception( weights="imagenet", # Load weights pre-trained on ImageNet. input_shape=(150, 150, 3), include_top=False, ) # Do not include the ImageNet classifier at the top. # Freeze the base_model base_model.trainable = False # Create new model on top inputs = keras.Input(shape=(150, 150, 3)) x = data_augmentation(inputs) # Apply random data augmentation # Pre-trained Xception weights requires that input be normalized # from (0, 255) to a range (-1., +1.), the normalization layer # does the following, outputs = (inputs - mean) / sqrt(var) norm_layer = keras.layers.experimental.preprocessing.Normalization() mean = np.array([127.5] * 3) var = mean ** 2 # Scale inputs to [-1, +1] x = norm_layer(x) norm_layer.set_weights([mean, var]) # The base model contains batchnorm layers. We want to keep them in inference mode # when we unfreeze the base model for fine-tuning, so we make sure that the # base_model is running in inference mode here. x = base_model(x, training=False) x = keras.layers.GlobalAveragePooling2D()(x) x = keras.layers.Dropout(0.2)(x) # Regularize with dropout outputs = keras.layers.Dense(1)(x) model = keras.Model(inputs, outputs) model.summary()
Model: "model_2" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_12 (InputLayer) [(None, 150, 150, 3)] 0 _________________________________________________________________ sequential_6 (Sequential) (None, 150, 150, 3) 0 _________________________________________________________________ normalization (Normalization (None, 150, 150, 3) 7 _________________________________________________________________ xception (Functional) (None, 5, 5, 2048) 20861480 _________________________________________________________________ global_average_pooling2d_2 ( (None, 2048) 0 _________________________________________________________________ dropout (Dropout) (None, 2048) 0 _________________________________________________________________ dense_16 (Dense) (None, 1) 2049 ================================================================= Total params: 20,863,536 Trainable params: 2,049 Non-trainable params: 20,861,487 _________________________________________________________________
トップ層を訓練する
model.compile( optimizer=keras.optimizers.Adam(), loss=keras.losses.BinaryCrossentropy(from_logits=True), metrics=[keras.metrics.BinaryAccuracy()], ) epochs = 20 model.fit(train_ds, epochs=epochs, validation_data=validation_ds)
Epoch 1/20 291/291 [==============================] - 26s 80ms/step - loss: 0.0948 - binary_accuracy: 0.9621 - val_loss: 0.0689 - val_binary_accuracy: 0.9738 Epoch 2/20 291/291 [==============================] - 22s 77ms/step - loss: 0.0891 - binary_accuracy: 0.9634 - val_loss: 0.0700 - val_binary_accuracy: 0.9746 Epoch 3/20 291/291 [==============================] - 22s 77ms/step - loss: 0.0850 - binary_accuracy: 0.9659 - val_loss: 0.0684 - val_binary_accuracy: 0.9751 Epoch 4/20 291/291 [==============================] - 23s 77ms/step - loss: 0.0860 - binary_accuracy: 0.9675 - val_loss: 0.0701 - val_binary_accuracy: 0.9738 Epoch 5/20 291/291 [==============================] - 23s 78ms/step - loss: 0.0955 - binary_accuracy: 0.9628 - val_loss: 0.0809 - val_binary_accuracy: 0.9678 Epoch 6/20 291/291 [==============================] - 23s 78ms/step - loss: 0.0942 - binary_accuracy: 0.9616 - val_loss: 0.0711 - val_binary_accuracy: 0.9742 Epoch 7/20 291/291 [==============================] - 23s 78ms/step - loss: 0.0962 - binary_accuracy: 0.9616 - val_loss: 0.0738 - val_binary_accuracy: 0.9716 Epoch 8/20 291/291 [==============================] - 22s 77ms/step - loss: 0.0858 - binary_accuracy: 0.9654 - val_loss: 0.0707 - val_binary_accuracy: 0.9716 Epoch 9/20 291/291 [==============================] - 23s 78ms/step - loss: 0.0940 - binary_accuracy: 0.9613 - val_loss: 0.0730 - val_binary_accuracy: 0.9703 Epoch 10/20 291/291 [==============================] - 23s 78ms/step - loss: 0.0944 - binary_accuracy: 0.9616 - val_loss: 0.0814 - val_binary_accuracy: 0.9699 Epoch 11/20 291/291 [==============================] - 23s 78ms/step - loss: 0.0937 - binary_accuracy: 0.9617 - val_loss: 0.0730 - val_binary_accuracy: 0.9699 Epoch 12/20 291/291 [==============================] - 23s 77ms/step - loss: 0.0907 - binary_accuracy: 0.9646 - val_loss: 0.0774 - val_binary_accuracy: 0.9690 Epoch 13/20 291/291 [==============================] - 23s 78ms/step - loss: 0.0974 - binary_accuracy: 0.9604 - val_loss: 0.0713 - val_binary_accuracy: 0.9742 Epoch 14/20 291/291 [==============================] - 23s 78ms/step - loss: 0.0921 - binary_accuracy: 0.9616 - val_loss: 0.0723 - val_binary_accuracy: 0.9725 Epoch 15/20 291/291 [==============================] - 23s 77ms/step - loss: 0.0910 - binary_accuracy: 0.9625 - val_loss: 0.0723 - val_binary_accuracy: 0.9712 Epoch 16/20 291/291 [==============================] - 23s 78ms/step - loss: 0.0953 - binary_accuracy: 0.9631 - val_loss: 0.0745 - val_binary_accuracy: 0.9721 Epoch 17/20 291/291 [==============================] - 23s 78ms/step - loss: 0.0865 - binary_accuracy: 0.9656 - val_loss: 0.0731 - val_binary_accuracy: 0.9733 Epoch 18/20 291/291 [==============================] - 23s 77ms/step - loss: 0.0833 - binary_accuracy: 0.9662 - val_loss: 0.0748 - val_binary_accuracy: 0.9725 Epoch 19/20 291/291 [==============================] - 23s 78ms/step - loss: 0.0974 - binary_accuracy: 0.9589 - val_loss: 0.0761 - val_binary_accuracy: 0.9708 Epoch 20/20 291/291 [==============================] - 22s 77ms/step - loss: 0.0990 - binary_accuracy: 0.9613 - val_loss: 0.0904 - val_binary_accuracy: 0.9686 CPU times: user 2min 17s, sys: 17.9 s, total: 2min 34s Wall time: 7min 34s
モデル全体の再調整のラウンドを行なう
最後に、基底モデルを解凍してそしてモデル全体を低い学習率で end-to-end に訓練しましょう。
重要なことは、基底モデルは訓練可能になりますが、それは依然として推論モードで動作していることです、何故ならばモデルを構築するときにそれを呼び出すとき training=False を渡したからです。これは、バッチ正規化層内部ではバッチ統計値を更新しないことを意味しています。それらが行なうのであれば、それらはそこまでにモデルにより学習された表現上で大暴れします (= wreck havoc)。
# Unfreeze the base_model. Note that it keeps running in inference mode # since we passed `training=False` when calling it. This means that # the batchnorm layers will not update their batch statistics. # This prevents the batchnorm layers from undoing all the training # we've done so far. base_model.trainable = True model.summary() model.compile( optimizer=keras.optimizers.Adam(1e-5), # Low learning rate loss=keras.losses.BinaryCrossentropy(from_logits=True), metrics=[keras.metrics.BinaryAccuracy()], ) epochs = 10 model.fit(train_ds, epochs=epochs, validation_data=validation_ds)
Model: "model_1" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_7 (InputLayer) [(None, 150, 150, 3)] 0 _________________________________________________________________ sequential_3 (Sequential) (None, 150, 150, 3) 0 _________________________________________________________________ normalization (Normalization (None, 150, 150, 3) 7 _________________________________________________________________ xception (Functional) (None, 5, 5, 2048) 20861480 _________________________________________________________________ global_average_pooling2d_1 ( (None, 2048) 0 _________________________________________________________________ dropout (Dropout) (None, 2048) 0 _________________________________________________________________ dense_8 (Dense) (None, 1) 2049 ================================================================= Total params: 20,863,536 Trainable params: 20,809,001 Non-trainable params: 54,535 _________________________________________________________________ Epoch 1/10 291/291 [==============================] - 89s 291ms/step - loss: 0.0808 - binary_accuracy: 0.9661 - val_loss: 0.0584 - val_binary_accuracy: 0.9772 Epoch 2/10 291/291 [==============================] - 86s 296ms/step - loss: 0.0623 - binary_accuracy: 0.9777 - val_loss: 0.0530 - val_binary_accuracy: 0.9781 Epoch 3/10 291/291 [==============================] - 87s 298ms/step - loss: 0.0485 - binary_accuracy: 0.9806 - val_loss: 0.0469 - val_binary_accuracy: 0.9794 Epoch 4/10 291/291 [==============================] - 89s 304ms/step - loss: 0.0337 - binary_accuracy: 0.9874 - val_loss: 0.0445 - val_binary_accuracy: 0.9789 Epoch 5/10 291/291 [==============================] - 88s 303ms/step - loss: 0.0269 - binary_accuracy: 0.9875 - val_loss: 0.0414 - val_binary_accuracy: 0.9837 Epoch 6/10 291/291 [==============================] - 88s 303ms/step - loss: 0.0264 - binary_accuracy: 0.9909 - val_loss: 0.0482 - val_binary_accuracy: 0.9819 Epoch 7/10 291/291 [==============================] - 87s 301ms/step - loss: 0.0239 - binary_accuracy: 0.9918 - val_loss: 0.0431 - val_binary_accuracy: 0.9824 Epoch 8/10 291/291 [==============================] - 87s 301ms/step - loss: 0.0176 - binary_accuracy: 0.9925 - val_loss: 0.0445 - val_binary_accuracy: 0.9837 Epoch 9/10 291/291 [==============================] - 87s 300ms/step - loss: 0.0163 - binary_accuracy: 0.9942 - val_loss: 0.0454 - val_binary_accuracy: 0.9828 Epoch 10/10 291/291 [==============================] - 87s 300ms/step - loss: 0.0110 - binary_accuracy: 0.9962 - val_loss: 0.0428 - val_binary_accuracy: 0.9832 <tensorflow.python.keras.callbacks.History at 0x7fda3008a278>
以上