pytorch 构建网络模型

1,502 阅读10分钟

已经更文1天


这篇文章将讨论 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.0733,  0.0827, -0.0799,  ..., -0.0047, -0.0145,  0.0811],
        [ 0.0373, -0.0382,  0.0251,  ...,  0.0102,  0.0958,  0.0954],
        [-0.0392, -0.0872, -0.0124,  ...,  0.0036, -0.0573,  0.0866],
        ...,
        [ 0.0207,  0.0762,  0.0568,  ...,  0.0358, -0.0502, -0.0854],
        [ 0.0110, -0.0783,  0.0798,  ..., -0.0674,  0.0707, -0.0573],
        [ 0.0931, -0.0836,  0.0651,  ...,  0.0504, -0.0510,  0.0326]],
       requires_grad=True)
Parameter containing:
tensor([-0.0784, -0.0219,  0.0615, -0.0429, -0.0886,  0.0956,  0.0486, -0.0246,
         0.0512, -0.0268, -0.0912, -0.0465,  0.0444, -0.0423,  0.0422, -0.0921,
         0.0066,  0.0730, -0.0635, -0.0871, -0.0754, -0.0775, -0.0731, -0.0203,
        -0.0397, -0.0839,  0.0873, -0.0658,  0.0592, -0.0129, -0.0767,  0.0479,
        -0.0548,  0.0883,  0.0948,  0.0110, -0.0411, -0.0999,  0.0783, -0.0062,
        -0.0588, -0.0235,  0.0512, -0.0002, -0.0120,  0.0468,  0.0920,  0.0214,
        -0.0722,  0.0564, -0.0611, -0.0322, -0.0749,  0.0029,  0.0574, -0.0806,
        -0.0831, -0.0968,  0.0755,  0.0752, -0.0398,  0.0976, -0.0182,  0.0774,
         0.0030,  0.0184,  0.0268, -0.0792, -0.0364, -0.0514,  0.0227, -0.0071,
        -0.0002, -0.0400,  0.0919,  0.0554, -0.0787, -0.0419, -0.0679, -0.0075,
        -0.0120,  0.0021, -0.0516,  0.0076, -0.0032,  0.0415, -0.0927, -0.0131,
        -0.0042, -0.0563,  0.0307, -0.0025, -0.0410,  0.0884,  0.0866,  0.0249,
        -0.0360,  0.0714, -0.0620, -0.0024,  0.0319, -0.0445,  0.0133,  0.0078,
         0.0346,  0.0903,  0.0287,  0.0506,  0.0180,  0.0383, -0.0708,  0.0298,
         0.0389,  0.0357,  0.0103,  0.0987,  0.0614, -0.0238,  0.0183,  0.0031,
        -0.0548, -0.0871,  0.0484, -0.0518,  0.0217, -0.0933,  0.0634, -0.0518,
        -0.0686,  0.0231,  0.0498, -0.0048,  0.0483,  0.0695, -0.0864, -0.0824,
        -0.0234,  0.0821, -0.0191, -0.0168,  0.0966,  0.0154,  0.0955, -0.0199,
        -0.0960, -0.0597,  0.0455, -0.0581, -0.0454, -0.0339, -0.0713,  0.0073,
        -0.0835,  0.0376, -0.0388,  0.0281, -0.0472,  0.0056, -0.0882, -0.0979,
        -0.0335, -0.0647,  0.0493, -0.0610, -0.0222, -0.0009, -0.0484, -0.0383,
        -0.0771, -0.0819,  0.0683, -0.0069, -0.0457, -0.0922,  0.0701, -0.0423,
         0.0887,  0.0354,  0.0126, -0.0216, -0.0935,  0.0550, -0.0288, -0.0348,
        -0.0170,  0.0856, -0.0243, -0.0235,  0.0155,  0.0032,  0.0180, -0.0363,
        -0.0113,  0.0317,  0.0878,  0.0536, -0.0212,  0.0306, -0.0298, -0.0506],
       requires_grad=True)
Parameter containing:
tensor([[ 0.0138,  0.0417,  0.0612,  ...,  0.0586, -0.0330,  0.0200],
        [-0.0003,  0.0521, -0.0482,  ...,  0.0545,  0.0045, -0.0574],
        [ 0.0238, -0.0396,  0.0244,  ..., -0.0126, -0.0124, -0.0655],
        ...,
        [ 0.0689, -0.0115, -0.0432,  ...,  0.0108, -0.0140, -0.0270],
        [-0.0198,  0.0386, -0.0223,  ..., -0.0491, -0.0279,  0.0223],
        [ 0.0532,  0.0536, -0.0224,  ...,  0.0574, -0.0182, -0.0555]],
       requires_grad=True)
Parameter containing:
tensor([ 0.0021, -0.0652, -0.0565,  0.0619,  0.0334,  0.0004,  0.0488, -0.0698,
         0.0265,  0.0037], requires_grad=True)


Layer params:
Parameter containing:
tensor([[ 0.0138,  0.0417,  0.0612,  ...,  0.0586, -0.0330,  0.0200],
        [-0.0003,  0.0521, -0.0482,  ...,  0.0545,  0.0045, -0.0574],
        [ 0.0238, -0.0396,  0.0244,  ..., -0.0126, -0.0124, -0.0655],
        ...,
        [ 0.0689, -0.0115, -0.0432,  ...,  0.0108, -0.0140, -0.0270],
        [-0.0198,  0.0386, -0.0223,  ..., -0.0491, -0.0279,  0.0223],
        [ 0.0532,  0.0536, -0.0224,  ...,  0.0574, -0.0182, -0.0555]],
       requires_grad=True)
Parameter containing:
tensor([ 0.0021, -0.0652, -0.0565,  0.0619,  0.0334,  0.0004,  0.0488, -0.0698,
         0.0265,  0.0037], requires_grad=True)

上边显示了 PyTorch 模型的基本结构:有一个 __init__()定义模型的层和其他组件,以及一个完成前向计算的forward()方法。可以打印模型或其任何子模块,以了解其结构。

1. 常见的图层类型

1.1 线性层 Linear Layers

最基本的神经网络层类型是线性全连接层。这两种层其中每个输入都会影响层的每个输出,其程度由层的权重指定。如果一个模型有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.4918, 0.3778, 0.1115]])


Weight and Bias parameters:
Parameter containing:
tensor([[-0.5406, -0.1404, -0.3121],
        [ 0.3023,  0.5478,  0.3452]], requires_grad=True)
Parameter containing:
tensor([-0.1040, -0.1175], requires_grad=True)


Output:
tensor([[-0.4577,  0.2766]], grad_fn=<AddmmBackward0>)

用输入x和线性层的权重做矩阵乘法,并加上偏差,这样就得到了输出向量 y

另一个需要注意的重要特性:当我们用lin.weight来检查层的权重时,打印结果为 Parameter(它是Tensor的子类),并让知道它正在用 autograd 跟踪梯度。这是ParameterTensor默认不相同的地方。

线性层广泛用于深度学习模型。它们的最常见的地方之一是分类器模型,通常在最后有一个或多个线性层,其中最后一层将有n 个输出,其中n是分类器的类数。

1.2 卷积层 Convolutional Layers

卷积层用于处理具有高度空间相关性的数据。在计算机视觉中非常常用,它们检测临近的特征分组,这些特征组合成更高级别的特征。它们也出现在其他上下文中——例如,在 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。
  • 卷积层就像一个滑动窗口,寻找它识别的模式。这些模式称为 特征, 卷积层的参数之一是希望它学习的特征数量。这是构造函数的第二个参数是输出特征的数量。 在这里,要求层学习 6 个特征。
  • 在上一点,把卷积层比作一个滑动窗口——但是窗口有多大?第三个参数是窗口或内核大小。 在这里,“5”表示我们选择了 5x5 内核。(如果您想要一个高度与宽度不同的内核,您可以为此参数指定一个元组 - 例如,(3, 5)获得一个 3x5 卷积内核。)

卷积层的输出是激活图——输入张量中特征存在的空间表示。 conv1会给我们一个 6x28x28 的输出张量;6 是特征的数量(也是输出通道的数量),28 是我们特征的高度和宽度。(28 表示 用5 像素窗口滑动在 32 像素上滑动时 ,只有 28 个有效位置。

28=((输入大小+2填充窗口大小)/步长)+1=((32+205)/1)+128=((输入大小+2*填充-窗口大小)/步长)+1= ((32+2*0-5)/1) +1)

然后,将卷积的输出通过 ReLU 激活函数(稍后将详细介绍激活函数),然后通过最大池化层。最大池化层在激活图中获取彼此靠近的特征并将它们组合在一起。它通过减少张量,将输出中的每 2x2 组单元格合并到一个单元格中,取4个单元格的最大值。这样就获得了一个低分辨率版本的激活图,尺寸为 6x14x14。

下一个卷积层conv2需要 6 个输入通道(对应于第一层输出的 6 个特征),具有 16 个输出通道和一个 3x3 内核。它输出了一个 16x12x12 的激活图,通过最大池化层再次减小到 16x6x6。在将此输出传递给线性层之前,它被重新整形为一个 16 * 6 * 6 = 576 元素的向量,以供下一层使用。

有用于处理 1D、2D 和 3D 张量的卷积层。卷积层构造函数还有更多可选参数,包括输入中的步长(例如,每次滑动间隔2个或3个位置)、填充(这样就可以滑动到输入的边缘)等等。有关更多信息,请参阅 文档 。

1.3 循环神经网络 RNN

循环神经网络(或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 网络 教程。

1.4 Transformers

Transformer是一种多用途网络,它使用 BERT 等模型取代了 NLP 的最新技术。Transformer架构的讨论超出了本文的范围,但 PyTorch 有一个 Transformer类允许您定义Transformer模型的整体参数——attention heads数量、encoder & decoder层的数量、dropout和activation函数,等等(甚至可以使用正确的参数从这个单个类构建 BERT 模型!)torch.nn.Transformer该类还具有封装各个组件(TransformerEncoder、 TransformerDecoder)和子组件(TransformerEncoderLayer、 TransformerDecoderLayer)的类。有关详细信息,请查看 有关Transformer 的文档以及 pytorch.org 上的相关教程 。

2. 其他层和功能

2.1 数据操作层 Data Manipulation Layers

还有其他类型的层在模型中承担重要功能,但它们本身不参与学习过程。

2.1.1 最大池化 Max pooling

最大池化(或最小池化)通过组合单元来减少张量,并将输入单元的最大值分配给输出单元。例如:

my_tensor = torch.rand(1, 6, 6)
print(my_tensor)

maxpool_layer = torch.nn.MaxPool2d(3)
print(maxpool_layer(my_tensor))
tensor([[[0.1341, 0.8054, 0.9556, 0.9469, 0.9370, 0.1569],
         [0.7301, 0.1094, 0.6096, 0.1307, 0.1772, 0.3516],
         [0.6364, 0.1435, 0.6513, 0.4279, 0.7217, 0.1505],
         [0.8106, 0.2606, 0.0401, 0.6107, 0.7780, 0.2600],
         [0.7780, 0.2192, 0.6693, 0.9899, 0.2879, 0.1346],
         [0.6453, 0.5904, 0.7523, 0.9691, 0.6647, 0.5977]]])
tensor([[[0.9556, 0.9469],
         [0.8106, 0.9899]]])

如果仔细查看上面的值,会发现 maxpooled 输出中的每个值都是 6x6 输入的每个象限的最大值。

2.1.2 归一化层Normalization layers

归一化层重新居中并归一化一层的输出,然后再将其送到另一层。居中和缩放中间张量有许多好处,例如可以使用更高的学习率而不会使梯度爆炸/消失。

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([[[12.4863,  7.3793, 20.3273, 11.7033],
         [ 7.3007, 17.3882, 15.7556, 17.3081],
         [12.9680,  6.7566, 13.8228, 13.7257],
         [12.1591,  8.6972, 12.3366,  9.9462]]])
tensor(12.5038)
tensor([[[-0.1044, -1.1981,  1.5747, -0.2721],
         [-1.7108,  0.7071,  0.3158,  0.6879],
         [ 0.3909, -1.7210,  0.6816,  0.6486],
         [ 0.8985, -1.3648,  1.0146, -0.5482]]],
       grad_fn=<NativeBatchNormBackward0>)
tensor(4.4703e-08, grad_fn=<MeanBackward0>)

运行上面的单元格,我们为输入张量添加了一个大的缩放因子和偏移量;你应该看到输入张量mean()在 15 附近的某个地方。在通过归一化层运行它之后,你可以看到值更小,并且在零附近分组 - 事实上,平均值应该非常小(> 1e-8) .

这是有益的,因为许多激活函数(下面讨论)在接近 0 时具有最强的梯度,但有时会遭受使它们远离零的输入的消失或爆炸梯度。将数据集中在最陡梯度区域往往意味着更快、更好的学习和更高的可行学习率。

2.1.3 Dropout 层

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.9528, 0.2316, 0.0000, 1.4835],
         [0.2677, 1.4359, 0.1310, 1.4788],
         [0.0000, 1.5414, 0.0000, 0.0000],
         [0.0000, 1.6624, 1.5313, 0.0000]]])
tensor([[[0.0000, 0.2316, 1.1927, 1.4835],
         [0.2677, 0.0000, 0.1310, 1.4788],
         [0.0000, 1.5414, 0.0000, 1.2506],
         [0.4419, 0.0000, 0.0000, 1.3829]]])

上面,可以看到 dropout 对样本张量的影响。您可以使用可选p参数来设置单个权重丢失的概率;如果没有设置p,则默认为 0.5。

2.2 激活函数 Activation Functions

激活函数使深度学习成为可能。神经网络实际上是一个拟合数学函数的程序——具有许多参数。如果只是简单重复堆叠多层张量,这样只能模拟线性函数; 这样的化,有很多层没有意义,因为整个网络其实和单个矩阵乘法效果差不多。在层之间插入 非线性激活函数允许深度学习模型模拟任何函数,而不仅仅是线性函数。

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

2.3 损失函数 Loss Functions

损失函数告诉模型的预测结果和正确结果相差多远。PyTorch 包含多种损失函数,包括常见的 MSE(均方误差 = L2 范数)、交叉熵损失和负似然损失(在分类器中使用)等。

原文地址