注意
點選此處下載完整示例程式碼
簡介 || 張量 || Autograd || 構建模型 || TensorBoard 支援 || 訓練模型 || 理解模型
使用 PyTorch 構建模型¶
建立日期:2021 年 11 月 30 日 | 最後更新:2024 年 10 月 15 日 | 最後驗證:2024 年 11 月 5 日
請觀看下方影片或在 YouTube 上觀看。
torch.nn.Module 和 torch.nn.Parameter¶
在本影片中,我們將討論 PyTorch 為構建深度學習網路提供的一些工具。
除 Parameter 外,我們在本影片中討論的所有類都是 torch.nn.Module 的子類。這是 PyTorch 的基類,旨在封裝 PyTorch 模型及其元件的特定行為。
torch.nn.Module 的一個重要行為是註冊引數。如果特定的 Module 子類具有學習權重,這些權重將表示為 torch.nn.Parameter 的例項。Parameter 類是 torch.Tensor 的子類,其特殊行為是當它們被賦值為 Module 的屬性時,它們會被新增到該模組的引數列表中。可以透過 Module 類上的 parameters() 方法訪問這些引數。
作為一個簡單的例子,這裡有一個包含兩個線性層和一個啟用函式的非常簡單的模型。我們將建立一個例項並讓它報告其引數
import torch
class TinyModel(torch.nn.Module):
def __init__(self):
super(TinyModel, self).__init__()
self.linear1 = torch.nn.Linear(100, 200)
self.activation = torch.nn.ReLU()
self.linear2 = torch.nn.Linear(200, 10)
self.softmax = torch.nn.Softmax()
def forward(self, x):
x = self.linear1(x)
x = self.activation(x)
x = self.linear2(x)
x = self.softmax(x)
return x
tinymodel = TinyModel()
print('The model:')
print(tinymodel)
print('\n\nJust one layer:')
print(tinymodel.linear2)
print('\n\nModel params:')
for param in tinymodel.parameters():
print(param)
print('\n\nLayer params:')
for param in tinymodel.linear2.parameters():
print(param)
The model:
TinyModel(
(linear1): Linear(in_features=100, out_features=200, bias=True)
(activation): ReLU()
(linear2): Linear(in_features=200, out_features=10, bias=True)
(softmax): Softmax(dim=None)
)
Just one layer:
Linear(in_features=200, out_features=10, bias=True)
Model params:
Parameter containing:
tensor([[ 0.0765, 0.0830, -0.0234, ..., -0.0337, -0.0355, -0.0968],
[-0.0573, 0.0250, -0.0132, ..., -0.0060, 0.0240, 0.0280],
[-0.0908, -0.0369, 0.0842, ..., -0.0078, -0.0333, -0.0324],
...,
[-0.0273, -0.0162, -0.0878, ..., 0.0451, 0.0297, -0.0722],
[ 0.0833, -0.0874, -0.0020, ..., -0.0215, 0.0356, 0.0405],
[-0.0637, 0.0190, -0.0571, ..., -0.0874, 0.0176, 0.0712]],
requires_grad=True)
Parameter containing:
tensor([ 0.0304, -0.0758, -0.0549, -0.0893, -0.0809, -0.0804, -0.0079, -0.0413,
-0.0968, 0.0888, 0.0239, -0.0659, -0.0560, -0.0060, 0.0660, -0.0319,
-0.0370, 0.0633, -0.0143, -0.0360, 0.0670, -0.0804, 0.0265, -0.0870,
0.0039, -0.0174, -0.0680, -0.0531, 0.0643, 0.0794, 0.0209, 0.0419,
0.0562, -0.0173, -0.0055, 0.0813, 0.0613, -0.0379, 0.0228, 0.0304,
-0.0354, 0.0609, -0.0398, 0.0410, 0.0564, -0.0101, -0.0790, -0.0824,
-0.0126, 0.0557, 0.0900, 0.0597, 0.0062, -0.0108, 0.0112, -0.0358,
-0.0203, 0.0566, -0.0816, -0.0633, -0.0266, -0.0624, -0.0746, 0.0492,
0.0450, 0.0530, -0.0706, 0.0308, 0.0533, 0.0202, -0.0469, -0.0448,
0.0548, 0.0331, 0.0257, -0.0764, -0.0892, 0.0783, 0.0062, 0.0844,
-0.0959, -0.0468, -0.0926, 0.0925, 0.0147, 0.0391, 0.0765, 0.0059,
0.0216, -0.0724, 0.0108, 0.0701, -0.0147, -0.0693, -0.0517, 0.0029,
0.0661, 0.0086, -0.0574, 0.0084, -0.0324, 0.0056, 0.0626, -0.0833,
-0.0271, -0.0526, 0.0842, -0.0840, -0.0234, -0.0898, -0.0710, -0.0399,
0.0183, -0.0883, -0.0102, -0.0545, 0.0706, -0.0646, -0.0841, -0.0095,
-0.0823, -0.0385, 0.0327, -0.0810, -0.0404, 0.0570, 0.0740, 0.0829,
0.0845, 0.0817, -0.0239, -0.0444, -0.0221, 0.0216, 0.0103, -0.0631,
0.0831, -0.0273, 0.0756, 0.0022, 0.0407, 0.0072, 0.0374, -0.0608,
0.0424, -0.0585, 0.0505, -0.0455, 0.0268, -0.0950, -0.0642, 0.0843,
0.0760, -0.0889, -0.0617, -0.0916, 0.0102, -0.0269, -0.0011, 0.0318,
0.0278, -0.0160, 0.0159, -0.0817, 0.0768, -0.0876, -0.0524, -0.0332,
-0.0583, 0.0053, 0.0503, -0.0342, -0.0319, -0.0562, 0.0376, -0.0696,
0.0735, 0.0222, -0.0775, -0.0072, 0.0294, 0.0994, -0.0355, -0.0809,
-0.0539, 0.0245, 0.0670, 0.0032, 0.0891, -0.0694, -0.0994, 0.0126,
0.0629, 0.0936, 0.0058, -0.0073, 0.0498, 0.0616, -0.0912, -0.0490],
requires_grad=True)
Parameter containing:
tensor([[ 0.0504, -0.0203, -0.0573, ..., 0.0253, 0.0642, -0.0088],
[-0.0078, -0.0608, -0.0626, ..., -0.0350, -0.0028, -0.0634],
[-0.0317, -0.0202, -0.0593, ..., -0.0280, 0.0571, -0.0114],
...,
[ 0.0582, -0.0471, -0.0236, ..., 0.0273, 0.0673, 0.0555],
[ 0.0258, -0.0706, 0.0315, ..., -0.0663, -0.0133, 0.0078],
[-0.0062, 0.0544, -0.0280, ..., -0.0303, -0.0326, -0.0462]],
requires_grad=True)
Parameter containing:
tensor([ 0.0385, -0.0116, 0.0703, 0.0407, -0.0346, -0.0178, 0.0308, -0.0502,
0.0616, 0.0114], requires_grad=True)
Layer params:
Parameter containing:
tensor([[ 0.0504, -0.0203, -0.0573, ..., 0.0253, 0.0642, -0.0088],
[-0.0078, -0.0608, -0.0626, ..., -0.0350, -0.0028, -0.0634],
[-0.0317, -0.0202, -0.0593, ..., -0.0280, 0.0571, -0.0114],
...,
[ 0.0582, -0.0471, -0.0236, ..., 0.0273, 0.0673, 0.0555],
[ 0.0258, -0.0706, 0.0315, ..., -0.0663, -0.0133, 0.0078],
[-0.0062, 0.0544, -0.0280, ..., -0.0303, -0.0326, -0.0462]],
requires_grad=True)
Parameter containing:
tensor([ 0.0385, -0.0116, 0.0703, 0.0407, -0.0346, -0.0178, 0.0308, -0.0502,
0.0616, 0.0114], requires_grad=True)
這展示了 PyTorch 模型的基本結構:有一個定義模型層和其他元件的 __init__() 方法,以及一個進行計算的 forward() 方法。請注意,我們可以列印模型或其任何子模組來了解其結構。
常見層型別¶
線性層¶
神經網路中最基本的層型別是 線性 或 全連線 層。在這種層中,每個輸入都會在一定程度上影響該層的每個輸出,影響程度由層的權重指定。如果模型有 m 個輸入和 n 個輸出,權重將是一個 m x n 的矩陣。例如
lin = torch.nn.Linear(3, 2)
x = torch.rand(1, 3)
print('Input:')
print(x)
print('\n\nWeight and Bias parameters:')
for param in lin.parameters():
print(param)
y = lin(x)
print('\n\nOutput:')
print(y)
Input:
tensor([[0.8790, 0.9774, 0.2547]])
Weight and Bias parameters:
Parameter containing:
tensor([[ 0.1656, 0.4969, -0.4972],
[-0.2035, -0.2579, -0.3780]], requires_grad=True)
Parameter containing:
tensor([0.3768, 0.3781], requires_grad=True)
Output:
tensor([[ 0.8814, -0.1492]], grad_fn=<AddmmBackward0>)
如果你將 x 與線性層的權重進行矩陣乘法,並加上偏置,你會發現得到輸出向量 y。
另一個需要注意的重要特性是:當我們使用 lin.weight 檢查層的權重時,它報告自己是 Parameter(它是 torch.Tensor 的子類),並告訴我們它正在使用 autograd 跟蹤梯度。這是 Parameter 的預設行為,與 Tensor 不同。
線性層廣泛應用於深度學習模型。最常見的使用場景之一是分類器模型,通常會在末尾有一個或多個線性層,其中最後一層將有 n 個輸出,n 是分類器處理的類別數量。
卷積層¶
卷積 層用於處理具有高度空間相關性的資料。它們在計算機視覺中非常常用,用於檢測特徵的緊密分組,然後將這些分組組合成更高級別的特徵。它們也出現在其他上下文中,例如在自然語言處理應用中,詞的即時上下文(即序列中附近的詞)會影響句子的含義。
我們在之前的影片中看到了 LeNet5 中的卷積層是如何工作的
import torch.functional as F
class LeNet(torch.nn.Module):
def __init__(self):
super(LeNet, self).__init__()
# 1 input image channel (black & white), 6 output channels, 5x5 square convolution
# kernel
self.conv1 = torch.nn.Conv2d(1, 6, 5)
self.conv2 = torch.nn.Conv2d(6, 16, 3)
# an affine operation: y = Wx + b
self.fc1 = torch.nn.Linear(16 * 6 * 6, 120) # 6*6 from image dimension
self.fc2 = torch.nn.Linear(120, 84)
self.fc3 = torch.nn.Linear(84, 10)
def forward(self, x):
# Max pooling over a (2, 2) window
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# If the size is a square you can only specify a single number
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:] # all dimensions except the batch dimension
num_features = 1
for s in size:
num_features *= s
return num_features
讓我們分解這個模型卷積層中發生的事情。從 conv1 開始
LeNet5 旨在接收 1x32x32 的黑白影像。卷積層建構函式的第一個引數是輸入通道數。 在這裡,它是 1。如果我們構建這個模型來處理 3 個顏色通道,它將是 3。
卷積層就像一個掃描影像的視窗,尋找它能識別的模式。這些模式被稱為 特徵, 卷積層的一個引數是我們希望它學習的特徵數量。這是建構函式的第二個引數,即輸出特徵的數量。 在這裡,我們要求我們的層學習 6 個特徵。
就在上面,我把卷積層比作一個視窗——但視窗有多大?第三個引數是視窗或核大小。 在這裡,“5”表示我們選擇了一個 5x5 的核。(如果你想要一個高寬不同的核,你可以為這個引數指定一個元組——例如,
(3, 5)來獲得一個 3x5 的卷積核。)
卷積層的輸出是 啟用圖 ——它是輸入張量中特徵存在情況的空間表示。conv1 將為我們提供一個 6x28x28 的輸出張量;6 是特徵數量,28 是我們圖的高度和寬度。(28 是因為當在 32 畫素的行上掃描 5 畫素的視窗時,只有 28 個有效位置。)
然後我們將卷積的輸出透過一個 ReLU 啟用函式(稍後會詳細介紹啟用函式),再透過一個最大池化層。最大池化層將啟用圖中彼此靠近的特徵組合在一起。它透過減小張量來實現這一點,將輸出中每 2x2 的單元格組合併為一個單元格,並將進入其中的 4 個單元格的最大值賦給這個單元格。這為我們提供了一個較低解析度的啟用圖版本,維度為 6x14x14。
我們的下一個卷積層 conv2 期望有 6 個輸入通道(對應於第一層尋找的 6 個特徵),有 16 個輸出通道,以及一個 3x3 的核。它輸出一個 16x12x12 的啟用圖,該圖再次被最大池化層縮小到 16x6x6。在將此輸出傳遞給線性層之前,它被重塑為 16 * 6 * 6 = 576 元素的向量,供下一層使用。
有用於處理一維、二維和三維張量的卷積層。卷積層建構函式還有更多可選引數,包括輸入中的步長(例如,只掃描每第二個或每第三個位置)、填充(以便你可以掃描到輸入的邊緣)等等。請參閱文件以獲取更多資訊。
迴圈層¶
迴圈神經網路(或 RNN)用於處理序列資料——從科學儀器的時序測量到自然語言句子再到 DNA 核苷酸,無所不包。RNN 透過維護一個 隱藏狀態 來實現這一點,隱藏狀態充當對它在序列中迄今為止所見內容的記憶。
RNN 層——或其變體 LSTM(長短期記憶)和 GRU(門控迴圈單元)——的內部結構相當複雜,超出了本影片的範圍,但我們將透過一個基於 LSTM 的詞性標註器(一種分類器,告訴你一個詞是名詞、動詞等等)向你展示它的實際應用。
class LSTMTagger(torch.nn.Module):
def __init__(self, embedding_dim, hidden_dim, vocab_size, tagset_size):
super(LSTMTagger, self).__init__()
self.hidden_dim = hidden_dim
self.word_embeddings = torch.nn.Embedding(vocab_size, embedding_dim)
# The LSTM takes word embeddings as inputs, and outputs hidden states
# with dimensionality hidden_dim.
self.lstm = torch.nn.LSTM(embedding_dim, hidden_dim)
# The linear layer that maps from hidden state space to tag space
self.hidden2tag = torch.nn.Linear(hidden_dim, tagset_size)
def forward(self, sentence):
embeds = self.word_embeddings(sentence)
lstm_out, _ = self.lstm(embeds.view(len(sentence), 1, -1))
tag_space = self.hidden2tag(lstm_out.view(len(sentence), -1))
tag_scores = F.log_softmax(tag_space, dim=1)
return tag_scores
建構函式有四個引數
vocab_size是輸入詞彙表中的單詞數量。每個單詞在vocab_size維空間中是一個 one-hot 向量(或單位向量)。tagset_size是輸出集合中的標籤數量。embedding_dim是詞彙表 嵌入 空間的大小。嵌入將詞彙表對映到低維空間,在該空間中,含義相似的詞彼此靠近。hidden_dim是 LSTM 記憶的大小。
輸入將是句子,單詞表示為 one-hot 向量的索引。然後嵌入層將這些向量對映到 embedding_dim 維空間。LSTM 接收這個嵌入序列並對其進行迭代,生成長度為 hidden_dim 的輸出向量。最後的線性層充當分類器;對最後一層的輸出應用 log_softmax() 會將輸出轉換為一組歸一化的估計機率,表示給定單詞對映到給定標籤的機率。
如果你想看到這個網路的實際應用,請檢視 pytorch.org 上的序列模型和 LSTM 網路教程。
Transformer¶
Transformer 是多用途網路,憑藉 BERT 等模型在自然語言處理領域取得了最先進的成果。Transformer 架構的討論超出了本影片的範圍,但 PyTorch 有一個 Transformer 類,允許你定義 Transformer 模型的整體引數——注意力頭的數量、編碼器和解碼器層的數量、dropout 和啟用函式等。(甚至可以使用這一個類構建 BERT 模型,只需提供正確的引數!)torch.nn.Transformer 類還包含封裝各個元件(TransformerEncoder、TransformerDecoder)和子元件(TransformerEncoderLayer、TransformerDecoderLayer)的類。有關詳細資訊,請查閱 Transformer 類的文件。
其他層和函式¶
資料操作層¶
還有其他層型別在模型中執行重要功能,但不參與學習過程本身。
最大池化(及其孿生兄弟最小池化)透過組合單元格來減小張量,並將輸入單元格的最大值賦給輸出單元格(我們已經看到了這一點)。例如
my_tensor = torch.rand(1, 6, 6)
print(my_tensor)
maxpool_layer = torch.nn.MaxPool2d(3)
print(maxpool_layer(my_tensor))
tensor([[[0.5036, 0.6285, 0.3460, 0.7817, 0.9876, 0.0074],
[0.3969, 0.7950, 0.1449, 0.4110, 0.8216, 0.6235],
[0.2347, 0.3741, 0.4997, 0.9737, 0.1741, 0.4616],
[0.3962, 0.9970, 0.8778, 0.4292, 0.2772, 0.9926],
[0.4406, 0.3624, 0.8960, 0.6484, 0.5544, 0.9501],
[0.2489, 0.8971, 0.7499, 0.1803, 0.9571, 0.6733]]])
tensor([[[0.7950, 0.9876],
[0.9970, 0.9926]]])
如果你仔細觀察上面的值,你會發現最大池化輸出中的每個值都是 6x6 輸入中每個象限的最大值。
歸一化層 在將一層輸出饋送到另一層之前,對其進行重新居中和歸一化。對中間張量進行居中和縮放具有許多有益效果,例如讓你在不出現梯度爆炸/消失的情況下使用更高的學習率。
my_tensor = torch.rand(1, 4, 4) * 20 + 5
print(my_tensor)
print(my_tensor.mean())
norm_layer = torch.nn.BatchNorm1d(4)
normed_tensor = norm_layer(my_tensor)
print(normed_tensor)
print(normed_tensor.mean())
tensor([[[ 7.7375, 23.5649, 6.8452, 16.3517],
[19.5792, 20.3254, 6.1930, 23.7576],
[23.7554, 20.8565, 18.4241, 8.5742],
[22.5100, 15.6154, 13.5698, 11.8411]]])
tensor(16.2188)
tensor([[[-0.8614, 1.4543, -0.9919, 0.3990],
[ 0.3160, 0.4274, -1.6834, 0.9400],
[ 1.0256, 0.5176, 0.0914, -1.6346],
[ 1.6352, -0.0663, -0.5711, -0.9978]]],
grad_fn=<NativeBatchNormBackward0>)
tensor(3.3528e-08, grad_fn=<MeanBackward0>)
執行上面的單元格,我們已經為輸入張量添加了一個較大的縮放因子和偏移量;你應該會看到輸入張量的 mean() 在 15 附近。透過歸一化層執行後,你會看到值變小了,並且聚集在零附近——事實上,均值應該非常小(> 1e-8)。
這是有益的,因為許多啟用函式(下面討論)在接近 0 的地方具有最強的梯度,但有時對於使它們遠離零的輸入會遭受梯度消失或爆炸的問題。將資料保持在最陡峭梯度區域附近,往往意味著更快、更好的學習和更高的可行學習率。
Dropout 層 是一種鼓勵模型中 稀疏表示 的工具——也就是說,推動它使用更少的資料進行推斷。
Dropout 層透過在 訓練期間 隨機設定輸入張量的部分來工作——Dropout 層在推理時總是關閉的。這迫使模型針對這個掩蔽或縮減的資料集進行學習。例如
my_tensor = torch.rand(1, 4, 4)
dropout = torch.nn.Dropout(p=0.4)
print(dropout(my_tensor))
print(dropout(my_tensor))
tensor([[[0.0000, 0.0000, 0.0000, 0.2878],
[0.0000, 0.6824, 0.0000, 0.5920],
[0.0000, 0.0000, 1.3319, 0.5738],
[0.5676, 0.8335, 0.9647, 0.2928]]])
tensor([[[0.0000, 0.0000, 0.2098, 0.0000],
[0.0000, 0.6824, 0.0000, 0.0000],
[0.0000, 0.0000, 0.0000, 0.5738],
[0.0000, 0.8335, 0.0000, 0.2928]]])
上面,你可以看到 dropout 對示例張量的影響。你可以使用可選的 p 引數設定單個權重 dropout 的機率;如果不設定,預設為 0.5。
啟用函式¶
啟用函式使深度學習成為可能。神經網路實際上是一個程式——具有許多引數——它 模擬數學函式。如果所有我們做的就是重複地用層權重乘以張量,我們只能模擬 線性函式; 此外,擁有許多層就沒有意義了,因為整個網路可以簡化為單次矩陣乘法。在層之間插入 非線性 啟用函式是深度學習模型能夠模擬任何函式,而不僅僅是線性函式的原因。
torch.nn.Module 包含了封裝所有主要啟用函式(包括 ReLU 及其許多變體、Tanh、Hardtanh、sigmoid 等)的物件。它還包括其他函式,例如 Softmax,這些函式在模型的輸出階段最有用。
損失函式¶
損失函式告訴我們模型的預測與正確答案相差多遠。PyTorch 包含多種損失函式,包括常見的 MSE(均方誤差 = L2 範數)、交叉熵損失和負對數似然損失(對分類器有用)等。
指令碼總執行時間: ( 0 分鐘 0.021 秒)