從基礎概念到實現,小白如何快速入門PyTorch

機器之心發表於2019-03-04
文章選自analyticsvidhya,機器之心編譯
PyTorch 是一個有潛力能改變深度學習實現面貌的 Python 庫,它的使用非常靈活與輕鬆。在本文中,我們將以更實用的方式探索 PyTorch,包括基礎知識和案例研究等。此外,本文還將比較使用 NumPy 和 PyTorch 從頭構建神經網路的方式,以瞭解它們在實現中的相似之處。
PyTorch 的構建者表明,PyTorch 的哲學是解決當務之急,也就是說即時構建和執行我們的計算圖。這恰好適合 Python 的程式設計方法,因為我們不需等待整個程式碼都被寫入才能知道是否起作用。我們很容易執行部分程式碼,並實時檢查它。
PyTorch 是一個基於 Python 的庫,旨在為深度學習提供一個靈活的開發平臺。PyTorch 的工作流程非常接近於 Python 的科學計算庫 NumPy。那麼為什麼我們需要使用 PyTorch 構建深度學習模型?以下作者根據實際經驗提供了三個理由:
  • 便於使用的 API:它的使用如同 Python 那樣簡單。
  • 支援 Python:正如上文所述,PyTorch 可以平滑地與 Python 資料科學棧相結合。它與 NumPy 一樣簡單,甚至我們都感覺不出它們的區別。
  • 動態計算圖:PyTorch 不再採用特定的函式預定義計算圖,而是提供構建動態計算圖的框架,甚至我們可以在執行時修正它們。這種動態框架在我們不知道所構建的神經網路需要多少記憶體時非常有用。
其它一些使用 PyTorch 的優點還有多 GPU 支援、自定義資料載入器和極簡的預處理過程等。自從它在 2016 年 1 月份釋出以來,許多研究者將其採用為標準的實現庫,因為它構建新穎的、極其複雜的計算圖同樣非常簡單。即使這樣,PyTorch 被主流資料科學家和研究員接收還是花了很長時間,因為它目前仍然是新的專案,且還有很多地方需要構建與完善。

PyTorch 基礎

在討論 PyTorch 的各個元件前,我們需要了解它的工作流。PyTorch 使用一種稱之為 imperative / eager 的正規化,即每一行程式碼都要求構建一個圖以定義完整計算圖的一個部分。即使完整的計算圖還沒有完成構建,我們也可以獨立地執行這些作為元件的小計算圖,這種動態計算圖被稱為「define-by-run」方法。
從基礎概念到實現,小白如何快速入門PyTorch
更多介紹請檢視:pytorch.org/about/
安裝 PyTorch 非常簡單,我們可以按照自己的系統跟隨官方文件的步驟輕鬆完成。例如以下選擇在 Linux、Python 3.5 和 CUDA 9.1 的環境下安裝 PyTorc
從基礎概念到實現,小白如何快速入門PyTorch
conda install pytorch torchvision cuda91 -c pytorch
我們在基礎部分主要需要了解的 PyTorch 元素有 PyTorch 張量、數學運算、自動求導模組、最優化模組和神經網路模組。下面本文會依次對這些模組進行簡要的介紹:

PyTorch 張量

正如 PyTorch 文件所說,如果我們熟悉 NumPy 的多維陣列,那麼 Torch 張量的很多操作我們能輕易地掌握。PyTorch 提供了 CPU 張量和 GPU 張量,並且極大地加速了計算的速度。
從基礎概念到實現,小白如何快速入門PyTorch

從張量的構建與執行就能體會到 PyTorch 相比 TensorFLow 需要宣告張量、初始化張量要簡潔地多。以下語句將隨機初始化一個 5×3 的二維張量,因為 PyTorch 是一種動態圖,所以它宣告和真實賦值是同時進行的。
torch.Tensor(5, 3)

---------------------------------------

2.4878e+04  4.5692e-41  2.4878e+04

4.5692e-41 -2.9205e+19  4.5691e-41

1.2277e-02  4.5692e-41 -4.0170e+19

4.5691e-41  1.2277e-02  4.5692e-41

0.0000e+00  0.0000e+00  0.0000e+00

[torch.FloatTensor of size 5x3]複製程式碼
若我們希望隨機初始化的張量服從某些分佈,那麼我們可以直接對張量物件使用一些方法。如下初始化的張量將服從均勻分佈:
torch.Tensor(5, 3).uniform_(-1, 1)

---------------------------------------------

-0.2767 -0.1082 -0.1339

-0.6477  0.3098  0.1642

-0.1125 -0.2104  0.8962

-0.6573  0.9669 -0.3806

0.8008 -0.3860  0.6816

[torch.FloatTensor of size 5x3]複製程式碼
在 PyTorch 中,torch.Tensor 是一種多維矩陣,其中每個元素都是一個單一的資料型別,且該建構函式預設的為 torch.FloatTensor。以下是具體張量的型別:
從基礎概念到實現,小白如何快速入門PyTorch
除了直接定義維度,一般我們還可以從 Python 列表或 NumPy 陣列中建立張量。而且根據 Python 列表和元組等資料結構的習慣,我們可以使用相似的索引方式進行取值或賦值等。以下通過 Python 列表建立一個 Torch 張量,並通過索引賦值:
>>> torch.FloatTensor([[1, 2, 3], [4, 5, 6]])

1  2  3

4  5  6

[torch.FloatTensor of size 2x3]

>>> print(x[1][2])

6.0

>>> x[0][1] = 8

>>> print(x)

1  8  3

4  5  6

[torch.FloatTensor of size 2x3]複製程式碼
若 x 為我們定義的 5×3 Torch 張量,且初始化數值服從-1 到 1 的均勻分佈,那麼我們可以執行很多基礎的數學運算。以下執行了一個簡單的矩陣間對應元素乘積。
x = torch.Tensor(5, 3).uniform_(-1, 1)

y = x * torch.randn(5, 3)

print(y)

---------------------------------------------

0.2200 -0.0368  0.4494

-0.2577 -0.0343  0.1587

-0.7503 -0.1729  0.0453

0.9296 -0.1067 -0.6402

-0.3276  0.0158 -0.0552

[torch.FloatTensor of size 5x3]複製程式碼
PyTorch 同樣支援廣播(Broadcasting)操作,一般它會隱式地把一個陣列的異常維度調整到與另一個運算元相匹配的維度以實現維度相容。為了定義兩個形狀是否是可相容的,PyTorch 會從最後開始往前逐個比較它們的維度大小。在這個過程中,如果兩者的對應維度相同,或者其一(或者全是)等於 1,則繼續進行比較,直到最前面的維度。若不滿足這兩個條件,程式就會報錯。如下展示了 PyTorch 的廣播操作:
print (x.size())

y = x + torch.randn(5, 1)

print(y)

---------------------------------------------

torch.Size([5, 3])

0.1919 -0.5006 -1.2410

-0.8080  0.1407 -0.6193

-1.6629 -0.1580 -0.3921

1.0395  0.7069 -0.1459

1.9027  1.4343  1.2299

[torch.FloatTensor of size 5x3]複製程式碼
正如 PyTorch 在官網上所說,PyTorch 是一個張量和動態神經網路 Python 庫,它有著極其強大的 GPU 加速效能。我們一般可以直接定義 GPU 張量,也可以由 CPU 張量轉化為 GPU 張量。如下,我們定義了兩個 GPU 張量,並對這兩個張量執行矩陣乘法。當然,我們也可以如下所示將 CPU 張量轉換為 GPU 張量。
x = torch.cuda.HalfTensor(5, 3).uniform_(-1, 1)

y = torch.cuda.HalfTensor(3, 5).uniform_(-1, 1)

torch.matmul(x, y)

-----------------------------------------------------

0.2456  1.1543  0.5376  0.4358 -0.0369

0.8247 -0.4143 -0.7188  0.3953  0.2573

-0.1346  0.7329  0.5156  0.0864 -0.1349

-0.3555  0.3135  0.3921 -0.1428 -0.1368

-0.4385  0.5601  0.6533 -0.2793 -0.5220

[torch.cuda.HalfTensor of size 5x5 (GPU 0)]

# 以下轉化CPU張量為GPU張量

x = torch.FloatTensor(5, 3).uniform_(-1, 1)

print(x)

x = x.cuda(device=0)

print(x)

x = x.cpu()

print(x)複製程式碼

數學運算

如 NumPy 一樣,高效地實現數學函式對於科學計算庫至關重要。PyTorch 提供了一個簡單的介面,並支援 200 多種數學運算,以下是 PyTorch 實現簡單加運算的過程:
a = torch.FloatTensor([2])
b = torch.FloatTensor([3])

a + b
 5
[torch.FloatTensor of size 1]複製程式碼
這種運算與 Python 非常像,我們可以在定義的 PyTorch 張量上執行多種矩陣運算。例如我們可以轉置二維張量:
matrix = torch.randn(3, 3)
matrix
-1.3531 -0.5394  0.8934
 1.7457 -0.6291 -0.0484
-1.3502 -0.6439 -1.5652
[torch.FloatTensor of size 3x3]
matrix.t()
-2.1139  1.8278  0.1976
 0.6236  0.3525  0.2660
-1.4604  0.8982  0.0428
[torch.FloatTensor of size 3x3]複製程式碼

AutoGrad 模組

TensorFlow、Caffe 和 CNTK 等大多數框架都是使用的靜態計算圖,開發者必須建立或定義一個神經網路,並重復使用相同的結構來執行模型訓練。改變網路的模式就意味著我們必須從頭開始設計並定義相關的模組。
但 PyTorch 使用的技術為自動微分(automatic differentiation)。在這種機制下,系統會有一個 Recorder 來記錄我們執行的運算,然後再反向計算對應的梯度。這種技術在構建神經網路的過程中十分強大,因為我們可以通過計算前向傳播過程中引數的微分來節省時間。
from torch.autograd import Variable

x = Variable(train_x)
y = Variable(train_y, requires_grad=False)複製程式碼
從概念上來說,Autograd 會維護一個圖並記錄對變數執行的所有運算。這會產生一個有向無環圖,其中葉結點為輸入向量,根結點為輸出向量。通過從根結點到葉結點追蹤圖的路徑,我們可以輕易地使用鏈式法則自動計算梯度。
從基礎概念到實現,小白如何快速入門PyTorch
在內部,Autograd 將這個圖表徵為 Function 物件的圖,並且可以應用 apply() 計算評估圖的結果。在計算前向傳播中,當 Autograd 在執行請求的計算時,它還會同時構建一個表徵梯度計算的圖,且每個 Variable 的 .grad_fn 屬性就是這個圖的輸入單元。在前向傳播完成後,我們可以在後向傳播中根據這個動態圖來計算梯度。
以下展示了通過 backward() 和 torch.autograd.grad 計算梯度的方法,其中 torch.eq() 評估表示式是不是相等,即 x.grad 的計算結果是不是等於 2x。
x = Variable(torch.Tensor(5, 3).uniform_(-1, 1), requires_grad=True)

y = Variable(torch.Tensor(5, 3).uniform_(-1, 1), requires_grad=True)

z = x ** 2 + 3 * y

z.backward(gradient=torch.ones(5, 3))

# eq computes element-wise equality

torch.eq(x.grad, 2 * x)

----------------------------------------------------------------------

Variable containing:

1  1  1

1  1  1

1  1  1

1  1  1

1  1  1

[torch.ByteTensor of size 5x3]複製程式碼
以下展示了對 y 求導的結果,即 dz/dy。從上面 z 的定義可知結果應該是 3,那麼以下展示了該計算過程:
y.grad

-------------------------------

Variable containing:

3  3  3

3  3  3

3  3  3

3  3  3

3  3  3

[torch.FloatTensor of size 5x3]複製程式碼
前面是使用 backward() 求解變數的梯度,後面我們也可以使用 torch.autograd.grad 計算梯度。如下所示,我們使用另外一種方式求解同一個函式的梯度。
x = Variable(torch.Tensor(5, 3).uniform_(-1, 1), requires_grad=True)

y = Variable(torch.Tensor(5, 3).uniform_(-1, 1), requires_grad=True)

z = x ** 2 + 3 * y


dz_dx = torch.autograd.grad(z, x, grad_outputs=torch.ones(5, 3))

dz_dy = torch.autograd.grad(z, y, grad_outputs=torch.ones(5, 3))複製程式碼

最優化模組

torch.optim 是實現神經網路中多種優化演算法的模組,它目前已經支援大多數一般的方法,所以我們不需要從頭構建優化演算法。以下展示了使用 Adam 優化器的基本程式碼:
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)複製程式碼

神經網路模組

PyTorch AutoGrad 使得計算圖的定義和梯度的計算十分簡單,但原版的 AutoGrad 可能對定義複雜的神經網路顯得太底層,因此我們需要神經網路模組幫助簡化工作。該 nn 包定義了一組函式,我們可以將其視為有一些可訓練權重的神經網路層級。我們也可以將該神經網路模組視為類似於 Keras 的 PyTorch 元件。
我們一般可以使用 torch.nn 包構建神經網路,下面提供了一些 API 的表達及意義:
  • 線性層- nn.Linear、nn.Bilinear
  • 卷積層 - nn.Conv1d、nn.Conv2d、nn.Conv3d、nn.ConvTranspose2d
  • 非線性啟用函式- nn.Sigmoid、nn.Tanh、nn.ReLU、nn.LeakyReLU
  • 池化層 - nn.MaxPool1d、nn.AveragePool2d
  • 迴圈網路 - nn.LSTM、nn.GRU
  • 歸一化 - nn.BatchNorm2d
  • Dropout - nn.Dropout、nn.Dropout2d
  • 嵌入 - nn.Embedding
  • 損失函式 - nn.MSELoss、nn.CrossEntropyLoss、nn.NLLLoss
import torch

# define model
model = torch.nn.Sequential(
 torch.nn.Linear(input_num_units, hidden_num_units),
 torch.nn.ReLU(),
 torch.nn.Linear(hidden_num_units, output_num_units),
)
loss_fn = torch.nn.CrossEntropyLoss()複製程式碼
以上就是 PyTorch 的基本元件,我們可以使用它們快速構建神經網路。當然以上只是簡單的概念介紹,每一個模組都有非常多的函式與方法,讀者可詳細查閱 PyTorch 文件瞭解更多。

構建神經網路(NumPy vs. PyTorch)

在這一部分中,我們分別使用 NumPy 和 PyTorch 構建簡單的神經網路以實現二元分類問題,本文的後面會對這一部分的程式碼進行解釋。
## Neural network in numpy

import numpy as np

#Input array
X=np.array([[1,0,1,0],[1,0,1,1],[0,1,0,1]])

#Output
y=np.array([[1],[1],[0]])

#Sigmoid Function
def sigmoid (x):
 return 1/(1 + np.exp(-x))

#Derivative of Sigmoid Function
def derivatives_sigmoid(x):
 return x * (1 - x)

#Variable initialization
epoch=5000 #Setting training iterations
lr=0.1 #Setting learning rate
inputlayer_neurons = X.shape[1] #number of features in data set
hiddenlayer_neurons = 3 #number of hidden layers neurons
output_neurons = 1 #number of neurons at output layer

#weight and bias initialization
wh=np.random.uniform(size=(inputlayer_neurons,hiddenlayer_neurons))
bh=np.random.uniform(size=(1,hiddenlayer_neurons))
wout=np.random.uniform(size=(hiddenlayer_neurons,output_neurons))
bout=np.random.uniform(size=(1,output_neurons))

for i in range(epoch):
  #Forward Propogation
  hidden_layer_input1=np.dot(X,wh)
  hidden_layer_input=hidden_layer_input1 + bh
  hiddenlayer_activations = sigmoid(hidden_layer_input)
  output_layer_input1=np.dot(hiddenlayer_activations,wout)
  output_layer_input= output_layer_input1+ bout
  output = sigmoid(output_layer_input)

  #Backpropagation
  E = y-output
  slope_output_layer = derivatives_sigmoid(output)
  slope_hidden_layer = derivatives_sigmoid(hiddenlayer_activations)
  d_output = E * slope_output_layer
  Error_at_hidden_layer = d_output.dot(wout.T)
  d_hiddenlayer = Error_at_hidden_layer * slope_hidden_layer
  wout += hiddenlayer_activations.T.dot(d_output) *lr
  bout += np.sum(d_output, axis=0,keepdims=True) *lr
  wh += X.T.dot(d_hiddenlayer) *lr
  bh += np.sum(d_hiddenlayer, axis=0,keepdims=True) *lr

print('actual :\n', y, '\n')
print('predicted :\n', output)複製程式碼

現在,我們會發現使用 PyTorch 實現相同的網路會非常簡單。以下的程式碼同樣也用粗體表示出它與 NumPy 的不同之處:
## neural network in pytorch*import torch*

#Input array
X = *torch.Tensor*([[1,0,1,0],[1,0,1,1],[0,1,0,1]])

#Output
y = *torch.Tensor*([[1],[1],[0]])

#Sigmoid Function
def sigmoid (x):
  return 1/(1 + *torch.exp*(-x))

#Derivative of Sigmoid Function
def derivatives_sigmoid(x):
  return x * (1 - x)

#Variable initialization
epoch=5000 #Setting training iterations
lr=0.1 #Setting learning rate
inputlayer_neurons = X.shape[1] #number of features in data set
hiddenlayer_neurons = 3 #number of hidden layers neurons
output_neurons = 1 #number of neurons at output layer

#weight and bias initialization
wh=*torch.randn*(inputlayer_neurons, hiddenlayer_neurons)*.type(torch.FloatTensor)*
bh=*torch.randn*(1, hiddenlayer_neurons)*.type(torch.FloatTensor)*
wout=*torch.randn*(hiddenlayer_neurons, output_neurons)
bout=*torch.randn*(1, output_neurons)

for i in range(epoch):

  #Forward Propogation
  hidden_layer_input1 = *torch.mm*(X, wh)
  hidden_layer_input = hidden_layer_input1 + bh
  hidden_layer_activations = sigmoid(hidden_layer_input)

  output_layer_input1 = *torch.mm*(hidden_layer_activations, wout)
  output_layer_input = output_layer_input1 + bout
  output = sigmoid(output_layer_input1)

  #Backpropagation
  E = y-output
  slope_output_layer = derivatives_sigmoid(output)
  slope_hidden_layer = derivatives_sigmoid(hidden_layer_activations)
  d_output = E * slope_output_layer
  Error_at_hidden_layer = *torch.mm*(d_output, wout.t())
  d_hiddenlayer = Error_at_hidden_layer * slope_hidden_layer
  wout += *torch.mm*(hidden_layer_activations.t(), d_output) *lr
  bout += d_output.sum() *lr
  wh += *torch.mm*(X.t(), d_hiddenlayer) *lr
  bh += d_output.sum() *lr

print('actual :\n', y, '\n')
print('predicted :\n', output)複製程式碼

對比其它深度學習庫

在一份基準指令碼中,它展示出 PyTorch 在訓練長短期記憶(LSTM)網路上比其它主要框架的表現都要好,因為它執行一個 Epoch 有最少的中位數時間。
PyTorch 中的資料載入 API 經過了優良的設計,介面是針對特定資料集、取樣器和資料載入器而構建的。對比於 TensorFlow 的資料載入工具(readers, queues 等),我發現 PyTorch 的資料載入模組更易於使用。同時它們還能無縫對接神經網路構建模組,所以我們不需要第三方高階庫。
然而,我並不推薦使用使用 PyTorch 部署模型,因為 PyTorch 仍然不是那麼成熟。正如 PyTorch 開發者所說:「我們經常看到使用者首先建立一個 PyTorch 模型來測試是否可行,然後當需要部署模型到生產中時,他們會轉化為 Caffe 2 等其他框架,並將其部署到移動端或其它平臺。」
從基礎概念到實現,小白如何快速入門PyTorch

案例研究

前面我們已經瞭解了PyTorch的基本組成元素與特性,下面我們會通過線性迴歸與手寫字型識別兩個具體的案例探討如何使用 PyTorch 構建高效地模型。

PyTorch 線性迴歸

定義資料:
import torch

from torch.autograd import Variable

x_data = Variable(torch.Tensor([[1.0], [2.0], [3.0]]))

y_data = Variable(torch.Tensor([[2.0], [4.0], [6.0]]))複製程式碼
定義模型,在 PyTorch 中,我們可以使用高階 API 來定義相關的模型或層級。如下定義了「torch.nn.Linear(1, 1)」,即一個輸入變數和一個輸出變數。
class Model(torch.nn.Module):

   def __init__(self):

       """

       In the constructor we instantiate two nn.Linear module

       """

       super(Model, self).__init__()

       self.linear = torch.nn.Linear(1, 1)  # One in and one out

   def forward(self, x):

       """

       In the forward function we accept a Variable of input data and we must return

       a Variable of output data. We can use Modules defined in the constructor as

       well as arbitrary operators on Variables.

       """

       y_pred = self.linear(x)

       return y_pred複製程式碼
構建損失函式和優化器,構建損失函式也可以直接使用「torch.nn.MSELoss(size_average=False)」呼叫均方根誤差函式。優化器可以使用「torch.optim.SGD()」提到用隨機梯度下降,其中我們需要提供優化的目標和學習率等引數。
# Construct our loss function and an Optimizer. The call to model.parameters()

# in the SGD constructor will contain the learnable parameters of the two

# nn.Linear modules which are members of the model.

criterion = torch.nn.MSELoss(size_average=False)

optimizer = torch.optim.SGD(model.parameters(), lr=0.01)複製程式碼
訓練模型,執行前向傳播計算損失函式,並優化引數:
# Training loop

for epoch in range(500):

       # Forward pass: Compute predicted y by passing x to the model

   y_pred = model(x_data)

   # Compute and print loss

   loss = criterion(y_pred, y_data)

   print(epoch, loss.data[0])

   # Zero gradients, perform a backward pass, and update the weights.

   optimizer.zero_grad()

   loss.backward()

   optimizer.step()複製程式碼

用 PyTorch 解決影象識別問題

為了進一步熟悉 PyTorch,我們將使用它解決 Analytics Vidhya 的深度學習實踐問題:識別手寫數字。我們的問題是給定一張 28 x 28 的影象,利用模型識別其所代表的手寫數字。
所以首先我們需要下載訓練集與測試集,資料集包含了一個壓縮檔案以儲存所有的影象。其中 train.csv 和 test.csv 分別儲存了訓練和測試影象,且影象的格式為 png。下面我們將一步步構建簡單的神經網路以實現手寫數字識別功能。

第 0 步:準備工作

a)匯入必要的函式庫
# import modules
%pylab inline
import os
import numpy as np
import pandas as pd
from scipy.misc import imread
from sklearn.metrics import accuracy_score複製程式碼
b)設定隨機的 Seed,因此我們能控制模型產生的隨機數基本不變(偽隨機數)。
# To stop potential randomness
seed = 128
rng = np.random.RandomState(seed)複製程式碼
c)設定工作目錄的路徑。
root_dir = os.path.abspath('.')
data_dir = os.path.join(root_dir, 'data')

# check for existence
os.path.exists(root_dir), os.path.exists(data_dir)複製程式碼

第 1 步:載入與預處理資料

a)現在讀取 CSV 格式的資料集,並獲取檔名與對應的標註。
# load dataset
train = pd.read_csv(os.path.join(data_dir, 'Train', 'train.csv'))
test = pd.read_csv(os.path.join(data_dir, 'Test.csv'))

sample_submission = pd.read_csv(os.path.join(data_dir, 'Sample_Submission.csv'))

train.head(複製程式碼
從基礎概念到實現,小白如何快速入門PyTorch
b)接下來可以列印準備好的圖片。
# print an image
img_name = rng.choice(train.filename)
filepath = os.path.join(data_dir, 'Train', 'Images', 'train', img_name)

img = imread(filepath, flatten=True)

pylab.imshow(img, cmap='gray')
pylab.axis('off')
pylab.show()複製程式碼
c)對於更簡單的資料操作,我們可以儲存所有的影象作為 NumPy 陣列。
# load images to create train and test set
temp = []
for img_name in train.filename:
  image_path = os.path.join(data_dir, 'Train', 'Images', 'train', img_name)
  img = imread(image_path, flatten=True)
  img = img.astype('float32')
  temp.append(img)

train_x = np.stack(temp)

train_x /= 255.0
train_x = train_x.reshape(-1, 784).astype('float32')

temp = []
for img_name in test.filename:
  image_path = os.path.join(data_dir, 'Train', 'Images', 'test', img_name)
  img = imread(image_path, flatten=True)
  img = img.astype('float32')
  temp.append(img)

test_x = np.stack(temp)

test_x /= 255.0
test_x = test_x.reshape(-1, 784).astype('float32')

train_y = train.label.values複製程式碼
d)因為這個是一個典型的機器學習問題,所以我們可以建立驗證集以監控模型的執行情況。下面我們以 7:3 的比例分割訓練集與驗證集。
# create validation set
split_size = int(train_x.shape[0]*0.7)

train_x, val_x = train_x[:split_size], train_x[split_size:]
train_y, val_y = train_y[:split_size], train_y[split_size:]複製程式碼

第 2 步:構建模型

a)下面是模型的主體,我們定義的神經網路共有三層,即輸入層、隱藏層和輸出層。輸入層和輸出層的神經元數量是固定的,即 28 x 28 和 10 x 1,它們分別代表了輸入影象的畫素和類別。我們在隱藏層採用了 50 個神經元,並採用 Adam 作為最優化演算法。
import torch
from torch.autograd import Variable
# number of neurons in each layer
input_num_units = 28*28
hidden_num_units = 500
output_num_units = 10

# set remaining variables
epochs = 5
batch_size = 128
learning_rate = 0.001複製程式碼
b)以下將開始訓練模型。
# define model
model = torch.nn.Sequential(
  torch.nn.Linear(input_num_units, hidden_num_units),
  torch.nn.ReLU(),
  torch.nn.Linear(hidden_num_units, output_num_units),
)
loss_fn = torch.nn.CrossEntropyLoss()

# define optimization algorithm
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
## helper functions
# preprocess a batch of dataset
def preproc(unclean_batch_x):
  """Convert values to range 0-1"""
  temp_batch = unclean_batch_x / unclean_batch_x.max()

  return temp_batch

# create a batch
def batch_creator(batch_size):
  dataset_name = 'train'
  dataset_length = train_x.shape[0]

  batch_mask = rng.choice(dataset_length, batch_size)

  batch_x = eval(dataset_name + '_x')[batch_mask]
  batch_x = preproc(batch_x)

  if dataset_name == 'train':
    batch_y = eval(dataset_name).ix[batch_mask, 'label'].values

  return batch_x, batch_y
# train network
total_batch = int(train.shape[0]/batch_size)

for epoch in range(epochs):
  avg_cost = 0
  for i in range(total_batch):
    # create batch
    batch_x, batch_y = batch_creator(batch_size)

    # pass that batch for training
    x, y = Variable(torch.from_numpy(batch_x)), Variable(torch.from_numpy(batch_y), requires_grad=False)
    pred = model(x)

    # get loss
    loss = loss_fn(pred, y)

    # perform backpropagation
    loss.backward()
    optimizer.step()
    avg_cost += loss.data[0]/total_batch

  print(epoch, avg_cost)
# get training accuracy
x, y = Variable(torch.from_numpy(preproc(train_x))), Variable(torch.from_numpy(train_y), requires_grad=False)
pred = model(x)

final_pred = np.argmax(pred.data.numpy(), axis=1)

accuracy_score(train_y, final_pred)
# get validation accuracy
x, y = Variable(torch.from_numpy(preproc(val_x))), Variable(torch.from_numpy(val_y), requires_grad=False)
pred = model(x)
final_pred = np.argmax(pred.data.numpy(), axis=1)

accuracy_score(val_y, final_pred)複製程式碼

  • 訓練準確度為:0.8779008746355685
  • 測試準確度為:0.867482993197279
這些分數非常令人滿意,因為我們只是用簡單的神經網路訓練了 5 個 Epoch。以上,本文介紹了簡單的 PyTorch 入門概念,並利用簡單的案例熟悉 PyTorch 的使用。讀者可以繼續閱讀 PyTorch 的文件以瞭解更多資訊。



相關文章