评价此页

简介 || 张量 || 自动微分 || 构建模型 || 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 秒)