關於:
本系列文將借用 Google 提供的 Colab 平台,在上面執行 30 個影像分類訓練任務,每個主題都會探討在不同的狀況或不同的超參數對於同一個任務會有什麼影響,在面對不同的領域時,機器學習運作起來就像個黑箱子,很難第一次訓練就得到最佳解,只能透過不斷的調教來慢慢修正,本篇系列文會拿我在機器學習工作中,有時想到但沒有時間細察的假設問題來當主題,並且在 Colab 上實際執行看結果如何,會有滿滿的假設與實作!
問題:做不做遷移式學習(Transfer Learning)的差異?
每當我們遇到一個新的訓練任務時,普遍的做法我們會拿預訓練模型(pre-train model)的權重值為基底再加上一層 Dense Layer 後開始訓練任務,這麼一來可以比從頭訓練(train from scratch)來得有效率多,但這時我不禁在想,這種做法是否也會因為前段缺少訓練而限制了模型能夠學到的天花板呢?
基於這個問題,我們用 tfds 的 oxford_flowers102 作為訓練任務,該資料集擁有102個花的分類,其中 train 和 validation 的部分都是每個分類 10 張圖片,共1020張。
我們將 Batch size 固定為32,學習率固定為0.1來實驗,訓練50個 epochs。第一個實驗我們直接拿 tensorflow 提供的 mobilenetV2 作為預訓練模型,但是將權重鎖住(freeze)並再最後加上 102個節點的 Dense Layer 來做輸出,也就是整個模型只有最後一層會用來學習。
LR = 0.1
EPOCHS = 50
base = tf.keras.applications.MobileNetV2(input_shape=(224, 224, 3), include_top=False, weights='imagenet')
base.trainable = False
net = tf.keras.layers.GlobalAveragePooling2D()(base.output)
net = tf.keras.layers.Dense(NUM_OF_CLASS)(net)
model = tf.keras.Model(inputs=[base.input], outputs=[net])
model.compile(
optimizer=tf.keras.optimizers.SGD(LR),
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=[tf.keras.metrics.SparseCategoricalAccuracy()],
)
history = model.fit(
ds_train,
epochs=EPOCHS,
validation_data=ds_test,
verbose=True)
val_loss = history.history['val_loss'][-1]
val_acc = history.history['val_sparse_categorical_accuracy'][-1]
print(f'val loss: {val_loss}')
print(f'val acc: {val_acc}')
loss: 0.0237 - sparse_categorical_accuracy: 1.0000 - val_loss: 0.7445 - val_sparse_categorical_accuracy: 0.8088
實驗結果得出 loss 值約為0.74,準確度80.9%
實驗二,我們同樣使用 mobilenetV2,但是權重是初始化的狀態來訓練。
LR = 0.1
EPOCHS = 50
base = tf.keras.applications.MobileNetV2(input_shape=(224, 224, 3), include_top=False, weights=None)
net = tf.keras.layers.GlobalAveragePooling2D()(base.output)
net = tf.keras.layers.Dense(NUM_OF_CLASS)(net)
model = tf.keras.Model(inputs=[base.input], outputs=[net])
model.compile(
optimizer=tf.keras.optimizers.SGD(LR),
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=[tf.keras.metrics.SparseCategoricalAccuracy()],
)
history = model.fit(
ds_train,
epochs=EPOCHS,
validation_data=ds_test,
verbose=True)
val_loss = history.history['val_loss'][-1]
val_acc = history.history['val_sparse_categorical_accuracy'][-1]
print(f'val loss: {val_loss}')
print(f'val acc: {val_acc}')
loss: 0.0118 - sparse_categorical_accuracy: 1.0000 - val_loss: 6.2431 - val_sparse_categorical_accuracy: 0.0098
再經過 50 個 epochs 後,我們發現學習效率非常之差,準確度僅有0.9%,訓練集卻已經100%,看來模型已經過擬和,無法真正有效學到特徵...
第三個實驗和第一個類似,我們使用 mobilenetV2 的預訓練權重,但是不把預訓練模型的權重鎖住,而是開放整個模型的權重都可以更新學習。
LR = 0.1
EPOCHS = 50
base = tf.keras.applications.MobileNetV2(input_shape=(224, 224, 3), include_top=False, weights='imagenet')
net = tf.keras.layers.GlobalAveragePooling2D()(base.output)
net = tf.keras.layers.Dense(NUM_OF_CLASS)(net)
model = tf.keras.Model(inputs=[base.input], outputs=[net])
model.compile(
optimizer=tf.keras.optimizers.SGD(LR),
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=[tf.keras.metrics.SparseCategoricalAccuracy()],
)
history = model.fit(
ds_train,
epochs=EPOCHS,
validation_data=ds_test,
verbose=True)
val_loss = history.history['val_loss'][-1]
val_acc = history.history['val_sparse_categorical_accuracy'][-1]
print(f'val loss: {val_loss}')
print(f'val acc: {val_acc}')
loss: 8.5328e-04 - sparse_categorical_accuracy: 1.0000 - val_loss: 0.5290 - val_sparse_categorical_accuracy: 0.8539
得到 loss 值為0.53,準確度85.4%,這樣的結論代表實驗一將預訓練模型鎖住雖然可以訓練得非常快(因為只有最後一層要學),但他的天花板卻被限制在80%。
那有沒有一個比較折衷的辦法?實驗四我嘗試 Unfreeze mobilenetV2 的倒數最後一個 bottleneck block,看看模型有無可能突破80%。
在 Unfreeze 之前,我們可以先用 for-loop 找到最後一個 block 是在第幾層,像這邊得出第142層的 block_15_add 是最後一個該被 Freeze 的 Layer 後,就可以鎖住該層並訓練。
for idx, layer in enumerate(model.layers):
print(f'{idx}, {layer.name}')
...
140, block_15_project
141, block_15_project_BN
142, block_15_add
143, block_16_expand
144, block_16_expand_BN
...
LR = 0.1
EPOCHS = 50
FREEZE_INDEX = 142 # include
base = tf.keras.applications.MobileNetV2(input_shape=(224, 224, 3), include_top=False, weights='imagenet')
net = tf.keras.layers.GlobalAveragePooling2D()(base.output)
net = tf.keras.layers.Dense(NUM_OF_CLASS)(net)
model = tf.keras.Model(inputs=[base.input], outputs=[net])
# Unfreeze weights
for idx, layer in enumerate(model.layers):
layer.trainable = FREEZE_INDEX < idx
model.compile(
optimizer=tf.keras.optimizers.SGD(LR),
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=[tf.keras.metrics.SparseCategoricalAccuracy()],
)
history = model.fit(
ds_train,
epochs=EPOCHS,
validation_data=ds_test,
verbose=True)
val_loss = history.history['val_loss'][-1]
val_acc = history.history['val_sparse_categorical_accuracy'][-1]
print(f'val loss: {val_loss}')
print(f'val acc: {val_acc}')
loss: 0.0022 - sparse_categorical_accuracy: 1.0000 - val_loss: 0.5326 - val_sparse_categorical_accuracy: 0.8510
訓練結果得到 loss 值為0.53準確度為85.1%,拿到了和實驗三差不多的準確度,但是訓練所需時間卻減少很多!