從實例逐步了解基本 PyTorch 如何運作

Chenyu Tsai
UXAI
Published in
28 min readJan 16, 2020

介紹

PyTorch 是近年來發展非常快速的深度學習框架之一。看名字可以猜到他很 pythonic,所以若是已經對 Python 很熟悉的人用起來應該會覺得很親切,所以文章也假設看的人對 Python 有基礎的了解,不會多花篇幅講解 Python 的語法。

寫這篇文章的原因是 PyTorch 的教學通常是給已經有一點基礎的人看的(也有可能只是我沒認真找),所以教學通常都很跳躍,會忽略很多細節,但這些細節對於了解整個框架的運作還蠻重要的,故整理一些英文資源,從基礎面了解 PyTorch 是如何運作的,像是 autograd, dynamic computation (動態計算圖) 等等。

簡單線性迴歸 (Simple Linear Regression, SLR)

很多教學都會從 CV 領域開始,丟給我們很多圖片,最常看到的大概是貓狗還有手寫辨識。當然這樣蠻酷的,可以馬上透過圖像看到 PyTorch 如何處理他們。不過看完常常還是有疑問,就是 PyTorch 到底是怎麼運作的?

這篇教學會從最基本面講起。這段落大概有高中數學的程度就能知道這邊在做什麼了(線性代數)。這邊有一個能夠快速了解線性迴歸如何運作的文章,若是不知道線性迴歸是什麼的話可以閱讀一下,內容非常親民,已經了解的則可以直接往下。

生成資料

首先我們先依照簡單線性迴歸的模型來生成 100 筆資料:

由上面的模型生成標記(label)Y,特徵 X ,a, b 分別是 a = 2, b = 5,最後再加上一點高斯分布的 noise:

import numpy as np
from matplotlib import pyplot as plt
np.random.seed(7) # 用來確保每次生成的隨機資料是一樣的,否則訓練結果無法比較
x = np.random.rand(100, 1)
y = 2 + 5 * x + .2 * np.random.randn(100, 1) # randn 的 n 為 normal distribution

生成 100 個索引(index)並打散:

idx = np.arange(100)
np.random.shuffle(idx) # 打散索引

切分訓練用資料和驗證用資料,一個用來訓練模型,一個用來驗證我們模型訓練的如何,切分開來是因為訓練和驗證若是為同一份資料的話,那就有點像是我們考期末考前,先知道考試的題目了,這樣我們就沒有辦法知道模型真實的表現如何:

train_idx = idx[:80] # 取前 80 筆為訓練資料
val_idx = idx[80:] #取後 20 筆為驗證資料

接著利用 index 來建立我們的 x, y 資料集:

x_train, y_train = x[train_idx], y[train_idx]
x_val, y_val = x[val_idx], y[val_idx]

簡單看一下我們的資料分布:

plt.scatter(x_train, y_train)
plt.show()

梯度下降 (Gradient Descent)

接下來我們要簡單介紹一下梯度下降。在訓練模型的時候,其實我們就是在讓模型找參數,機器學習和深度學習所說的學習,就是這個找參數的過程,梯度下降法則是一種透過不斷更新參數,來找到最佳解的方法。當然還有許多基於梯度下降的優化算法,核心概念都是利用梯度下降法下去改良,但不是本篇文章的重點,故不多做敘述。以下我們將梯度下降分為三個步驟,對此熟悉的人可以放心跳過。

步驟一:計算 loss

以我們在處理的迴歸問題為例,我們要用 Mean Square Error, MSE 作為 loss function 來計算我們的 loss,也就是計算所有預測 a + bx 與標記 y 之間,差的平方和取平均。

當我們透過 loss function 來計算資料集中每一筆資料的 loss 並加總,他們的平均就叫做 cost function C。

步驟二:計算梯度

我們這邊所說的梯度就是在求偏導數,在這個例子中,我們需要計算兩個參數 a, b 的偏導數。導數反映的是函數在某一點處沿 x 軸正方向的變化率,更簡單的來說,就是當我們改變一個量時,我們原本給定的量會如何變化。從 loss function 來說,即當我們變動兩個參數 a, b 時,MSE loss 會如何變化。

在下列式子中,我們先分別對 a, b 求導,接著利用鏈鎖率(Chain Rule),來代入對應元素,最後得到一般我們在做簡單迴歸時會看到的梯度回歸計算式。

步驟三:更新參數

接著我們要利用梯度來更新參數,因為我們現在要求的是最小值,而梯度的方向是面向極大值的,故我們需要減去梯度,在這裡我們用 n 來代表學習率 learning rate,即每次移動的步伐大小,來乘以梯度,以下為計算方式。

最後,我們再用更新後的參數回到步驟一計算梯度,直到我們找到最佳解。

一些關於梯度下降比較詳細的數學,還有學習率對梯度下降的影響可以參考這裡

用 Numpy 建立線性迴歸

接下來我們試著先用 Numpy 來建立一個線性回歸模型,一是為了理解線性迴歸的架構,二是之後可以比較一下使用 numpy 和使用 PyTorch 之間的差別。

首先我們先隨便選參數 a, b 的初始值和設定基本的超參數 (hyper-parameter),像是學習率,epoch 數等等:

# 隨機給定數值,初始化 a, b 的值
np.random.seed(7)
a = np.random.randn(1)
b = np.random.randn(1)
# 設定學習率
lr = 1e-1
# 設定 epochs
epochs = 500

在每個 epoch 中,我們需要做四件事情:

  • 計算模型的預測,也是所謂的正向傳遞
  • 用預測和標記來計算 loss
  • 計算兩個參數的梯度
  • 更新參數
for epoch in range(n_epochs): 
# 計算模型的預測
yhat = a + b * x_train
# 用預測和標記來計算 error
error = (y_train - yhat)
# 用 error 來計算 loss
loss = (error ** 2).mean()

# 計算兩個參數的梯度
a_grad = -2 * error.mean()
b_grad = -2 * (x_train * error).mean()

# 用梯度和學習率來更新參數
a = a - lr * a_grad
b = b - lr * b_grad

print(a, b)

為了確認我們沒有算錯,我們用 scikit-learn (機器學習庫) 的 linear regression 驗證看看

from sklearn.linear_model import LinearRegression
LR = LinearRegression()
LR.fit(x_train, y_train)
print(LR.intercept_, LR.coef_[0])

結果:

# 我們的
[1.95761357] [5.08223998]
# SKlearn
[1.94426643] [5.10697899]

看起來是差不多的,若是把 epoch 的次數再調高一些到 1000,結果可能會更相近。

PyTorch

接下來終於要進入 PyTorch 了!不過在用 PyTorch 建立模型前,我們要先有一些 PyTorch 中的基本概念。像是 tensor 是什麼,如何建立參數和該存放在哪裡等。

Tensor

在機器學習領域中,我們可以把張量 (Tensor) 想像成一種有多個維度的數據容器,在物理和數學中,tensor 的定義又更嚴謹些。首先我們先看這個表:

從表中可以看到,rank0 的 tensor 是純量 (scalar),1 是 vector,2 是 matrix,包含 3 以上則是 tensor,像是圖像、時間、影像都是要用 ≥ 3 維的方式,也就是 rank 3 的 tensor 來儲存。

透過實際的例子我們比較好了解究竟是怎麼回事:

來源

讀取資料和裝置

PyTorch 有一個很方便的功能就是,能夠直接將 numpy array 轉為 PyTorch tensor。不過呢,轉換過來的 tensor 會存放在 cpu 上面,但是我們都知道嘛,用 gpu 訓練模型比用 cpu 快得多,所以我們就要把 tensor 轉到 gpu 上面。

接下來就用實例看看如何做到上述的操作,首先看看我們的電腦能不能使用CUDA (Nvidia牌的)

import torch
device = 'cuda' if torch.cuda.is_available() else 'cpu'

將我們先前用 numpy 建立了資料轉成 tensor

x_train_tensor = torch.from_numpy(x_train).to(device)
y_train_tensor = torch.from_numpy(y_train).to(device)

看一下各自的型別

print(type(x_train), type(x_train_tensor), x_train_tensor.type())

檢查 tensor 存放在哪

x_train_tensor.type()

會得到 torch.cuda.DoubleTensor, 沒 gpu 則是 torch.DoubleTensor。

當然我們也可以把 tensor 轉回去 numpy array,不過假如我們的 tensor 是在 gpu 上,我們需要先把他轉存到 cpu。

x_train_tensor.cpu().numpy()

建立參數

剛剛我們只是單純建立一個資料,那要怎麼樣讓這個資料可以用來訓練呢?還記得我們在前面提到的梯度吧?沒錯!在 tensor 我們同樣要計算他的梯度,好消息是我們可以叫 PyTorch 幫我們做!只要在建立資料時,用 requires_grad=True 就可以了。

a = torch.randn(1, requires_grad=True)
b = torch.randn(1, requires_grad=True)

若是要在 gpu 做運算的話,則需要先傳到 gpu 上後在使用 requires_grad

a = torch.randn(1).to(device)
b = torch.randn(1).to(device)
a.requires_grad_()
b.requires_grad_()

注意 requires_grad 後面的底線「 _ 」,在 PyTorch 中,用「 _ 」結尾的方法都會改變變數本身。

最後我們也可以直接在建立參數時,直接指定 device argument。

torch.manual_seed(7)
a = torch.randn(1, requires_grad=True, device=device)
b = torch.randn(1, requires_grad=True, device=device)

Autograd

Autograd 是 PyTorch 的自動微分 package,有了它我們就不用在那邊手算那些導數拉!我們直接呼叫 backward() 就能幫我們計算所有的梯度了。

那究竟 backward() 要在哪呼叫的,就在我們一開始所提到的 loss,像這樣 loss.backward()。

在 PyTorch 中,梯度會不斷往上加而不是替換掉,但是累加梯度的話會讓我們每次的梯度有偏差而影響方向,所以每次我們要用梯度去更新參數之後,需要用 zero_() 將梯度清零,那你一定會好奇為何不直接替代就好,因為在訓練 RNN 模型時,累積梯度就很方便。反正只多一個程序而已嘛~

接下來我們來試試看如何改寫前面的迴歸模型

lr = 1e-1
epochs = 500
torch.manual_seed(42)
a = torch.randn(1, requires_grad=True, device=device)
b = torch.randn(1, requires_grad=True, device=device)
for epoch in range(epochs):
yhat = a + b * x_train_tensor
error = y_train_tensor - yhat
loss = (error ** 2).mean()
# 不用再自己計算梯度了
# a_grad = -2 * error.mean()
# b_grad = -2 * (x_tensor * error).mean()

# 從 loss 做 backward 來幫我們取得梯度
loss.backward()

with torch.no_grad():
a -= lr * a.grad
b -= lr * b.grad

# 清除梯度
a.grad.zero_()
b.grad.zero_()

print(a, b)

這邊要注意,當我們要更新參數時,必須用 with torch.no_gra() 來告訴 pytorch 我們不會動到梯度的計算,若是少了這行,就會影響到參數梯度的計算。例如我們直接用 a = a- lr * a.grad,我們的梯度就會消失, .grad 就會變成 NoneType 導致後面沒辦法清零。這跟 PyTorch 的動態計算圖有關,後面會提到。

動態計算圖(Dynamic Computation Graph)

動態計算圖光用文字很想像和說明他到底在做什麼,這邊我們運用 PyTorchViz 來把動態計算圖視覺話,讓大家能夠比較好理解。

首先先來看看預測

torch.manual_seed(7)
a = torch.randn(1, requires_grad=True, device=device)
b = torch.randn(1, requires_grad=True, device=device)
yhat = a + b * x_train_tensor
error = y_train_tensor - yhat
loss = (error ** 2).mean()

make_dot(yhat)

用 yhat 來計算 error make_dot(error)

用 error 計算 MSE make_dot(loss)

接下來看看他們各自代表什麼

  • 藍色方格:他們代表我們用來作為參數的 tensor,也就是我們要 PyTorch 拿來計算梯度那些,以第一個圖為例,可以看到有 a, b 兩個藍色方格,接著下面的灰色方格為 b * x。你可能會有疑問,為何 x 也是資料也用 tensor 表示,但是他沒有用藍格表示?那是因為我們不需要計算他的梯度,在動態計算圖中,只會列出需要計算其梯度的 tensor。所以只要我們把 a 的 requires_grad 設成 False,他就不會出現在計算圖中了。
  • 灰色方格:是跟計算梯度有關的 Python 操作。
  • 綠色方格:跟灰色一樣,只是它是計算梯度的起始點(假設 backward() 是在這個點呼叫 ),在計算圖中,計算方式是由底至上(bottom-up)。可以由此看到,上面三個圖之間因為變數都是拿來做下一個操作,所以他們和前者相較起來,不同的只有中間灰格的數量。

優化器(Optimizer)

目前為止我們都是利用梯度,手動地來更新參數。更新兩個可能不是什麼大問題,但是在實務上我們可能會有幾萬甚至幾億個參數需要更新,這時候我們就需要優化器了(Optimizer),像是什麼 SGD, Adam, Adagrad 之類的。

PyTorch 的 optimizer 會透過 step() 方法,依據我們設定的學習率還有其他超參數等,來更新我們的參數。另外有了優化器,我們也不用再對每一個參數做清零 zero_grad() 了,只需要透過優化器來做就好。

接著我們就用 SGD 優化器來示範一下:

from torch import optim
torch.manual_seed(7)
a = torch.randn(1, requires_grad=True)
b = torch.randn(1, requires_grad=True)
print(a, b)
lr = 1e-1
epochs = 500
# 指定我們的優化器為 SGD
optimizer = optim.SGD([a, b], lr=lr)
for epoch in range(epochs):
yhat = a + b * x_train_tensor
error = y_train_tensor - yhat
loss = (error ** 2).mean()
loss.backward()

# 不用手動更新梯度
# a -= lr * a.grad
# b -= lr * b.grad
optimizer.step()

# 不用自己將梯度清零
# a.grad.zero_()
# b.grad.zero_()
optimizer.zero_grad()

print(a, b)

Loss

有沒有人想到我們目前還是自己算 loss 呢?答對了!PyTorch 也可以幫我們算!我們一開始提到的 MSE 只是其中一種 loss function,像在分類問題,cross entropy 就是比較常見的 loss function。接下來示範如何在 PyTorch 中選擇 loss function。

from torch import nn
torch.manual_seed(7)
a = torch.randn(1, requires_grad=True)
b = torch.randn(1, requires_grad=True)
print(a, b)
lr = 1e-1
epochs = 500
# 指定一個 loss function
MSELoss = nn.MSELoss(reduction='mean')
optimizer = optim.SGD([a, b], lr=lr)for epoch in range(epochs):
yhat = a + b * x_train_tensor

# 不用再自己算 loss 了
# error = y_tensor - yhat
# loss = (error ** 2).mean()
loss = MSELoss(y_train_tensor, yhat)
loss.backward()
optimizer.step()
optimizer.zero_grad()

print(a, b)

模型(Model)

在 PyTorch 中的 model 是用從 PyTorch Module 繼承來的 Python class 表示的。

一些基礎的 methods 有這些:

  • __init__(self):定義了模型的組成。在我們的例子中,是參數a, b。當然我們也能定義一些參數之外的東西,像是層數之類的。
  • forward(self, x):執行計算,就是讓資料通過參數來得到現在的預測,不過使用的時候不是呼叫 forward() ,而是呼叫 model 本身,他就會輸出預測了。

接下來建立一個簡單的模型吧!

class PyTorchLinearRegression(nn.Module):
def __init__(self):
super().__init__()
# 使用 nn.Parameter 來表示 a, b 為參數
self.a = nn.Parameter(torch.randn(1, requires_grad=True))
self.b = nn.Parameter(torch.randn(1, requires_grad=True))

def forward(self, x):
# 計算我們的輸出,也就是預測
return self.a + self.b * x

__init__ 中,我們用 Parameter() class 定義了變數 a, b,來告訴 PyTorch 要把這兩個 tensors 作為模型的參數。此外我們還能透過 state_dict() 來看一下我們現在所有的參數。

接下來來看看如何把前面定義的模型用來訓練

torch.manual_seed(7)model = PyTorchLinearRegression()#.to(device)
print(model.state_dict())
lr = 1e-1
epochs = 500
MSELoss = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=lr)
for epoch in range(epochs):

model.train()
# 不用再手動做 output 了
# yhat = a + b * x_tensor
yhat = model(x_train_tensor)

loss = MSELoss(y_train_tensor, yhat)
loss.backward()
optimizer.step()
optimizer.zero_grad()

print(model.state_dict())

這邊要注意的是,假如資料在 GPU 上,參數也必須在 GPU 上,這時就要用 to_device() 來把參數 tensor 也傳到 GPU 上。

model = PyTorchLinearRegression().to(device)

另外這裡我們也看到一段 model.train() 的程式碼,這段程式碼能夠讓我們將模型轉換到訓練模式,但並不會幫我們開始訓練模型,我們後面還是得定義訓練模型的步驟,後面會再解釋為何需要將模型轉為訓練模式。

巢狀模型

現在的線性模型是我們自己手刻出來的,PyTorch 同樣也有提供線性模型

在下面的程式碼中,我們來把它包到我們自己的 model class 中讓他變成一個 attribute,這樣我們就有一個巢狀模型了。另外在 forward() 中,我們直接呼叫巢狀模型來進行預測的輸出,而不是呼叫 self.linear.forward(x)

class LinearRegressionLayer(nn.Module):
def __init__(self):
super().__init__()
# 我們用一個有著 1 個輸入和輸出的線性模型層來取代原本的 2 個自訂參數
self.linear = nn.Linear(1, 1)

def forward(self, x):
# 現在只要呼叫 linear() 就可以得到預測
return self.linear(x)

在建立好我們的巢狀模型後,可以透過 [*LinearRegressionLayer().parameters()] 來查看我們的參數。

順序模型(Sequential Model)

我們的模型已經夠簡單了,不過你可能會想,還敢不敢再更簡單一點?沒問題!

像我們這種很簡單的只是將資料向前傳遞,輸出用參數處理過後的值,之後再作為下一個層的輸入繼續計算下去的過程,就可以使用順序模型

在我們的例子中,我們要建一個有一個引數(Argument),也就是我們用來做線性迴歸的 linear 層:

model = nn.Sequential(nn.Linear(1, 1))

訓練過程

目前為止我們定義了一個優化器、一個損失函數(loss function)和一個模型,加上我們的資料(feature, data)和標記(label)。在訓練的過程中,我們可能需要嘗試不同的優化器、損失函數或是模型,基於這點,我們可以把函數設計的更泛用一些。

我們首先定義一個函數,引數為 optimizer, loss function 和 model,並在這個函數中,定義我們的訓練過程,引數為 data 和 label,在這個訓練過程中則執行每個訓練步驟並回傳 loss:

# 建立一個有完整訓練過程的函數
def build_train_step(model, loss_fn, optimizer):

def train_step(x, y):

# 訓練模式
model.train()

# 預測
yhat = model(x)

# 計算 loss
loss = loss_fn(y, yhat)

# 計算梯度
loss.backward()

# 更新參數並清零梯度
optimizer.step()
optimizer.zero_grad()

# 回傳 loss
return loss.item()

# 回傳會在函數內被呼叫的訓練過程
return train_step
# 以我們定義好的 model, lossfunction 和 optimizer 來建立 train_step
train_step = build_train_step(model, MSELoss, optimizer)
for epoch in range(epochs):
# 執行一次訓練並回傳 loss
loss = train_step(x_train_tensor, y_train_tensor)

# 確認參數
print(model.state_dict())

資料集(Dataset)

前面我們大多在講優化器、模型和損失函數,現在我們要來看一下資料的部分。我們先前的資料都是用 Numpy 和 PyTorch tensor 來表示,不過我們有更好的選擇,PyTorch 中的 dataset 也是用 Python class 來表示,其繼承了 PyTorch Dataset class。我們可以把他想成由 tuple 組成的 list,每一個 tuple 代表一個資料和標籤(x, y)。

在 dataset 中有幾個比較常用的方法:

  • __init__ (self):任何可以拿來建立 tuple 列表的引數都可以在這裡定義,像是我們前面用的最基本的 tensor, 或是一個 CSV 的檔名,在之後可以用來讀取資料。

我們不用一次就讀取全部的資料集,當資料集很大的時候會浪費很多記憶空間,比較好的方式是透過 __get_item__ 在需要時來讀取資料,這也是為何我們前面設定的是 CSV 檔名,我們可以在這裡來透過檔名來讀取資料。

  • __get_item__(self, index) :為資料集建立索引,好讓我們可以像用 list[i] 這樣的互動方式來操作資料和其標籤。
  • __len__(self) :回傳 dataset 的大小,有時會需要用到 dataset 的長度資訊。

接下來我們來建立一個有兩個 tensor 的 dataset,一個資料 x,一個標籤 y。

from torch.utils.data import Datasetclass SLRDataset(Dataset):
def __init__(self, x_tensor, y_tensor):
self.x = x_tensor
self.y = y_tensor

def __getitem__(self, index):
return (self.x[index], self.y[index])
def __len__(self):
return len(self.x)
# 不用傳到 GPU 上
x_train_tensor = torch.from_numpy(x_train)
y_train_tensor = torch.from_numpy(y_train)
training_data = SLRDataset(x_train_tensor, y_train_tensor)
print(training_data[0])

你可能會覺得我們不過就是用兩個 tensor 嘛,搞那麼麻煩幹嘛?如果我們只有幾個 tensor 的話,也可以用 TensorDataset 來做就好。

大家可以看到我們的 training tensors 沒有丟到 GPU 上,那是因為我們不希望在這個階段就把資料讀到 GPU 中,必須保留空間拿來訓練模型。

接下來我們要來看看為何需要這樣整理 data。

Dataloader

目前為止我們做的都是 batch grdient descent,也就是一次把所有的 data 拿來做梯度下降。這在我們這個小到不行的 data 當然沒有問題,但是資料一大可就不能這樣做事情了。

當資料量相當大時,我們要做 mini-batch gradient descent ,也就是一次讀只拿一部分資料來做 gradient descent,因此需要把資料切片,當然我們不會想自己手切嘛,這就是 dataloader 的作用了!

Dataloader 就像一個迭代器(iterator),會 loop 過資料並逐次取出不同份的 mini-batch。

from torch.utils.data import DataLoadertrain_loader = DataLoader(dataset=train_data, batch_size=16, shuffle=True)

跟 keras 的很像,我們可以透過 next() 來讓 dataloader 逐次吐出 data。

next(iter(train_loader))

將 dataloader 用到我們的模型中

losses = []
train_step = build_train_step(model, MSELoss, optimizer)
for epoch in range(epochs):
for x_batch, y_batch in train_loader:
# 現在 dataset 存放在 CPU 上,訓練的時候我們要把它轉移到 GPU 上
x_batch = x_batch.to(device)
y_batch = y_batch.to(device)

loss = train_step(x_batch, y_batch)
losses.append(loss)

print(model.state_dict())
print(losses[-1])

我們用了一個內迴圈來利用 dataloader 逐次讀取 mini-batch 的資料,同時也只將一份 mini-batch 傳到 GPU 上。雖然我們的資料小到不需要特定這樣做,不過在處理大的資料集時,都是先利用 dataset 的 __get_item__ 來先將資料存在 CPU,接著再將同屬一個 mini-batch 的資料傳到 GPU 做訓練,讓 GPU 的記憶空間能夠有效使用。

Random Split

目前我們都還在使用 training data,接下來我們同樣透過 dataset 和 dataloader 來處理我們的 validation data。除了我們在最一開始用的,將兩者取前 80, 後 20 的方式外,還可以使用 random_split()

接下來實例介紹一下如何使用 random_split() method,不過要注意我們是要用在整個 dataset 上,不是用在 training data 上,不然就會變成 training data 又再下去分,這樣就會有三份資料。

from torch.utils.data.dataset import random_split
from torch.utils.data import TensorDataset
x_tensor = torch.from_numpy(x)
y_tensor = torch.from_numpy(y)
dataset = TensorDataset(x_tensor, y_tensor)train_dataset, val_dataset = random_split(dataset, [80, 20])train_loader = DataLoader(dataset=train_dataset, batch_size=16)
val_loader = DataLoader(dataset=val_dataset, batch_size=20)

評估(Evaluation)

最後我們要來把 evaluation 加到我們的模型中,讓模型能夠計算 validation loss,絕大多數的過程都樣,用 dataloader 讀 mini-batch 進來,預測後計算 loss。

除了兩點需要做一些改變:

  • torch.no_grad():在 evaluation 階段,我們不需要去計算梯度,只有在訓練時才要。評估時我們只是將訓練好的參數,拿來用在 validation set 上看看表現如何而已。
  • eval():跟做 train() 對應,把我們的模型轉到 evalutaion 模式。
losses = []
val_losses = []
train_step = build_train_step(model, MSELoss, optimizer)
for epoch in range(epochs):
for x, y in train_loader:
x = x.to(device)
y = y.to(device)
loss = train_step(x, y)
losses.append(loss)

with torch.no_grad():
for x, y in val_loader:
x = x.to(device)
y = y.to(device)

model.eval()
yhat = model(x)
val_loss = MSELoss(y, yhat)
val_losses.append(val_loss.item())
print(model.state_dict())
print(losses[-1], val_losses[-1])

結語

以上就是基本的 PyTorch 範例,雖然實際在處理深度學習問題時比文章中的例子還要複雜得多,但是希望透過這篇文章能夠讓初入 PyTorch 的人,能夠透過逐步拆解、漸進式的方式,理解如何在 PyTorch 中部署我們的熟悉的 Model。接下來在看其他進階 PyTorch 教學時,應該能夠更理解它是如何運作的了。

Reference

https://towardsdatascience.com/understanding-pytorch-with-an-example-a-step-by-step-tutorial-81fc5f8c4e8e

--

--