9.2 经典深度神经网络与数据增强

> 导入ImageNet图像分类模型

怎样导入分类模型呢?这个过程可以分解为两个步骤:

首先,要选择预训练好的ImageNet图像识别模型。

然后,对这个模型进行导入。

在选择预训练好的ImageNet图像模型之前,先来简单介绍一下经典的卷积神经网络,这样大家就可以根据自己项目的需求来选择合适自己的、已经预训练好的图像识别模型。

> 经典卷积神经网络

卷积神经网络的演进从LeNet到AlexNet到VGGNet、GoogleNet到ResNet。演进的方式是有一定规律的,并且它们也都在ImageNet LSVRC竞赛上,用120万张图片,1000类的标记,取得了很好的成绩。

卷积神经网络发展的起点是神经认知机模型,在当时已经出现了卷积结构。在1989年时,Yann Lecun提出了第一个卷积神经网络,在1998年时,他又提出了LeNet,但随后,这个卷积神经网络的锋芒逐渐被支持向量积等手工设计的特征的分类器盖过,随着ReLU和Dropout的提出,以及GPU和大数据带来的一些历史机遇,卷积神经网络在2012年迎来了历史性的突破。

从图中可以看出,AlexNet之后的卷积神经网络的演化过程主要有四个方向:网络加深,增加卷积层的功能,从分类任务到检测任务以及增加新的功能模块。下面,就简单地从网络加深和增加卷积层的功能这两个分支去介绍神经网络结构的一个演变的过程。

> AlexNet

AlexNet是2012年ImageNet图像分类竞赛冠军,它之所以能成功地让卷积的方法重新回到人们的视野,在于使用了如下的方法:

首先是防止过拟合。在防止过拟合方面,它使用了两个方法:一个是数据增强(data augmentation),一个是dropout。

> 数据增强

数据增强是增加训练数据来防止过拟合。当训练数据非常有限的时候,可以通过一些变换,用已有的训练集生成一些新的数据,来扩大训练数据量。

通常可以采用以下几个方式:水平翻转、改变对比度、随机裁剪。

通过这些方式,训练的数据集可以有效地增加。

由于数据增强是图像任务中很常用的技术,所以我们来学习几个TensorFlow的图像处理函数。

首先是图像的编码处理

一张RGB色彩模式的图像可以看成一个三维矩阵,矩阵中每一个数表示了图像上不同位置、不同颜色的亮度。但图像在存储的时候,并不是直接记录这些矩阵中的数字,而是记录经过压缩编码之后的结果。所以,要将一张图像还原成一个三维矩阵,还需要解码的过程。

TensorFlow提供了对jpg和png格式图像的编码和解码函数。

上图示范了如何使用TensorFlow中对jpg格式图像进行编码的函数。

首先读取图像的原始数据,该图像的格式是jpg,然后利用tf.image.decode_jpeg()函数对图像进行jpg格式的解码,从而得到图像对应的三维矩阵。TensorFlow还提供了tf.image.decode_png()函数,可以对png格式的图像进行解码。解码之后的结果是一个张量,所以需要eval获取它的值,并通过print输出。最后,对这张图像进行可视化:

它输出了解码之后的三维矩阵以及可视化输出的图像。

下面将继续使用这张图像来介绍TensorFlow其他图像处理的函数。

一般来说,网络上获取图像的大小不是固定的,但神经网络输入的结点个数是固定的。比如在MNIST案例中,输入层是28×28=784个结点,同样的,在其他神经网络中也是这样,它的输入结点的个数是固定的。所以,在将图像的像素作为输入提供给神经网络之前,先要将图像的大小进行统一,这就是图像缩放。

TensorFlow提供了四种不同的方法,并且把它们都封装到tf.image.resize_images()函数里。

它有一个参数是method,method的取值有四个,对应了四种不同的插值法。

method=0时,采用的是双线性插值法。

method=1时,采用的是最近邻插值法。

method=2时,采用的是双立方插值法。

method=3时,采用的是像素区域插值法(AREA)。

如果运行这四种代码会发现,不同的算法调整出来的结果差别并不大。

除了将整张图像的信息完整保存,TensorFlow还提供了API对图像进行裁剪或是填充。

上面这两段代码分别表示了通过TensorFlow的函数对图像进行裁剪和填充。

第一段代码的运行结果(裁剪):

第二段代码的运行结果(填充):

如果原始图像的尺寸大于我们的目标尺寸,比如这里原始图像的尺寸是400和400,这个函数就会自动截取原图中居中的部分。因此,第一段代码的输出图像就是原图最中央的地方。

如果目标图像的尺寸大于原始图像的尺寸,比如这里目标图像的尺寸是2000和2000,这个函数就会自动在原始图像的四周补充全零背景,也就是黑色。

上面介绍的图像裁剪函数都是截取或者填充图像的中央部分的,其实TensorFlow还提供了其他一些函数来随机裁剪或是填充这些图像。

在这个函数里,比如现在想输出的图像是600×600的尺寸,比原来小,就需要对它进行裁剪。如果重复输出几次,会发现每次的结果都是不一样的,说明它是随机裁剪。

TensorFlow还提供了一些函数支持图像翻转。

水平翻转:

上下翻转:

为什么要进行翻转呢?因为在很多图像识别问题中,图像的翻转不应该影响识别的结果,所以,在训练图像识别的神经网络模型时,可以随机翻转训练图像,这样训练得到的模型,可以识别不同角度的实体。比如在训练数据中,所有狗的朝向是向右的,那么训练出来的模型就没办法很好地识别狗的朝向是向左的照片,对它的分类有可能不是很准确。虽然这个问题可以通过收集更多的训练数据来解决,但随机翻转训练图像的方式,是在零成本的情况下解决或者说缓解这个问题的。所以随机翻转训练是非常好用和常用的图像预处理方式。

tf.image.flip_left_right()函数,实现了对图像的水平翻转(左右翻转)。tf.image.flip_up_down()函数,实现了对图像的上下翻转,就像湖面上的倒影一样。

在训练VGGNet、ResNet等大型的神经网络的时候,都采用了对图像进行翻转的技术。

和图像翻转类似,调整图像的亮度、对比度、饱和度和色相等,在很多图像识别应用中,都不会影响识别的结果。所以,在训练神经网络模型的时候,可以随机调整训练图像的这些属性,从而使得训练得到的模型尽可能小的受到无关因素的影响。

以下代码显示了如何修改图像的对比度:

修改图像的对比度有两个函数,一个是tf.image.adjust_contrast()函数,当你输入一个比1小的参数时,对比度降低,反之,对比度提高。降低对比度和提高对比度得到的结果相差还是挺大的。还有一个是tf.image.random_contrast()函数,它会在给定的范围内随机调整图像的对比度,它所输入的参数是下限和上限。

除了调整图像的亮度、对比度、色相等,TensorFlow还提供了一些函数对图像进行标准化。标准化就是将图像上亮度的均值变成0,方差变成1。

它可以通过下图代码实现:

这里主要是调用了tf.image.per_image_standardization()函数对图像进行标准化(归一化)。这是经过归一化处理之后的图像:

利用这些函数对图像进行预处理或者说对样本进行数据增强,一般都可以获得比没有使用图像增强的时候更好的一些效果。

当然,TensorFlow所提供的对图像进行预处理的函数不仅只有刚刚介绍的这些,其他的大家可以回去试试。

> dropout

另一个防止过拟合的技巧是dropout。AlexNet以一定的概率(比如0.5),将每个隐层的神经元的输出设为0。

上图中打叉的地方说明,这个神经元不参与前向和后向传播。因此,每次输一个样本,就相当于这个神经网络尝试了一种新的结构。但这些所有的结构之间的权重是共享的,这种技术降低了神经元复杂的一个的互适应的关系,但dropout的缺点非常明显,它使得收敛所需要的迭代次数增加大概一倍左右。

除了数据增强和dropout,AlexNet还使用了非线性激活函数ReLU代替之前的Sigmoid函数。用了ReLu后得到的随机梯度下降的收敛速度比之前用Sigmoid和用tanh的时候快很多。

此外,AlexNet将网络分布到两个GPU上:

特征图分为了两组,各自在一个独立的GPU上进行运算,这样能够直接从另外一个GPU的内存读入和写出而不需要通过主机内存,因此它能够极大地增加训练的规模。

> VGGNet

VGGNet和后面要提到的GoogleNet分别是2014年ImageNet竞赛的第二名和第一名。那么,VGGNet跟AlexNet有什么相同和不同呢?VGGNet可以看成是加深版本的AlexNet。

可以看一下上面这个表格,这个表格里的Conv X-Y,X表示卷积核的尺寸,Y表示卷积核的深度,即输出通道的数量。我们可以看到从配置A到配置E,网络的层数在逐步增加。配置A一共有11层,配置E一共有19层。

VGGNet和AlexNet的相似之处在于都使用了5个卷积组,不管是配置A还是B、C、D、E都使用了5个卷积组。类似的,AlexNet使用了5个卷积层,在卷积层后接了全连接层以及softmax分类层。他们的总体结构非常相像,所以说VGGNet是加深版本的AlexNet。

VGGNet和AlexNet的区别在哪里呢?首先,VGGNet的层数更多。 AlexNet只有8层,VGGNet有11~19层,其次,在VGGNet中,卷积核的大小绝大部分都是3×3的,而在AlexNet里还使用了5×5的卷积核以及11×11的大尺寸卷积核。还有就是卷积核的深度,在VGGNet里,卷积核的深度是很明显逐层递增的,从64到128到256到512,而在AlexNet里,虽然后面的层数也是比较多的,但是没有呈现特别明显的逐层递增。

在VGGNet这一系列中,VGG-16和VGG-19用的非常多,经常用作迁移学习里的预训练模型。如果去看VGGNet论文可以知道,随着卷积层从配置A到配置E的一步步加深,通过加深卷积核的层数已经达到准确率提升的瓶颈了,这个时候再加深模型,错误率已经非常难以降低了,所以,在VGGNet中,没有提出VGG-29、39、49,而是只到了19,就已经是VGGNet系列里面最好的配置方案了。

> GoogleNet

GoogleNet是由谷歌公司提出的。

在2014年获得了LSVRC挑战赛的冠军,当时它将top-5错误率降到了百分之六点多。关于GoogleNet具体的技术细节在这里就不一一展开了。

总的来说,GoogleNet的思想主要是围绕着深度和宽度去实现的。在深度上,它的层数比之前提出的神经网络更深,在论文里,它用了22层,为了避免梯度消失的问题,GoogleNet很巧妙地在网络的不同深度的地方增加了两个损失函数(箭头指向的地方)来避免反向传播时的梯度消失问题。在宽度方面,GoogleNet增加了多种大小的卷积核。

可以看一下更具体的结构图:

在论文里提出了很多种类似上图的结构,把它称之为Inception结构或Inception模块。左边这个是最原始的Inception设计,在这里它增加了多种大小的卷积核。在原来的神经网络里,输入、卷积、输出,中间卷积部分只有单一尺度的卷积核,而在Inception里,采用了1×1、3×3、5×5多尺度的卷积核,然后再把这些不同尺度的卷积核连接起来,作为下一层的输入。但这样会有一个问题,把它们都连接起来之后,特征映射的厚度会非常大,原来只有单一尺寸的话,5×5尺寸的卷积核有128个,现在增加了1×1、3×3的卷积核,还要增加最大池化层,厚度就会非常大。为了解决这个问题,提出了Inception为0的一个降维版本,即Inception V1。它在3×3、5×5的卷积之前以及最大池化之后都分别增加了1×1的卷积核。这个1×1卷积核的作用就是降维。在使用1×1的卷积核进行降维之后,网络的宽度和深度同时都得到了扩大。实验结果发现,我们使用了Inception结构可以达到2到3倍的加速。

> ResNet

VGG-16是AlexNet的网络加深, GoogleNet增加了卷积层的功能,把网络加深和增加卷积层功能这两个演化的方向结合起来,就诞生了ResNet。

ResNet是2015年LSVRC竞赛中物体检测和物体识别两个项目的冠军,他的训练深度达到了152层。

从上图可以看到,ResNet是非常深的。由于这里地方不够,就没有把完整的结构图展开,大家可以去看一下ResNet的一些结构。

ResNet怎样实现卷积功能的增强呢?按照一般的经验,只要没有发生梯度消失或梯度爆炸,而且不过拟合的话,网络应该是越深越好的,但是论文作者发现,层数从20层增加到56层,训练的错误率上升了。

这个现象不仅仅在训练上,在测试上也是,所以它并不是一个过拟合问题。那为什么会导致这个现象呢?是梯度消失。梯度消失导致了网络没办法对前面网络层的权重进行很有效的调整,这种情况也被称为网络退化。因此,ResNet中引入了shortcut connections,即捷径连接。

捷径连接指的就是这个弧线的箭头部分。它将输入直接跳层传递与卷积核的结果相加,并不依次经过中间层。关于ResNet里残差的含义:如果能用几层网络逼近一个复杂的非线性映射,那么同样也可以逼近它的残差映射,而且残差映射比复杂的映射学习起来要简单的多。

因此,ResNet是一个非常有效的网络结构。它使得我们的学习更加高效并且错误率大大地降低。

TensorFlow目前有完善的预训练模型社区:TensorFlow Model Zoo

大家可以了解一下,并对自己感兴趣的模型进行下载。在熟练之后,可以找到跟自己需求模型相近的预训练模型进行训练。

Last updated