Last Updated on 2021-04-13 by Clay
所謂的『神經網路』,便是我們想要進行深度學習所建的『模型架構』。在 PyTorch 官方的教學裡頭,第一句話就明確地闡明了:
『可以使用 torch.nn 來建構神經網路。』
nn 包含了模型層以及一個 forward()
的函式,並且會返回 output。這在之後的程式碼中可以清楚看見。
首先來闡述一個神經網路的基本訓練流程:
- 定義神經網路、並設置學習的參數或權重
- 迭代輸入 Training data
- 設定好的神經網路開始處理輸入資料
- 計算 Loss function (損失函數),即為計算『output 與正確答案的差距』
- 藉由 Loss function 得到應該更改的權重
- 更新神經網路的權重,通常使用較簡單的規則,比如: 新的權重 = 舊的權重 - (Learning rate (學習率) * gradient (梯度))
那麼,以下就來直接看段官方的 Sample code 吧。
在那之前,若是你想直接參考官方的 Tutorial,連結在這:https://pytorch.org/tutorials/beginner/blitz/neural_networks_tutorial.html#sphx-glr-beginner-blitz-neural-networks-tutorial-py
基本上我也是按照他們的教學來學習的,強力推薦。
定義神經網路
以下是一段非常基本的 CNN 程式,基本上我在網路上查看過各式各樣這樣的模型設計,所以還是別搞怪,直接上官方的程式碼吧:
# -*- coding: utf-8 -*- import torch import torch.nn as nn import torch.nn.functional as F class Net(nn.Module): def __init__(self): super(Net, self).__init__() # Kernel self.conv1 = nn.Conv2d(1, 6, 3) self.conv2 = nn.Conv2d(6, 16, 3) # Affine operation 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 x = F.max_pool2d(F.relu(self.conv1(x)), (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): size = x.size()[1:] num_features = 1 for s in size: num_features *= s return num_features if __name__ == '__main__': net = Net() print(net)
Output:
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)
)
正如前一篇筆記所提到的,神經網路有 forward propagation 以及 backward propagation 兩部份,在 PyTorch 當中,我們繼承了 nn.Module 之後,需要親自定義『forward』這個 function;至於 backward propagation,正如前一篇《Getting Start: 自動求導》所提及的,可以使用『autograd』來取得梯度。
params = list(net.parameters()) print(len(params)) for n in range(len(params)): print(params[n].size())
Output:
10
torch.Size([6, 1, 3, 3])
torch.Size([6])
torch.Size([16, 6, 3, 3])
torch.Size([16])
torch.Size([120, 576])
torch.Size([120])
torch.Size([84, 120])
torch.Size([84])
torch.Size([10, 84])
torch.Size([10])
按照官方的解釋,以上這 10 個為模型的『可學習參數』的 size。每個參數都有著不一樣大小的 size。
接下來我們就要開始產生一個隨機輸入了。為了要符合 Mnist 圖形(我在這篇教學《使用 CNN 進行 MNIST 的手寫數字辨識 —— by Keras (實戰篇)》中有示範如何用 Keras 訓練模型來分類),我們要將尺寸定為 32x32。
大家熟悉的 Mnist 似乎都是 28x28 的?關於這個解答,其實我有在 so 上面找到:
https://stackoverflow.com/questions/28525436/why-the-lenet5-uses-32%C3%9732-image-as-input
廢話不多說,就來看輸入的程式碼吧!
input = torch.randn(1, 1, 32, 32) out = net(input) print(out)
Output:
tensor([[-0.0130, -0.0792, -0.1078, -0.1333,
0.0815, 0.0615, -0.1001, -0.1203,
0.0502, -0.0364]], grad_fn=<AddmmBackward>)
我們得到的值為我們輸入值通過 forward 之後返回的值。接下來我們要進行在 PyTorch 中相當重要的動作:『將參數、梯度緩衝區歸零』——若是不這樣的做的話,每次的梯度都會累積起來,產生錯誤的訓練結果。
程式碼如下:
net.zero_grad() out.backward(torch.randn(1, 10))
Loss function
Loss function (或者你習慣稱之為『損失函數』),在 PyTorch 中需要以 (預測結果, 正確答案) 這樣的格式輸入。 Loss function 的計算方式很多,例如『均方誤差』(Mean square error, MSE)、『平均絕對值誤差』(Mean absolute error, MAE)、『交叉熵』(Cross-entropy) ..... 等等。
我們實作預測任務的模型時,通常都希望模型預測的通通都是正解,然後,通常我們預測的結果都會和 Training data 的正確答案有出入。這兩者之間的差距即是 loss。
今天我們首先以官網所示範的均方誤差 (MSE) 來示範。我們可以在 nn 底下找到這個 loss function 的函式。
由於我們的輸入是亂數決定的,所以我們也來亂數決定一個正確結果吧?畢竟我們的目的只是想知道如何計算 loss function。
# Input & Target input = torch.randn(1, 1, 32, 32) out = net(input) target = torch.randn(10).view(1, -1) print(out.size()) print(target.size())
Output:
torch.Size([1, 10])
torch.Size([1, 10])
別忘了,我們要使用 view()
來將預測的結果與標準答案 size 對齊。這裡我們可以看到兩者的維度都是相同的。
然後我們就可以使用 PyTorch 提供的 function 來計算 loss 了:
# Loss function criterion = nn.MSELoss() loss = criterion(output, target) print(loss)
Output:
tensor(0.579, grad_fn=<MseLossBackward>)
以上,就是我們計算 loss 的方法。
Backward propagation
Backward propagation,又稱反向傳播,為我們通過計算預測結果與正解的距離(即 loss function),來反向地更新我們模型的權重,使模型越來越準確。
# Backprop net.zero_grad() # Before print('Before:', net.conv1.bias.grad) # After loss.backward() print('After:', net.conv1.bias.grad)
Output:
Before: tensor([0., 0., 0., 0., 0., 0.])
After: tensor([-0.0186, 0.0026, 0.0069, -0.0020, 0.0032, -0.0144])
要記得使用 zero_grad()
來清除緩衝的梯度,不然會逐漸累積,導致不正確的結果哦。
更新權重
還記得一開始我筆記裡的權重更新函數嗎?通常都是:
新的權重 = 舊的權重 - (Learning rate (學習率) * gradient (梯度))
這被稱為『隨機梯度下降』(SGD),官網給予了一段簡潔的程式碼來實作:
learning_rate = 0.01 for f in net.parameters(): f.data.sub_(f.grad.data * learning_rate)
除此之外,你也可以嘗試不同的更新規則,例如使用經典的 SGD、Adam、RMSProp ...... 等等,都可以在 torch.optim 裡面呼叫。
那麼,今天紀錄了 PyTorch 在神經網路中訓練的流程,希望很快就開始訓練模型。