iT邦幫忙

2021 iThome 鐵人賽

DAY 20
0
AI & Data

AI Facial Expression Recognition: Data, Model, Application系列 第 20

[Day 20] 兩段式訓練比兩段式左轉更安全 (遷移學習技巧)

前言

走過了資料分析、演算法選擇後,
我們得知了有些可以改善模型的方向:

  1. 解決資料不平衡(Done)
  2. 學習率的設定(Done)
  3. 訓練輪數(Nice to have)
  4. 模型深度(No, I want my model to be more efficient ! )
  5. 階段式訓練(Now)

訓練輪數(epoch)

這邊我做實驗都是為了比較不同方法帶給模型的提升,
而並非追求最佳模型,
所以在未來我的最終訓練輪數絕對會遠遠超過30,
很有可能落在100到300之間,
這樣我要跑一個模型就要3小時以上,
抱歉我只能說我不OK,你先train
如果大家真的想不開要去找最佳epoch,
建議大家使用Google Colab完成。
(如果資料量很大,建議不要忍一下,直接買Pro方案)
https://ithelp.ithome.com.tw/upload/images/20210926/20141294cFTixk3v6z.jpg


遷移學習

在傳統的機器學習領域,我們獲得訓練資料和標籤,
訓練一下就可以獲得還不錯的模型。
但在現今深度學習領域,我們擁有巨量的訓練資料,
可惜的是標籤並沒有辦法也這麼大量。
那怎麼辦呢?
那就是Transfer Learning!

機器學習的困境

有學過一點統計的人,
應該聽過分布相同假設,
或更常聽到的是「我們假設這兩個隨機變數都來自常態分布,所以我們可以推導出...」,
很多傳統機器學習方法本質上就是統計方法。
那些方法會需要在訓練資料和測試資料相同分布的前提下才能使用。
如果分布不同,則應用在測試資料時會悽慘無比。

為什麼要遷移學習

遷移學習就是把用現有資料學習好的模型直接拿來使用,
因為卷積神經網路的前幾層具有通用性廣泛的特徵(低階特徵)
這些特徵在不同分布的圖片中都可以使用。
比如說貓狗辨識網路的前兩層可以遷移到虎狼辨識網路上。
因為老虎和狼的照片與貓和狗的照片有點相似,卻又不完全一樣。
如果直接拿貓狗辨識網路來分辨老虎和狼的話,應該會很落漆~

諸如Google、Facebook等大公司有著巨量的資料、大量的運算資源和源源不絕的工作應聘者
很多深度學習網路只有他們才能訓練出來。
他們已經用我們無法企及的大數據訓練出一個很棒的網路,
我們拿來微調最後的分類層就好了/images/emoticon/emoticon07.gif

遷移學習三方法

隨著測試資料和預訓練資料的差異越大,我們有1~3的方法。
越後面介紹的方法會調整越多預訓練的參數。

1. Extract Feature Vector

這是最簡單的版本,單純把預訓練CNN的最後一層卷積層輸出(feature map)拿出來,
然後就沒有預訓練CNN的事情了。
之後用feature map當作一張圖片的特徵,去訓練一個簡單的機器學習模型,
像是SVM、RF、NN等等。

2. Transfer Learning

這是真正意義上的遷移學習,
我們把預訓練模型拿出來,
只對最後的輸出層進行訓練,
其他層的參數都固定不動。

3. Fine-tune

這是實際上最常用的方法,
相比於第2個方法只訓練輸出層,
Fine-tune會多訓練幾層。(通常是靠近輸出層的網路層)
這個「多訓練幾層」也可以是「訓練全部網路層」,
我們把它叫做「微調」。

在實務上,
我們會從後面的網路層開始逐漸解凍,
每解凍一個區域就訓練個幾輪。

例如:

  1. 解凍最後一層,訓練10輪
  2. 解凍最後兩層,再訓練10輪
  3. 解凍最後三層,再訓練10輪
    這樣模型才會穩定的收斂,
    因為(在有些仍凍結的情況下)一次微調太多層的參數很容易不收斂,
    有可能還不如把全部預訓練參數都進行微調。

程式碼

建立一個凍結參數的預訓練模型

def build_model(preModel=EfficientNetB0,
                pretrained=True,
                num_classes=7,
                input_shape=(48, 48, 3),
                l2_coef=0.0001):

    pre_model = preModel(include_top=False, weights='imagenet' if pretrained == True else None,
                         input_shape=input_shape,
                         pooling='max', classifier_activation='softmax')

    for layer in pre_model.layers:
        layer.trainable = False

    x = Dropout(0.2)(pre_model.output)

    output = Dense(
        num_classes, activation="softmax", name="main_output",
        kernel_regularizer=regularizers.l2(l2_coef))(x)

    freezed_model = tf.keras.Model(pre_model.input, output)
    
    freezed_model.compile(optimizer=tf.keras.optimizers.Adam(),
                          loss=tf.keras.losses.CategoricalCrossentropy(),
                          metrics=['accuracy'])

    return freezed_model

如何解開凍結的參數

如果你想要把神經網路的最後n層變成可訓練的:

def unfreeze_model(model, n=1, is_unfreeze_BN=False):
    # We unfreeze the top n layers while leaving BatchNorm layers frozen
    # n = 6 (~ block-top)
    # n = 19 (~ block-7)
    # n = 78 (~ block-6)
    if is_unfreeze_BN == False:
        for layer in model.layers[-n:]:
            if not isinstance(layer, BatchNormalization):
                layer.trainable = True
    else:
        for layer in model.layers[-n:]:
            layer.trainable = True

記得最後要再執行一次model.compile()
這些改動才會生效。

如何做二階段式訓練

這裡我第一階段先解凍6層,訓練15輪。
第二階段解凍19層(包含那6層),再訓練15輪。
總共30輪。

  • 6和19不是隨便選的數字,這兩個數字分別是EfficinetNetB0的最後兩個Block的網路層
unfreeze_n = [6, 19]
phase_epochs = [15, 30]
phase_batch_size = [32, 32]
phases = len(phase_epochs)

model = build_model()
for i in range(phases):
    unfreeze_model(model, n=unfreeze_n[i])
    model.fit(X_train, y_train_oh,
                     validation_data=(X_val, y_val_oh),
                     initial_epoch=0 if i == 0 else phase_epochs[i-1],
                     epochs=phase_epochs[i],
                     batch_size=phase_batch_size[i])

實驗結果

我把模型分成三個區塊:

區塊名稱 從後面數來的層數
Block-top 6
Block-7 19
Block-6 78
Block-all 全部

把訓練好的模型用以下稱呼:
初始模型: EFN_base_init(不使用預訓練參數,直接訓練Block-all)
模型零: EFN_base(單一階段微調Block-all)
模型一: EFN_2StepsToBlock7(先微調Block-top、再微調Block-7)
模型二: EFN_2StepsToAll(先微調Block-top、再微調Block-all)
模型三: EFN_3StepsToAll(先微調Block-top、再微調Block-7、再微調Block-all)

初始模型 vs 模型零

3
從val loss來看,初始模型比較沒有過擬合的問題。
4
但是從 val accuracy來看,模型零有預訓練的優勢,準確率高不只一點。

我推斷只要給初始模型更多訓練輪數,
他能夠無限趨近於模型零,
但最後還是會過擬合,
所以不如一開始就用模型零!
這就是遷移學習的好處。

模型一 vs 模型二

1
在前15輪中,兩者不相上下。
但後15輪中,模型二收斂的比較好,

模型二 vs 模型三

2
在第25輪,模型三的validation loss大幅上升。
我重複同樣實驗三次都得到這個結果。
我判斷是學習率太大,導致我跳出local minimum,
然後跳不回去了/images/emoticon/emoticon02.gif

如果沒有發生這種意外的話,模型三應當比較好才對

模型三學壞了!

全部都預測成Happy
應該是嗑藥了/images/emoticon/emoticon19.gif
https://ithelp.ithome.com.tw/upload/images/20210926/20141294SQQgX3xOo4.png

結語

如果單純比較val acc,
那還是微調 Block-all 30輪勝出。
但這是不公平的,
因為每開放一個block,都應該要微調30輪才對。
但我為了節省時間,把總輪數設固定是30輪。
才讓兩段式學習看起來不如全直接部微調。
https://ithelp.ithome.com.tw/upload/images/20210926/20141294tLlNZtRxYA.jpg

如果你觀察 val loss,會發現多階段訓練還是比較有潛力的(訓練更多輪)。
反之,看看EFN_base,
如果把模型都再訓練10輪,
EFN_base的損失值簡直快要飛上天和太陽肩並肩 = =
https://ithelp.ithome.com.tw/upload/images/20210926/201412949Z3xYNUUaA.jpg


上一篇
[Day 19] 還是學不會,再縮小一點 ~ (學習率衰減)
下一篇
[Day 21] 媽! Keras 和 TensorFlow 在亂存模型啦! ( TFLite 輕量模型)
系列文
AI Facial Expression Recognition: Data, Model, Application30

尚未有邦友留言

立即登入留言