PyTorch教程-3:PyTorch中神經網絡的構建與訓練基礎

筆者PyTorch的全部簡單教程請訪問:http://www.lxweimin.com/nb/48831659

PyTorch教程-3:PyTorch中神經網絡的構建與訓練基礎

基本原理

在PyTorch中定義一個網絡模型,需要讓自定義的網絡類繼承自 torch.nn.Module,并且比較重要的是需要重寫其 forward 方法,也就是對網絡結構的前向傳播做出定義,即在forward方法中,需要定義一個輸入變量 input 是如何經過哪些運算得到輸出結果的。這樣,當一個網絡作用于輸入變量后,就能得到輸出的值(output = MyNet(input))。然后通過計算損失(loss),也就是網絡的預測值與真實值之間的差距,再將這個損失反向傳播,loss.backward() 就可以計算得到loss對網絡中所有參數的反向傳播后的梯度值,這里的backward就是依賴于forward定義的運算規則而自動計算的。最后在利用梯度值來更新網絡的參數從而完成一步訓練。

大體來說,訓練一個網路通常需要經理如下的步驟:

  • 定義網絡結構以及其中要學習的參數
  • 從數據庫獲取輸入值
  • 將輸入值輸入網絡得到輸出值
  • 計算輸出值與標簽之間的loss
  • 將loss做反向傳播求得loss之于所有參數的導數
  • 更新參數,比如SGD的更新方式:weight_new = weights_old ? learning_rate × gradient

本文使用一個最簡單的LeNet為例,該網絡的輸入是一個 32×32 大小的單通道灰度圖,輸出為 10 個分類的值(1×10的向量),具有兩個卷積層(與池化層),三個全連接層(與激發函數)。

1.png
input -> (convolution -> acrivate function ->pooling) * 2 -> (fully-connection -> activate function) * 2 -> fully-connection -> output

定義網絡

定義網路的類要繼承自 torch.nn.Module,并且必須至少重寫 forward 方法來定義網絡結構,只要定義了forward函數,autogradbackward方法可以自動完成。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()

重要參考索引

我們涉及到的三個重要的torch包/模塊,它們其中提供了大量的對于神經網絡的方法,它們完整的參考列表如下(強烈建議過一遍):

模塊名 主要內容 參考鏈接
torch.nn 給出了大量神經網絡的層、函數、損失計算方法、其他工具等,是最強大最重要的包 https://pytorch.org/docs/stable/nn.html
torch.nn.functional 給出了大量神經網絡的層、函數、損失計算方法、其他工具等的函數實現 https://pytorch.org/docs/stable/nn.functional.html
torch.optim 實現了很多神經網絡的參數更新方法(優化器) https://pytorch.org/docs/stable/optim.html
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容