4.6 VGG网络#

经过4.4节4.5节内容的介绍我们已经了解了LeNet5和AlexNet这两种卷积网络模型,但是总体上来说两者的网络结构几乎并没有太大的差别。在接下来的这节内容中,我们将会介绍卷积网络中的第3个经典模型VGG[1]。

4.6.1 VGG动机#

随着卷积网络在计算机视觉领域的快速发展,越来越多的研究人员开始通过改变模型的网络结构在提高在图像识别任务中的精度,例如使用更小的卷积核和步长[2]。基于类似的想法,论文作者提出可以尝试通过改变卷积网络深度来提高模型的分类精度。VGG模型于2014年诞生于Visual Geometry Group 实验室,而这3个单词的首字母也代表了VGG的含义。VGG网络总体上一共有5种网络架构,但是从本质上来说这5种网络架构都是一样的,仅仅只是在卷积的深度上有所差别,因此VGG也可以看作是不同卷积深度对模型效果影响的一次探索。

在论文中,作者对卷积网络卷积深度的设计进行了探索,并且通过尝试逐步加深网络的深度来提高模型的整体性能,这使得VGG在当年的ILSVRC任务中以稳定的优势分别取得了两项比赛的第1名和第2名。下面,我们将开始一步一步地来介绍VGG模型的网络结构。

4.6.2 VGG结构#

如图4-36所示一共有6列,其中第2列是在第1列的基础上加入了LRN标准化操作,网络最少有11层,最多有19层。在整个网络的训练过中,VGG会固定输入图片的大小为$224\times224$的RGB图像,并且在预处理中仅仅只是做了去均值化,即在训练集中每个像素值都会减去整体像素的一个平均值。接着,预处理后的图片将会被输入到一系列仅仅只由窗口大小为$3\times3$的卷积核堆叠而成的卷积网络中。并且从图4-36中的模型C可以看出,其还使用了窗口大小为$1\times1$的卷积核,这是因为$1\times1$的卷积既可以增加模型的非线性拟合能力,同时还不会改变卷积层的可视野。

图 4-36. VGG网络架构图

同时,在这5种网络架构中,所有卷积运算的步长都被设置成了固定的$1$,并且为了使得卷积后特征图的大小同输入时保持一致,网络在每次卷积之前均做了对应的填充处理,即特征图的大小只会在池化后产生变化。在池化方面,5种网络模型均使用了5次最大池化操作,其窗口大小均为$2\times2$,移动步长均为$2$。从图4-36可以看出,VGG-19网络结构的参数量大约在1亿1千4百万左右,假如每个权重参数均使用32位浮点数进行表示,则每个权重参数将占用4个字节,则VGG-19模型的大小约为550MB。

在完成一系列的卷积处理后,VGG会将卷积得到的特征图再输入到全连接网络中,其中前两个全连接层均包含有$4096$个神经元,而最后一个全连接层神经元的个数则是对应的分类数$1000$,紧接着再是一个Softmax的分类层。对于所有的5种网络结构来说,这部分都采用了相同的配置。最后,在VGG中所有的隐藏层(所有卷积层和前两个全连接层)都使用了ReLU非线性变换。

从图4-36所示的网络结构可以看出,在整个过程中VGG都仅仅只使用了$3\times3$大小的卷积核,而摒弃了诸如$5\times5$或者是$7\times7$这类更大的卷积核。因为论文作者研究发现,连续2次(中间没有池化)使用窗口为$3$的卷积核卷积后的可视野等同于一次窗口大小为$5$的卷积过程;而连续3次(中间同样没有池化)使用$3\times3$卷积,其效果等价于1次窗口大小为$7$的卷积过程。

图 4-37. 不同窗口大小卷积对比图

如图4-37所示,左右两边均是大小为$5\times5$的输入,左边通过连续两次$3\times3$大小的卷积核进行卷积后能够实现$5\times5$的可视野;而右边仅用一次$5\times5$大小的卷积核进行卷积后同样也能够实现$5\times5$的可视野。那这样做的好处是什么呢?以窗口大小为$7$和连续3个窗口大小为$3$的卷积过程为例,作者认为:①连续3次卷积的同时也进行非线性变化得到的模型,比仅仅只进行一次卷积和非线性变换得到的模型要更具有泛化能力,尽管两者能够获得同样大小的可视野,而这也可以看作是对$7\times7$的卷积核施加了一次正则化的结果;②可以有效减少参数量,假设卷积时输入输出的通道数均为$C$,则一次$7\times7$的卷积需要的参数量为$7^2C^2=49C^2$,而3次$3\times3$的卷积需要的参数量为$(3^2C^2)3=27C^2$,前者比后者多了$81\%$的参数量。

4.6.3 VGG实现#

从图4-36可以看出VGG有多种不同类型的网络配置,如果是按照之前的实现思路那么就得写多份代码,但显然这里面有很多代码是可以复用。所以首先需要实现一个通用模块,然后只需要传入对应的配置参数就能够实现对应的网络结构。以下完整示例代码可以参见Code/Chapter04/C05_VGG/文件。

1. 辅助模块

如下代码所示就是A、B、D和E这4种网络结构的配置参数,其中'M'表示该层为最大池化层,而其它的数字则表示对应的卷积核个数。至于网络结构C这里就不进行示例了,有兴趣的读者可以自己修改。

1 vgg_config={'A':[64,'M',128,'M',256,256,'M',512,512,'M',512,512,'M'],
2 'B':[64,64,'M',128,128,'M',256,256,'M',512,512,'M',512,512,'M'],
3 'D':[64,64,'M',128,128,'M',256,256,256,'M',512,512,512,'M',512,512,512,'M'],
4 'E'[64,64,'M',128,128,'M',256,256,256,256,'M',512,512,512,512,'M',512,512,512,512,'M']}

进一步,在定义完这个配置字典后便可以实现构造网络结构的辅助函数,示例代码如下所示:

 1 def make_layers(config):
 2     layers = []
 3     in_channels = config.in_channels
 4     cfg = vgg_config[config.vgg_type]
 5     for v in cfg:
 6         if v == 'M':
 7             layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
 8         else:
 9             conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
10             layers += [conv2d, nn.ReLU(inplace=True)]
11             in_channels = v
12     return nn.Sequential(*layers)  # *号的作用解包这个list

在上述代码中,第1行是传入的模型配置信息。第2行用来保存所有的网络层。第4行用于根据传入的参数返回VGG中对应的网络结构。第5~11行是依次遍历每个配置参数来构建对应的VGG网络结构。第12行则是将列表中的所有网络层放入到nn.Sequential()中。

52