評價此頁

簡介 || 張量 || 自動微分 || 構建模型 || TensorBoard 支援 || 訓練模型 || 模型理解

使用 PyTorch 構建模型#

創建於:2021年11月30日 | 最後更新於:2024年10月15日 | 最後驗證於:2024年11月05日

請跟隨下面的影片或者在youtube上觀看。

torch.nn.Moduletorch.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.0540,  0.0889, -0.0109,  ..., -0.0959,  0.0421,  0.0948],
        [-0.0192,  0.0616,  0.0658,  ...,  0.0874, -0.0139,  0.0918],
        [ 0.0931,  0.0411,  0.0915,  ..., -0.0565,  0.0179, -0.0704],
        ...,
        [-0.0601, -0.0116,  0.0308,  ..., -0.0949, -0.0367,  0.0736],
        [ 0.0848, -0.0630, -0.0730,  ..., -0.0832, -0.0086, -0.0087],
        [ 0.0875,  0.0732, -0.0594,  ...,  0.0169,  0.0162,  0.0542]],
       requires_grad=True)
Parameter containing:
tensor([-2.8391e-02,  8.8786e-02, -6.4435e-03,  1.9568e-02,  6.6545e-02,
        -3.8073e-02,  4.0056e-02,  9.8252e-02,  6.0742e-02,  2.6323e-02,
        -6.3688e-02,  9.5054e-02,  8.1455e-02,  2.7224e-03,  2.7485e-02,
        -5.3290e-02,  8.9486e-02, -3.0375e-02, -1.6629e-02, -9.4276e-02,
         6.3886e-02, -1.7389e-02,  1.6478e-03, -6.8702e-02, -2.5034e-02,
        -2.9890e-02,  1.2130e-02,  7.0402e-02, -2.6131e-02,  3.0848e-02,
        -2.3914e-03, -6.8471e-02, -1.6653e-02,  3.0541e-02,  7.3755e-02,
        -4.1249e-02,  9.4892e-02, -9.2014e-02, -9.5326e-02,  6.7583e-03,
        -4.8404e-02,  7.3692e-02, -9.5953e-03,  2.0520e-02,  9.6995e-02,
        -9.6371e-02, -9.3585e-02,  8.1368e-02,  6.1899e-02, -1.9492e-03,
        -2.7659e-02, -2.4900e-03,  1.0500e-02, -8.0740e-02, -6.1757e-02,
         7.2164e-02,  6.2586e-02, -7.9982e-02, -5.4769e-02, -4.9737e-02,
        -6.4661e-02,  4.1963e-02, -8.7076e-02, -5.0482e-03, -3.0410e-05,
         8.8162e-02, -5.6084e-02,  9.3488e-02,  8.9329e-02,  1.5383e-02,
        -5.5996e-03, -9.7878e-02,  8.8348e-02, -7.0886e-02,  5.7076e-02,
         8.5237e-02,  6.7058e-02, -4.5111e-02,  3.6577e-02, -8.0919e-02,
        -2.8820e-02,  6.7889e-02,  1.8501e-02, -8.4626e-02,  1.0139e-02,
        -5.2166e-02,  8.8196e-03,  3.7661e-02,  3.5405e-02, -5.7670e-02,
        -3.9214e-02,  9.2920e-02,  9.1581e-02,  9.5697e-02, -6.1620e-02,
        -9.0639e-02, -2.7645e-02,  5.5318e-02,  5.2429e-02,  4.9890e-02,
        -8.5084e-02, -6.8121e-04,  1.6863e-02, -5.6012e-03, -9.4513e-02,
         4.7324e-02, -1.6331e-03, -5.7407e-03, -4.8910e-02,  2.7390e-02,
        -2.9120e-02,  5.2268e-02,  7.9739e-03,  5.9733e-02,  1.4329e-02,
         5.4806e-02, -9.2461e-02, -4.2292e-02,  7.1391e-02, -9.3267e-03,
         2.5865e-02, -3.2159e-02, -3.5534e-02,  4.5665e-03,  4.3144e-03,
         1.6937e-02, -6.3085e-03,  4.5387e-03, -8.1251e-02,  2.7151e-02,
        -9.3098e-02, -3.0626e-02, -1.6267e-02,  6.1479e-02,  9.2800e-02,
         4.5886e-02,  7.1244e-02, -6.4789e-02, -9.4300e-02, -8.9892e-02,
        -9.6265e-02,  5.7603e-02,  2.7417e-02, -9.3216e-02, -2.9369e-02,
        -9.0568e-02,  5.2199e-02, -5.3580e-02,  5.1615e-02, -6.1951e-02,
         1.7894e-02, -7.9597e-02, -3.8138e-02, -2.8243e-02,  2.8240e-03,
        -6.0696e-02,  4.4213e-02, -4.6199e-02,  6.5946e-02,  1.4723e-02,
         8.3900e-02,  8.1386e-02,  1.3186e-02, -3.9898e-02, -8.6006e-02,
         8.7549e-02, -7.3356e-02,  7.0558e-02,  1.7812e-02,  6.3452e-02,
        -6.6243e-02, -7.6435e-02,  5.1467e-02,  7.3187e-03, -4.1000e-02,
         9.1473e-03, -4.3123e-02,  4.6625e-02, -3.0680e-02,  2.0004e-02,
        -3.2730e-02,  7.6111e-03,  5.6459e-02, -5.9493e-02, -6.5789e-02,
         8.8485e-02, -5.5954e-03,  3.0834e-02, -1.7522e-02,  8.6342e-02,
        -8.5151e-02, -9.9866e-02, -2.2536e-02,  5.8566e-02, -7.6556e-02,
         9.1213e-02,  9.7890e-02, -2.7655e-02, -2.7763e-02,  8.5908e-02],
       requires_grad=True)
Parameter containing:
tensor([[ 0.0287, -0.0437, -0.0418,  ...,  0.0395,  0.0280, -0.0323],
        [-0.0242,  0.0524, -0.0388,  ..., -0.0188,  0.0374, -0.0056],
        [-0.0486,  0.0385, -0.0122,  ...,  0.0675,  0.0428, -0.0242],
        ...,
        [-0.0644, -0.0628, -0.0046,  ..., -0.0388,  0.0258,  0.0546],
        [ 0.0386,  0.0101,  0.0022,  ...,  0.0001, -0.0164, -0.0397],
        [ 0.0271,  0.0234,  0.0067,  ..., -0.0335, -0.0107,  0.0539]],
       requires_grad=True)
Parameter containing:
tensor([ 0.0093, -0.0178, -0.0259,  0.0465, -0.0456,  0.0262, -0.0185, -0.0208,
        -0.0189, -0.0548], requires_grad=True)


Layer params:
Parameter containing:
tensor([[ 0.0287, -0.0437, -0.0418,  ...,  0.0395,  0.0280, -0.0323],
        [-0.0242,  0.0524, -0.0388,  ..., -0.0188,  0.0374, -0.0056],
        [-0.0486,  0.0385, -0.0122,  ...,  0.0675,  0.0428, -0.0242],
        ...,
        [-0.0644, -0.0628, -0.0046,  ..., -0.0388,  0.0258,  0.0546],
        [ 0.0386,  0.0101,  0.0022,  ...,  0.0001, -0.0164, -0.0397],
        [ 0.0271,  0.0234,  0.0067,  ..., -0.0335, -0.0107,  0.0539]],
       requires_grad=True)
Parameter containing:
tensor([ 0.0093, -0.0178, -0.0259,  0.0465, -0.0456,  0.0262, -0.0185, -0.0208,
        -0.0189, -0.0548], 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.3388, 0.3983, 0.9729]])


Weight and Bias parameters:
Parameter containing:
tensor([[-0.0160, -0.2901,  0.5313],
        [-0.3114, -0.5067, -0.1549]], requires_grad=True)
Parameter containing:
tensor([-0.2716,  0.1484], requires_grad=True)


Output:
tensor([[ 0.1244, -0.3096]], grad_fn=<AddmmBackward0>)

如果你將 x 與線性層的權重進行矩陣乘法,並加上偏置項,你會發現你得到了輸出向量 y

還有另一個重要特性值得注意:當我們使用 lin.weight 檢查我們層的權重時,它將自己報告為 Parameter(它是 Tensor 的子類),並告知我們它正在透過自動微分跟蹤梯度。這是 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 是這樣來的:當一個 5 畫素的視窗掃描一個 32 畫素的行時,只有 28 個有效位置。)

然後我們透過 ReLU 啟用函式(稍後會詳細介紹啟用函式)將卷積的輸出傳遞,然後透過一個最大池化層。最大池化層獲取啟用圖上彼此靠近的特徵並將它們分組。它透過減小張量來做到這一點,將輸出中的每 2x2 個單元格組合成一個單元格,併為該單元格分配這 4 個單元格的最大值。這使我們得到一個較低解析度的啟用圖,尺寸為 6x14x14。

我們的下一個卷積層,conv2,期望 6 個輸入通道(對應於第一個層所尋找的 6 個特徵),有 16 個輸出通道,以及一個 3x3 的卷積核。它輸出一個 16x12x12 的啟用圖,該啟用圖再次透過最大池化層減小到 16x6x6。在將此輸出傳遞給線性層之前,它被重塑為一個 16 * 6 * 6 = 576 元素的向量,供下一層使用。

有用於處理 1D、2D 和 3D 張量的卷積層。卷積層建構函式還有許多可選引數,包括步長(例如,僅每隔一個或每隔兩個位置掃描)和填充(這樣你就可以掃描到輸入的邊緣),以及更多。有關更多資訊,請參閱文件

迴圈層#

迴圈神經網路(或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 維空間中的獨熱向量(或單位向量)。

  • tagset_size 是輸出集合中標籤的數量。

  • embedding_dim 是詞彙表嵌入空間的大小。嵌入將詞彙表對映到一個低維空間,其中意義相似的詞在這個空間中彼此靠近。

  • hidden_dim 是 LSTM 記憶體的大小。

輸入將是一個句子,單詞表示為獨熱向量的索引。嵌入層然後將這些對映到 embedding_dim 維空間。LSTM 接收這個嵌入序列並迭代它,輸出一個長度為 hidden_dim 的向量。最後的線性層充當一個分類器;將最後一個層的輸出應用 log_softmax() 可以將輸出轉換為一個標準化的估計機率集合,表示給定單詞對映到一個給定標籤。

如果你想看這個網路是如何工作的,請檢視 pytorch.org 上的序列模型和 LSTM 網路教程。

Transformer#

Transformer 是多用途網路,它們憑藉 BERT 等模型在 NLP 領域取得了最先進的成果。Transformer 架構的討論超出了本影片的範圍,但 PyTorch 有一個 Transformer 類,允許你定義 Transformer 模型的主要引數 - 注意力頭的數量、編碼器和解碼器層的數量、dropout 和啟用函式等。(你甚至可以用這個單個類構建 BERT 模型,只需設定正確的引數!)torch.nn.Transformer 類還有用於封裝各個元件(TransformerEncoderTransformerDecoder)和子元件(TransformerEncoderLayerTransformerDecoderLayer)的類。有關詳細資訊,請參閱關於 Transformer 類的文件

其他層和函式#

資料操作層#

還有其他層型別在模型中執行重要功能,但它們本身不參與學習過程。

最大池化(及其孿生體最小池化)透過組合單元格來減小張量,並將輸入單元格的最大值分配給輸出單元格(我們已經看到過)。例如

tensor([[[0.1425, 0.6701, 0.5321, 0.2460, 0.6473, 0.1689],
         [0.9504, 0.0171, 0.7118, 0.5366, 0.1898, 0.1828],
         [0.8439, 0.3830, 0.2629, 0.8551, 0.6914, 0.0509],
         [0.9571, 0.1262, 0.3355, 0.1401, 0.2683, 0.0348],
         [0.4683, 0.0492, 0.4513, 0.9115, 0.1125, 0.4711],
         [0.8113, 0.8980, 0.6683, 0.7515, 0.9382, 0.4620]]])
tensor([[[0.9504, 0.8551],
         [0.9571, 0.9382]]])

如果你仔細檢視上面的值,你會發現 maxpooled 輸出中的每個值都是 6x6 輸入每個象限的最大值。

歸一化層在將一個層的輸出饋送到另一個層之前對其進行重新居中和歸一化。對中間張量進行居中和縮放有許多有益的效果,例如允許你使用更高的學習率而不會出現梯度爆炸/消失。

tensor([[[16.3621, 19.9235,  5.9900,  6.7841],
         [13.5389, 14.9176, 23.1250, 18.2477],
         [ 6.7600,  9.0198,  6.0819, 12.0730],
         [ 5.8679, 24.6508, 13.8068, 18.3923]]])
tensor(13.4713)
tensor([[[ 0.6808,  1.2727, -1.0427, -0.9108],
         [-1.0611, -0.6877,  1.5347,  0.2140],
         [-0.7365,  0.2291, -1.0262,  1.5336],
         [-1.4326,  1.3099, -0.2734,  0.3961]]],
       grad_fn=<NativeBatchNormBackward0>)
tensor(6.7055e-08, grad_fn=<MeanBackward0>)

執行上面的單元格,我們給輸入張量添加了一個大的縮放因子和偏移量;你應該看到輸入張量的 mean() 大約在 15 附近。在透過歸一化層執行後,你可以看到值變小了,並且圍繞零聚集 - 實際上,均值應該非常小(> 1e-8)。

這是有益的,因為許多啟用函式(下面將討論)在接近 0 的地方具有最強的梯度,但有時對於驅動它們遠離零的輸入,會受到梯度消失或爆炸的影響。將資料保持在最陡梯度區域周圍,通常意味著更快的、更好的學習和更高的可行學習率。

Dropout 層是一種鼓勵模型中稀疏表示的工具 - 也就是說,迫使模型使用更少的資料進行推理。

Dropout 層的工作方式是在訓練期間隨機設定輸入張量的部分 - Dropout 層在推理時始終關閉。這迫使模型在這種掩碼或縮減的資料集上進行學習。例如

tensor([[[1.2432, 0.5842, 0.0000, 0.0000],
         [1.0097, 0.0000, 0.9396, 1.2765],
         [0.0000, 0.0000, 0.0000, 0.3423],
         [0.0000, 0.0000, 0.0000, 1.1063]]])
tensor([[[1.2432, 0.5842, 1.0750, 0.0640],
         [1.0097, 0.0000, 0.0000, 0.0000],
         [0.0000, 0.5611, 0.0567, 0.3423],
         [0.0000, 1.0410, 0.0000, 0.0000]]])

上面,你可以看到 dropout 對樣本張量的影響。你可以使用可選的 p 引數來設定單個權重丟失的機率;如果你不設定,它預設為 0.5。

啟用函式#

啟用函式使深度學習成為可能。神經網路實際上是一個程式 - 帶有許多引數 - 它模擬一個數學函式。如果我們只是一遍又一遍地將張量乘以層權重,我們只能模擬線性函式;此外,擁有多個層就沒有意義了,因為整個網路可以簡化為一次矩陣乘法。在層之間插入非線性啟用函式,可以使深度學習模型模擬任何函式,而不僅僅是線性函式。

torch.nn.Module 包含封裝所有主要啟用函式的物件,包括 ReLU 及其許多變體、Tanh、Hardtanh、sigmoid 等。它還包括其他函式,例如 Softmax,這些函式在模型的輸出階段最有用。

損失函式#

損失函式告訴我們模型預測與正確答案之間的差距。PyTorch 包含各種損失函式,包括常見的 MSE(均方誤差 = L2 範數)、交叉熵損失和負對數似然損失(對分類器有用)等。

指令碼總執行時間: (0 分鐘 0.035 秒)