Keras 2 : ガイド : 転移学習と再調整 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 10/29/2021 (keras 2.6.0)
* 本ページは、Keras の以下のドキュメントを翻訳した上で適宜、補足説明したものです:
- Transfer learning & fine-tuning (Author: fchollet)
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
Keras 2 : ガイド : 転移学習と再調整
セットアップ
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 [==============================] - 0s 333ms/step - loss: 0.1007
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.
それから、基底モデルを凍結します。
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.RandomFlip("horizontal"), layers.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[0]))
plt.axis("off")
モデルを構築する
さて、先に説明したブループリントに従ってモデルを構築しましょう。
以下に注意してください :
- (初期化時に [0, 255] 範囲内の) 入力値を [-1, 1] 範囲にスケールするために Rescaling 層を追加します。
- 正則化のために、分類層の前に 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 scaled
# from (0, 255) to a range of (-1., +1.), the rescaling layer
# outputs: `(inputs * scale) + offset`
scale_layer = keras.layers.Rescaling(scale=1 / 127.5, offset=-1)
x = scale_layer(x)
# 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" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_5 (InputLayer) [(None, 150, 150, 3)] 0 _________________________________________________________________ sequential_3 (Sequential) (None, 150, 150, 3) 0 _________________________________________________________________ rescaling (Rescaling) (None, 150, 150, 3) 0 _________________________________________________________________ xception (Functional) (None, 5, 5, 2048) 20861480 _________________________________________________________________ global_average_pooling2d (Gl (None, 2048) 0 _________________________________________________________________ dropout (Dropout) (None, 2048) 0 _________________________________________________________________ dense_7 (Dense) (None, 1) 2049 ================================================================= Total params: 20,863,529 Trainable params: 2,049 Non-trainable params: 20,861,480 _________________________________________________________________
トップ層を訓練する
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 [==============================] - 133s 451ms/step - loss: 0.1670 - binary_accuracy: 0.9267 - val_loss: 0.0830 - val_binary_accuracy: 0.9716 Epoch 2/20 291/291 [==============================] - 135s 465ms/step - loss: 0.1208 - binary_accuracy: 0.9502 - val_loss: 0.0768 - val_binary_accuracy: 0.9716 Epoch 3/20 291/291 [==============================] - 135s 463ms/step - loss: 0.1062 - binary_accuracy: 0.9572 - val_loss: 0.0757 - val_binary_accuracy: 0.9716 Epoch 4/20 291/291 [==============================] - 137s 469ms/step - loss: 0.1024 - binary_accuracy: 0.9554 - val_loss: 0.0733 - val_binary_accuracy: 0.9725 Epoch 5/20 291/291 [==============================] - 137s 470ms/step - loss: 0.1004 - binary_accuracy: 0.9587 - val_loss: 0.0735 - val_binary_accuracy: 0.9729 Epoch 6/20 291/291 [==============================] - 136s 467ms/step - loss: 0.0979 - binary_accuracy: 0.9577 - val_loss: 0.0747 - val_binary_accuracy: 0.9708 Epoch 7/20 291/291 [==============================] - 134s 462ms/step - loss: 0.0998 - binary_accuracy: 0.9596 - val_loss: 0.0706 - val_binary_accuracy: 0.9725 Epoch 8/20 291/291 [==============================] - 133s 457ms/step - loss: 0.1029 - binary_accuracy: 0.9592 - val_loss: 0.0720 - val_binary_accuracy: 0.9733 Epoch 9/20 291/291 [==============================] - 135s 466ms/step - loss: 0.0937 - binary_accuracy: 0.9625 - val_loss: 0.0707 - val_binary_accuracy: 0.9721 Epoch 10/20 291/291 [==============================] - 137s 472ms/step - loss: 0.0967 - binary_accuracy: 0.9580 - val_loss: 0.0720 - val_binary_accuracy: 0.9712 Epoch 11/20 291/291 [==============================] - 135s 463ms/step - loss: 0.0961 - binary_accuracy: 0.9612 - val_loss: 0.0802 - val_binary_accuracy: 0.9699 Epoch 12/20 291/291 [==============================] - 134s 460ms/step - loss: 0.0963 - binary_accuracy: 0.9638 - val_loss: 0.0721 - val_binary_accuracy: 0.9716 Epoch 13/20 291/291 [==============================] - 136s 468ms/step - loss: 0.0925 - binary_accuracy: 0.9635 - val_loss: 0.0736 - val_binary_accuracy: 0.9686 Epoch 14/20 291/291 [==============================] - 138s 476ms/step - loss: 0.0909 - binary_accuracy: 0.9624 - val_loss: 0.0766 - val_binary_accuracy: 0.9703 Epoch 15/20 291/291 [==============================] - 136s 467ms/step - loss: 0.0949 - binary_accuracy: 0.9598 - val_loss: 0.0704 - val_binary_accuracy: 0.9725 Epoch 16/20 291/291 [==============================] - 133s 456ms/step - loss: 0.0969 - binary_accuracy: 0.9586 - val_loss: 0.0722 - val_binary_accuracy: 0.9708 Epoch 17/20 291/291 [==============================] - 135s 464ms/step - loss: 0.0913 - binary_accuracy: 0.9635 - val_loss: 0.0718 - val_binary_accuracy: 0.9716 Epoch 18/20 291/291 [==============================] - 137s 472ms/step - loss: 0.0915 - binary_accuracy: 0.9639 - val_loss: 0.0727 - val_binary_accuracy: 0.9725 Epoch 19/20 291/291 [==============================] - 134s 460ms/step - loss: 0.0938 - binary_accuracy: 0.9631 - val_loss: 0.0707 - val_binary_accuracy: 0.9733 Epoch 20/20 291/291 [==============================] - 134s 460ms/step - loss: 0.0971 - binary_accuracy: 0.9609 - val_loss: 0.0714 - val_binary_accuracy: 0.9716 <keras.callbacks.History at 0x7f4494e38f70>
(訳注 : Tesla P100 実験結果)
Epoch 1/20 291/291 [==============================] - 35s 61ms/step - loss: 0.1682 - binary_accuracy: 0.9286 - val_loss: 0.0827 - val_binary_accuracy: 0.9699 Epoch 2/20 291/291 [==============================] - 15s 51ms/step - loss: 0.1130 - binary_accuracy: 0.9517 - val_loss: 0.0758 - val_binary_accuracy: 0.9729 Epoch 3/20 291/291 [==============================] - 15s 51ms/step - loss: 0.1108 - binary_accuracy: 0.9542 - val_loss: 0.0749 - val_binary_accuracy: 0.9712 Epoch 4/20 291/291 [==============================] - 15s 51ms/step - loss: 0.1073 - binary_accuracy: 0.9563 - val_loss: 0.0733 - val_binary_accuracy: 0.9708 Epoch 5/20 291/291 [==============================] - 15s 51ms/step - loss: 0.1023 - binary_accuracy: 0.9584 - val_loss: 0.0704 - val_binary_accuracy: 0.9738 Epoch 6/20 291/291 [==============================] - 15s 51ms/step - loss: 0.0974 - binary_accuracy: 0.9563 - val_loss: 0.0702 - val_binary_accuracy: 0.9738 Epoch 7/20 291/291 [==============================] - 15s 51ms/step - loss: 0.0961 - binary_accuracy: 0.9612 - val_loss: 0.0772 - val_binary_accuracy: 0.9721 Epoch 8/20 291/291 [==============================] - 15s 51ms/step - loss: 0.0992 - binary_accuracy: 0.9579 - val_loss: 0.0725 - val_binary_accuracy: 0.9708 Epoch 9/20 291/291 [==============================] - 15s 51ms/step - loss: 0.0994 - binary_accuracy: 0.9606 - val_loss: 0.0706 - val_binary_accuracy: 0.9733 Epoch 10/20 291/291 [==============================] - 15s 51ms/step - loss: 0.0908 - binary_accuracy: 0.9610 - val_loss: 0.0766 - val_binary_accuracy: 0.9708 Epoch 11/20 291/291 [==============================] - 15s 51ms/step - loss: 0.0942 - binary_accuracy: 0.9625 - val_loss: 0.0746 - val_binary_accuracy: 0.9695 Epoch 12/20 291/291 [==============================] - 15s 51ms/step - loss: 0.0949 - binary_accuracy: 0.9601 - val_loss: 0.0704 - val_binary_accuracy: 0.9721 Epoch 13/20 291/291 [==============================] - 15s 51ms/step - loss: 0.0970 - binary_accuracy: 0.9612 - val_loss: 0.0712 - val_binary_accuracy: 0.9716 Epoch 14/20 291/291 [==============================] - 15s 51ms/step - loss: 0.0955 - binary_accuracy: 0.9629 - val_loss: 0.0701 - val_binary_accuracy: 0.9729 Epoch 15/20 291/291 [==============================] - 15s 51ms/step - loss: 0.0966 - binary_accuracy: 0.9600 - val_loss: 0.0702 - val_binary_accuracy: 0.9725 Epoch 16/20 291/291 [==============================] - 15s 51ms/step - loss: 0.0916 - binary_accuracy: 0.9613 - val_loss: 0.0776 - val_binary_accuracy: 0.9682 Epoch 17/20 291/291 [==============================] - 15s 51ms/step - loss: 0.0909 - binary_accuracy: 0.9607 - val_loss: 0.0754 - val_binary_accuracy: 0.9708 Epoch 18/20 291/291 [==============================] - 15s 51ms/step - loss: 0.0955 - binary_accuracy: 0.9620 - val_loss: 0.0709 - val_binary_accuracy: 0.9725 Epoch 19/20 291/291 [==============================] - 15s 51ms/step - loss: 0.0904 - binary_accuracy: 0.9631 - val_loss: 0.0722 - val_binary_accuracy: 0.9712 Epoch 20/20 291/291 [==============================] - 15s 51ms/step - loss: 0.0969 - binary_accuracy: 0.9592 - val_loss: 0.0701 - val_binary_accuracy: 0.9716 CPU times: user 2min 47s, sys: 12 s, total: 2min 59s Wall time: 6min 18s
モデル全体の再調整のラウンドを行なう
最後に、基底モデルを解凍してそしてモデル全体を低い学習率で 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" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_5 (InputLayer) [(None, 150, 150, 3)] 0 _________________________________________________________________ sequential_3 (Sequential) (None, 150, 150, 3) 0 _________________________________________________________________ rescaling (Rescaling) (None, 150, 150, 3) 0 _________________________________________________________________ xception (Functional) (None, 5, 5, 2048) 20861480 _________________________________________________________________ global_average_pooling2d (Gl (None, 2048) 0 _________________________________________________________________ dropout (Dropout) (None, 2048) 0 _________________________________________________________________ dense_7 (Dense) (None, 1) 2049 ================================================================= Total params: 20,863,529 Trainable params: 20,809,001 Non-trainable params: 54,528 _________________________________________________________________ Epoch 1/10 291/291 [==============================] - 567s 2s/step - loss: 0.0749 - binary_accuracy: 0.9689 - val_loss: 0.0605 - val_binary_accuracy: 0.9776 Epoch 2/10 291/291 [==============================] - 551s 2s/step - loss: 0.0559 - binary_accuracy: 0.9770 - val_loss: 0.0507 - val_binary_accuracy: 0.9798 Epoch 3/10 291/291 [==============================] - 545s 2s/step - loss: 0.0444 - binary_accuracy: 0.9832 - val_loss: 0.0502 - val_binary_accuracy: 0.9807 Epoch 4/10 291/291 [==============================] - 558s 2s/step - loss: 0.0365 - binary_accuracy: 0.9874 - val_loss: 0.0506 - val_binary_accuracy: 0.9807 Epoch 5/10 291/291 [==============================] - 550s 2s/step - loss: 0.0276 - binary_accuracy: 0.9890 - val_loss: 0.0477 - val_binary_accuracy: 0.9802 Epoch 6/10 291/291 [==============================] - 588s 2s/step - loss: 0.0206 - binary_accuracy: 0.9916 - val_loss: 0.0444 - val_binary_accuracy: 0.9832 Epoch 7/10 291/291 [==============================] - 542s 2s/step - loss: 0.0206 - binary_accuracy: 0.9923 - val_loss: 0.0502 - val_binary_accuracy: 0.9828 Epoch 8/10 291/291 [==============================] - 544s 2s/step - loss: 0.0153 - binary_accuracy: 0.9939 - val_loss: 0.0509 - val_binary_accuracy: 0.9819 Epoch 9/10 291/291 [==============================] - 548s 2s/step - loss: 0.0156 - binary_accuracy: 0.9934 - val_loss: 0.0610 - val_binary_accuracy: 0.9807 Epoch 10/10 291/291 [==============================] - 546s 2s/step - loss: 0.0176 - binary_accuracy: 0.9936 - val_loss: 0.0561 - val_binary_accuracy: 0.9789 <keras.callbacks.History at 0x7f4495056040>
(訳注 : Tesla P100 実験結果)
Epoch 1/10 291/291 [==============================] - 71s 228ms/step - loss: 0.0807 - binary_accuracy: 0.9655 - val_loss: 0.0529 - val_binary_accuracy: 0.9785 Epoch 2/10 291/291 [==============================] - 65s 222ms/step - loss: 0.0529 - binary_accuracy: 0.9784 - val_loss: 0.0525 - val_binary_accuracy: 0.9807 Epoch 3/10 291/291 [==============================] - 65s 223ms/step - loss: 0.0455 - binary_accuracy: 0.9830 - val_loss: 0.0542 - val_binary_accuracy: 0.9794 Epoch 4/10 291/291 [==============================] - 65s 223ms/step - loss: 0.0344 - binary_accuracy: 0.9868 - val_loss: 0.0490 - val_binary_accuracy: 0.9807 Epoch 5/10 291/291 [==============================] - 65s 224ms/step - loss: 0.0251 - binary_accuracy: 0.9910 - val_loss: 0.0441 - val_binary_accuracy: 0.9824 Epoch 6/10 291/291 [==============================] - 65s 223ms/step - loss: 0.0219 - binary_accuracy: 0.9922 - val_loss: 0.0459 - val_binary_accuracy: 0.9819 Epoch 7/10 291/291 [==============================] - 65s 223ms/step - loss: 0.0226 - binary_accuracy: 0.9909 - val_loss: 0.0544 - val_binary_accuracy: 0.9824 Epoch 8/10 291/291 [==============================] - 65s 222ms/step - loss: 0.0202 - binary_accuracy: 0.9929 - val_loss: 0.0443 - val_binary_accuracy: 0.9828 Epoch 9/10 291/291 [==============================] - 65s 223ms/step - loss: 0.0149 - binary_accuracy: 0.9943 - val_loss: 0.0516 - val_binary_accuracy: 0.9811 Epoch 10/10 291/291 [==============================] - 65s 222ms/step - loss: 0.0121 - binary_accuracy: 0.9962 - val_loss: 0.0436 - val_binary_accuracy: 0.9819
After 10 epochs, fine-tuning gains us a nice improvement here.
以上