虽然 AlexNet 证明深层神经网络卓有成效,但它没有提供一个通用的模板来指导后续的研究人员设计新的网络。
也就是说尽管我知道了更深更大的网络更有效,但是不清楚怎么让它更深更大,从而起到一个更好的效果。
于是,研究人员开始从单个神经元的角度思考问题,发展到整个层,现在又转向块,重复层的模式。
使用块的想法首先出现在牛津大学的视觉几何组的 VGG 网络中,通过使用循环和子程序,可以很容易地在任何现代深度学习框架的代码中实现这些重复的架构。
一、VGG 块
经典卷积神经网络的基本组成部分是下面的这个序列:
- 带填充以保持分辨率的卷积层;
- 非线性激活函数;
- 汇聚层。
而一个 VGG 块与之类似,由一系列卷积层组成,后面再加上用于空间下采样的最大汇聚层。
VGG 块有 3 个参数可以控制:卷积层数、输入通道数和输出通道数。以上一节的 AlexNet 架构为例,其可以看成 3 个 VGG 块和 3 个全连接层组成。
其中,第一个 VGG 块卷积层数是 1,输入通道是 1,输出通道是 96;第二个 VGG 块卷积层数是 1,输入通道数是前一个块的输出通道数,输出通道数是 256;第三个块卷积层数为 3,输入通道为 256,输出通道为 384。
代码实现如下:
def vgg_block(num_convs, in_channels, out_channels): # 定义vgg块
layers = []
for _ in range(num_convs):
layers.append(nn.Conv2d(in_channels, out_channels,
kernel_size=3, padding=1)) # 加入卷积层
layers.append(nn.ReLU()) # 加入激活函数
in_channels = out_channels # 下一层的输入通道数为上一层的输出通道数
layers.append(nn.MaxPool2d(kernel_size=2, stride=2)) # 加入池化层
return nn.Sequential(*layers)
二、VGG 网络
有了 VGG 块,我们可以很轻松地构造起 VGG 网络:将定义好的 VGG 块拼起来,最后加上全连接层即可。
由于各层的输入通道数和输出通道数是串联起来的,在定义 VGG 网络时我们实际上只需要传入每个 VGG 块的卷积层数和输出通道数即可。
代码实现如下:
def vgg(conv_arch): # 构造 VGG 网络
conv_blks = []
in_channels = 1 # 初始输入通道数
# 卷积层部分
for (num_convs, out_channels) in conv_arch:
conv_blks.append(vgg_block(num_convs, in_channels, out_channels))
in_channels = out_channels
return nn.Sequential(
*conv_blks, nn.Flatten(),
# 全连接层部分
# 原始输入经过5层汇聚层后,大小变为 7*7
nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 10))
我们可以构造一个类似 Fashion-MNIST 数据集中图像的输入数据样本,来观察网络中各层的输出尺寸。
X = torch.randn(size=(1, 1, 224, 224))
for blk in net:
X = blk(X)
print(blk.__class__.__name__, 'output shape:\t', X.shape)
各层输出结果如下。初始输入尺寸是 224 × \times × 224,经过 5 个 vgg 块中的最大汇聚层,尺寸缩小为 7 × \times × 7。
注意,输入的四个数字分别代表批量数、通道数、高度和宽度。
随后展平成向量,大小为 512$\times 7 7 7\times$7=25088,接着通过全连接层,最后输出对应于所有分类类别的 10 维向量。
Sequential output shape: torch.Size([1, 64, 112, 112])
Sequential output shape: torch.Size([1, 128, 56, 56])
Sequential output shape: torch.Size([1, 256, 28, 28])
Sequential output shape: torch.Size([1, 512, 14, 14])
Sequential output shape: torch.Size([1, 512, 7, 7])
Flatten output shape: torch.Size([1, 25088])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 10])
训练模型
为减小计算量,加快训练速度,我们构建通道数较少的 VGG 网络,便足够用于训练 Fashion-MNIST 数据集了。
conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512)) # VGG架构
ratio = 4 # 通道数减少的比例
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
net = vgg(small_conv_arch)
除了使用较高的学习率外(0.05),其余训练的代码和 AlexNet 一模一样。
另外这次的代码加入了计时。
训练的相关结果如下,可以发现效果是不错的,精度突破了 90%。不过这次运行时间比 AlexNet 长得多,而且这还只是缩小版的 VGG。
第10轮的训练损失为0.179
第10轮的训练精度为0.933
第10轮的测试集精度为0.918
486.4 examples/sec on cuda:0
总结一下:
- 不同的 VGG 模型可以通过每个 VGG 块中的卷积层数量和输出通道数量的差异来定义。
- 块的使用让网络的定义变得相当简洁,而且它可以帮助我们有效地进行复杂网络的设计。
- 在 VGG 论文中,作者发现,深层且窄(3$\times$3)的卷积比浅层而宽的卷积更为有效。
本次模型训练的代码见附件: