PyTorch教程-3:PyTorch中神經網路的構建與訓練基礎
PyTorch 教程系列:https://blog.csdn.net/qq_38962621/category_10652223.html
PyTorch教程-3:PyTorch中神經網路的構建與訓練基礎
基本原理
在PyTorch中定義一個網路模型,需要讓自定義的網路類繼承自 torch.nn.Module
,並且比較重要的是需要重寫其 forward
方法,也就是對網路結構的前向傳播做出定義,即在forward
方法中,需要定義一個輸入變數 input
是如何經過哪些運算得到輸出結果的。這樣,當一個網路作用於輸入變數後,就能得到輸出的值(output = MyNet(input)
loss.backward()
就可以計算得到loss
對網路中所有引數的反向傳播後的梯度值,這裡的backward
就是依賴於forward
定義的運算規則而自動計算的。最後在利用梯度值來更新網路的引數從而完成一步訓練。
大體來說,訓練一個網路通常需要經理如下的步驟:
- 定義網路結構以及其中要學習的引數
- 從資料庫獲取輸入值
- 將輸入值輸入網路得到輸出值
- 計算輸出值與標籤之間的loss
- 將loss做反向傳播求得loss之於所有引數的導數
- 更新引數,比如SGD的更新方式:weight_new = weights_old − learning_rate × gradient
本文使用一個最簡單的LeNet為例,該網路的輸入是一個 32×32 大小的單通道灰度圖,輸出為 10 個分類的值(1×10的向量),具有兩個卷積層(與池化層),三個全連線層(與激發函式)。
input -> (convolution -> acrivate function ->pooling) * 2 -> (fully-connection -> activate function) * 2 -> fully-connection -> output
定義網路
定義網路的類要繼承自 torch.nn.Module
,並且必須至少重寫 forward
forward
函式,autograd
的backward
方法可以自動完成。torch.nn
模組中定義了很多定義網路的常用層、函式,而 torch.nn.functional
模組中則定義了很多網路中常用的函式,這裡給出了一個定義LeNet的例子,其中用到了常用的卷積層、池化層、全連線層、啟用函式等:
import torch
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
# convolution layers
self.conv1 = nn.Conv2d(1,6,3)
self.conv2 = nn.Conv2d(6,16,3)
# fully-connection layers
self.fc1 = nn.Linear(16*6*6,120)
self.fc2 = nn.Linear(120,84)
self.fc3 = nn.Linear(84,10)
def forward(self,x):
# max pooling over convolution layers
x = F.max_pool2d(F.relu(self.conv1(x)),2)
x = F.max_pool2d(F.relu(self.conv2(x)),2)
# fully-connected layers followed by activation functions
x = x.view(-1,16*6*6)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
# final fully-connected without activation functon
x = self.fc3(x)
return x
net = Net()
print(net)
Net(
(conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))
(conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
(fc1): Linear(in_features=576, out_features=120, bias=True)
(fc2): Linear(in_features=120, out_features=84, bias=True)
(fc3): Linear(in_features=84, out_features=10, bias=True)
)
定義完成的網路,可以使用 parameters()
來獲得其所有的引數:
parameters = list(net.parameters())
print(len(parameters))
10
向網路中輸入值
定義完一個網路結果,接下來我們需要向網路中輸入值,從而獲得輸出結果。
在PyTorch中,torch.nn
僅支援mini-batches的型別,所以無法單獨輸入任何一個input
,哪怕是一個輸入也要包裝成單個sample的batch,即batch-size設定為1。比如說上述網路的第一層是二維卷積從 nn.Conv2d
,其實他接受的是一個四維tensor:樣本數×通道數×高×寬。
對於一個單獨的輸入樣本,可以通過使用 torch.Tensor.unsqueeze(dim)
或者 torch.unsqueeze(Tensor, dim)
實現。
torch.Tensor.unsqueeze(dim)
:為原來的tensor增加一個維度,返回一個新的tensor。其接受一個整數做引數,用於標示要增加的維度,比如0
表示在第一維增加一維torch.unsqueeze(Tensor, dim)
:將Tensor增加一個維度並返回新的tensor,第二個引數dim
同上
下邊是一個例子可以驗證上述的方法,隨機生成了一個 1×32×32 大小的tensor作為網路的輸入,但是需要先提前將其包裝成1個大小的batch(等同於直接生成一個隨機的 1×1×32×32 大小的tensor):
x = torch.rand(1,32,32)
print(x.size())
y = x.unsqueeze(0)
print(y.size())
z = torch.unsqueeze(x,0)
print(z.size())
torch.Size([1, 32, 32])
torch.Size([1, 1, 32, 32])
torch.Size([1, 1, 32, 32])
將一個單元素的batch餵給我們的網路並獲取輸出的例子:
x=torch.rand(1,1,32,32)
out = net(x)
print(out)
tensor([[-0.1213, 0.0420, -0.0926, 0.0741, 0.0615, -0.1131, 0.0136, -0.0526,
-0.0172, 0.0244]], grad_fn=<AddmmBackward>)
計算損失(Loss)
網路的訓練需要基於loss,也就是網路預測值與標籤真實值之間的差距。nn.Module中同樣定義了很多損失函式(loss function),可以直接使用,比如這裡使用的平方平均值誤差(mean-squared error)MSELoss。
已知我們獲得的對於輸入x的網路預測值為out,然後生成一個隨機的label值(目標值)target(這裡和輸入值需要保持一致,因此1×10表示1是batch-size,10才是單個標籤的大小),計算兩個值的損失:
x=torch.rand(1,1,32,32)
out = net(x)
target = torch.rand(1,10)
loss_function = nn.MSELoss()
loss = loss_function(out,target)
print(loss)
tensor(0.3597, grad_fn=<MseLossBackward>)
反向傳播
有了loss之後,我們就要通過反向傳播計算loss對於每一個引數的導數,很簡單,使用 loss.backward()
即可,因為loss就是對於所有引數進行了一定的計算後得到的一個單標量的tensor,且在計算過程中追蹤記錄了所有的操作。在進行反向傳播前,不要忽略了使用 net.zero_grad()
將所有引數的梯度快取置0。
net.zero_grad()
loss.backward()
print(net.conv1.bias.grad)
tensor([-0.0007, 0.0007, -0.0005, 0.0068, 0.0026, 0.0000])
更新引數
得到了每個引數的梯度,最後就是要更新這些引數,比如在隨機梯度下降(Stochastic Gradient Descent,SGD)中的更新方法是:
weight_new = weights_old − learning_rate × gradient
直接寫程式碼完成上述操作即:
lr = 0.01
for p in net.parameters():
p.data.sub_(p.grad.data * lr)
當然,更好更快捷的方法就是使用PyTorch提供的包與已有的函式:使用 torch.optim
來完成,其中實現了很多常用的更新引數的方法,比如SGD,Adam,RMSProp等。使用optim
中方法例項的step
方法來進行一步引數更新。
import torch.optim as optim
optimizer = optim.SGD(net.parameters(),lr =0.01)
optimizer.zero_grad()
out = net(x)
loss = loss_function(out,target)
net.zero_grad()
loss.backward()
optimizer.step()