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

1.1 起源:LeNet-5和AlexNet

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

BN(6.2节);

Dropout(6.1节)。

1.1.1 从LeNet-5开始

使用CNN解决图像分类问题可以往前追溯到1998年LeCun发表的论文[1],其中提出了用于解决手写数字识别问题的LeNet。LeNet又名LeNet-5,是因为在LeNet中使用的均是的卷积核。LeNet-5的网络结构如图1.1所示。


[1] 参见Yann LeCun、Léon Bottou、Yoshua Bengio等人的论文“Gradient-based learning applied to document recognition”。

图1.1 LeNet-5的网络结构

LeNet-5中使用的结构直接影响了其后的几乎所有CNN,卷积层 + 降采样层 + 全连接层至今仍然是最主流的结构。卷积操作使网络可以响应和卷积核形状类似的特征,而降采样操作则使网络拥有了一定程度的不变性。下面我们简单分析一下LeNet-5的网络结构。

输入:的手写数字(数据集中共10类)的黑白图片。

C1:C1层使用了6个卷积核,每个卷积核的大小均是,pad = 0,stride = 1(有效卷积,与有效卷积对应的是same卷积),激活函数使用的是tanh(双曲正切),表达式为式(1.1),tanh激活函数的值域是。所以在第一次卷积之后,特征图的大小变为,该层共有个神经元。加上偏置,该层共有个参数。

  (1.1)

S2:S2层是CNN常使用的降采样层。在LeNet-5中,降采样的过程是将窗口内的3个输入相加,乘一个可训练参数再加上一个偏置。经过S2层,特征图的大小缩小,变成。该层共有个神经元,参数数量是

C3:C3层跟S2层并不是密集连接的,具体连接方式是,C3层的前6个特征图以S2层中3个相邻的特征图子集为输入,接下来6个特征图以S2层中4个相邻特征图子集为输入,然后的3个特征图以不相邻的4个特征图子集为输入,最后一个特征图以S2层中所有特征图为输入,如图1.2所示。这两个层采用的稀疏连接的方式已被抛弃,目前普遍使用的是密集连接,或轻量级网络中使用的深度可分离卷积、分组卷积。

图1.2 LeNet-5中C3层和S2层的连接方式

C3层包括16个大小为、通道数为6的same卷积,pad = 0,stride = 1,激活函数同样为tanh。一次卷积后,特征图的大小是,神经元数量为,可训练参数数量为

S4:与S2层的计算方法类似,该层使特征图的大小变成,共有个神经元,可训练参数数量是

C5:节点数为120的全连接层,激活函数是tanh,参数数量是

F6:节点数为84的全连接层,激活函数是tanh,参数数量是

输出:10个分类的输出层,使用的是softmax激活函数,如式(1.2)所示,参数数量是。softmax用于分类有如下优点:

ex使所有样本的值均大于0,且指数的性质使样本的区分度尽量高;

softmax所有可能值的和为1,反映出分类为该类别的概率,输出概率最高的类别即可。

  (1.2)

使用Keras搭建LeNet-5网络的核心代码如下,其是基于LeNet-5网络,在MNIST手写数字识别数据集上的实现。完整的LeNet-5在MNIST上的训练过程见随书资料。

注意,这里使用的都是密集连接,没有复现C3层和S2层之间的稀疏连接。

# 构建LeNet-5网络
model = Sequential()
model.add(Conv2D(input_shape = (28,28,1), filters=6, kernel_size=(5,5), 
          padding='valid', activation='tanh'))
model.add(MaxPool2D(pool_size=(2,2), strides=2))
model.add(Conv2D(input_shape=(14,14,6), filters=16, kernel_size=(5,5), 
          padding='valid', activation='tanh'))
model.add(MaxPool2D(pool_size=(2,2), strides=2))
model.add(Flatten())
model.add(Dense(120, activation='tanh'))
model.add(Dense(84, activation='tanh'))
model.add(Dense(10, activation='softmax'))

如图1.3所示,经过10个epoch后,LeNet-5基本收敛。

图1.3 LeNet-5在MNIST数据集上的收敛情况

1.1.2 觉醒:AlexNet

LeNet-5之后,CNN沉寂了约14年。直到2012年,AlexNet在ILSVRC中一举夺魁,直接把在ImageNet数据集上的精度提升了约10个百分点,它将CNN的深度和宽度都提升到了传统算法无法企及的新高度。从此,深度学习开始在CV的各个领域“披荆斩棘”,至今深度学习仍是人工智能最热门的话题。AlexNet作为教科书式的网络,值得每个学习深度学习的人深入研究。

AlexNet的名字取自该模型的第一作者Alex Krizhevsky。AlexNet在ImageNet中的120万张图片的1 000类分类任务上的top-1错误率是37.5%,top-5错误率则是15.3%(直接比第二名的26.2%低了约10个百分点)。AlexNet如此成功的原因是其使网络的宽度和深度达到了前所未有的高度,而该模型也使网络的可学习参数达到了58 322 314个。为了学习这些参数,AlexNet并行使用了两块GTX 580,大幅提升了训练速度。

笔记 AlexNet当初使用分组卷积是因为硬件资源有限,不得不将模型分到两块GPU上运行。相关研究者并没有给出分组卷积的概念,而且没有对分组卷积的性能进行深入探讨。ResNeXt的相关研究者则明确给出了分组卷积的定义,并证明和验证了分组卷积有接近普通卷积的精度。

当想要使用机器学习解决非常复杂的问题时,我们必须使用容量足够大的模型。在深度学习中,增加网络的宽度和深度会提升网络的容量,但是提升容量的同时也会带来两个问题:

计算资源的消耗;

模型容易过拟合。

计算资源是当时限制深度学习发展的瓶颈,2011年Ciresan等人提出了使用GPU部署CNN的技术框架[2],由此深度学习得到了可以解决其计算瓶颈问题的硬件支持。


[2] 参见Dan C. Ciresan、Ueli Meier、Jonathan Masci等人的论文“Flexible, High Performance Convolutional Neural Networks for Image Classification”。

下面来详细分析一下AlexNet。AlexNet的网络结构如图1.4所示。

图1.4 AlexNet的网络结构

AlexNet基于Keras的实现代码如下。

# 构建AlexNet网络
model = Sequential()
model.add(Conv2D(input_shape = (227,227,3), strides = 4, filters=96, kernel_size=(11,11),
          padding='valid', activation='relu'))
model.add(BatchNormalization())
model.add(MaxPool2D(pool_size=(3,3), strides=2))
model.add(Conv2D(filters=256, kernel_size=(5,5), padding='same', activation='relu'))
model.add(BatchNormalization())
model.add(MaxPool2D(pool_size=(3,3), strides=2))
model.add(Conv2D(filters=384, kernel_size=(3,3), padding='same', activation='relu'))
model.add(BatchNormalization())
model.add(Conv2D(filters=384, kernel_size=(3,3), padding='same', activation='relu'))
model.add(BatchNormalization())
model.add(Conv2D(filters=256, kernel_size=(3,3), padding='same', activation='relu'))
model.add(BatchNormalization())
model.add(MaxPool2D(pool_size=(2,2), strides=2))
model.add(Flatten())
model.add(Dense(4096, activation='tanh'))
model.add(Dropout(0.5))
model.add(Dense(4096, activation='tanh'))
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))
model.summary()

根据Keras提供的summary()函数,可以得到图1.5所示的AlexNet的参数数量的统计结果[3],计算方法参照LeNet-5,不赘述。


[3] 这里参数数量不同是因为代码没有将模型部署在两块显卡上。

图1.5 通过Keras的summary()函数得到的AlexNet参数数量

1.多GPU训练

首先对比图1.1和图1.4,我们发现AlexNet将网络分成了两个部分。由于当时显卡的显存大小有限,因此作者使用了两块GPU并行训练模型,例如第二个卷积(图1.4中通道数为128的卷积)只使用一个GPU自身显存中的特征图,而第三个卷积需要使用另外一个GPU显存中的特征图。不过得益于TensorFlow等开源框架对多机多卡的支持和显卡显存的提升,AlexNet部署在单块GPU上已毫无压力,所以这一部分就不赘述。

2.ReLU

在LeNet-5中,使用了tanh作为激活函数,tanh的函数曲线如图1.6所示。tanh是一个以原点为中心点、值域为(-1,1)的激活函数。在反向传播过程中,局部梯度会与整个损失函数关于该局部输出的梯度相乘。当tanh(x)中的x的绝对值比较大的时候,该局部的梯度会非常接近于0,在深度学习中,该现象叫作“饱和”。同样,另一个常用的sigmoid激活函数也存在饱和的现象。sigmoid的函数如式(1.3)所示,函数曲线如图1.7所示。

  (1.3)

图1.6 tanh的函数曲线

图1.7 sigmoid的函数曲线

饱和现象带来了一个深度学习中非常严重的问题,那便是梯度消失。梯度消失是由反向传播中链式法则的乘法特性导致的,反映在深度学习的训练过程中便是越接近损失函数的参数梯度越大,从而使得这一部分参数成为主要学习的参数,而远离损失函数的参数的梯度则非常接近0,导致几乎没有梯度传到这一部分参数,从而使得这一部分参数很难学习到。

为了解决这个问题,AlexNet引入了ReLU激活函数,如式(1.4)所示。

  (1.4)

ReLU的函数曲线如图1.8所示。

图1.8 ReLU的函数曲线

在ReLU中,无论x的取值有多大,f(x)的导数都是1,也就不存在导数小于1导致的梯度消失的现象了。图1.9所示的是我们在MNIST数据集上,根据LeNet-5使用tanh和ReLU两个激活函数得到的不同模型的收敛情况,旨在对比两个不同的激活函数的模型效果。

图1.9 LeNet-5使用不同激活函数的收敛情况

此外,由于ReLU将小于0的部分全部置0,因此ReLU的另外一个特点就是具有稀疏性,不仅可以优化网络的性能,还可以缓解过拟合现象。

虽然使用ReLU的节点不会有饱和问题,但是会“死掉”,即大部分甚至所有的值为负值,从而导致该层的梯度都为0。“死神经元”是由进入网络的负值引起的(例如在大规模的梯度更新之后可能出现),减小学习率能缓解该现象。

3.LRN

局部响应归一化(local response normalization,LRN)模拟的是动物神经中的横向抑制效应,是一个已经被淘汰的算法。在VGG[4]的相关论文中已经指出,LRN并没有什么效果。在现在的网络中,LRN已经被其他归一化方法所替代,例如在上面代码中使用的批归一化(batch normalization,BN)[5]。LRN是使用同一位置临近的特征图来归一化当前特征图的值的一种方法,其表达式如式(1.5)所示:


[4] 参见Karen Simonyan、Andrew Zisserman的论文“Very Deep Convolutional Networks for Large-Scale Image Recognition”。

[5] 参见Sergey Ioffe、Christian Szegedy的论文“Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift”。

  (1.5)

其中,N表示特征图的数量,a是输入特征图,b是输出特征图,(x, y)是特征图上的坐标,,这些值均由验证集得出。

另外,AlexNet把LRN放在池化层之前,这在计算上是非常不经济的,一种更好的做法是把LRN放在池化层之后。

4.覆盖池化

当进行池化的时候,如果步长(stride)小于池化核的尺寸,相邻的池化核会相互覆盖,这种方式叫作覆盖池化(overlap pooling)。AlexNet的论文中指出这种方式可以缓解过拟合。

5.Dropout

在AlexNet的前两层,作者使用了Dropout[6]来缓解容量高的模型容易发生过拟合的现象。Dropout的使用方法是在训练过程中随机将一定比例的隐层节点置0。Dropout能够缓解过拟合的原因是每次训练都会采样一个不同的网络结构,但是这些架构是共享权值的。这种技术减轻了节点之间的耦合性,因为一个节点不能依赖网络的其他节点。因此,节点能够学习到更健壮的特征。只有这样,节点才能适应每次采样得到的不同的网络结构。注意在测试时,我们是不对节点进行丢弃的。


[6] 参见Nitish Srivastava、Geoffrey Hinton、Alex Krizhevsky等人的论文“Dropout: A Simple Way to Prevent Neural Networks from Overfitting”。

虽然Dropout会减慢收敛速度,但其在缓解过拟合方面的优异表现仍旧使其在当前的网络中得到广泛的使用。

图1.10所示的是LeNet-5中加入Dropout之后模型的训练损失曲线。从图1.10中我们可以看出,加入Dropout之后,训练速度放缓了一些。20个epoch之后,训练集的损失函数曲线仍高于没有Dropout的。加入Dropout之后,虽然损失值为0.073 5,远高于没有Dropout的0.015 5,但是测试集的准确率从0.982 6上升到0.984 1。具体的实验数据见随书代码。可见Dropout对于缓解过拟合还是非常有帮助的。

图1.10 有Dropout与没有Dropout对比