深度学习高手笔记(卷1):基础算法
上QQ阅读APP看书,第一时间看更新

1.3 更宽:GoogLeNet

在本节中,先验知识包括:

AlexNet(1.1节)。

2012年之后,CNN的研究分成了两大流派,并且两大流派都在2014年有重要的研究成果发表。一个流派的研究方向是增加CNN的深度和宽度,经典的网络有2013年ILSVRC的冠军作品ZFNet和我们在1.2节中介绍的VGG系列。另外一个流派的研究方向是增加卷积核的拟合能力,或者说是增加网络的多样性,典型的网络有可以拟合任意凸函数的Maxout网络[11]、可以拟合任意函数的NIN,以及本节要解析的基于Inception的GoogLeNet。为了能更透彻地了解GoogLeNet的思想,我们首先需要了解Maxout和NIN两种结构。


[11] 参见Ian J. Goodfellow、David Warde-Farley、Mehdi Mirza等人的论文“Maxout Networks”。

1.3.1 背景知识

1.Maxout网络

在之前介绍的AlexNet中,它引入了Dropout来减轻模型的过拟合问题。Dropout可以看作一种集成模型,在训练的每步中,Dropout会将网络的隐层节点以概率P置0。Dropout和传统的装袋(bagging)方法主要有以下两个方面不同:

Dropout的每个子模型的权值是共享的;

在训练的每步中,Dropout会使用不同的样本子集训练不同的子网络。

这样在训练的每步中都会有不同的节点参与训练,可以减轻节点之间的耦合性。在测试时,Dropout使用的是整个网络的所有节点,只是节点的输出值要乘p。因为在测试时,我们不会进行Dropout操作。为了避免Dropout丢失节点带来的缩放问题,我们会将该层节点值乘p来达到Dropout引起的缩放效果。

作者认为,与其像Dropout这样平均地选择,不如有条件地选择节点来生成网络。在传统的神经网络中,第i个隐层的计算方式(暂时不考虑激活函数)如式(1.6)所示:

  (1.6)

假设第个隐层和第i个隐层的节点数分别是dm,那么W是一个的二维矩阵。而在Maxout网络中,W是一个三维矩阵,矩阵的维度是,其中k表示Maxout网络的通道数,是Maxout网络唯一的参数。Maxout网络的数学表达式如式(1.7)所示:

  (1.7)

其中

下面我们通过一个简单的例子来说明Maxout网络的工作方式。对于一个传统的网络,假设第i个隐层有两个节点,第i + 1个隐层有1个节点,那么多层感知机(multi-layer perceptron,MLP)的计算方式如式(1.8)所示:

  (1.8)

其中是激活函数,如tanh、ReLU等,X是输入数据的集合。从图1.14可以看出,传统神经网络的输出节点是由两个输入节点计算得到的。

图1.14 传统神经网络

如果我们将Maxout的参数k设置为5,Maxout网络可以展开成图1.15所示的形式:

图1.15 Maxout网络

其中z = max(z1,z2,z3,z4,z5)。z1z5为线性函数,所以z可以看作分段线性的激活函数。Maxout网络的论文中给出了证明,当k足够大时,Maxout单元可以以任意小的精度逼近任何凸函数,如图1.16所示,图中每条直线代表一个输出节点zi

图1.16 Maxout单元的凸函数无限逼近性

在Keras 2.0之前的版本中,我们可以找到Maxout网络的实现,其核心代码只有一行。

output = K.max(K.dot(X, self.W) + self.b, axis=1)

Maxout网络存在的最大的一个问题是网络的参数数量是传统神经网络的k倍,而k倍的参数数量并没有带来等价的精度提升,所以现在Maxout网络基本已被工业界淘汰。

2.NIN

Maxout单元可以逼近任何凸函数,而NIN的节点理论上可以逼近任何函数。在NIN中,作者也采用整图滑窗的形式,只是将CNN的卷积核替换成了一个小型MLP网络,如图1.17所示。

图1.17 NIN网络结构

在卷积操作中,一次卷积操作仅相当于卷积核和滑窗的一次矩阵乘法,其拟合能力有限。而MLP替代卷积操作增加了每次滑窗的拟合能力。图1.18展示了将LeNet-5改造成NIN在MNIST上的训练过程收敛曲线。通过实验,我们根据实验结果得到了3个重要信息:

NIN的参数数量远大于同类型的CNN;

NIN的收敛速度快于经典网络;

NIN的训练速度慢于经典网络。

图1.18 NIN与LeNet-5对比

通过Keras实现NIN的代码片段如下,全部实验内容见随书资料。

NIN = Sequential()
NIN.add(Conv2D(input_shape=(28,28,1), filters= 8, kernel_size = (5,5),
        padding = 'same',activation = 'relu'))
NIN.add(Conv2D(input_shape=(28,28,1), filters= 8, kernel_size = (1,1),
        padding = 'same',activation = 'relu'))
NIN.add(Flatten())
NIN.add(Dense(196,activation = 'relu'))
NIN.add(Reshape((14,14,1),input_shape = (196,1)))
NIN.add(Conv2D(16,(5,5),padding = 'same',activation = 'relu'))
NIN.add(Conv2D(16,(1,1),padding = 'same',activation = 'relu'))
NIN.add(Flatten())
NIN.add(Dense(120,activation = 'relu'))
NIN.add(Dense(84,activation = 'relu'))
NIN.add(Dense(10))
NIN.add(Activation('softmax'))
NIN.summary()

对比全连接,NIN中的卷积操作保存了网络隐层节点和输入图像的位置关系,卷积的这个特点使其在物体检测和分割任务上得到了更广泛的应用。除了保存特征图的位置关系,卷积还有两个用途:

实现特征图的升维和降维;

实现跨特征图的交互。

另外,NIN提出了使用全局平均池化(global average pooling)来减轻全连接层的过拟合问题,即在卷积的最后一层直接对每个特征图求均值,然后执行softmax操作。

1.3.2 Inception v1

GoogLeNet的核心部件叫作Inception。根据感受野的递推公式,不同大小的卷积核对应不同大小的感受野。例如在VGG的最后一层,卷积核的感受野分别是196、228、260。我们根据感受野的计算公式也可以知道,网络的层数越多,不同大小的卷积核对应在原图的感受野的大小差距越大,这也就是Inception通常在越深的层次中效果越明显的原因。在每个Inception模块中,作者并行使用了这3个不同大小的卷积核。同时,考虑到池化一直在CNN中扮演着积极的作用,所以作者建议Inception中也要加入一个并行的步长为1的最大池化。至此,一个朴素版本的Inception便诞生了,如图1.19所示。

图1.19 朴素版本的Inception

但是这个朴素版本的Inception会使网络的特征图的数量乘4。随着Inception数量的增长,特征图的数量会呈指数级增长,这意味着大量计算资源被消耗。为了提升运算速度,Inception使用了NIN中介绍的卷积在卷积操作之前进行降采样,由此便诞生了Inception v1,如图1.20所示。

图1.20 Inception v1结构

Inception的代码也比较容易实现,建立4个并行的分支并在最后将其合并到一起即可。为了在MNIST数据集上使用Inception,我使用了更窄的网络(特征图的数量均为4,官方特征图的数量已注释在代码中)。

def inception(x):
    inception_1x1 = Conv2D(4,(1,1), padding='same', activation='relu')(x) #64
    inception_3x3_reduce = Conv2D(4,(1,1), padding='same', activation='relu')(x) #96
    inception_3x3 = Conv2D(4,(3,3), padding='same', activation='relu')
        (inception_3x3_reduce) #128
    inception_5x5_reduce = Conv2D(4,(1,1), padding='same', activation='relu')(x) #16
    inception_5x5 = Conv2D(4,(5,5), padding='same', activation='relu')
        (inception_5x5_reduce) #32
    inception_pool = MaxPool2D(pool_size=(3,3), strides=(1,1), padding='same')(x) #192
    inception_pool_proj = Conv2D(4,(1,1), padding='same', activation='relu')
        (inception_pool) #32
    inception_output = merge([inception_1x1, inception_3x3, inception_5x5, 
                              inception_pool_proj], mode='concat', concat_axis=3)
    return inception_output

图1.21展示了使用相同通道数的卷积核的Inception在MNIST数据集上收敛速度的对比。从实验结果可以看出,对于比较小的数据集,Inception的提升非常有限。对比两个网络的容量,我们发现Inception和采用相同特征图的卷积拥有相同数量的参数,实验内容见随书资料。

图1.21 Inception与CNN对比

1.3.3 GoogLeNet

GoogLeNet的命名方式是为了致敬第一代深度卷积网络LeNet-5,作者通过堆叠Inception的方法构造了一个包含9个Inception模块、共22层的网络,并一举拿下了2014年ILSVRC的物体分类任务的冠军。GoogLeNet的网络结构如图1.22所示,高清大图参考其论文。

图1.22 GoogLeNet的网络结构

对比其他网络,GoogLeNet的一个最大的不同是在中间多了两个softmax分支作为辅助损失(auxiliary loss)函数。在训练时,这两个softmax分支的损失会以0.3的比例添加到损失函数上。根据论文的解释,该分支有如下两个作用:

保证较低层提取的特征也有分类的能力;

具有提供正则化并解决梯度消失问题的能力。

需要注意的是,在测试的时候,这两个softmax分支会被移除。

辅助损失函数的提出,是为了遵循信息论中的数据处理不等式(data processing inequality, DPI)原则。所谓数据处理不等式,是指数据处理的步骤越多,则丢失的信息也会越多,其数学建模方式如式(1.9)所示。

  (1.9)

式(1.9)表明,在数据传输的过程中,信息有可能消失,但绝对不会凭空增加。反映到反向传播中,也就是在计算梯度的时候,梯度包含信息的损失会逐层减少,所以GoogLeNet的中间层添加了两组损失函数以防止信息的过度丢失。

1.3.4 Inception v2

我们知道,一个的卷积核与两个的卷积核拥有相同大小的感受野,但是两个的卷积核拥有更强的拟合能力,所以在Inception v2[12]的版本中,作者将的卷积核替换为两个的卷积核,如下面代码所示。Inception v2如图1.23所示。


[12] 参见G. E. Hinton、N. Srivastava、A. Krizhevsky等人的论文“Improving neural networks by preventing co-adaptation of feature detectors”。

def inception_v2(x):
    inception_1x1 = Conv2D(4,(1,1), padding='same', activation='relu')(x)
    inception_3x3_reduce = Conv2D(4,(1,1), padding='same', activation='relu')(x)
    inception_3x3 = Conv2D(4,(3,3), padding='same', activation='relu')
        (inception_3x3_reduce)
    inception_5x5_reduce = Conv2D(4,(1,1), padding='same', activation='relu')(x)
    inception_5x5_1 = Conv2D(4,(3,3), padding='same', activation='relu')
        (inception_5x5_reduce)
    inception_5x5_2 = Conv2D(4,(3,3), padding='same', activation='relu')
        (inception_5x5_1)
    inception_pool = MaxPool2D(pool_size=(3,3), strides=(1,1), padding='same')(x)
    inception_pool_proj = Conv2D(4,(1,1), padding='same', activation='relu')
        (inception_pool)
    inception_output = merge([inception_1x1, inception_3x3, inception_5x5_2, 
                              inception_pool_proj], mode='concat', concat_axis=3)
    return inception_output

图1.23 Inception v2

1.3.5 Inception v3

Inception v3[13]将Inception v1和Inception v2中的卷积换成一个和一个的卷积,如图1.24所示。这样做带来的好处有如下几点:


[13] 参见Christian Szegedy、Vincent Vanhoucke、Sergey Ioffe等人的论文“Rethinking the Inception Architecture for Computer Vision”。

(1)节约了大量参数,提升了训练速度,减轻了过拟合的问题;

(2)多层卷积增加了模型的拟合能力;

(3)非对称卷积核的使用增加了特征的多样性。

def inception_v3(x):
    inception_1x1 = Conv2D(4,(1,1), padding='same', activation='relu')(x)
    inception_3x3_reduce = Conv2D(4,(1,1), padding='same', activation='relu')(x)
    inception_3x1 = Conv2D(4,(3,1), padding='same', activation='relu')
        (inception_3x3_reduce)
    inception_1x3 = Conv2D(4,(1,3), padding='same', activation='relu')(inception_3x1)
    inception_5x5_reduce = Conv2D(4,(1,1), padding='same', activation='relu')(x)
    inception_5x1 = Conv2D(4,(5,1), padding='same', activation='relu')
        (inception_5x5_reduce)
    inception_1x5 = Conv2D(4,(1,5), padding='same', activation='relu')(inception_5x1)
    inception_pool = MaxPool2D(pool_size=(3,3), strides=(1,1), padding='same')(x)
    inception_pool_proj = Conv2D(4,(1,1), padding='same', activation='relu')
        (inception_pool)
    inception_output = merge([inception_1x1, inception_1x3, inception_1x5, 
                              inception_pool_proj], mode='concat', concat_axis=3)
    return inception_output

图1.24 Inception v3

1.3.6 Inception v4

Inception v4[14]的论文中提出了Inception v4、Inception-ResNet v1和Inception-ResNet v2共3个模型架构。其中Inception v4延续了Inception v2和Inception v3的思想,而Inception-ResNet v1和Inception-ResNet v2则将Inception和残差网络进行了结合。


[14] 参见Christian Szegedy、Sergey Ioffe、Vincent Vanhoucke等人的论文“Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning”。

Inception v4的整体结构如图1.25所示,它的核心模块是一个骨干(Stem)模块、3个不同的Inception和两个不同的缩减(Reduction)模块。

图1.25 Inception v4的整体结构

图1.26所示的是Inception v4和Inception-ResNet v2的骨干模块,它由线性结构和包含两路分支的Inception结构组成。特征图的降采样通过步长为2的卷积来完成。图1.26中带有“V”符号的表示padding = 0的有效卷积。

图1.26 Inception v4和Inception-ResNet v2的骨干模块

图1.27所示的是Inception v4的3个Inception模块,这3个Inception模块并不会改变输入特征图的尺寸。从3个Inception模块的结构中我们可以看出它基本沿用了Inception v2和Inception v3的思想,即使用多层小卷积核代替单层大卷积核和变形卷积核。

图1.27 Inception v4的3个Inception模块

图1.28所示的是Inception v4的缩减模块,它也沿用了Inception v2和Inception v3的思想,并使用步长为2的卷积或者池化来进行降采样,其中Reduction-A也复用到了1.3.7节介绍的Inception-ResNet模块中。

图1.28 Inception v4的缩减模块

1.3.7 Inception-ResNet

Inception-ResNet共有v1和v2两个版本,它们都将Inception和残差网络的思想进行了整合,并且拥有相同的流程框架,如图1.29所示。

图1.29 Inception-ResNet v1和Inception-ResNet v2的流程框架

1.Inception-ResNet v1

Inception-ResNet v1的骨干模块并没有使用并行结构,仅仅由不同类型(填充、步长和卷积核尺寸)的卷积操作组成,如图1.30所示。

图1.30 Inception-ResNet v1的骨干模块

Inception-ResNet v1在Inception模块中插入了一条捷径,也就是将Inception和残差网络的思想进行了结合。它的3个Inception模块的结构如图1.31所示。

图1.31 Inception-ResNet v1的3个Inception模块的结构

Inception-ResNet v1的Reduction-A复用了Inception v4的Reduction-A,它的Reduction-B的结构如图1.32所示。

图1.32 Inception-ResNet v1的Reduction-B的结构

2.Inception-ResNet v2

Inception-ResNet v2采用了Inception v4的骨干模块,它的Reduction-A则和其他两个模块保持相同,剩下的3个Inception模块和Reduction-B的结构分别如图1.33和图1.34所示。

图1.33 Inception-ResNet v2的3个Inception模块的结构

从图1.33中可以看出,Inception-ResNet v2的3个Inception模块分别和Inception-ResNet v1的3个Inception模块保持了相同的网络结构,不同的仅有通道数。图1.34体现了Inception-ResNet v2的Reduction-B和图1.32的Inception-ResNet v1的Reduction-B一样具有相同架构、不同通道数的特点。

图1.34 Inception-ResNet v2的Reduction-B的结构

3.残差的缩放

作者重新研究了残差连接的作用,指出残差连接并不会明显提升模型精度,而是会加快训练收敛速度。另外,引入残差连接以后,网络太深了,不稳定,不太好训练,到后面模型的参数可能全变为0了,可通过引入尺度变量(scale)来使得网络更加稳定,如图1.35所示,其中Inception可以用任意其他子网络替代,将其输出乘一个很小的缩放系数(通常在0.1到0.3内),激活缩放之后执行单位加和ReLU激活。

图1.35 Inception v4中提出的残差缩放