当前位置:   article > 正文

「PyTorch自然语言处理系列」4. 自然语言处理的前馈网络(中)

pytorch channel dimensionality

来源 |  Natural Language Processing with PyTorch

作者 | Rao,McMahan

译者 | Liangchu

校对 | gongyouliu

编辑 | auroral-L

全文共4631字,预计阅读时间30分钟。

上下拉动翻看这个书签

4.1 多层感知机
  4.1.1 一个简单示例:XOR
  4.1.2 在 PyTorch 中实现多层感知机
 4.2 示例:使用多层感知机对姓氏进行分类
  4.2.1 姓氏数据集
  4.2.2 Vocabulary,Vectorizer和DataLoader
   4.2.2.1 Vocabulary类
   4.2.2.2 SurnameVectorizer
  4.2.3 SurnameClassifier模型
  4.2.4 训练例程
   4.2.4.1 训练循环(training loop)
  4.2.5 模型评估和预测
   4.2.5.1 在测试集上评估
   4.2.5.2 分类一个新姓氏
   4.2.5.3 获取新姓氏的前k个预测
  4.2.6 MLPs正则化:权重正则化和结构正则化(或Dropout) 

4.3 卷积神经网络
  4.3.1 CNN 超参数
   4.3.1.1 卷积操作的维度
   4.3.1.2 通道
   4.3.1.3 核大小
   4.3.1.4 Stride
   4.3.1.5 Padding
   4.3.1.6 Dilation
  4.3.2 在 PyTorch 实现 CNNs
 4.4 示例:使用 CNN 对姓氏进行分类
  4.4.1 SurnameDataset类
  4.4.2 Vocabulary,Vectorizer和DataLoader
  4.4.3 使用 CNN 重新实现SurnameClassifier
  4.4.4 训练例程
  4.4.5 模型评估和预测
   4.4.5.1 在测试集上评估
   4.4.5.2 为新的姓氏分类或获取最佳预测
 4.5 CNN 中的其他话题
  4.5.1 池化操作
  4.5.2 批量规范化(BatchNorm)
  4.5.3 网络中的网络连接(1x1卷积)
  4.5.4 残差连接/残差块
 4.6 总结

4.3 卷积神经网络

在本章的第一部分中,我们深入研究了多层感知机,一个由一系列线性层和非线性函数构建的神经网络。多层感知机不是利用顺序模式的最佳工具。例如,在姓氏数据集中,姓氏有些片段可以显示相当多有关其起源的信息(如O'Neill中的O、Antonopoulos中的opoulos、Nagasawa中的sawa或Zhu中的Zh)。这些片段的长度不定,而挑战就是在不显式编码的情况下捕获它们。

在本节中,我们将介绍卷积神经网络(convolutional neural network,CNN),这是一种非常适合检测空间子结构(并因此创建有意义的空间子结构)的神经网络。卷积神经网络通过使用少量的权重以扫描输入数据张量来实现这一点。通过这种扫描,它们产生表示子结构检测(或不检测)的输出张量。

在本节中,我们首先介绍 CNN 的工作方式,以及在设计 CNN 时应该考虑的问题。我们将深入研究 CNN 超参数,以提供关于这些超参数对输出的行为和影响的直观信息。最后,我们会通过几个简单示例逐步说明CNN的机制。在“示例:使用 CNN 对姓氏进行分类”一节中,我们将研究一个更深入的示例。


历史背景

CNN的名字和基本功能起源于一个经典的数学运算:卷积(convolution)。几十年来,卷积已应用于各种工程学科,包括数字信号处理和计算机图形学。通常,卷积使用程序员指定的参数,这些参数被指定来匹配一些功能设计,如突出边缘或抑制高频声音。事实上,许多 Photoshop 滤镜都是应用于图像的固定卷积运算。然而,在深度学习和本章中,我们从数据中学习卷积滤波器的参数,因此它是对于解决当前任务的最佳方法。


4.3.1 CNN 超参数

为了理解不同的设计决策对 CNN 的影响,我们在下图(4-6)中展示了一个示例。在本例中,单个“核”(kernel)应用于一个输入矩阵。一个卷积运算(线性算子)的精确数学表达式对于理解本节并不重要。从这个图中可以直观地看出,核是一个小的方阵,它被系统地应用于输入矩阵的不同位置。

cda1fe48f572b64cbb2bfd9ea5402455.png

虽然经典的卷积是通过指定核的具体值来设计的,但 CNN 是通过指定控制 CNN 行为的超参数然后使用梯度下降来为给定数据集找到最佳参数来设计的。两个主要的超参数控制卷积的形状(称为kernel_size)以及卷积将在输入数据张量(称为stride)中相乘的位置。还有一些其他的超参数控制输入数据张量被 0 填充了多少(称为padding)以及当应用到输入数据张量(称为dilation)时,乘法应该相隔多远。在下面的小节中,我们将更详细地介绍这些超参数。

4.3.1.1 卷积操作的维度

首先要理解的概念是卷积运算的维度(dimensionality)。在上图(4-6)和本节的其他图中,我们使用二维卷积进行演示,但是根据数据的性质,还有更适合的其他维度的卷积。在 PyTorch 中,卷积可以是一维、二维或三维的,分别由Conv1d、Conv2d和Conv3d模块实现。一维卷积对于每个时间步都有一个特征向量的时间序列非常有用,在这种情况下,我们可以学习序列维度上的模式。NLP 中的卷积运算大多是一维的。此外,二维卷积试图捕捉数据中沿两个方向上的时空模式——比如,在图像中沿高度和宽度这两个维度——这也是二维卷积在图像处理中很流行的原因。类似地,在三维卷积中,模式是沿着数据中的三个维度捕获的,例如,在视频数据中,信息是三维的(两个维度表示图像的帧,时间维度表示帧的序列)。就本书而言,我们主要使用Conv1d。

4.3.1.2 通道

非正式地说,通道(channel)是指沿输入中的每个点的特征维度。例如,在图像中,对应于 RGB 组件的图像中的每个像素都有三个通道。在使用卷积时,文本数据也可以采用类似的概念。从概念上来说,如果文本文档中的“像素”(pixel)是单词,那么通道的数量就是词汇表的大小。如果我们更细粒度地考虑字符的卷积,那么通道的数量就是字符集的大小(在本例中刚好就是词汇表)。在 PyTorch 卷积实现中,输入中通道的数量是in_channels参数。卷积操作可以在输出(out_channels)中产生多个通道。可以将此看作是卷积操作将输入特征维数映射到了输出特征维数。下图(4-7和4-8)解释了这个概念:

577866343793fd56aede88d1d5ce8d8a.png

ceec0142aa0593fe7ecd5636dc7796b8.png

很难马上就知道对于当前问题而言最合适的输出通道数。为了简化这个问题,我们假设边界是1和1024——我们可以有只有一个通道的卷积,也可以有多达1024个通道的卷积。现在有了边界,接下来考虑有多少个输出通道。一种常见的设计模式是,从一个卷积层到下一个卷积层,通道数量的缩减不超过 2 倍。这不是一个硬性规则,但是它至少让你知道适当数量的out_channels是什么样子。

4.3.1.3 核大小

核矩阵的宽度称为核大小(kernel size)(PyTorch 中是kernel_size)。在上图(4-6)中,核大小为 2,而在下图(4-9)中,我们展示了一个大小为 3 的核。你应该直观感受到:卷积将输入中的空间(或时间)局部信息组合在一起,每个卷积的局部信息量由内核大小控制。然而,随着核大小的增加,输出的大小也应该相应减少(Dumoulin 和 Visin, 2016)。这就是为什么当核大小为 3 时,输出矩阵是下图(4-9)中的2x2,而当核大小为 2 时,输出矩阵是上图(4-6)中的3x3。

cf7bde93913a62bf331836a923a46f13.png

此外,你可以将 NLP 应用中核大小的行为视作类似于n-gram的行为,n-gram通过查看词组来捕获语言模式。核大小越小,模式捕捉得越频繁,核大小越大,捕捉的模式越大,可能更有意义,但是其频率也会降低。较小的核大小会导致输出的细粒度特性,而较大的核大小会导致粗粒度特性。

4.3.1.4 Stride

stride控制卷积之间的步长。如果步长与核相同,则内核计算不会重叠。此外,如果跨度为 1,则内核最大限度地重叠。通过增加stride,可以有意缩小输出张量以总结信息,如下图(4-10)所示:

6a5fd2e18aebb0998ef5a88ea8c0522a.png

4.3.1.5 Padding

尽管stride和kernel_size允许控制每个计算得到的特征值的范围,但它们也有一个有害的、有时是无意的副作用,那就是会缩小特征映射(卷积的输出)的总大小。为了抵消这一点,输入数据张量被人为地增加了长度(如果是一维、二维或三维)、高度(如果是二维或三维)和深度(如果是三维),这是通过在每个维度上附加和前置 很多个0实现的。这意味着CNN将执行更多的卷积,同时保证输出形状可控,因此不会影响所需的kernel size、stride或padding。下图(4-11)展示了padding的运行步骤:

73f02b2ee08ffe8e7b16f4e4cc8457cb.png

4.3.1.6 Dilation

膨胀(dilation)控制卷积核如何应用于输入矩阵。在下图(4-12)中将展示:将dilation从 1(默认值)增加到 2 意味着当应用于输入矩阵时,核的元素彼此之间是两个空格。思考这种问题的另一种方式是在核中stride——在核中的元素或核的应用之间存在一个步长,即存在“hole”,这有助于在不增加参数数量的情况下汇总输入空间的更大区域。当叠加卷积层时,证明了dilation卷积的有用性。连续扩张(dilated)的卷积以指数方式增加“receptive field”的大小,即预测之前网络看到的输入空间的大小。

8e080edac6dc84271a7a9b8ec0675552.png

4.3.2 在 PyTorch 实现 CNNs

在本节中,我们将利用到上面介绍的概念演示一个端到端的示例。一般来说,神经网络设计的目标是找到一个能够完成任务的超参数配置。再次考虑“示例:使用多层感知机对姓氏进行分类”一节中的姓氏分类任务,但这里我们将使用CNN而不是多层感知机。我们仍然要在最后应用一个线性层,它将学会从一系列卷积层创建的特征向量得到预测向量,这意味着我们的目标是确定卷积层的配置,从而得到所需的特征向量。所有 CNN 应用都是如此:首先有一组卷积层,它们提取一个特征映射,然后将其作为上游处理的输入。在分类中,上游处理几乎总是应用线性(或 fc)层。

本节中的实现将迭代设计决策,以构建特征向量。我们首先人为构造一个数据张量,以反映实际数据的形状。数据张量的大小是三维——这是向量化文本数据的minibatch的大小。倘若你对一个字符序列中的每个字符都使用独热向量,那么独热向量序列就是一个矩阵,而独热矩阵的minibatch就是一个三维张量。使用卷积的术语,每个独热向量(通常是词汇表的大小)的大小就是输入通道(input channel)的数量,字符序列的长度是宽度(width)。

在下例(4-14)中,构造特征向量的第一步是将 PyTorch 的Conv1d类的一个实例应用到三维数据张量。通过检查输出的大小,你可以知道张量减少了多少。我们建议你参考图4-9来直观地理解为什么输出张量在收缩。

示例 4-14:人造数据和使用Conv1d类

  1. Input[0]
  2. batch_size = 2
  3. one_hot_size = 10
  4. sequence_width = 7
  5. data = torch.randn(batch_size, one_hot_size, sequence_width)
  6. conv1 = Conv1d(in_channels=one_hot_size, out_channels=16,
  7. kernel_size=3)
  8. intermediate1 = conv1(data)
  9. print(data.size())
  10. print(intermediate1.size())
  11. Output[0]
  12. torch.Size([2, 10, 7])
  13. torch.Size([2, 16, 5])

进一步减小输出张量的主要方法有三种:第一种是创建额外的卷积并按顺序应用它们,最终对应的sequence_width(dim=2)维度的大小将为 1。我们在下例(4-15)中展示了额外应用两个卷积的结果。一般来讲,应用卷积来减小输出张量的过程是迭代的,需要进行一些猜测。我们的示例是这样构造的:经过三次卷积之后,产生的输出在最终维度上size=1。

示例 4-15:卷积在数据上的迭代应用

  1. Input[0]
  2. conv2 = nn.Conv1d(in_channels=16, out_channels=32, kernel_size=3)
  3. conv3 = nn.Conv1d(in_channels=32, out_channels=64, kernel_size=3)
  4. intermediate2 = conv2(intermediate1)
  5. intermediate3 = conv3(intermediate2)
  6. print(intermediate2.size())
  7. print(intermediate3.size())
  8. Output[0]
  9. torch.Size([2, 32, 3])
  10. torch.Size([2, 64, 1])
  11. Input[1]
  12. y_output = intermediate3.squeeze()
  13. print(y_output.size())
  14. Output[1]
  15. torch.Size([2, 64])

在每次卷积中,通道维度的大小都会增加,这是因为通道维度是每个数据点的特征向量。要使张量实际上成为一个特征向量,最后一步是去除讨厌的size=1维度。可以使用squeeze()方法来实现这一点,该方法将删除size=1的所有维度并返回结果。然后,产生的特征向量可以与其他神经网络组件(如线性层)一起用于计算预测向量。

此外,还有两种方法可将张量减少为每个数据点一个特征向量:将剩余的值压平为特征向量;在额外维度上求平均值。这两种方法如下例(4-16)所示。使用第一种方法,只需使用 PyTorch 的view()方法将所有向量展平为单个向量。第二种方法使用一些数学运算来汇总向量中的信息,最常见的操作是算术平均值,但沿特征映射维数求和和使用最大值也蛮常见。每种方法都有其优缺点:展平的方法保留了所有的信息,但会导致比预期(或计算上可行)更大的特征向量;求平均值与额外维度的大小无关,但可能会丢失信息。

示例 4-16:减少到特征向量的两种其他方法

  1. Input[0]
  2. # Method 2 of reducing to feature vectors
  3. print(intermediate1.view(batch_size, -1).size())
  4. # Method 3 of reducing to feature vectors
  5. print(torch.mean(intermediate1, dim=2).size())
  6. # print(torch.max(intermediate1, dim=2).size())
  7. # print(torch.sum(intermediate1, dim=2).size())
  8. Output[0]
  9. torch.Size([2, 80])
  10. torch.Size([2, 16])

这种设计一系列卷积的方法是基于经验的:从数据的预期大小开始,处理一系列卷积,最终得到适合的特征向量。虽然这种方法在实践中效果很好,但在给定卷积的超参数和输入张量的情况下,还有另一种计算张量输出大小的方法,即使用从卷积运算本身推导出的数学公式。

0e388572bdef59638a44870a3952195c.png

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号