線性模型在處理複雜非線性關係時存在侷限性,而神經網路則能有效克服這個問題。藉由在輸入層和輸出層之間加入隱藏層,並引入非線性啟用函式,神經網路可以模擬更複雜的資料模式。本文使用 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')
])
內容解密:
tf.keras.layers.Flatten將輸入資料展平為一維向量,以便輸入到Dense層。tf.keras.layers.Dense(128, activation='relu')定義了一個具有128個神經元的隱藏層,使用ReLU作為啟用函式。- ReLU(Rectified Linear Unit)是一種常用的非線性啟用函式,能夠輸出非負值。
- 當輸入大於0時,ReLU輸出輸入值;否則輸出0。
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)
內容解密:
optimizer='adam'指定了使用Adam最佳化器進行梯度下降。loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False)指定了損失函式為稀疏分類別交叉熵。metrics=['accuracy']指定了評估指標為準確率。model.fit()方法用於訓練模型,傳入訓練資料集、驗證資料集和訓練輪數。
實驗結果與改進方向
實驗結果顯示,具有一個隱藏層的神經網路在驗證集上的準確率與線性模型相近,但損失函式值更低。這表明神經網路能夠更好地擬合訓練資料,但仍有改進空間。
未來的改進方向包括:
- 調整學習率和損失函式
- 更好地利用驗證資料集進行模型調優
調整學習率與正則化以改善模型效能
在訓練神經網路模型時,學習率(learning rate)與正則化(regularization)是兩個重要的超引數,它們會顯著影響模型的收斂速度和泛化能力。
學習率的影響
學習率控制著每次梯度下降(gradient descent)時的步長。如果學習率太高,模型可能會錯過最優解;而如果學習率太低,模型的收斂速度會變慢,並且可能陷入區域性最優解。
以Adam最佳化器為例,其預設學習率為0.001。我們可以透過修改compile()函式中的最佳化器來調整學習率:
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001),
loss=..., metrics=...)
內容解密:
learning_rate=0.0001:將學習率設定為0.0001,降低了每次梯度下降的步長。optimizer=tf.keras.optimizers.Adam():使用Adam最佳化器進行梯度下降。- 降低學習率後,模型的損失曲線和準確率曲線變得更加平滑,但需要更多的迭代次數才能收斂。
正則化的作用
正則化是一種用於防止過擬合(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')
])
內容解密:
tf.keras.regularizers.l2(0.001):定義了一個L2正則化項,其係數為0.001。kernel_regularizer=regularizer:將正則化項應用於Dense層的權重上。- L2正則化透過對大權重施加懲罰,減少了模型的過擬合傾向。
早期停止(Early Stopping)
早期停止是一種用於防止過擬合的另一個技術。當驗證集上的準確率不再提升時,我們可以停止訓練,以避免模型過度擬合訓練資料中的噪聲。
我們可以透過在fit()函式中新增EarlyStopping回撥函式來實作早期停止:
history = model.fit(train_dataset,
validation_data=eval_dataset,
epochs=10,
callbacks=[tf.keras.callbacks.EarlyStopping(patience=1)])
內容解密:
tf.keras.callbacks.EarlyStopping(patience=1):當驗證集上的效能在連續1個epoch內沒有提升時,停止訓練。callbacks=[...]:將EarlyStopping回撥函式傳遞給fit()函式。- 早期停止可以避免不必要的訓練,並減少過擬合的風險。
超引數調校
在建立模型時,我們選擇了多個引數,例如隱藏節點的數量、學習率、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_dataset和eval_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,損失曲線也更加平滑。