在深度學習中,歸一化技術是提高模型訓練效率和性能的重要手段。歸一化通過調整輸入數據的分布,使得模型在訓練過程中更易于收斂,減少過擬合的風險。本文將介紹幾種常見的歸一化技術,包括特征歸一化、批歸一化、層歸一化和實例歸一化。
一、特征歸一化(Feature Normalization)
在深度學習和機器學習中,特征之間的量綱差異可能會對模型的訓練和性能產生顯著影響。當特征的量綱差異較大時,某些特征可能會在模型訓練中占據主導地位,從而導致模型對這些特征的過度依賴,而忽略其他特征。這可能導致模型無法有效學習到數據的整體結構。假設我們有一個數據集,其中包含兩個特征:
- 特征 A:房屋面積(單位:平方英尺),取值范圍為 [500, 5000]
- 特征 B:房屋價格(單位:美元),取值范圍為 [50,000, 500,000]
在這種情況下,特征 A 的值相對較小,而特征 B 的值相對較大。如果不進行歸一化,模型在訓練時可能會更關注特征 B,因為它的數值范圍更大。這可能導致模型對房屋價格的預測過于敏感,而忽略了房屋面積的重要性。
特征歸一化是將每個特征的值縮放到一個特定的范圍,通常是 [0, 1] 或 [-1, 1]。這種方法可以消除特征之間的量綱差異,使得模型在訓練時更快收斂。特征歸一化通常用于輸入數據的預處理階段,尤其是在使用基于梯度的優化算法時。
對于特征xxx,特征歸一化的公式如下:
x′=x?min(x)max(x)?min(x)x' = \frac{x - \text{min}(x)}{\text{max}(x) - \text{min}(x)}x′=max(x)?min(x)x?min(x)?
import numpy as np# 創建示例數據
data = np.array([[10, 200, 30],[20, 150, 40],[30, 300, 50],[40, 250, 60],[50, 100, 70]])print("原始數據:")
print(data)# 特征歸一化函數
def feature_normalization(data):# 計算每個特征的最小值和最大值min_vals = np.min(data, axis=0)max_vals = np.max(data, axis=0)# 應用歸一化公式normalized_data = (data - min_vals) / (max_vals - min_vals)return normalized_data# 進行特征歸一化
normalized_data = feature_normalization(data)print("\n歸一化后的數據:")
print(normalized_data)
原始數據:
[[ 10 200 30][ 20 150 40][ 30 300 50][ 40 250 60][ 50 100 70]]歸一化后的數據:
[[0. 0.5 0. ][0.25 0.25 0.25][0.5 1. 0.5 ][0.75 0.75 0.75][1. 0. 1. ]]
二、批歸一化(Batch Normalization)
批歸一化(Batch Normalization)是一種在神經網絡中廣泛使用的技術,旨在提高模型的訓練速度和穩定性。它通過對每一層的輸入進行歸一化,使得輸入的均值為 0,方差為 1,從而減少內部協變量偏移(Internal Covariate Shift)。
批歸一化的公式如下:
x^=x?μBσB2+?\hat{x} = \frac{x - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}}x^=σB2?+??x?μB??
其中:
- x^\hat{x}x^是歸一化后的輸出。
- xxx是當前批次的輸入。
- μB\mu_BμB?是當前批次的均值,計算公式為:
μB=1m∑i=1mxi\mu_B = \frac{1}{m} \sum_{i=1}^{m} x_iμB?=m1?i=1∑m?xi?
其中mmm是當前批次的樣本數量。 - σB2\sigma_B^2σB2?是當前批次的方差,計算公式為:
σB2=1m∑i=1m(xi?μB)2\sigma_B^2 = \frac{1}{m} \sum_{i=1}^{m} (x_i - \mu_B)^2σB2?=m1?i=1∑m?(xi??μB?)2 - ?\epsilon?是一個小常數,通常設置為 1e?51e-51e?5或 1e?81e-81e?8,用于防止除零錯誤。
批歸一化的主要步驟如下:
-
計算均值和方差:
- 對于每個批次,計算當前批次的均值μB\mu_BμB?和方差σB2\sigma_B^2σB2?。
-
歸一化:
- 使用計算得到的均值和方差對輸入進行歸一化。
-
縮放和偏移:
- 在歸一化后,批歸一化還引入了兩個可學習的參數γ\gammaγ和β\betaβ,用于縮放和偏移:
y=γx^+βy = \gamma \hat{x} + \betay=γx^+β
其中yyy是最終的輸出,γ\gammaγ和β\betaβ是在訓練過程中學習到的參數。
- 在歸一化后,批歸一化還引入了兩個可學習的參數γ\gammaγ和β\betaβ,用于縮放和偏移:
在訓練階段,批歸一化會使用當前批次的均值和方差進行歸一化。每個批次的均值和方差是動態計算的,這使得模型能夠適應訓練數據的變化。具體步驟如下:
- 計算當前批次的均值和方差。
- 使用這些統計量對輸入進行歸一化。
- 更新移動均值和方差,以便在推理階段使用:
μmoving=(1?α)?μB+α?μmoving\mu_{\text{moving}} = (1-\alpha) \cdot \mu_B + \alpha \cdot \mu_{\text{moving}}μmoving?=(1?α)?μB?+α?μmoving?
σmoving2=(1?α)?σB2+α?σmoving2\sigma_{\text{moving}}^2 = (1-\alpha) \cdot \sigma_B^2 + \alpha \cdot \sigma_{\text{moving}}^2σmoving2?=(1?α)?σB2?+α?σmoving2?
其中α\alphaα是滑動平均系數,通常設置為接近 1(如 0.9 或 0.99)。
在推理階段,批歸一化的處理方式與訓練階段有所不同。推理階段不再使用當前批次的均值和方差,而是使用在訓練階段計算得到的移動均值和方差。具體步驟如下:
- 使用訓練階段計算的移動均值和方差進行歸一化:
x^=x?μmovingσmoving2+? \hat{x} = \frac{x - \mu_{\text{moving}}}{\sqrt{\sigma_{\text{moving}}^2 + \epsilon}}x^=σmoving2?+??x?μmoving?? - 應用縮放和偏移:
y=γx^+βy = \gamma \hat{x} + \betay=γx^+β
下面代碼展示了,手動計算的,批歸一化,移動均值和方差與模型自動計算的結果一致性。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset# 定義簡單的全連接神經網絡模型
class SimpleFC(nn.Module):def __init__(self):super(SimpleFC, self).__init__()self.fc1 = nn.Linear(20, 1) # 從 1 個輸入特征到 1 個輸出self.bn1 = nn.BatchNorm1d(1) # 批歸一化層def forward(self, x):fc_output = self.fc1(x) # 全連接層x = self.bn1(fc_output) # 批歸一化return x, fc_output# 創建模型實例
model = SimpleFC()# 定義損失函數和優化器
criterion = nn.MSELoss() # 均方誤差損失
optimizer = optim.Adam(model.parameters(), lr=0.001) # Adam 優化器# 生成隨機正態分布數據
num_samples = 10000 # 樣本數量
X = torch.randn(num_samples, 20) # 1 個特征
y = torch.randn(num_samples, 1) # 目標值# 創建數據集和數據加載器
dataset = TensorDataset(X, y)
batch_size = 5000 # 每個批次的樣本數量
train_loader = DataLoader(dataset, batch_size=batch_size, shuffle=True)# 訓練模型
num_epochs = 1 # 訓練輪數
alpha = 0.9 # 滑動平均系數
moving_mean = torch.zeros(1) # 初始化移動均值
moving_var = torch.ones(1) # 初始化移動方差for epoch in range(num_epochs):model.train() # 設置模型為訓練模式for batch_idx, (images, labels) in enumerate(train_loader):optimizer.zero_grad() # 清零梯度outputs, fc_output = model(images) # 前向傳播loss = criterion(outputs, labels) # 計算損失loss.backward() # 反向傳播optimizer.step() # 更新參數# 獲取當前批次的均值和方差batch_mean = fc_output.mean(dim=0) # 當前批次的均值batch_var = fc_output.var(dim=0) # 當前批次的方差# 獲取模型的批歸一化層的移動均值和方差model_moving_mean = model.bn1.running_meanmodel_moving_var = model.bn1.running_var# 手動更新移動均值和方差moving_mean = alpha * moving_mean + (1 - alpha) * batch_meanmoving_var = alpha * moving_var + (1 - alpha) * batch_var# 打印當前批次的均值和方差print(f'Batch {batch_idx + 1}, Batch Mean: {batch_mean.data.numpy()}, Batch Var: {batch_var.data.numpy()}')# 打印模型自動計算的移動均值和方差print(f'Batch {batch_idx + 1}, Model Moving Mean: {model_moving_mean.data.numpy()}, Model Moving Var: {model_moving_var.data.numpy()}')# 打印手動計算的移動均值和方差print(f'Batch {batch_idx + 1}, Manual Moving Mean: {moving_mean.data.numpy()}, Manual Moving Var: {moving_var.data.numpy()}')# 驗證手動計算的移動均值和方差與模型的自動計算結果一致assert torch.allclose(moving_mean, model_moving_mean), "Manual moving mean does not match model moving mean!"assert torch.allclose(moving_var,model_moving_var), "Manual moving variance does not match model moving variance!"# 測試模型
model.eval() # 設置模型為評估模式
with torch.no_grad(): # 不計算梯度test_outputs, fc_output = model(X) # 前向傳播test_loss = criterion(test_outputs, y) # 計算測試損失# 打印推理階段的均值和方差
print(f'Test Loss: {test_loss.item()}')
print(f'Model Moving Mean: {model.bn1.running_mean.data.numpy()}')
print(f'Model Moving Var: {model.bn1.running_var.data.numpy()}')
Batch 1, Batch Mean: [0.07945078], Batch Var: [0.0101127]
Batch 1, Model Moving Mean: [0.00794508], Model Moving Var: [0.9010112]
Batch 1, Manual Moving Mean: [0.00794508], Manual Moving Var: [0.9010112]
Batch 2, Batch Mean: [0.07626408], Batch Var: [0.00997146]
Batch 2, Model Moving Mean: [0.01477698], Model Moving Var: [0.81190723]
Batch 2, Manual Moving Mean: [0.01477698], Manual Moving Var: [0.81190723]
Test Loss: 0.9921324253082275
Model Moving Mean: [0.01477698]
Model Moving Var: [0.81190723]
三、層歸一化(Layer Normalization)
層歸一化(Layer Normalization)是一種歸一化技術,主要用于深度學習模型,特別是在處理序列數據(如循環神經網絡 RNN 和 Transformer)時表現良好。與批歸一化不同,層歸一化是對每個樣本的所有特征進行歸一化,而不是對整個批次進行歸一化。
在層歸一化中,“層”指的是神經網絡中的一層,通常是一個全連接層或卷積層。層歸一化的目標是對該層的輸出進行歸一化,以提高模型的訓練效率和穩定性。
“樣本特征”是指輸入數據中每個樣本的特征向量。在層歸一化中,每個樣本的特征向量包含多個特征值,這些特征值可以是不同的輸入變量。例如,在圖像分類任務中,一個樣本的特征可能是圖像的像素值;在文本處理任務中,一個樣本的特征可能是詞嵌入向量。
在層歸一化中,我們會對每個樣本的所有特征進行歸一化處理。
x^=x?μσ2+?\hat{x} = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}}x^=σ2+??x?μ?
其中:
- x^\hat{x}x^是歸一化后的輸出。
- xxx是當前樣本的輸入特征向量。
- μ\muμ是當前樣本的均值,計算公式為:
μ=1d∑i=1dxi\mu = \frac{1}{d} \sum_{i=1}^{d} x_iμ=d1?i=1∑d?xi?
其中ddd是特征的維度。 - σ2\sigma^2σ2是當前樣本的方差,計算公式為:
σ2=1d∑i=1d(xi?μ)2\sigma^2 = \frac{1}{d} \sum_{i=1}^{d} (x_i - \mu)^2σ2=d1?i=1∑d?(xi??μ)2 - ?\epsilon?是一個小常數,通常設置為1e?51e-51e?5或1e?81e-81e?8,用于防止除零錯誤。
層歸一化的主要步驟如下:
-
計算均值和方差:
- 對于每個樣本,計算該樣本全部特征的均值μ\muμ和方差σ2\sigma^2σ2。
-
歸一化:
- 使用計算得到的均值和方差對輸入特征進行歸一化。
-
縮放和偏移:
- 在歸一化后,層歸一化還引入了兩個可學習的參數γ\gammaγ和 β\betaβ,用于縮放和偏移:
y=γx^+β y = \gamma \hat{x} + \betay=γx^+β
其中yyy是最終的輸出,γ\gammaγ和β\betaβ是在訓練過程中學習到的參數。
- 在歸一化后,層歸一化還引入了兩個可學習的參數γ\gammaγ和 β\betaβ,用于縮放和偏移:
在層歸一化中,訓練和推理階段的處理方式是相同的。與批歸一化不同,層歸一化不依賴于批次的統計量,而是對每個樣本的特征進行歸一化。因此,無論是在訓練階段還是推理階段,層歸一化都使用當前樣本的均值和方差進行歸一化。這使得層歸一化在處理小批量數據或單個樣本時表現良好。
import torchdef layer_normalization(X, epsilon=1e-5):"""計算層歸一化:param X: 輸入特征矩陣,形狀為 (num_samples, num_features):param epsilon: 防止除零錯誤的小常數:return: 歸一化后的特征矩陣"""# 計算均值和方差mu = X.mean(dim=1, keepdim=True) # 每個樣本的均值sigma_squared = X.var(dim=1, keepdim=True) # 每個樣本的方差# 進行歸一化X_normalized = (X - mu) / torch.sqrt(sigma_squared + epsilon)return X_normalized# 隨機生成樣本
num_samples = 5 # 樣本數量
num_features = 4 # 每個樣本的特征數量
X = torch.randn(num_samples, num_features) # 生成隨機正態分布數據print("原始特征矩陣:")
print(X)# 計算層歸一化
X_normalized = layer_normalization(X)print("\n層歸一化后的特征矩陣:")
print(X_normalized)
原始特征矩陣:
tensor([[-0.4186, 1.8211, -0.6178, -2.0494],[-0.3354, -1.9183, -0.5551, 0.7775],[ 0.0546, 0.5884, -0.8421, -0.2335],[ 0.3276, -0.5106, -0.0648, 0.0211],[ 0.6945, -0.8199, 0.5595, -2.3835]])層歸一化后的特征矩陣:
tensor([[-0.0640, 1.3364, -0.1886, -1.0837],[ 0.1558, -1.2746, -0.0427, 1.1615],[ 0.2730, 1.1685, -1.2312, -0.2102],[ 1.1095, -1.3107, -0.0234, 0.2246],[ 0.8222, -0.2314, 0.7283, -1.3191]])