快速入門PyTorch(2)--如何構建一個神經網絡

原文連接:mp.weixin.qq.com/s/Q8tNXsDh6…html

快速入門 PyTorch 教程第二篇,這篇介紹如何構建一個神經網絡。上一篇文章:python

本文的目錄:git


3. 神經網絡

在 PyTorch 中 torch.nn 專門用於實現神經網絡。其中 nn.Module 包含了網絡層的搭建,以及一個方法-- forward(input) ,並返回網絡的輸出 outptu .程序員

下面是一個經典的 LeNet 網絡,用於對字符進行分類。github

對於神經網絡來講,一個標準的訓練流程是這樣的:算法

  • 定義一個多層的神經網絡
  • 對數據集的預處理並準備做爲網絡的輸入
  • 將數據輸入到網絡
  • 計算網絡的損失
  • 反向傳播,計算梯度
  • 更新網絡的梯度,一個簡單的更新規則是 weight = weight - learning_rate * gradient

3.1 定義網絡

首先定義一個神經網絡,下面是一個 5 層的卷積神經網絡,包含兩層卷積層和三層全鏈接層:緩存

import torch
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
    
    def __init__(self):
        super(Net, self).__init__()
        # 輸入圖像是單通道,conv1 kenrnel size=5*5,輸出通道 6
        self.conv1 = nn.Conv2d(1, 6, 5)
        # conv2 kernel size=5*5, 輸出通道 16
        self.conv2 = nn.Conv2d(6, 16, 5)
        # 全鏈接層
        self.fc1 = nn.Linear(16*5*5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
        
    def forward(self, x):
        # max-pooling 採用一個 (2,2) 的滑動窗口
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # 核(kernel)大小是方形的話,可僅定義一個數字,如 (2,2) 用 2 便可
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x
    
    def num_flat_features(self, x):
        # 除了 batch 維度外的全部維度
        size = x.size()[1:]
        num_features = 1
        for s in size:
            num_features *= s
        return num_features

net = Net()
print(net)
複製代碼

打印網絡結構:微信

Net(
  (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=400, 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)
)
複製代碼

這裏必須實現 forward 函數,而 backward 函數在採用 autograd 時就自動定義好了,在 forward 方法能夠採用任何的張量操做。網絡

net.parameters() 能夠返回網絡的訓練參數,使用例子以下:機器學習

params = list(net.parameters())
print('參數數量: ', len(params))
# conv1.weight
print('第一個參數大小: ', params[0].size())
複製代碼

輸出:

參數數量:  10
第一個參數大小:  torch.Size([6, 1, 5, 5])
複製代碼

而後簡單測試下這個網絡,隨機生成一個 32*32 的輸入:

# 隨機定義一個變量輸入網絡
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
複製代碼

輸出結果:

tensor([[ 0.1005,  0.0263,  0.0013, -0.1157, -0.1197, -0.0141,  0.1425, -0.0521,
          0.0689,  0.0220]], grad_fn=<ThAddmmBackward>)
複製代碼

接着反向傳播須要先清空梯度緩存,並反向傳播隨機梯度:

# 清空全部參數的梯度緩存,而後計算隨機梯度進行反向傳播
net.zero_grad()
out.backward(torch.randn(1, 10))
複製代碼

注意

torch.nn 只支持**小批量(mini-batches)**數據,也就是輸入不能是單個樣本,好比對於 nn.Conv2d 接收的輸入是一個 4 維張量--nSamples * nChannels * Height * Width

因此,若是你輸入的是單個樣本,須要採用 input.unsqueeze(0) 來擴充一個假的 batch 維度,即從 3 維變爲 4 維

3.2 損失函數

損失函數的輸入是 (output, target) ,即網絡輸出和真實標籤對的數據,而後返回一個數值表示網絡輸出和真實標籤的差距。

PyTorch 中其實已經定義了很多的損失函數,這裏僅採用簡單的均方偏差:nn.MSELoss ,例子以下:

output = net(input)
# 定義僞標籤
target = torch.randn(10)
# 調整大小,使得和 output 同樣的 size
target = target.view(1, -1)
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)
複製代碼

輸出以下:

tensor(0.6524, grad_fn=<MseLossBackward>)
複製代碼

這裏,整個網絡的數據輸入到輸出經歷的計算圖以下所示,其實也就是數據從輸入層到輸出層,計算 loss 的過程。

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
      -> view -> linear -> relu -> linear -> relu -> linear
      -> MSELoss
      -> loss
複製代碼

若是調用 loss.backward() ,那麼整個圖都是可微分的,也就是說包括 loss ,圖中的全部張量變量,只要其屬性 requires_grad=True ,那麼其梯度 .grad 張量都會隨着梯度一直累計。

用代碼來講明:

# MSELoss
print(loss.grad_fn)
# Linear layer
print(loss.grad_fn.next_functions[0][0])
# Relu
print(loss.grad_fn.next_functions[0][0].next_functions[0][0])
複製代碼

輸出:

<MseLossBackward object at 0x0000019C0C349908>

<ThAddmmBackward object at 0x0000019C0C365A58>

<ExpandBackward object at 0x0000019C0C3659E8>
複製代碼

3.3 反向傳播

反向傳播的實現只須要調用 loss.backward() 便可,固然首先須要清空當前梯度緩存,即.zero_grad() 方法,不然以前的梯度會累加到當前的梯度,這樣會影響權值參數的更新。

下面是一個簡單的例子,以 conv1 層的偏置參數 bias 在反向傳播先後的結果爲例:

# 清空全部參數的梯度緩存
net.zero_grad()
print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)

loss.backward()

print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)
複製代碼

輸出結果:

conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])

conv1.bias.grad after backward
tensor([ 0.0069,  0.0021,  0.0090, -0.0060, -0.0008, -0.0073])
複製代碼

瞭解更多有關 torch.nn 庫,能夠查看官方文檔:

pytorch.org/docs/stable…

3.4 更新權重

採用隨機梯度降低(Stochastic Gradient Descent, SGD)方法的最簡單的更新權重規則以下:

weight = weight - learning_rate * gradient

按照這個規則,代碼實現以下所示:

# 簡單實現權重的更新例子
learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)
複製代碼

可是這只是最簡單的規則,深度學習有不少的優化算法,不單單是 SGD,還有 Nesterov-SGD, Adam, RMSProp 等等,爲了採用這些不一樣的方法,這裏採用 torch.optim 庫,使用例子以下所示:

import torch.optim as optim
# 建立優化器
optimizer = optim.SGD(net.parameters(), lr=0.01)

# 在訓練過程當中執行下列操做
optimizer.zero_grad() # 清空梯度緩存
output = net(input)
loss = criterion(output, target)
loss.backward()
# 更新權重
optimizer.step()
複製代碼

注意,一樣須要調用 optimizer.zero_grad() 方法清空梯度緩存。

本小節教程:

pytorch.org/tutorials/b…

本小節的代碼:

github.com/ccc013/Deep…


小結

第二篇主要介紹了搭建一個神經網絡,包括定義網絡、選擇損失函數、反向傳播計算梯度和更新權值參數。

歡迎關注個人微信公衆號--機器學習與計算機視覺,或者掃描下方的二維碼,你們一塊兒交流,學習和進步!

往期精彩推薦

機器學習系列
Github項目 & 資源教程推薦
相關文章
相關標籤/搜索