本文尽量不涉及 CNN(卷积神经网络)的原理,仅讨论 CNN 的 PyTorch 实现。CNN 独有的层包括卷积层(convolution layer),池化层(pooling layer),转置卷积层(transposed convolution layer),反池化层(unpooling layer)。卷积层与池化层在 CNN 中最常用,而转置卷积层与反池化层通常用于计算机视觉应用里的图像再生,对于 NLP 来说应用不多,不再赘述。
1. 卷积神经网络工作原理
从工程实现的角度来说,一个 CNN 网络可以分成两部分:特征学习阶段与分类阶段。
特征学习层由多层卷积层与池化层叠加,之间使用 relu 作为激活函数。卷积层的作用是使信息变深(层数增加),通常会使层的长宽减小;池化层的作用是使信息变窄,提取主要信息。之后进入分类层,将信息变成一维向量,经过 1-3 层全连接层与 relu 之后,经过最终的 softmax 层进行分类;若目标为二分类,则也可以经过 sigmoid 层。
2. convolution layer 卷积层
卷积层有三个类,分别是:
torch.nn.Conv1d
torch.nn.Conv2d
torch.nn.Conv3d
这三个类分别对应了文本(一维数据)、图片(二维数据)和视频(三维数据)。它们的维度如下:- 一维数据是一个 3 维张量:batch * channel * feature;
- 二维数据是一个 4 维张量:batch * channel * weight * height;
- 三维数据是一个 5 维张量:batch * channel * frame * weight * height。
可见,三个类处理的数据的前两维是完全一致的。此外,三个类的参数也完全一致,以 torch.nn.Conv2d
为例:
1 | torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros') |
in_channels
:输入张量的层数;out_channels
:输出张量的层数;kernel_size
:卷积核的大小,整数或元组;stride
:卷积的步长,整数或元组;padding
:填充的宽度,整数或元组;dilation
:稀释的跨度,整数或元组;groups
:卷积的分组;bias
:偏置项;padding_mode
:填充的方法。
当所有尺寸均为矩形的时候,输出张量的长和宽的数值为:
$$dimension=\frac{H_{in}+2\times padding-dilution\times (kernel_size-1)-1}{stride}$$
- 一个 trick:当 $kernel_size=3$,$padding=1$,$stride=1$ 的时候,输入张量和输出张量的长宽是不变的。
池化层的权重是随机初始化的,不过我们也可以手动设定。
1 | 1, 1, 3, bias=0.) # 定义一个 3x3 的卷积核 conv = torch.nn.Conv2d( |
上例中,卷积核是一个 $3\times3$ 的全 1 张量;在卷积运算中,卷积核先与张量中前三排中的前三个元素进行 elementwise 的乘法,然后相加,得到输出张量中的第一个元素。然后向右滑动一个元素(因为 stride
默认是 1),重复卷积运算;既然达到末尾,返回左侧向下滑动一个单位,继续运算,直到到达末尾。
3. pool layer 池化层
与卷积层对应的,池化层分为最大池化和平均池化两种,每种也有三个类:
torch.nn.MaxPool1d
torch.nn.MaxPool2d
torch.nn.MaxPool3d
torch.nn.AvgPool1d
torch.nn.AvgPool2d
torch.nn.AvgPool3d
所谓“池化”,就是按照一定的规则(选取最大值或计算平均值)在输入层的窗口里计算数据,返回计算结果。它们的参数也一致,最大池化层只有三个参数:
kernel_size
:卷积核的大小,整数或元组;stride
:卷积的步长,整数或元组;padding
:填充的宽度,整数或元组;
一维平均池化层有额外的两个参数:
ceil_mode
:对结果进行上取整;count_include_pad
:是否将 padding 纳入计算;
二维及三维平均池化层有额外的一个参数:
divisor_override
:指定一个除数。一个 trick:当 $\text{kernel_size}=2$,$\text{stride}=2$ 的时候,输出张量的尺寸是输入张量的一半。
1
2
3
42) # 定义一个大小为 2x2 的核 pool = torch.nn.MaxPool2d(
# 池化操作 pool(tensor)
tensor([[[[16., 14.],
[ 8., 6.]]]])4. CNN 实战
我们还是使用《[DL] PyTorch 折桂 11:使用全连接网络进行手写数字识别》 里的任务,只不过这一次我们使用 CNN 搭建神经网络。除了第 2、5 步,其它代码都是一样的,所以这里只有这两步的代码,其它代码请看前文。
构建神经网络时唯一要注意的是最后的全连接层的入度。
1 | class CNN(nn.Module): |
这个模型里,每一个 batch 经过卷积层以前的维度是 [batch, 1, 28, 28]
,经过卷积层后长宽不变而通道数变成了 4;通过池化层以后每个 batch 的维度变成了 [batch, 4, 14, 14]
,所以全连接层的入度不变。还有一点要注意的是因为 CNN 接受一个二维张量。所以打平这个操作要放在模型里面的全连接层之前,而不是训练中。训练 15 个 epoch:
1 | Epoch 0 - Training loss: 0.5372020660528242 |
上一次使用全连接神经网络训练 15 轮后的 loss 是 0.27,看来 CNN 网络的效果好很多。测试一下:
1 | 0, 0 correct_count, all_count = |
准确率果然超过了 97%。CNN YES!
欢迎关注我的微信公众号“花解语 NLP”: