Shortcuts

简介 || 张量 || Autograd || 构建模型 || TensorBoard支持 || 训练模型 || 模型理解

使用PyTorch构建模型

Created On: Nov 30, 2021 | Last Updated: Oct 15, 2024 | Last Verified: Nov 05, 2024

按照以下视频或在`YouTube <https://www.youtube.com/watch?v=OSqIP-mOWOI>`__上学习。

torch.nn.Moduletorch.nn.Parameter

在这段视频中,我们将讨论PyTorch提供的一些用于构建深度学习网络的工具。

除了``Parameter``外,在视频中讨论的类都是``torch.nn.Module``的子类。这是一个PyTorch基类,用于封装PyTorch模型及其组件的特定行为。

``torch.nn.Module``的重要行为之一是注册参数。如果一个特定``Module``子类有学习权重,这些权重将以``torch.nn.Parameter``实例的形式表达。``Parameter``类是``torch.Tensor``的子类,具有特殊行为:当它们被分配为``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.0754, -0.0260, -0.0680,  ...,  0.0112,  0.0585,  0.0605],
        [ 0.0465, -0.0836, -0.0761,  ..., -0.0265,  0.0487, -0.0196],
        [-0.0470,  0.0887,  0.0489,  ..., -0.0659, -0.0635,  0.0243],
        ...,
        [-0.0940, -0.0101,  0.0879,  ..., -0.0223,  0.0540, -0.0323],
        [-0.0004,  0.0558,  0.0434,  ...,  0.0686,  0.0249,  0.0857],
        [-0.0143, -0.0903,  0.0267,  ...,  0.0203,  0.0928, -0.0193]],
       requires_grad=True)
Parameter containing:
tensor([ 0.0034, -0.0795,  0.0841, -0.0040, -0.0421,  0.0144,  0.0526,  0.0692,
         0.0579,  0.0194,  0.0359, -0.0268, -0.0277, -0.0990,  0.0293,  0.0783,
        -0.0060, -0.0792, -0.0398,  0.0847, -0.0031,  0.0052,  0.0107, -0.0024,
        -0.0235, -0.0219,  0.0832,  0.0355,  0.0602, -0.0553, -0.0055, -0.0333,
         0.0673, -0.0866, -0.0785,  0.0697,  0.0520,  0.0678, -0.0426, -0.0207,
        -0.0155,  0.0215,  0.0423,  0.0467, -0.0113,  0.0809,  0.0439,  0.0523,
        -0.0758, -0.0708, -0.0289,  0.0880, -0.0172, -0.0770,  0.0336, -0.0162,
        -0.0131, -0.0231, -0.0891,  0.0176,  0.0735, -0.0410, -0.0916,  0.0024,
         0.0166, -0.0619, -0.0997, -0.0403, -0.0609, -0.0532,  0.0707, -0.0546,
         0.0619,  0.0774,  0.0814,  0.0956, -0.0826, -0.0731, -0.0359,  0.0088,
        -0.0292,  0.0952,  0.0737, -0.0263, -0.0239,  0.0401, -0.0811, -0.0135,
         0.0820,  0.0442,  0.0363, -0.0820, -0.0102,  0.0630, -0.0101,  0.0515,
        -0.0111, -0.0303,  0.0944,  0.0845, -0.0947, -0.0334,  0.0626, -0.0438,
         0.0944,  0.0889, -0.0445,  0.0051, -0.0897, -0.0526,  0.0943,  0.0533,
        -0.0448, -0.0502, -0.0767, -0.0349,  0.0894,  0.0640, -0.0331,  0.0720,
        -0.0693,  0.0908,  0.0159,  0.0909,  0.0091,  0.0384, -0.0241,  0.0742,
        -0.0511,  0.0092, -0.0477,  0.0139,  0.0103, -0.0334, -0.0405,  0.0648,
         0.0796, -0.0451,  0.0266, -0.0016, -0.0747, -0.0822, -0.0890, -0.0067,
         0.0182, -0.0705,  0.0386, -0.0670, -0.0664, -0.0656, -0.0885, -0.0706,
        -0.0416, -0.0066,  0.0132, -0.0477, -0.0310,  0.0592,  0.0436, -0.0008,
        -0.0487, -0.0904,  0.0722, -0.0199, -0.0222,  0.0326,  0.0610,  0.0947,
        -0.0904, -0.0902,  0.0333, -0.0198, -0.0070,  0.0456,  0.0989,  0.0290,
         0.0473, -0.0389, -0.0374, -0.0148, -0.0970,  0.0059, -0.0468, -0.0795,
        -0.0287,  0.0425, -0.0120,  0.0445,  0.0793, -0.0949,  0.0508, -0.0737,
         0.0319,  0.0610,  0.0762,  0.0533, -0.0183, -0.0955,  0.0204,  0.0579],
       requires_grad=True)
Parameter containing:
tensor([[ 0.0528,  0.0093, -0.0454,  ...,  0.0501,  0.0140, -0.0076],
        [ 0.0107,  0.0555, -0.0466,  ..., -0.0263,  0.0295,  0.0478],
        [-0.0157, -0.0654, -0.0168,  ..., -0.0163,  0.0209,  0.0033],
        ...,
        [ 0.0012, -0.0508,  0.0189,  ...,  0.0121, -0.0413, -0.0690],
        [-0.0304,  0.0075,  0.0027,  ..., -0.0158,  0.0483, -0.0156],
        [ 0.0486,  0.0021, -0.0505,  ...,  0.0594,  0.0689,  0.0534]],
       requires_grad=True)
Parameter containing:
tensor([ 0.0404,  0.0616, -0.0645, -0.0092,  0.0600, -0.0520,  0.0380,  0.0333,
        -0.0300, -0.0060], requires_grad=True)


Layer params:
Parameter containing:
tensor([[ 0.0528,  0.0093, -0.0454,  ...,  0.0501,  0.0140, -0.0076],
        [ 0.0107,  0.0555, -0.0466,  ..., -0.0263,  0.0295,  0.0478],
        [-0.0157, -0.0654, -0.0168,  ..., -0.0163,  0.0209,  0.0033],
        ...,
        [ 0.0012, -0.0508,  0.0189,  ...,  0.0121, -0.0413, -0.0690],
        [-0.0304,  0.0075,  0.0027,  ..., -0.0158,  0.0483, -0.0156],
        [ 0.0486,  0.0021, -0.0505,  ...,  0.0594,  0.0689,  0.0534]],
       requires_grad=True)
Parameter containing:
tensor([ 0.0404,  0.0616, -0.0645, -0.0092,  0.0600, -0.0520,  0.0380,  0.0333,
        -0.0300, -0.0060], 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.8282, 0.6515, 0.7522]])


Weight and Bias parameters:
Parameter containing:
tensor([[ 0.2365,  0.2305, -0.5484],
        [ 0.5066, -0.4887, -0.0207]], requires_grad=True)
Parameter containing:
tensor([-0.1213,  0.4951], requires_grad=True)


Output:
tensor([[-0.1877,  0.5807]], grad_fn=<AddmmBackward0>)

如果你对``x``与线性层的权重执行矩阵乘法,并加上偏置,你会发现得到的是输出向量``y``。

另一个需要注意的重要特性是:当我们通过``lin.weight``检查层的权重时,它将其自身报告为一个``Parameter``(这是``Tensor``的子类),并告诉我们它正在使用自动微分跟踪梯度。这是``Parameter``的默认行为,和``Tensor``不同。

线性层在深度学习模型中被广泛使用。最常见的地方之一是分类器模型,其中通常在最后会有一个或多个线性层,最后一层会有*n*个输出,其中*n*是分类器处理的类别数量。

卷积层

*卷积层*专为处理具有高级空间相关性的数据而设计。它们非常常见于计算机视觉领域,用于检测特征的局部分组并将其组合为更高级的特征。它们也会出现在其他领域——例如,自然语言处理(NLP)应用中,其中一个单词的上下文(即在序列中附近的其他单词)会影响句子的含义。

我们在之前的视频中看到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元素向量以供下一个层使用。

针对1D、2D和3D张量都有卷积层可用。构造函数还有许多可选参数,包括步幅(例如,仅每隔第二个或每第三个位置扫描输入)、填充(使你可以扫描到输入的边缘),等等。有关更多信息,请参阅`文档 <https://pytorch.org/docs/stable/nn.html#convolution-layers>`__。

循环层

*循环神经网络(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网络教程 <https://pytorch.org/tutorials/beginner/nlp/sequence_models_tutorial.html>`__。

Transformers(变换器)

*Transformers*是多用途网络,随着像BERT这样的模型,它们已成为NLP领域最新的技术标准。关于变换器架构的讨论超出了此视频范围,但PyTorch有一个``Transformer``类,可以定义变换器模型的总体参数——包括注意力头的数量、编码器和解码器层数、dropout和激活函数等。(使用正确的参数,甚至可以从此单分类构建BERT模型!)``torch.nn.Transformer``类还封装了子组件(如``TransformerEncoder``、TransformerDecoder``以及``TransformerEncoderLayerTransformerDecoderLayer)。详情请参阅变换器类的`文档 <https://pytorch.org/docs/stable/nn.html#transformer-layers>`__。

其他层和函数

数据操作层

还有一些层类型执行模型中的重要功能,但它们本身不参与学习过程。

**最大池化**(以及它的孪生兄弟,最小池化)通过组合输入单元格来减少张量,并将输入单元格的最大值赋给输出单元格(我们已经看到过这一点)。例如:

tensor([[[0.4246, 0.9950, 0.1673, 0.8358, 0.0582, 0.1874],
         [0.8761, 0.1085, 0.9898, 0.5233, 0.6777, 0.7067],
         [0.7278, 0.0982, 0.0856, 0.7291, 0.3509, 0.6735],
         [0.6529, 0.7919, 0.8538, 0.9308, 0.5194, 0.5882],
         [0.5002, 0.4345, 0.9487, 0.1001, 0.7517, 0.5321],
         [0.2296, 0.2472, 0.4831, 0.3386, 0.3483, 0.5019]]])
tensor([[[0.9950, 0.8358],
         [0.9487, 0.9308]]])

仔细观察上述的数值,你会发现max pooled结果中的每个值都是6x6输入中每个象限的最大值。

**归一化层**在将一个层的输出传递给另一个层之前重新中心化并归一化操作。中心化和缩放中间张量具有一些有益效果,例如允许使用更高的学习率而不会出现梯度爆炸或梯度消失问题。

tensor([[[ 5.8610, 23.8407, 21.0855, 15.3688],
         [ 6.7692, 10.0197, 16.6761, 13.7145],
         [17.3777, 23.0578, 17.8931,  7.3597],
         [11.1264, 20.6643, 20.9893, 13.8442]]])
tensor(15.3530)
tensor([[[-1.5519,  1.0612,  0.6608, -0.1701],
         [-1.3441, -0.4748,  1.3055,  0.5134],
         [ 0.1681,  1.1674,  0.2588, -1.5943],
         [-1.2915,  0.9362,  1.0121, -0.6567]]],
       grad_fn=<NativeBatchNormBackward0>)
tensor(-5.9605e-08, grad_fn=<MeanBackward0>)

运行上述单元格时,我们已经向输入张量添加了一个大的缩放因子和偏移量;你应该看到输入张量的``mean()``大约接近15。通过归一化层后,可以看到值变小并集中在零附近——实际上,平均值应该非常小(> 1e-8)。

这是有益的,因为许多激活函数(将在下方讨论)在0附近拥有最强的梯度,但有时在输入值使它们远离零时会出现梯度消失或梯度爆炸问题。让数据保持在梯度最陡区附近通常意味着更快速、更好的学习以及更高的可行学习率。

**Dropout层**是一种鼓励模型中*稀疏表示*的工具——即推动它使用更少的数据进行推理。

Dropout层通过在训练期间随机设置部分输入张量实现——训练时使用dropout层,在推理时总是关闭它。这迫使模型基于被屏蔽或减少的数据集学习。例如:

tensor([[[0.0000, 0.0000, 0.5726, 0.0000],
         [1.5877, 0.4433, 1.2624, 0.7835],
         [0.0000, 1.5222, 1.3698, 0.2493],
         [0.9970, 0.0000, 1.3737, 0.0000]]])
tensor([[[0.0000, 0.0861, 0.5726, 1.6432],
         [0.0000, 0.0000, 1.2624, 0.7835],
         [0.0000, 1.5222, 1.3698, 0.2493],
         [0.9970, 0.0000, 1.3737, 0.4865]]])

上面展示了dropout对样本张量的影响。你可以使用可选的``p``参数设置单个权重丢弃的概率;如果不设置,它默认为0.5。

激活函数

激活函数使深度学习成为可能。神经网络实际上是一个程序——含有许多参数——其目的是*模拟数学函数*。如果我们只是重复通过层权重乘以张量,我们只能模拟*线性函数*;此外,拥有多个层也没有意义,因为整个网络可以简化为单个矩阵乘法。通过在层之间插入*非线性*激活函数,这使得深度学习模型可以模拟任何函数,而不仅仅是线性函数。

``torch.nn.Module``提供了封装所有主要激活函数的对象,包括ReLU及其许多变体、Tanh、Hardtanh、sigmoid以及更多函数。它还包括其他在模型输出阶段最有用的函数,比如Softmax。

损失函数

损失函数表明模型的预测与正确答案之间的偏差程度。PyTorch提供了多种损失函数,包括常用的MSE(均方误差 = L2范数)、交叉熵损失和负似然损失(对于分类器非常有用)以及其他函数。

**脚本总运行时间:**(0分钟 0.024秒)

画廊由 Sphinx-Gallery 生成

文档

访问 PyTorch 的详细开发者文档

查看文档

教程

获取针对初学者和高级开发人员的深入教程

查看教程

资源

查找开发资源并获得问题的解答

查看资源