線性模型在處理複雜非線性關係時存在侷限性,而神經網路則能有效克服這個問題。藉由在輸入層和輸出層之間加入隱藏層,並引入非線性啟用函式,神經網路可以模擬更複雜的資料模式。本文使用 TensorFlow 和 Keras 建構神經網路模型,並逐步說明如何調整學習率、應用 L1/L2 正則化、以及使用 Early Stopping 技巧來提升模型效能,避免過擬合。同時,我們也介紹如何使用 Keras Tuner 進行超引數調校,找出最佳的模型引陣列合,提升模型泛化能力。最後,我們將探討深層神經網路的架構,並介紹 Dropout 和 Batch Normalization 等最佳化技術,以進一步提升模型的穩定性和準確度。

深度學習模型:從線性模型到神經網路

在機器學習領域,線性模型是一種簡單且常見的模型,用於描述輸入資料和輸出結果之間的線性關係。然而,現實世界中的問題往往更為複雜,需要更強大的模型來描述。神經網路是一種模仿人腦神經元結構的機器學習模型,能夠處理更為複雜的非線性關係。

線性模型的限制

線性模型使用權重張量(W)和偏差張量(B)來描述輸入張量(X)和輸出張量(Y)之間的關係,通常以矩陣形式表示為:

Y = § B + WX

其中§代表softmax函式。線性模型的優點是簡單易懂,但其表達能力有限,難以處理複雜的非線性關係。

神經網路的引入

為了克服線性模型的限制,我們可以在輸入層和輸出層之間新增一個或多個Dense層,形成神經網路。神經網路中的隱藏層可以包含非線性啟用函式,從而使模型能夠表示更複雜的關係。

隱藏層與非線性啟用函式

假設我們線上性模型中新增一個Dense層,形成具有一個隱藏層的神經網路:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=(IMG_HEIGHT, IMG_WIDTH, 3)),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(len(CLASS_NAMES), activation='softmax')
])

內容解密:

  1. tf.keras.layers.Flatten 將輸入資料展平為一維向量,以便輸入到Dense層。
  2. tf.keras.layers.Dense(128, activation='relu') 定義了一個具有128個神經元的隱藏層,使用ReLU作為啟用函式。
    • ReLU(Rectified Linear Unit)是一種常用的非線性啟用函式,能夠輸出非負值。
    • 當輸入大於0時,ReLU輸出輸入值;否則輸出0。
  3. tf.keras.layers.Dense(len(CLASS_NAMES), activation='softmax') 定義了輸出層,使用softmax函式將輸出轉換為機率分佈。

在數學上,具有一個隱藏層的神經網路可以表示為:

Y = § B2 + W2 A(B1 + W1X)

其中A代表非線性啟用函式。這個公式表明,神經網路透過非線性啟用函式能夠表示更複雜的非線性關係。

訓練神經網路

訓練神經網路與訓練線性模型類別似,需要編譯模型並呼叫model.fit()方法:

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
              metrics=['accuracy'])
history = model.fit(train_dataset,
                    validation_data=eval_dataset,
                    epochs=10)

內容解密:

  1. optimizer='adam' 指定了使用Adam最佳化器進行梯度下降。
  2. loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False) 指定了損失函式為稀疏分類別交叉熵。
  3. metrics=['accuracy'] 指定了評估指標為準確率。
  4. model.fit() 方法用於訓練模型,傳入訓練資料集、驗證資料集和訓練輪數。

實驗結果與改進方向

實驗結果顯示,具有一個隱藏層的神經網路在驗證集上的準確率與線性模型相近,但損失函式值更低。這表明神經網路能夠更好地擬合訓練資料,但仍有改進空間。

未來的改進方向包括:

  • 調整學習率和損失函式
  • 更好地利用驗證資料集進行模型調優

調整學習率與正則化以改善模型效能

在訓練神經網路模型時,學習率(learning rate)與正則化(regularization)是兩個重要的超引數,它們會顯著影響模型的收斂速度和泛化能力。

學習率的影響

學習率控制著每次梯度下降(gradient descent)時的步長。如果學習率太高,模型可能會錯過最優解;而如果學習率太低,模型的收斂速度會變慢,並且可能陷入區域性最優解。

以Adam最佳化器為例,其預設學習率為0.001。我們可以透過修改compile()函式中的最佳化器來調整學習率:

model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001),
              loss=..., metrics=...)

內容解密:

  1. learning_rate=0.0001:將學習率設定為0.0001,降低了每次梯度下降的步長。
  2. optimizer=tf.keras.optimizers.Adam():使用Adam最佳化器進行梯度下降。
  3. 降低學習率後,模型的損失曲線和準確率曲線變得更加平滑,但需要更多的迭代次數才能收斂。

正則化的作用

正則化是一種用於防止過擬合(overfitting)的技術。當模型的複雜度遠高於訓練資料的規模時,模型可能會開始記憶訓練資料中的噪聲,從而導致驗證集上的效能下降。

常見的正則化方法包括L1正則化和L2正則化:

  • L1正則化:loss = cross-entropy + Σw_i,傾向於將部分權重驅動為零,適合用於模型剪枝。
  • L2正則化:loss = cross-entropy + Σw_i^2,傾向於將所有權重限制在較小的非零值,適合用於限制過擬合。

我們可以透過在Dense層中新增kernel_regularizer引數來應用L2正則化:

regularizer = tf.keras.regularizers.l2(0.001)
model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=(IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS)),
    tf.keras.layers.Dense(num_hidden,
                          kernel_regularizer=regularizer,
                          activation=tf.keras.activations.relu),
    tf.keras.layers.Dense(len(CLASS_NAMES),
                          kernel_regularizer=regularizer,
                          activation='softmax')
])

內容解密:

  1. tf.keras.regularizers.l2(0.001):定義了一個L2正則化項,其係數為0.001。
  2. kernel_regularizer=regularizer:將正則化項應用於Dense層的權重上。
  3. L2正則化透過對大權重施加懲罰,減少了模型的過擬合傾向。

早期停止(Early Stopping)

早期停止是一種用於防止過擬合的另一個技術。當驗證集上的準確率不再提升時,我們可以停止訓練,以避免模型過度擬合訓練資料中的噪聲。

我們可以透過在fit()函式中新增EarlyStopping回撥函式來實作早期停止:

history = model.fit(train_dataset,
                    validation_data=eval_dataset,
                    epochs=10,
                    callbacks=[tf.keras.callbacks.EarlyStopping(patience=1)])

內容解密:

  1. tf.keras.callbacks.EarlyStopping(patience=1):當驗證集上的效能在連續1個epoch內沒有提升時,停止訓練。
  2. callbacks=[...]:將EarlyStopping回撥函式傳遞給fit()函式。
  3. 早期停止可以避免不必要的訓練,並減少過擬合的風險。

超引數調校

在建立模型時,我們選擇了多個引數,例如隱藏節點的數量、學習率、L2 正則化等。如何確定這些引數是最佳的?實際上,我們並不知道。我們需要對這些超引數進行調校。

使用 Keras Tuner 進行超引數調校

一種方法是使用 Keras Tuner。首先,我們需要實作一個模型建構函式,以便使用超引數(完整程式碼請參考 GitHub 上的 02_ml_models/02b_neural_network.ipynb 檔案):

import kerastuner as kt

def build_model(hp):
    lrate = hp.Float('lrate', 1e-4, 1e-1, sampling='log')
    l1 = 0
    l2 = hp.Choice('l2', values=[0.0, 1e-1, 1e-2, 1e-3, 1e-4])
    num_hidden = hp.Int('num_hidden', 32, 256, 32)
    regularizer = tf.keras.regularizers.l1_l2(l1, l2)

    # 建立具有一個隱藏層的神經網路
    model = tf.keras.Sequential([
        tf.keras.layers.Flatten(input_shape=(IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS)),
        tf.keras.layers.Dense(num_hidden, kernel_regularizer=regularizer, activation=tf.keras.activations.relu),
        tf.keras.layers.Dense(len(CLASS_NAMES), kernel_regularizer=regularizer, activation='softmax')
    ])

    model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=lrate),
                  loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
                  metrics=['accuracy'])
    return model

內容解密:

  • hp.Float('lrate', 1e-4, 1e-1, sampling='log'):定義學習率的範圍為 $10^{-4}$ 至 $10^{-1}$,並以對數方式取樣。這使得學習率在較小的範圍內有更多的可能值。
  • hp.Choice('l2', values=[0.0, 1e-1, 1e-2, 1e-3, 1e-4]):定義 L2 正則化的可能值。這裡選擇了五個不同的值,包含 $0$ 和四個不同的正則化強度。
  • hp.Int('num_hidden', 32, 256, 32):定義隱藏層節點數量的範圍,從 $32$ 到 $256$,以 $32$ 為間隔遞增。
  • tf.keras.regularizers.l1_l2(l1, l2):建立一個正則化器,使用 L1 和 L2 正則化,L1 正則化強度設為 $0$,L2 正則化強度由超引數 l2 控制。

將模型建構函式傳入 Keras Tuner

tuner = kt.BayesianOptimization(
    build_model,
    objective=kt.Objective('val_accuracy', 'max'),
    max_trials=10,
    num_initial_points=2,
    overwrite=False)  # 設定為 True 以重新開始

內容解密:

  • kt.BayesianOptimization:使用貝葉斯最佳化演算法進行超引數搜尋。
  • objective=kt.Objective('val_accuracy', 'max'):設定目標是最大化驗證準確率。
  • max_trials=10:最多進行 $10$ 次試驗。
  • num_initial_points=2:初始隨機點的數量為 $2$。

執行超引數搜尋

tuner.search(
    train_dataset, 
    validation_data=eval_dataset,
    epochs=5,
    callbacks=[tf.keras.callbacks.EarlyStopping(patience=1)]
)

內容解密:

  • tuner.search:開始執行超引數搜尋。
  • train_dataseteval_dataset:分別是訓練資料集和驗證資料集。
  • epochs=5:每個試驗執行 $5$ 個訓練週期。
  • callbacks=[tf.keras.callbacks.EarlyStopping(patience=1)]:當驗證損失停止改善時,提前停止訓練。

取得最佳超引數和模型

topN = 2
for x in range(topN):
    print(tuner.get_best_hyperparameters(topN)[x].values)
    print(tuner.get_best_models(topN)[x].summary())

內容解密:

  • tuner.get_best_hyperparameters(topN):取得前 topN 個最佳超引陣列合。
  • tuner.get_best_models(topN):取得前 topN 個最佳模型。

深層神經網路

線性模型的準確率約為 $0.4$,具有一個隱藏層的神經網路準確率約為 $0.46$。如果我們增加更多的隱藏層會怎樣?

建立深層神經網路

def train_and_evaluate(batch_size=32,
                       lrate=0.0001,
                       l1=0,
                       l2=0.001,
                       num_hidden=[64, 16]):
    # 建立具有多個隱藏層的神經網路
    layers = [
        tf.keras.layers.Flatten(input_shape=(IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS), name='input_pixels')
    ]
    
    regularizer = tf.keras.regularizers.l1_l2(l1, l2)
    
    layers = layers + [
        tf.keras.layers.Dense(nodes, kernel_regularizer=regularizer, activation=tf.keras.activations.relu, name='hidden_dense_{}'.format(hno))
        for hno, nodes in enumerate(num_hidden)
    ]
    
    layers = layers + [
        tf.keras.layers.Dense(len(CLASS_NAMES), kernel_regularizer=regularizer, activation='softmax', name='flower_prob')
    ]
    
    model = tf.keras.Sequential(layers, name='flower_classification')

內容解密:

  • 使用多個隱藏層來建立深層神經網路,每層使用 ReLU 啟用函式和正則化。
  • 輸出層使用 softmax 啟用函式進行多類別分類別。

Dropout 正則化技術

Dropout 是深度學習中一種常見的正則化技術。在每個訓練迭代中,Dropout 層會隨機丟棄一部分神經元(設定輸出為零),丟棄的機率為 $p$(通常在 $25%$ 至 $50%$ 之間)。這樣可以避免模型過度依賴某些特定的神經元,從而減少過擬合。

圖表說明

此圖示展示了 Dropout 的工作原理。在訓練過程中,部分神經元被隨機丟棄,剩下的神經元繼續參與前向和後向傳播。這種隨機丟棄機制迫使模型學習更魯棒的特徵表示,而不是過度依賴某些特定的神經元。

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title 從線性模型到神經網路

package "線性模型" {
    component [輸入 X] as input
    component [權重 W] as weights
    component [偏差 B] as bias
    component [Y = WX + B] as linear
}

package "神經網路架構" {
    component [輸入層] as input_layer
    component [隱藏層 (Dense)] as hidden
    component [啟用函式 (ReLU)] as relu
    component [輸出層 (Softmax)] as output
}

package "最佳化技術" {
    component [L1/L2 正則化] as regularize
    component [Dropout] as dropout
    component [Batch Normalization] as bn
    component [Early Stopping] as early
}

package "超參數調校" {
    component [Keras Tuner] as tuner
    component [學習率] as lr
    component [網路深度/寬度] as arch
}

input --> weights : 特徵
weights --> bias : 加權
bias --> linear : 線性組合

input_layer --> hidden : 展平
hidden --> relu : 非線性
relu --> output : 機率分佈

regularize --> dropout : 限制複雜度
bn --> early : 穩定訓練
tuner --> lr : 搜尋最佳
tuner --> arch : 架構優化

note right of linear
  線性模型限制:
  - 只能擬合線性關係
  - 表達能力有限
end note

note right of relu
  ReLU 優點:
  - 非線性轉換
  - 計算高效
  - 緩解梯度消失
end note

@enduml

圖表內容解密:

  • 輸入層將資料傳遞給隱藏層。
  • 隱藏層中的部分神經元被隨機丟棄,這些被丟棄的神經元不參與當前的訓練迭代。
  • 經過 Dropout 處理後的資料傳遞給輸出層進行最終的預測。

深度神經網路的最佳化:Dropout 與 Batch Normalization

在深度學習的神經網路中,模型的複雜度往往導致過擬合(overfitting)和訓練不穩定等問題。為瞭解決這些問題,研究人員提出了許多最佳化技術,其中 Dropout 和 Batch Normalization 是兩種非常有效的技術。

Dropout:隨機丟棄神經元

Dropout 是一種在訓練過程中隨機丟棄神經元的技術,以避免神經網路過度依賴某些特定的神經元,從而減少過擬合的風險。在 Keras 中,可以使用 tf.keras.layers.Dropout 層來實作 Dropout。

tf.keras.layers.Dropout(rate=0.4)

內容解密:

  • rate=0.4 表示在訓練過程中,40% 的神經元會被隨機丟棄。
  • 在訓練過程中,Dropout 層會隨機丟棄神經元,而在評估和預測過程中,所有神經元都會被保留。
  • 這種技術可以強制神經網路學習更加穩健的特徵表示,避免過度依賴某些特定的神經元。

Batch Normalization:批次標準化

Batch Normalization 是一種透過標準化神經網路中間層的輸出,以穩定訓練過程和提高模型效能的技術。在 Keras 中,可以使用 tf.keras.layers.BatchNormalization 層來實作 Batch Normalization。

tf.keras.layers.BatchNormalization(scale=False, center=True)

內容解密:

  • scale=False 表示不對輸出進行縮放。
  • center=True 表示對輸出進行中心化處理。
  • Batch Normalization 可以減少內部共變數偏移(Internal Covariate Shift),使得神經網路的訓練更加穩定和快速。
  • 在預測過程中,Batch Normalization 會使用訓練過程中計算的執行平均值和標準差。

結合 Dropout 和 Batch Normalization

在實際應用中,可以將 Dropout 和 Batch Normalization 結合使用,以進一步提高模型的效能。

for hno, nodes in enumerate(num_hidden):
    layers.extend([
        tf.keras.layers.Dense(nodes,
                              kernel_regularizer=regularizer,
                              name='hidden_dense_{}'.format(hno)),
        tf.keras.layers.BatchNormalization(scale=False, 
                                          center=False, 
                                          name='batchnorm_dense_{}'.format(hno)),
        tf.keras.layers.Activation('relu',
                                   name='relu_dense_{}'.format(hno)),
        tf.keras.layers.Dropout(rate=0.4,
                                name='dropout_dense_{}'.format(hno)),
    ])

內容解密:

  • 將 Batch Normalization 層放在 Dense 層之後,Activation 層之前。
  • 使用 ReLU 啟用函式,並將其放在 Batch Normalization 層之後。
  • 在每個隱藏層之後新增 Dropout 層,以避免過擬合。

實驗結果

透過結合使用 Dropout 和 Batch Normalization,可以顯著提高模型的效能和泛化能力。實驗結果表明,模型的準確率從 0.40 提高到 0.48,損失曲線也更加平滑。