Skip to content

[PyTorch] Getting Start: Neural Networks 神經網路的基本介紹

Last Updated on 2021-04-13 by Clay

所謂的『神經網路』,便是我們想要進行深度學習所建的『模型架構』。在 PyTorch 官方的教學裡頭,第一句話就明確地闡明了:

可以使用 torch.nn 來建構神經網路。

nn 包含了模型層以及一個 forward() 的函式,並且會返回 output。這在之後的程式碼中可以清楚看見。


首先來闡述一個神經網路的基本訓練流程:

  1. 定義神經網路、並設置學習的參數或權重
  2. 迭代輸入 Training data
  3. 設定好的神經網路開始處理輸入資料
  4. 計算 Loss function (損失函數),即為計算『output 與正確答案的差距』
  5. 藉由 Loss function 得到應該更改的權重
  6. 更新神經網路的權重,通常使用較簡單的規則,比如: 新的權重 = 舊的權重 – (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 訓練模型來分類),我們要將尺寸定為 32×32。

大家熟悉的 Mnist 似乎都是 28×28 的?關於這個解答,其實我有在 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 在神經網路中訓練的流程,希望很快就開始訓練模型。


Read More

Leave a Reply