九、批量標準化
是一種廣泛使用的神經網絡正則化技術,對每一層的輸入進行標準化,進行縮放和平移,目的是加速訓練,提高模型穩定性和泛化能力,通常在全連接層或是卷積層之和,激活函數之前使用
核心思想
對每一批數據的通道進行標準化,解決內部協變量偏移
? ? ? ? 加速網絡訓練;運行使用更大的學習率;減少對初始化的依賴;提供輕微的正則化效果
思路:在輸入上執行標準化操作,學習兩可訓練的參數:縮放因子γ和偏移量β
?批量標準化操作 在訓練階段和測試階段行為是不同的。測試階段沒有mini_batch數據,無法直接計算當前batch的均值和方差,所以使用訓練階段計算的全局統量(均值和方差)進行標準化
1. 訓練階段的批量標準化
1.1 計算均值和方差
對于給定的神經網絡層,輸入,m是批次大小。我們計算該批次數據的均值和方差
均值
方差
1.2 標準化
用計算得到的均值和方差對數據進行標準化,使得沒個特征的均值為0,方差為1
標準化后的值
ε是很小的常數,防止除0
1.3 縮放和平移
標準化的數據通常會通過可訓練的參數進行縮放和平移,以揮發模型的表達能力
縮放
平移
γ和β是在訓練過程中學習到的參數,會隨著網絡的訓練過程通過反向傳播進行更新
1.4 更新全局統計量
指數移動平均更新全局均值和方差
momentum是超變量,控制當前mini-batch統計量對全局統計量的貢獻
它在0到1之間,控制mini-batch統計量的權重,在pytorch默認為0.1
與優化器中的momentum的區別
標準化中的:
更新全局統計量
控制當前mini-batch統計量對全局統計量的貢獻
優化器中:
加速梯度下降,跳出局部最優
2.測試階段的批量標準化
測試階段沒有mini-batch數據,所以通過EMA計算的全局統計量來進行標準化
測試階段用全局統計量對輸入數據進行標準化
對標準化后的數據進行縮放和平移
為什么用全局統計量
一致性:
在測試階段,輸入數據通常是單個樣本或少量樣本,無法準確計算均值和方差。
使用全局統計量可以確保測試階段的行為與訓練階段一致。
穩定性:
全局統計量是通過訓練階段的大量 mini-batch 數據計算得到的,能夠更好地反映數據的整體分布。
使用全局統計量可以減少測試階段的隨機性,使模型的輸出更加穩定。
效率:
在測試階段,使用預先計算的全局統計量可以避免重復計算,提高效率。
3. 作用
3.1 緩解梯度問題
防止激活值過大或過小,避免激活函數的飽和,緩解梯度消失或爆炸
3.2 加速訓練
輸入值分布更穩定,提高學習訓練的效率,加速收斂
3.3 減少過擬合
類似于正則化,有助于提高模型的泛化能力
避免對單一數據點的過度擬合
4. 函數說明
torch.nn.BatchNorm1d
是 PyTorch 中用于一維數據的批量標準化(Batch Normalization)模塊。
torch.nn.BatchNorm1d(num_features, ? ? ? ? # 輸入數據的特征維度eps=1e-05, ? ? ? ? ? # 用于數值穩定性的小常數momentum=0.1, ? ? ? ?# 用于計算全局統計量的動量affine=True, ? ? ? ? # 是否啟用可學習的縮放和平移參數track_running_stats=True, ?# 是否跟蹤全局統計量device=None, ? ? ? ? # 設備類型(如 CPU 或 GPU)dtype=None ? ? ? ? ? # 數據類型 )
參數說明:
eps:用于數值穩定性的小常數,添加到方差的分母中,防止除零錯誤。默認值:1e-05
momentum:用于計算全局統計量(均值和方差)的動量。默認值:0.1,參考本節1.4
affine:是否啟用可學習的縮放和平移參數(γ和 β)。如果 affine=True,則模塊會學習兩個參數;如果 affine=False,則不學習參數,直接輸出標準化后的值 。默認值:True
track_running_stats:是否跟蹤全局統計量(均值和方差)。如果 track_running_stats=True,則在訓練過程中計算并更新全局統計量,并在測試階段使用這些統計量。如果 track_running_stats=False,則不跟蹤全局統計量,每次標準化都使用當前 mini-batch 的統計量。默認值:True
4. 代碼實現
import torch
from torch import nn
from matplotlib import pyplot as pltfrom sklearn.datasets import make_circles
from sklearn.model_selection import train_test_split
from torch.nn import functional as F
from torch import optim# 生成數據集:兩個同心圓,內圈和外圈的點分別屬于兩個類別
x, y = make_circles(n_samples=2000, noise=0.1, factor=0.4, random_state=42)
# 轉換為PyTorch張量
x = torch.tensor(x, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.long)# 劃分訓練集和測試集
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3,random_state=42)# 可視化數據集
plt.scatter(x[:, 0], x[:, 1], c=y, cmap='coolwarm', edgecolors="k")
plt.show()# 定義帶批量歸一化的神經網絡
class NetWithBN(nn.Module):def __init__(self):super().__init__()# 第一層全連接層,輸入維度2,輸出維度64self.fc1 = nn.Linear(2, 64)# 第一層批量歸一化self.bn1 = nn.BatchNorm1d(64)# 第二層全連接層,輸入維度64,輸出維度32self.fc2 = nn.Linear(64, 32)# 第二層批量歸一化self.bn2 = nn.BatchNorm1d(32)# 第三層全連接層,輸入維度32,輸出維度2(兩個類別)self.fc3 = nn.Linear(32, 2)def forward(self, x):# 前向傳播:ReLU激活函數+批量歸一化+全連接層x = F.relu(self.bn1(self.fc1(x)))x = F.relu(self.bn2(self.fc2(x)))x = self.fc3(x)return x# 定義不帶批量歸一化的神經網絡
class NetWithoutBN(nn.Module):def __init__(self):super().__init__()# 第一層全連接層,輸入維度2,輸出維度64self.fc1 = nn.Linear(2, 64)# 第二層全連接層,輸入維度64,輸出維度32self.fc2 = nn.Linear(64, 32)# 第三層全連接層,輸入維度32,輸出維度2(兩個類別)self.fc3 = nn.Linear(32, 2)def forward(self, x):# 前向傳播:ReLU激活函數+全連接層x = F.relu(self.fc1(x))x = F.relu(self.fc2(x))x = self.fc3(x)return x# 定義訓練函數
def train(model, x_train, y_train, x_test, y_test, name, lr=0.1, epoches=500):# 定義交叉熵損失函數criterion = nn.CrossEntropyLoss()# 定義SGD優化器optimizer = optim.SGD(model.parameters(), lr=lr)# 用于記錄訓練損失和測試準確率train_loss = []test_acc = []for epoch in range(epoches):# 設置模型為訓練模式model.train()# 前向傳播y_pred = model(x_train)# 計算損失loss = criterion(y_pred, y_train)# 反向傳播optimizer.zero_grad()loss.backward()optimizer.step()# 記錄訓練損失train_loss.append(loss.item())# 設置模型為評估模式model.eval()# 禁用梯度計算with torch.no_grad():# 前向傳播y_test_pred = model(x_test)# 獲取預測類別_, pred = torch.max(y_test_pred, dim=1)# 計算正確預測的數量correct = (pred == y_test).sum().item()# 計算測試準確率test_acc.append(correct / len(y_test))# 每100個epoch打印一次日志if epoch % 100 == 0:print(F"{name}|Epoch:{epoch},loss:{loss.item():.4f},acc:{test_acc[-1]:.4f}")return train_loss, test_acc# 創建帶批量歸一化的模型
model_bn = NetWithBN()
# 創建不帶批量歸一化的模型
model_nobn = NetWithoutBN()# 訓練帶批量歸一化的模型
bn_train_loss, bn_test_acc = train(model_bn, x_train, y_train, x_test, y_test,name="BN")
# 訓練不帶批量歸一化的模型
nobn_train_loss, nobn_test_acc = train(model_nobn, x_train, y_train, x_test, y_test,name="NoBN")# 定義繪圖函數
def plot(bn_train_loss, nobn_train_loss, bn_test_acc, nobn_test_acc):# 創建繪圖窗口fig = plt.figure(figsize=(10, 5))# 添加子圖1:訓練損失ax1 = fig.add_subplot(1, 2, 1)ax1.plot(bn_train_loss, "b", label="BN")ax1.plot(nobn_train_loss, "r", label="NoBN")ax1.legend()# 添加子圖2:測試準確率ax2 = fig.add_subplot(1, 2, 2)ax2.plot(bn_test_acc, "b", label="BN")ax2.plot(nobn_test_acc, "r", label="NoBN")ax2.legend()# 顯示圖像plt.show()# 調用繪圖函數
plot(bn_train_loss, nobn_train_loss, bn_test_acc, nobn_test_acc)
?
十、模型的保存和加載
?1.標準網絡模型構建
class MyModel(nn.Module):def __init__(self,input_size,output_size):super(MyModel,self).__init__()self.fc1 = nn.Linear(input_size,128)self.fc2 = nn.Linear(128,64)self.fc3 = nn.Linear(64,output_size)def forward(self,x):x = self.fc1(x)x = self.fc2(x)output = self.fc3(x)return outputmodel = MyModel(input_size=10,output_size = 2)
x =torch.randn(5,10)output = model(x)
?2. 序列化模型對象
模型保存:
torch.save(obj, f, pickle_module=pickle, pickle_protocol=DEFAULT_PROTOCOL, _use_new_zipfile_serialization=True)
參數說明:
obj:要保存的對象,可以是模型、張量、字典等。
f:保存文件的路徑或文件對象。可以是字符串(文件路徑)或文件描述符。
pickle_module:用于序列化的模塊,默認是 Python 的 pickle 模塊。
pickle_protocol:pickle 模塊的協議版本,默認是 DEFAULT_PROTOCOL(通常是最高版本)。
模型加載:
torch.load(f, map_location=None, pickle_module=pickle, **pickle_load_args)
參數說明:
f:文件路徑或文件對象。可以是字符串(文件路徑)或文件描述符。
map_location:指定加載對象的設備位置(如 CPU 或 GPU)。默認是 None,表示保持原始設備位置。例如:map_location=torch.device('cpu') 將對象加載到 CPU。
pickle_module:用于反序列化的模塊,默認是 Python 的 pickle 模塊。
pickle_load_args:傳遞給 pickle_module.load() 的額外參數。
import torch
import torch.nn as nn
import pickleclass MyModel(nn.Module):def __init__(self,input_size,output_size):super(MyModel,self).__init__()self.fc1 = nn.Linear(input_size,output_size,128)self.fc2 = nn.Linear(128,64)self.fc3 = nn.Linear(64,output_size)def forward(self,x):x = self.fc1(x)x = self.fc2(x)output = self.fc3(x)return output
def test001():model = MyModel(input_size=128,output_size=32)torch.save(model,"model.pkl",pickle_module=pickle,pickle_protocol=2)def test002():model = torch.load("model.pkl",map_location = "cpu",pickle_module=pickle)print(model)test001()
test002()
.pkl是二進制文件,內容是通過pickle模塊化序列的python對象。可能存在兼容問題(python2,3的區別)
.pth是二進制文件,序列化的pytorch模型或張量。
3. 模型保存參數
import torch
import torch.nn as nn
import torch.optim as optim
import pickleclass MyModle(nn.Module):def __init__(self,input_size,output_size):super(MyModle,self).__init__()self.fc1 = nn.Linear(input_size,128)self.fc2 = nn.Linear(128,64)self.fc3 = nn.Linear(64,output_size)def forward(self,x):x = self.fc1(x)x = self.fc2(x)output = self.fc3(x)return outputdef test003():model = MyModle(input_size=128,output_size=32)optimizer = optim.SGD(model.parameters(),lr = 0.01)save_dict = {"init_params":{"input_size":128,"output_size":32,},"accuracy":0.99,"model_state_dict":model.state_dict(),"optimizer_state_dict":optimizer.state_dict(),}torch.save(save_dict,"model_dict.pth")def test004():save_dict = torch.load("model_dict.pth")model = MyModle(input_size = save_dict["init_params"]["input_size"],output_size = save_dict["init_params"]["output_size"],)model.load_state_dict(save_dict["model_state_dict"])optimizer = optim.SGD(model.parameters(),lr = 0.01)optimizer.load_state_dict(save_dict["optimizer_state_dict"])print(save_dict["accuracy"])print(model)test003()
test004()
推理時加載模型參數簡單如下:
# 保存模型狀態字典 torch.save(model.state_dict(), 'model.pth') ? # 加載模型狀態字典 model = MyModel(128, 32) model.load_state_dict(torch.load('model.pth')) ?