4.8 GoogLeNet网络#

在前面两节内容中我们分别介绍了VGG和NIN模型中基于自定义块来构建网络的思想,其核心观点均是认为传统的单一卷积操作很难提取到高级的抽象特征,因此需要重新构造新的基模块,例如VGG块和多层感知机卷积块。在接下来的这节内容中,我们将再次介绍另外一种同样也是基于“块”思想的网络模型——GoogLeNet模型[1]。

4.8.1 GoogLeNet动机#

4.4节开始介绍的第1个卷积神经网络LeNet5模型到4.7节中的NIN网络模型,这4个模型除了在深度上有着明显的区别,另一个明显的差异之处就在于卷积核大小的变化以及与池化层的组合方式。

图 4-41. LeNet5、AlexNet、VGG和NIN网络局部对比图

如图4-41所示,在LeNet5模型中作者仅仅只是采用了窗口大小为$5\times5$的卷积核进行特征提,而到了AlexNet中则引入了窗口大小为$3\times3$卷积操作,进一步在VGG和NIN模型中还出现了$1\times1$大小的卷积操作并同时摒弃了$5\times5$的卷积核。同时,不同的网络模型对于池化层的位置也有不同的处理方式。因此,对于某个网络层来说到底是应该使用卷积层还是池化层呢?如果使用卷积层该选择什么样的窗口大小呢?

基于这样的动机,谷歌公司在2015年的一篇论文中提出了一种并行的网络结构块Inception来解决这一问题,并以Inception块为基础构建得到了整个GoogLeNet网络模型。

4.8.2 GoogLeNet结构#

在正式介绍GoogLeNet模型的网络结构之前,我们先来介绍其中的核心部分Inception模块和$1\times1$卷积的作用。

1. 理解Inception模块

如图4-42所示便是Inception块的构成元素。从图中可以看出,对于任意Inception块的输入来说从左到右并行有a、b、c和d共4条路径选择,分别对应了不同大小卷积核的卷积操作和池化操作。

图 4-42. Inception模块结构图

进一步,将不同路径下运算得到的结果在通道这一维度上进行堆叠便得到了Inception块的输出。最终,通过以Inception块来代替传统卷积的方式便能够在同一层获取得到不同卷积尺度的计算结果,这便是Inception块的核心思想。可以看出GoogLeNet这个名字和LetNet并没有任何关系,仅仅只是在向后者致敬。

2. $1\times1$卷积的作用

此时可能有读者会问,为什么Inception块的中间两条路径也会有$1\times1$大小的卷积操作?

图 4-43. $1\times1$卷积作用图

如图4-43所示,现有特征图的形状为$28\times28\times192$,如果直接使用32个窗口大小为$5\times5$的卷积核进行卷积操作,那么此时一共有153600个权重参数;而如果先用16个窗口大小为$1\times1$的卷积核对原始特征图进行降维,然后再进行窗口大小为$5\times5$的卷积操作,那么此时一共有15872个权重参数。可以看出,后者的参数量仅仅只有前者的约十分之一,同时整个计算量也变成了前者的十分之一。因此,Inception模块中间两个$1\times1$卷积操作的目的便是降低模型的参数量。

3. 整体网络结构

在介绍完Inception结构的思想原理后我们再来看GoogLeNet的整个网络结构。总的来说,除了传统的3个卷积运算之外,GoogLeNet一共由9个Inception块所构成,而这9个Inception块又可以分为三个阶段,前两个阶段结束之后特征图的大小均变成了之前的一半,最后一个阶段结束后便是一个全局池化层并通过一个全连接层来完成分类任务。

图 4-44. GoogLeNet网络结构图

如图4-44所示便是GoogLeNet的网络结构图。以原始输入大小为3通道$224\times224$的图像为例,在经过前面两个卷积层和池化层之后特征图的形状变成了[28,28,192]。进一步,在经过模块Inception(3a)时,4个分支路径的特征图形状分别为[28,28,64][28,28,128][28,28,32][28,28,32],然后在通道这个维度上进行堆叠得到形状为[28,28,256]的特征图。从这里可以看出,在Inception模块的4个路径中$3\times3$大小的卷积核最多,并且后续也一直保持了这个规律,一定程度上反映出$3\times3$大小的卷积核更具有优势。紧接着便是Inception(3b)、Inception(4a)、Inception(4b)、Inception(4c)、Inception(4d)、Inception(4e)、Inception(5a)和Inception(5b)这8个Inception块,输出形状为[7,7,1024]。最后,再通过一个维度为1024的全连接层完成分类任务。具体每个Inception块计算后特征图的形状可以参见图4-44中的对应标注。

当然,GoogLeNet除了图4-44所示的网络结构外,作者认为处于网络模型中间层的特征往往具有更强的判别能力,因此GoogLeNet的另一个版本还在网络的中间部分额外的添加了两个分类器,即在分别再取Inception(4a)和Inception(4d)的输出结果进行后续的分类任务。

4.8.3 GoogLeNet实现#

在介绍完GoogLeNet模型的网络结构之后我们再来看如何一步步实现整个模型。类似于实现NIN模型一样,这里需要先定义实现Inception模块,然后再以此为基础来实现GoogLeNet模型。以下完整示例代码可以参见Code/Chapter04/C07_GoogLeNet/文件。

1. 辅助模块

首先需要实现两个辅助类来完成Inception块的计算过程,实现代码如下所示:

 1 class BasicConv2d(nn.Module):
 2     def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):
 3         super(BasicConv2d, self).__init__()
 4         self.conv = nn.Conv2d(in_channels, out_channels,
 5                               kernel_size, stride, padding)
 6         self.relu = nn.ReLU(inplace=True)
 7 
 8     def forward(self, x):
 9         x = self.conv(x)
10         return self.relu(x)

在上述代码中,类BasicConv2d的作用便是同时完成一个卷积操作和一次非线性变换。因为是继承自nn.Module,所以后续可以将BasicConv2d整体作为一个网络层进行使用。

55