动手学深度学习 5.8 网络中的网络(NiN)

前言

从零开始学习ai文章系列计划是个人在《动手学深度学习》和《磨菇书》两本书的学习中的个人笔记,文章也会以课本中的章节分开,即每个章节一片笔记。我会尽量的把主要内容以及遇到的难点进行记录与解决,如果哪里有错误的欢迎指正。或者不清晰的可以直接查看原文部分。

《动手学深度学习》原文(课本):https://tangshusen.me/Dive-into-DL-PyTorch/#/

《动手学深度学习》代码:https://github.com/ShusenTang/Dive-into-DL-PyTorch

(由于有时候公式太多,可能会直接贴图片)

前几节介绍的LeNetAlexNetVGG在设计上的共同之处是:先以由卷积层构成的模块充分抽取空间特征,再以由全连接层构成的模块来输出分类结果。也就是 卷积层+全连接 的模式。

其中,AlexNetVGGLeNet的改进主要在于如何对这两个模块加宽(增加通道数)和加深。

本节我们介绍网络中的网络(NiN)[1]。它提出了另外一个思路,即串联多个由 卷积层和“全连接”层 构成的小网络来构建一个深层网络。

1. NiN块

我们知道,卷积层的输入和输出通常是四维数组(样本,通道,高,宽),而全连接层的输入和输出则通常是二维数组(样本,特征)。

如果想在全连接层后再接上卷积层,则需要将全连接层的输出变换为四维。

回忆在5.3节(多输入通道和多输出通道)里介绍的1×1卷积层。它可以看成全连接层,其中空间维度(高和宽)上的每个元素相当于样本,通道相当于特征。因此,NiN使用1×1卷积层来替代全连接层,从而使空间信息能够自然传递到后面的层中去。

1x1卷积层不能等价于传统的全连接层,可以理解为:传统的全连接层是长宽都为1,通道数为n的特殊1x1卷积。然而我们使用1x1在图像的通道上做全连接计算,能够保留各个元素间的空间信息。

图5.7对比了NiNAlexNetVGG等网络在结构上的主要区别。

NiN块是NiN中的基础块。它由一个卷积层加两个充当全连接层的1×1卷积层串联而成。其中第一个卷积层的超参数可以自行设置,而第二和第三个卷积层的超参数一般是固定的。

1
2
3
4
5
6
7
8
9
10
11
12
13
def nin_block(in_channels, out_channels, kernel_size, stride, padding):
blk = nn.Sequential(
# 1层卷积
nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding),
nn.ReLU(),

# 2层1x1卷积
nn.Conv2d(out_channels, out_channels, kernel_size=1),
nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=1),
nn.ReLU()
)
return blk

1x1卷积则作用于通道维度,在空间维度不变的情况下,进行跨通道特征变换。这样,组合使用卷积+1x1卷积,有助于在局部区域提取空间特征的同时,融合和重组通道特征

在前期就使用1x1卷积,有助于从低层开始就对特征通道进行非线性组合与优化,提高整个网络的信息流动与表达能力。

NIN块中使用2个连续的1x1卷积层,主要是为了让局部感知器具备更深的非线性特征抽象能力,就像在一个局部区域上堆叠了一个小型多层感知网络

单个1x1卷积 + ReLU 相当于“局部一次非线性变化”。2个 1x1卷积 + ReLU 堆叠,相当于“局部两次非线性变化”,就能逐层组合出更复杂的局部特征模式,这是深度网络的基本原则。那为什么不3层呢?这个问题涉及到设计的权衡

  1. 每多加一层1x1卷积意味着更多的参数、计算量,尤其在早期卷积层(特征图尺寸较大)时影响更明显。
  2. NIN设计初衷是局部多层感知器,而不是在每个像素点上搭建一个“深网络”。在局部区域过度堆叠层数(3层或更多),会使得模型在局部区域内过拟合,破坏全局特征的整合。

总结来说,2层足以实现有效的局部多层特征抽象

2. NiN模型

NiN是在AlexNet问世不久后提出的。它们的卷积层设定有类似之处。NiN使用卷积窗口形状分别为11×11、5×5和3×3的卷积层,相应的输出通道数也与AlexNet中的一致。每个NiN块后接一个步幅为2、窗口形状为3×3的最大池化层。

除使用NiN块以外,NiN还有一个设计与AlexNet显著不同:NiN去掉了AlexNet最后的3个全连接层,取而代之地,NiN使用了输出通道数等于标签类别数的NiN块,然后使用全局平均池化层对每个通道中所有元素求平均并直接用于分类。

这里的全局平均池化层即窗口形状等于输入空间维形状的平均池化层。NiN的这个设计的好处是可以显著减小模型参数尺寸,从而缓解过拟合。然而,该设计有时会造成获得有效模型的训练时间的增加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import torch.nn.functional as F
class GlobalAvgPool2d(nn.Module):
# 全局平均池化层可通过将池化窗口形状设置成输入的高和宽实现
def __init__(self):
super(GlobalAvgPool2d, self).__init__()
def forward(self, x):
# print(x.size())
# print(x.size()[2:])
return F.avg_pool2d(x, kernel_size=x.size()[2:])

net = nn.Sequential(
nin_block(1, 96, kernel_size=11, stride=4, padding=0),
nn.MaxPool2d(kernel_size=3, stride=2),

nin_block(96, 256, kernel_size=5, stride=1, padding=2),
nn.MaxPool2d(kernel_size=3, stride=2),

nin_block(256, 384, kernel_size=3, stride=1, padding=1),
nn.MaxPool2d(kernel_size=3, stride=2),

nn.Dropout(0.5),
# 标签类别数是10

nin_block(384, 10, kernel_size=3, stride=1, padding=1),
GlobalAvgPool2d(),
# 将四维的输出转成二维的输出,其形状为(批量大小, 10)
d2l.FlattenLayer())

我们构建一个数据样本来查看每一层的输出形状。

1
2
3
4
X = torch.rand(1, 1, 224, 224)
for name, blk in net.named_children():
X = blk(X)
print(name, 'output shape: ', X.shape)

可以看到经过GlobalAvgPool2d()后,长宽为5的图像变成了长宽为1。

可以理解为,输出结果是网络学会的的每个类的一张响应图,通过GAP获取每张图的平均值,用来判断是否存在该类。

3. 获取数据和训练模型

我们依然使用Fashion-MNIST数据集来训练模型。NiN的训练与AlexNet和VGG的类似,但这里使用的学习率更大。