原文連接:mp.weixin.qq.com/s/Q8tNXsDh6…html
快速入門 PyTorch 教程第二篇,這篇介紹如何構建一個神經網絡。上一篇文章:python
本文的目錄:git
在 PyTorch 中 torch.nn
專門用於實現神經網絡。其中 nn.Module
包含了網絡層的搭建,以及一個方法-- forward(input)
,並返回網絡的輸出 outptu
.程序員
下面是一個經典的 LeNet 網絡,用於對字符進行分類。github
對於神經網絡來講,一個標準的訓練流程是這樣的:算法
weight = weight - learning_rate * gradient
首先定義一個神經網絡,下面是一個 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 維。
損失函數的輸入是 (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>
複製代碼
反向傳播的實現只須要調用 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
庫,能夠查看官方文檔:
採用隨機梯度降低(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()
方法清空梯度緩存。
本小節教程:
本小節的代碼:
第二篇主要介紹了搭建一個神經網絡,包括定義網絡、選擇損失函數、反向傳播計算梯度和更新權值參數。
歡迎關注個人微信公衆號--機器學習與計算機視覺,或者掃描下方的二維碼,你們一塊兒交流,學習和進步!