PyTorch實戰(2)——使用PyTorch構建神經網絡
- 0. 前言
- 1. PyTorch 構建神經網絡初體驗
- 1.1 使用 PyTorch 構建神經網絡
- 1.2 神經網絡數據加載
- 1.3 模型測試
- 1.4 獲取中間層的值
- 2. 使用 Sequential 類構建神經網絡
- 3. PyTorch 模型的保存和加載
- 3.1 模型保存所需組件
- 3.2 模型狀態
- 3.3 模型保存
- 3.4 模型加載
- 小結
- 系列鏈接
0. 前言
我們已經學習了深度學習的基本概念,神經網絡通常包括輸入層、隱藏層、輸出層、激活函數、損失函數和學習率等基本組件。在本節中,我們將學習如何在簡單數據集上使用 PyTorch
構建神經網絡,利用張量對象操作和梯度值計算更新網絡權重。
1. PyTorch 構建神經網絡初體驗
1.1 使用 PyTorch 構建神經網絡
為了介紹如何使用 PyTorch
構建神經網絡,我們將嘗試解決兩個數字的相加問題。
(1) 初始化數據集,定義輸入 (x
) 和輸出 (y
) 值:
import torch
x = [[1,2],[3,4],[5,6],[7,8]]
y = [[3],[7],[11],[15]]
在初始化的輸入和輸出變量中,輸入中的每個列表的值之和就是輸出列表中對應的值。
(2) 將輸入列表轉換為張量對象:
X = torch.tensor(x).float()
Y = torch.tensor(y).float()
在以上代碼中,將張量對象轉換為浮點對象。此外,將輸入 ( X
) 和輸出 ( Y
) 數據點注冊到 device
中:
device = 'cuda' if torch.cuda.is_available() else 'cpu'
X = X.to(device)
Y = Y.to(device)
(3) 定義神經網絡架構。
導入 torch.nn
模塊用于構建神經網絡模型:
from torch import nn
創建神經網絡架構類 MyNeuralNet
,繼承自 nn.Module
,nn.Module
是所有神經網絡模塊的基類:
class MyNeuralNet(nn.Module):
在類中,使用 __init__
方法初始化神經網絡的所有組件,調用 super().__init__()
確保類繼承 nn.Module
:
def __init__(self):super().__init__()
使用以上代碼,通過指定 super().__init__()
可以利用為 nn.Module
編寫的所有預構建函數,初始化的組件將用于 MyNeuralNet
類中的不同方法。
定義神經網絡中的網絡層:
self.input_to_hidden_layer = nn.Linear(2,8)self.hidden_layer_activation = nn.ReLU()self.hidden_to_output_layer = nn.Linear(8,1)
在以上代碼中,指定了神經網絡的所有層——一個全連接層 (self.input_to_hidden_layer
),使用 ReLU
激活函數 (self.hidden_layer_activation
),最后仍是一個全連接層 (self.hidden_to_output_layer
)。
將初始化后的神經網絡組件連接在一起,并定義網絡的前向傳播方法 forward
:
def forward(self, x):x = self.input_to_hidden_layer(x)x = self.hidden_layer_activation(x)x = self.hidden_to_output_layer(x)return x
必須使用 forward
作為前向傳播的函數名,因為 PyTorch
保留此函數作為執行前向傳播的方法,使用其他名稱都會引發錯誤。
通過打印 nn.Linear
方法的輸出了解函數作用:
print(nn.Linear(2, 7))
# Linear(in_features=2, out_features=7, bias=True)
在以上代碼中,全連接層以 2
個值作為輸入并輸出 7
個值,且包含與之關聯的偏置參數。
(4) 通過執行以下代碼訪問每個神經網絡組件的初始權重。
創建 MyNeuralNet
類對象的實例并將其注冊到 device
:
mynet = MyNeuralNet().to(device)
可以通過類似代碼訪問每一層的權重和偏置:
print(mynet.input_to_hidden_layer.weight)
代碼輸出結果如下:
Parameter containing:
tensor([[ 0.0984, 0.3058],[ 0.2913, -0.3629],[ 0.0630, 0.6347],[-0.5134, -0.2525],[ 0.2315, 0.3591],[ 0.1506, 0.1106],[ 0.2941, -0.0094],[-0.0770, -0.4165]], device='cuda:0', requires_grad=True)
每次執行時輸出的值并不相同,因為神經網絡每次都使用隨機值進行初始化。如果希望每次在執行相同代碼時保持相同輸出,則需要在創建類對象的實例之前使用 Torch
中的 manual_seed
方法指定隨機種子 torch.manual_seed(0)
。
(5) 可以通過以下代碼獲得神經網絡的所有參數:
mynet.parameters()
以上代碼會返回一個生成器對象,最后通過生成器循環獲取參數:
for param in mynet.parameters():print(param)
代碼輸出結果如下:
Parameter containing:
tensor([[ 0.2955, 0.3231],[ 0.5153, 0.1734],[-0.6359, -0.1406],[ 0.3820, -0.1085],[ 0.2816, -0.2283],[ 0.4633, 0.6564],[-0.1605, -0.4450],[ 0.0788, -0.0147]], device='cuda:0', requires_grad=True)
Parameter containing:
tensor([[ 0.2955, 0.3231],[ 0.5153, 0.1734],[-0.6359, -0.1406],[ 0.3820, -0.1085],[ 0.2816, -0.2283],[ 0.4633, 0.6564],[-0.1605, -0.4450],[ 0.0788, -0.0147]], device='cuda:0', requires_grad=True)
Parameter containing:
tensor([-0.4761, 0.6370, 0.6744, -0.4103, -0.3714, 0.1491, -0.2304, 0.5571],device='cuda:0', requires_grad=True)
Parameter containing:
tensor([[-0.0440, 0.0028, 0.3024, 0.1915, 0.1426, -0.2859, -0.2398, -0.2134]],device='cuda:0', requires_grad=True)
Parameter containing:
tensor([-0.3238], device='cuda:0', requires_grad=True)
該模型已將這些張量注冊為跟蹤前向和反向傳播所必需的特殊對象,在 __init__
方法中定義 nn
神經網絡層時,它會自動創建相應的張量并同時進行注冊,也可以使用 nn.parameter(<tensor>)
函數手動注冊這些參數。因此,本節定義的神經網絡類 myNeuralNet
等價于以下代碼:
class MyNeuralNet(nn.Module):def __init__(self):super().__init__()self.input_to_hidden_layer = nn.parameter(torch.rand(2,8))self.hidden_layer_activation = nn.ReLU()self.hidden_to_output_layer = nn.parameter(torch.rand(8,1))def forward(self, x):x = x @ self.input_to_hidden_layerx = self.hidden_layer_activation(x)x = x @ self.hidden_to_output_layerreturn x
(6) 定義損失函數,由于需要預測連續輸出,因此使用均方誤差作為損失函數:
loss_func = nn.MSELoss()
通過將輸入值傳遞給神經網絡對象,然后計算給定輸入的損失函數值:
_Y = mynet(X)
loss_value = loss_func(_Y,Y)
print(loss_value)
# tensor(127.4498, device='cuda:0', grad_fn=<MseLossBackward>)
在以上代碼中,mynet(X)
根據給定輸入通過神經網絡計算輸出值,loss_func
函數計算對應于神經網絡預測 (_Y
) 和實際值 (Y
) 的 MSELoss
值。需要注意的是,根據 PyTorch
約定,在計算損失時,我們總是首先傳入預測結果,然后傳入實際標記值。
(7) 定義用于降低損失值的優化器,優化器的輸入是與神經網絡相對應的參數(權重和偏置項)以及更新權重時的學習率。本節,我們使用隨機梯度下降 (Stochastic Gradient Descent
, SGD
)。從 torch.optim
模塊中導入 SGD
方法,然后將神經網絡對象 (mynet
) 和學習率 (lr
) 作為參數傳遞給 SGD
方法:
from torch.optim import SGD
opt = SGD(mynet.parameters(), lr = 0.001)
(8) 一個 epoch
訓練過程包含以下步驟:
- 計算給定輸入和輸出對應的損失值
- 計算參數對應的梯度
- 根據參數的學習率和梯度更新權重
- 更新權重后,確保在下一個
epoch
計算梯度之前刷新上一步中計算的梯度
opt.zero_grad()loss_value = loss_func(mynet(X),Y)loss_value.backward()opt.step()
使用 for
循環重復執行上述步驟。在以下代碼中,執行 50
個 epoch
,此外,在 loss_history
列表中存儲每個 epoch
中的損失值:
loss_history = []
for _ in range(50):opt.zero_grad()loss_value = loss_func(mynet(X),Y)loss_value.backward()opt.step()loss_history.append(loss_value.item())
繪制損失隨 epoch
的變化情況:
import matplotlib.pyplot as plt
plt.plot(loss_history)
plt.title('Loss variation over increasing epochs')
plt.xlabel('epochs')
plt.ylabel('loss value')
plt.show()
1.2 神經網絡數據加載
批大小 (batch size
) 是神經網絡中的重要超參數,批大小是指計算損失值或更新權重時考慮的數據樣本數。假設數據集中有數百萬個數據樣本,一次將所有數據點用于一次權重更新并非最佳選擇,因為內存可能無法容納如此多數據。使用抽樣樣本可以充分代表數據,批大小可以用于獲取具有足夠代表性的多個數據樣本。在本節中,我們指定計算權重梯度時要考慮的批大小以更新權重,然后計算更新后的損失值。
(1) 導入用于加載數據和處理數據集的方法:
from torch.utils.data import Dataset, DataLoader
import torch
import torch.nn as nn
(2) 導入數據,將數據轉換為浮點數,并將它們注冊到相應設備中:
x = [[1,2],[3,4],[5,6],[7,8]]
y = [[3],[7],[11],[15]]X = torch.tensor(x).float()
Y = torch.tensor(y).float()device = 'cuda' if torch.cuda.is_available() else 'cpu'
X = X.to(device)
Y = Y.to(device)
(3) 創建數據集類 MyDataset
:
class MyDataset(Dataset):
在 MyDataset
類中,存儲數據信息以便可以將一批 (batch
) 數據點捆綁在一起(使用 DataLoader
),并通過一次前向和反向傳播更新權重。
定義 __init__
方法,該方法接受輸入和輸出對并將它們轉換為 Torch
浮點對象:
def __init__(self,x,y):self.x = x.clone().detach() # torch.tensor(x).float()self.y = y.clone().detach() # torch.tensor(y).float()
指定輸入數據集的長度 (__len__
):
def __len__(self):return len(self.x)
__getitem__
方法用于獲取指定數據樣本:
def __getitem__(self, ix):return self.x[ix], self.y[ix]
在以上代碼中,ix
表示要從數據集中獲取的數據索引。
(4) 創建自定義類的實例:
ds = MyDataset(X, Y)
(5) 通過 DataLoader
傳遞數據集實例,從原始輸入輸出張量對象中獲取 batch_size
個數據點:
dl = DataLoader(ds, batch_size=2, shuffle=True)
在以上代碼中,指定從原始輸入數據集 (ds
) 中獲取兩個 (batch_size=2
) 隨機樣本 (shuffle=True
)數據點。
循環遍歷 dl
獲取批數據信息:
for x, y in dl:print(x, y)
輸出結果如下所示:
tensor([[3., 4.],[5., 6.]], device='cuda:0') tensor([[ 7.],[11.]], device='cuda:0')
tensor([[1., 2.],[7., 8.]], device='cuda:0') tensor([[ 3.],[15.]], device='cuda:0')
可以看到以上代碼生成了兩組輸入-輸出
對,因為原始數據集中共有 4
個數據點,而指定的批大小為 2
。
(6) 定義神經網絡類:
class MyNeuralNet(nn.Module):def __init__(self):super().__init__()self.input_to_hidden_layer = nn.Linear(2,8)self.hidden_layer_activation = nn.ReLU()self.hidden_to_output_layer = nn.Linear(8,1)def forward(self, x):x = self.input_to_hidden_layer(x)x = self.hidden_layer_activation(x)x = self.hidden_to_output_layer(x)return x
(7) 定義模型對象 (mynet
)、損失函數 (loss_func
) 和優化器 (opt
):
mynet = MyNeuralNet().to(device)
loss_func = nn.MSELoss()
from torch.optim import SGD
opt = SGD(mynet.parameters(), lr = 0.001)
(8) 最后,循環遍歷批數據點以最小化損失值:
import time
loss_history = []
start = time.time()
for _ in range(50):for data in dl:x, y = dataopt.zero_grad()loss_value = loss_func(mynet(x),y)loss_value.backward()opt.step()loss_history.append(loss_value.item())
end = time.time()
print(end - start)
# 0.08548569679260254
雖然以上代碼與上一小節中使用的代碼非常相似,但與上一小節相比,每個 epoch
更新權重的次數變為原來的 2
倍,因為本節中使用的批大小為 2
,而上一小節中的批大小為 4
(即一次使用全部數據點)。
1.3 模型測試
在上一小節中,我們學習了如何在已知數據點上擬合模型。在本節中,我們將學習如何利用上一小節訓練的 mynet
模型中定義的前向傳播方法 forward
來預測模型沒有見過的數據點(測試數據)。
(1) 創建用于測試模型的數據點:
val_x = [[10,11]]
新數據集 (val_x
) 與輸入數據集相同,也是由列表數據組成的列表。
(2) 將新數據點轉換為張量浮點對象并注冊到 device
中:
val_x = torch.tensor(val_x).float().to(device)
(3) 通過訓練好的神經網絡 (mynet
) 傳遞張量對象,與通過模型執行前向傳播的用法相同:
print(mynet(val_x))
# tensor([[20.0105]], device='cuda:0', grad_fn=<AddmmBackward>)
以上代碼返回模型對輸入數據點的預測輸出值。
1.4 獲取中間層的值
在實際應用中,我們可能需要獲取神經網絡的中間層值,例如風格遷移和遷移學習等,PyTorch
提供了兩種方式獲取神經網絡中間值。
一種方法是直接調用神經網絡層,將它們當做函數使用:
print(mynet.hidden_layer_activation(mynet.input_to_hidden_layer(X)))
需要注意的是,我們必須安裝模型輸入、輸出順序調用相應神經網絡層,例如,在以上代碼中 input_to_hidden_layer
的輸出是 hidden_layer_activation
層的輸入。
另一種方法是在 forward
方法中指定想要查看的網絡層,雖然以下代碼與上一小節中的 MyNeuralNet
類基本相同,但 forward
方法不僅返回輸出,還返回激活后的隱藏層值 (hidden2
):
class MyNeuralNet(nn.Module):def __init__(self):super().__init__()self.input_to_hidden_layer = nn.Linear(2,8)self.hidden_layer_activation = nn.ReLU()self.hidden_to_output_layer = nn.Linear(8,1)def forward(self, x):hidden1 = self.input_to_hidden_layer(x)hidden2 = self.hidden_layer_activation(hidden1)x = self.hidden_to_output_layer(hidden2)return x, hidden2
通過使用以下代碼訪問隱藏層值,mynet
的第 0
個索引輸出是網絡前向傳播的最終輸出,而第 1
個索引輸出是隱藏層激活后的值:
print(mynet(X)[1])
2. 使用 Sequential 類構建神經網絡
我們已經學習了如何通過定義一個類來構建神經網絡,在該類中定義了層以及層之間的連接方式。然而,除非需要構建一個復雜的網絡,否則,只需要利用 Sequential
類并指定層和層堆疊的順序即可搭建神經網絡,本節繼續使用簡單數據集訓練神經網絡。
(1) 導入相關庫并定義使用的設備:
import torch
import torch.nn as nn
import numpy as np
from torch.utils.data import Dataset, DataLoader
device = 'cuda' if torch.cuda.is_available() else 'cpu'
(2) 定義數據集與數據集類 (MyDataset
):
x = [[1,2],[3,4],[5,6],[7,8]]
y = [[3],[7],[11],[15]]class MyDataset(Dataset):def __init__(self, x, y):self.x = torch.tensor(x).float().to(device)self.y = torch.tensor(y).float().to(device)def __getitem__(self, ix):return self.x[ix], self.y[ix]def __len__(self): return len(self.x)
(3) 定義數據集 (ds
) 和數據加載器 (dl
) 對象:
ds = MyDataset(x, y)
dl = DataLoader(ds, batch_size=2, shuffle=True)
(4) 使用 nn
模塊中 Sequential
類定義模型架構:
model = nn.Sequential(nn.Linear(2, 8),nn.ReLU(),nn.Linear(8, 1)
).to(device)
在以上代碼中,我們定義了與上小節相同的網絡架構,nn.Linear
接受二維輸入并為每個數據點提供八維輸出,nn.ReLU
在八維輸出之上執行 ReLU
激活,最后,使用 nn.Linear
接受八維輸入并得到一維輸出。
(5) 打印模型的摘要 (summary
),查看模型架構信息。
為了查看模型摘要,需要使用 pip
安裝 torchsummary
庫:
pip install torchsummary
安裝完成后,導入庫 torchsummary
:
from torchsummary import summary
打印模型摘要,函數接受模型名稱及模型輸入大小(需要使用整數元組)作為參數:
print(summary(model, (2,)))
輸出結果如下所示:
----------------------------------------------------------------Layer (type) Output Shape Param #
================================================================Linear-1 [-1, 8] 24ReLU-2 [-1, 8] 0Linear-3 [-1, 1] 9
================================================================
Total params: 33
Trainable params: 33
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.00
Params size (MB): 0.00
Estimated Total Size (MB): 0.00
----------------------------------------------------------------
以第一層輸出為例,其形狀為 (-1, 8)
,其中 -1
表示 batch size
,8
表示對于每個數據點會得到一個 8
維輸出,得到形狀為 batch size x 8
的輸出。
(6) 接下來,定義損失函數 (loss_func
) 和優化器 (opt
) 并訓練模型:
loss_func = nn.MSELoss()
from torch.optim import SGD
opt = SGD(model.parameters(), lr = 0.001)
import time
loss_history = []
start = time.time()
for _ in range(50):for ix, iy in dl:opt.zero_grad()loss_value = loss_func(model(ix),iy)loss_value.backward()opt.step()loss_history.append(loss_value.item())
end = time.time()
print(end - start)
(7) 訓練模型后,在驗證數據集上預測值。
定義驗證數據集:
val = [[8,9],[10,11],[1.5,2.5]]
將驗證數據轉換為浮點數,然后將它們轉換為張量對象并將它們注冊到 device
中,通過模型傳遞驗證數據預測輸出:
val = torch.tensor(val).float()
print(model(val.to(device)))
"""
tensor([[16.7774],[20.6186],[ 4.2415]], device='cuda:0', grad_fn=<AddmmBackward>)
"""
3. PyTorch 模型的保存和加載
神經網絡模型處理的一個重要方面是在訓練后保存和加載模型,保存模型后,我們可以利用已經訓練好的模型中進行推斷,只需要加載已經訓練過的模型,而無需再次對其進行訓練。
3.1 模型保存所需組件
首先了解保存神經網絡模型所需的完整組件:
- 每個張量(參數)的唯一名稱(鍵)
- 網絡中張量間的連接的方式
- 每個張量的值(權重/偏置值)
第一個組件是在定義的 __init__
階段處理的,而第二個組件是在前向計算方法定義期間處理的。默認情況下,張量中的值在 __init__
階段隨機初始化,但加載預訓練模型時我們需要加載一組在訓練模型時學習到的固定權重值,并將每個值與特定名稱相關聯。
3.2 模型狀態
model.state_dict()
可以用于了解保存和加載 PyTorch
模型的工作原理,model.state_dict()
中的字典 (OrderedDict
) 對應于模型的參數名稱(鍵)及其值(權重和偏置值),state
指的是模型的當前快照,返回的輸出中,鍵是模型網絡層的名稱,值對應于這些層的權重:
print(model.state_dict())
"""
OrderedDict([('0.weight', tensor([[-0.4732, 0.1934],[ 0.1475, -0.2335],[-0.2586, 0.0823],[-0.2979, -0.5979],[ 0.2605, 0.2293],[ 0.0566, 0.6848],[-0.1116, -0.3301],[ 0.0324, 0.2609]], device='cuda:0')), ('0.bias', tensor([ 0.6835, 0.2860, 0.1953, -0.2162, 0.5106, 0.3625, 0.1360, 0.2495],device='cuda:0')), ('2.weight', tensor([[ 0.0475, 0.0664, -0.0167, -0.1608, -0.2412, -0.3332, -0.1607, -0.1857]],device='cuda:0')), ('2.bias', tensor([0.2595], device='cuda:0'))])
"""
3.3 模型保存
使用 torch.save(model.state_dict(), 'mymodel.pth')
可以將模型以 Python
序列化格式保存在磁盤上,其中 mymodel.pth
表示文件名。在調用 torch.save
之前最好將模型傳輸到 CPU
中,將張量保存為 CPU
張量有助于將模型加載到任意機器上:
save_path = 'mymodel.pth'
torch.save(model.state_dict(), save_path)
3.4 模型加載
加載模型首先需要初始化模型,然后從 state_dict
中加載權重:
(1) 使用與訓練時相同的代碼創建一個空模型:
model = nn.Sequential(nn.Linear(2, 8),nn.ReLU(),nn.Linear(8, 1)
).to(device)
(2) 從磁盤加載模型并反序列化以創建一個 OrderedDict
值:
state_dict = torch.load('mymodel.pth')
(3) 加載 state_dict
到模型中,并將其注冊到 device
中,執行預測任務:
model.load_state_dict(state_dict)
model.to(device)val = [[8,9],[10,11],[1.5,2.5]]
val = torch.tensor(val).float()
model(val.to(device))
小結
在本節中,我們使用 PyTorch
在簡單數據集上構建了一個神經網絡,訓練神經網絡來映射輸入和輸出,并通過執行反向傳播來更新權重值以最小化損失值,并利用 Sequential
類簡化網絡構建過程;介紹了獲取網絡中間值的常用方法,以及如何使用 save
、load
方法保存和加載模型,以避免再次訓練模型。
系列鏈接
PyTorch實戰(1)——深度學習概述