簡介
在之前專欄的兩篇文章中我主要介紹了數據的準備以及模型的構建,模型構建完成的下一步就是模型的訓練優化,訓練完成的模型用於實際應用中。
損失函數
損失函數用於衡量預測值與目標值之間的誤差,通過最小化損失函數達到模型的優化目標。不同的損失函數其衡量效果不同,未必都是出於最好的精度而設計的。PyTorch對於很多常用的損失函數進行了封裝,均在torch.nn
模塊下,它們的使用方法類似,實例化損失計算對象,然後用實例化的對象對預測值和目標值進行損失計算即可。
- L1損失
nn.L1Loss(reduction)
- 計算L1損失即絕對值誤差。
- reduce參數表示是否返回標量,默認返回標量,否則返回同維張量。
- size_average參數表示是否返回的標量爲均值,默認爲均值,否則爲求和結果。
- reduction參數取代了上述兩個參數,
mean
、sum
和None
的取值對應上面的結果。 - 下面代碼可以演示損失的計算流程。
import torch from torch import nn pred = torch.ones(100, 1) * 0.5 label = torch.ones(100, 1) l1_mean = nn.L1Loss() l1_sum = nn.L1Loss(reduction='sum') print(l1_mean(pred, label)) print(l1_sum(pred, label))
- MSE損失
nn.MSELoss(reduction='mean')
- 計算均方誤差,常用於迴歸問題。
- 參數同上。
- CE損失
nn.CrossEntropyLoss(weight=None, ignore_index=-100, reduction='mean')
- 計算交叉熵損失,常用於分類問題。並非標準的交叉熵,而是結合了Softmax的結果,也就是說會將結果先進行softmax計算爲概率分佈。
- weight參數是每個類別的權重,用於解決樣本不均衡問題。
- reduction參數類似上面的損失函數。
- ignore_index參數表示忽略某個類別,不計算其損失。
- KL散度
nn.KLDivLoss(reduction='mean')
- 計算KL散度。
- 參數同上。
- 二分交叉熵
nn.BCELoss(reduction='mean')
- 計算二分交叉熵損失,一般用於二分類問題。
- 邏輯二分交叉熵
nn.BCEWithLogitsLoss()
- 輸入先經過sigmoid變換再計算損失,類似CE損失。
上述只是提到了幾個常用的簡單損失函數,更加複雜的可以查看官方文檔,一共封裝了近20個損失,當然,也可以自定義損失函數,返回一個張量或者標量即可(事實上這些損失函數就是這麼幹的)。
優化器
數據、模型、損失函數都確定了,那這個深度模型任務其實已經完成了大半,接下來就是選擇合適的優化器對模型進行優化訓練。
首先,要了解PyTorch中優化器的機制,其所有優化器都是繼承自Optimizer類,該類封裝了一套基礎的方法如state_dict()
、load_state_dict()
等。
參數組(param_groups)
任何優化器都有一個屬性爲param_groups
,這是因爲優化器對參數的管理是基於組進行的,爲每一組參數配置特定的學習率、動量比例、衰減率等等,該屬性爲一個列表,裏面多個字典,對應不同的參數及其配置。
例如下面的代碼中只有一個組。
import torch
import torch.optim as optim
w1 = torch.randn(2, 2)
w2 = torch.randn(2, 2)
optimizer = optim.SGD([w1, w2], lr=0.1)
print(optimizer.param_groups)
梯度清零
事實上,PyTorch不會在一次優化完成後清零之前計算得到的梯度,所以需要每次優化完成後手動清零,即調用優化器的zero_grad()
方法。
參數組添加
通過調用優化器的add_param_group()
方法可以添加一組定製的參數。
常用優化器
PyTorch將這些優化算法均封裝於torch.optim模塊下,其實現時對原論文有所改動,具體參見源碼。
- 隨機梯度下降
optim.SGD(params, lr, momentum, weight_decay)
- 隨機梯度下降優化器。
- params參數表示需要管理的參數組。
- lr參數表示初始學習率,可以按需調整學習率。
- momentum參數表示動量SGD中的動量值,一般爲0.9。
- weight_decay參數表示權重衰減係數,也是L2正則係數。
- 隨機平均梯度下降
optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-8, weight_decay=0, amsgrad=False)
- Adam優化算法的實現。
- 參數類似上面。
下圖演示了各種算法相同情境下的收斂效果。
學習率調整策略
合適的學習率可以使得模型迅速收斂,這也是Adam等算法的初衷,一般我們訓練時會在開始給一個較大的學習率,隨着訓練的進行逐漸下調這個學習率。那麼何時下調、下調多少,相關的問題就是學習率調整策略,PyTorch提供了6中策略以供使用,它們都在torch.optim.lr_scheduler
中,分爲有序調整(較爲死板)、自適應調整(較爲靈活)和自定義調整(適合各種情況)。
下面介紹最常用的自動學習率調整機制。它封裝爲optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=10, verbose=False, threshold=0.0001,threshold_mode='rel', cooldown=0, min_lr=0, eps=1e-8)
。
當指標不再變化時即調整學習率,這是一種非常實用的學習率調整策略。例如,當驗證集的損失不再下降即即將陷入過擬合,進行學習率調整。
- mode參數由兩種爲
min
和max
,當指標不再變低或者變高時調整。 - factor參數表示學習率調整比例。
- patience參數表示等待耐心,當patience個step指標不變即調整學習率。
- verbose參數表示調整學習率是否可見。
- cooldown參數表示冷卻時間,調整後冷卻時間內不再調整。
- min_lr參數表示學習率下限。
- eps參數表示學習率衰減最小值,學習率變化小於該值不調整。
訓練流程實戰
下面的代碼演示了數據的導入、模型構建、損失函數使用以及優化器的優化整個流程,大部分時候我們使用PyTorch進行模型訓練都是這個思路。
import torch
from torch import nn
import torch.nn.functional as F
from torch import optim
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=(3, 3))
self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
self.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3)
self.pool2 = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(64*54*54, 256)
self.fc2 = nn.Linear(256, 128)
self.fc3 = nn.Linear(128, 101)
def forward(self, x):
x = self.pool1(F.relu(self.conv1(x)))
x = self.pool2(F.relu(self.conv2(x)))
x = x.view(-1, 64*54*54)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
x = torch.randn((32, 3, 224, 224))
y = torch.ones(32, ).long()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9, dampening=0.1)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.1)
epochs = 10
losses = []
for epoch in range(epochs):
correct = 0.0
total = 0.0
optimizer.zero_grad()
outputs = net(x)
loss = criterion(outputs, y)
loss.backward()
optimizer.step()
scheduler.step()
_, predicted = torch.max(outputs.data, 1)
total += y.size(0)
correct += (predicted == y).squeeze().sum().numpy()
losses.append(loss.item())
print("loss", loss.item(), "acc", correct / total)
import matplotlib.pyplot as plt
plt.plot(list(range(len(losses))), losses)
plt.savefig('his.png')
plt.show()
其訓練損失變化圖如下,由於只是給出的demo數據,訓練很快收斂,準確率一輪達到100%。
補充說明
本文介紹了PyTorch中損失函數的使用以及優化器的優化流程,這也是深度模型訓練的最後步驟,比較重要。本文的所有代碼均開源於我的Github,歡迎star或者fork。