5.3 VGG
VGG是牛津大学计算机视觉组和DeepMind公司共同研发的一种深度卷积神经网络,并在2014年的ILSVRC(ImageNet Large Scale Visual Recognition Competition)比赛上获得了分类项目的第二名和定位项目的第一名。VGG是Visual Geometry Group, Department of Engineering Science, University of Oxford的缩写。他们在参加ILSVRC 2014时,组名叫VGG,所以提交的网络结构也叫VGG,或者叫VGGNet。
5.3.1VGG网络结构
VGG使用小卷积核和增加卷积神经网络的深度提升分类识别效果。VGG共有6种网络结构,如图5.6所示,其中最广为流传的两种结构是VGG16和VGG19,两者并没有本质上的区别,只是网络深度不同,前者是16层,后者是19层。
图5.6 VGG原论文里的概述图
从图5.6可以看出,无论哪种网络结构,VGG都包含5组卷积操作,每组卷积包含一定数量的卷积层,所以这可以看作一个五阶段的卷积特征提取。每组卷积后都进行一个2×2的最大值池化,最后是三个全连接层。尽管A~E网络的结构在逐步加深,但是参数个数并没有显著增加,这是因为最后3个全连接层的参数占据了绝大多数,而这3层在A~E这5种网络结构中是完全相同的。接下来以VGG16为例,介绍VGG的网络结构,它有5组卷积层和3个全连接层。
输入层:224像素×224像素×3的彩色图像。
第1组卷积层(2次卷积):Conv2D(3×3,64), Stride(1), same, ReLU, Output: 224×224×64。
第1个池化层:MaxPooling2D(2×2), Stride(2), Output: 112×112×64。
第2组卷积层(2次卷积):Conv2D(3×3,128), Stride(1), same, ReLU, Output: 112×112×128。
第2个池化层:MaxPooling2D(2×2), Stride(2), Output:56×56×128。
第3组卷积层(3次卷积):Conv2D(3×3,256), Stride(1), same, ReLU, Output: 56×56×256。
第3个池化层:MaxPooling2D(2×2), Stride(2),Output:28×28×256。
第4组卷积层(3次卷积):Conv2D(3×3,512), Stride(1), same, ReLU, Output: 28×28×512。
第4个池化层:MaxPooling2D(2×2),Stride(2), Output:14×14×512。
第5组卷积层(3次卷积):Conv2D(3×3,512), Stride(1), same, ReLU, Output: 14×14×512。
第5个池化层:MaxPooling2D(2×2),Stride(2), Output:7×7×512。
输出层:Flatten,Dense(4096),Dense(4096),Dense(1000)。
最后输出的全连接层有1 000个神经元,这是因为VGG处理的是1000分类问题。图5.7为VGG16的网络结构图解。
图5.7 VGG16网络结构图解
5.3.2案例:加利福尼亚理工学院鸟类数据库分类
下面以加利福尼亚理工学院鸟类数据的分类为例,介绍VGG的代码实现,这是2011年的数据,一共有11788张图像,总共将鸟分成了200个类别。
01
数据准备与处理
首先利用5.2.3学习的数据生成器ImageDataGenerator()函数产生训练和测试数据集,具体如代码示例5-9。
代码示例5-9:数据生成器生成训练集与测试集
from Keras.preprocessing.image import ImageDataGeneratorIMSIZE = 224train_generator = ImageDataGenerator(rescale=1. / 255).flow_from_directory( './data_vgg/train', target_size=(IMSIZE, IMSIZE), batch_size=100, class_mode='categorical')validation_generator = ImageDataGenerator( rescale=1. / 255).flow_from_directory( './data_vgg/test', target_size=(IMSIZE, IMSIZE), batch_size=100, class_mode='categorical')
数据生成之后,将测试集中的前10张图像展示出来,具体如代码示例5-10所示。
代码示例5-10:图像展示
from matplotlib import pyplot as pltplt.figure()fig, ax = plt.subplots(2, 5)fig.set_figheight(6)fig.set_figwidth(15)ax = ax.flatten()X, Y = next(validation_generator)for i in range(10): ax[i].imshow(X[i, :, :, ])
输出:
02
VGG16代码实现
接下来用VGG16来解决鸟的分类问题,只要按照前面讲解的VGG网络结构一一对应实现即可,具体如代码示例5-11所示。
代码示例5-11:VGG16代码实现
from Keras.layers import Conv2D, MaxPooling2Dfrom Keras.layers import Flatten, Dense, Input, Activationfrom Keras import Modelfrom Keras.layers import GlobalAveragePooling2DIMSIZE = 224input_shape = (IMSIZE, IMSIZE, 3)input_layer = Input(input_shape)x = input_layerx = Conv2D(64, [3, 3], padding='same', activation='relu')(x)x = Conv2D(64, [3, 3], padding='same', activation='relu')(x)x = MaxPooling2D((2, 2))(x)x = Conv2D(128, [3, 3], padding='same', activation='relu')(x)x = Conv2D(128, [3, 3], padding='same', activation='relu')(x)x = MaxPooling2D((2, 2))(x)x = Conv2D(256, [3, 3], padding='same', activation='relu')(x)x = Conv2D(256, [3, 3], padding='same', activation='relu')(x)x = Conv2D(256, [3, 3], padding='same', activation='relu')(x)x = MaxPooling2D((2, 2))(x)x = Conv2D(512, [3, 3], padding='same', activation='relu')(x)x = Conv2D(512, [3, 3], padding='same', activation='relu')(x)x = Conv2D(512, [3, 3], padding='same', activation='relu')(x)x = MaxPooling2D((2, 2))(x)x = Conv2D(512, [3, 3], padding='same', activation='relu')(x)x = Conv2D(512, [3, 3], padding='same', activation='relu')(x)x = Conv2D(512, [3, 3], padding='same', activation='relu')(x)x = MaxPooling2D((2, 2))(x)x = GlobalAveragePooling2D()(x)x = Dense(200)(x)x = Activation('softmax')(x)output_layer = xmodel_vgg16 = Model(input_layer, output_layer)model_vgg16.summary()
通过model.summary(),依然可以获取VGG16的模型概要表,如图5.8所示。作为示例,前几层参数个数的计算过程如下。
(1)输入图像的像素大小是224像素×224像素×3,经过3×3的卷积,消耗的参数个数是3×3×3+1=28,因为有64个卷积核,因此消耗的参数个数就是28×64=1792。
(2)3×3的卷积,消耗参数个数为3×3×64+1=577,577×64=36928。
(3)2×2的池化,不消耗任何参数。
之后各层的参数个数请读者自行计算,并与图5.8所示的结果对比。
图5.8 VGG16模型概要表
03
VGG16编译运行
最后,通过编译运行上述代码发现,VGG16在该数据集上的分类准确率并不是很高,只有0.51%。一个可能的原因是GPU显存不足,设置的epochs和batch_size的参数不足。具体如代码示例5-12所示。
代码示例5-12:VGG模型的编译与拟合
from Keras.optimizers import Adammodel_vgg16.compile(loss='categorical_crossentropy',optimizer=Adam(lr=0.001),metrics=['accuracy'])model_vgg16.fit_generator(train_generator,epochs=20,validation_data=validation_generator)
输出:
04
VGG16 + BN代码实现
以看到,VGG16在鸟类分类上的准确率并不是很高,为了提高分类的准确率,可以尝试在每一层进行Batch Normalization的操作。
Batch Normalization是把每层神经网络任意神经元输入值的分布变为均值为0,方差为1的标准正态分布,以尽可能使在深度神经网络训练过程中,每一层神经网络的输入保持相同分布。关于Batch Normalization的思想与原理,将在5.4节详细介绍。它的代码实现非常简单,只需在每一层之前加入BatchNormalization()函数。具体如代码示例5-13所示。
代码示例5-13:VGG16+BN代码实现
from keras.layers import Conv2D, BatchNormalization, MaxPooling2Dfrom keras.layers import Flatten, Dense, Input, Activationfrom keras import Modelfrom keras.layers import GlobalAveragePooling2DIMSIZE = 224input_shape = (IMSIZE, IMSIZE, 3)input_layer = Input(input_shape)x = input_layerx = BatchNormalization(axis=3)(x)x = Conv2D(64, [3, 3], padding='same', activation='relu')(x)x = BatchNormalization(axis=3)(x)x = Conv2D(64, [3, 3], padding='same', activation='relu')(x)x = MaxPooling2D((2, 2))(x)x = BatchNormalization(axis=3)(x)x = Conv2D(128, [3, 3], padding='same', activation='relu')(x)x = BatchNormalization(axis=3)(x)x = Conv2D(128, [3, 3], padding='same', activation='relu')(x)x = MaxPooling2D((2, 2))(x)x = BatchNormalization(axis=3)(x)x = Conv2D(256, [3, 3], padding='same', activation='relu')(x)x = BatchNormalization(axis=3)(x)x = Conv2D(256, [3, 3], padding='same', activation='relu')(x)x = BatchNormalization(axis=3)(x)x = Conv2D(256, [3, 3], padding='same', activation='relu')(x)x = MaxPooling2D((2, 2))(x)x = BatchNormalization(axis=3)(x)x = Conv2D(512, [3, 3], padding='same', activation='relu')(x)x = BatchNormalization(axis=3)(x)x = Conv2D(512, [3, 3], padding='same', activation='relu')(x)x = BatchNormalization(axis=3)(x)x = Conv2D(512, [3, 3], padding='same', activation='relu')(x)x = MaxPooling2D((2, 2))(x)x = BatchNormalization(axis=3)(x)x = Conv2D(512, [3, 3], padding='same', activation='relu')(x)x = BatchNormalization(axis=3)(x)x = Conv2D(512, [3, 3], padding='same', activation='relu')(x)x = BatchNormalization(axis=3)(x)x = Conv2D(512, [3, 3], padding='same', activation='relu')(x)x = MaxPooling2D((2, 2))(x)x = GlobalAveragePooling2D()(x)x = Dense(200, activation='softmax')(x)output_layer = xmodel_vgg16_b = Model(input_layer, output_layer)model_vgg16_b.summary()
最终的训练结果如图5.9所示,可以看出,20次epochs之后能达到32.68%的外样本准确率,这较之前没有进行BN操作的精度(0.51%)有非常大的提升。
图5.9 VGG16+BN训练结果
5.4Batch Normalization的技巧
在前面介绍VGG16网络时,提到一个操作叫Batch Normalization,究竟什么是BatchNormalization?它的原理是什么?本节讲解它的核心思想。Batch Normalization是2015年Google研究员在论文Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift中提出来的,同时他也将这个方法用在了GoogleNet上,一个非常经典的CNN网络Inception-v2。很多经验表明,在某些数据集上,Batch Normalization起的作用非常巨大。
5.4.1 Batch Normalization的核心思想
在介绍Batch Normalization的核心思想之前,首先需要理解什么是batch。所谓batch,就是只使用训练集中的一小部分样本对模型权重进行一次反向传播的参数更新,这一小部分样本被称作batch,也称之为批次。例如,对一些数据进行随机排序之后,样本量为1万,如果定义batch size=200,那么1万个随机排序后的数据被以200为基本规格的batch切割成了50份,这就是50个batch。
在做数据模型优化时,读入第一个batch,在这个batch上计算梯度方向,因为batch是随机的,所以这个方向可能也是随机的。接着参数按这个方向做一定的更新迭代,再计算下一个batch。当50个batch全部做完之后,所有的样本都被遍历了一遍,这叫一个epoch循环,batch和epoch的详细图解如图5.10所示。在做模型优化时,需要多少个epoch循环,batch size设定为多少,是不容易确定的选择,常常要经过很多尝试,才可能找到一个比较让人满意的组合。
图5.10 batch和epoch的详细图解
由于数据是分批次读入的,所以会有很多随机变异性,可能会产生很多问题。在传统的数据分析中,已被广为验证的一个基本方法就是,把数据适当的标准化,标准化后,模型会变得更加稳定。最简单的标准化方法是将数据变为均值为0,标准差为1。如果把一个batch每层不同通道的像素取值看作一个整体,Batch Normalization的核心就是让像素取值变为均值为0,方差为1。Batch Normalization的核心思想如图5.11所示。对图5.11中的一些参数解释如下。
(1)计算样本均值
,这是第一个参数,接着计算方差
,这是第二个参数。
(2)对
和
进行合理的线性变化,因此有
和
,这样才能保证输出不会被激活函数全部变成0,或者没有改变。
(3)Batch Normalization之后,这些像素数据的激活程度由
和
来确定,其中
和
是可以直接计算的,不需要训练,而
和
是要根据具体的模型结构和数据,让模型按照给定的优化算法和目标函数进行优化,这就是Batch Normalization需要的4个参数。
(4)图5.11中
这个公式并不是严格的标准化公式,因为方差后面还加了一个
,这是因为图像数据有可能会出现方差为0的情况。例如,某个特定的像素点在边缘上,它的取值全部为0,此时分母为0,计算机会报错。因此为了数值计算的稳定,要在分母上加一个
,它的数值由TensorFlow固定,是一个很小的正数。
图5.11 Batch Normalization的核心思想
5.4.2 带有BN的逻辑回归
下面通过一个非常有趣的案例数据来介绍Batch Normalization,该数据集的核心任务是对猫狗进行分类。数据的训练集和测试集分别存储在本地目录./data/CatDog/train/和./data/CatDog/train/下,每个目录下有两个类别,分别是cats和dogs,如图5.12所示。
图5.12 猫狗数据存放目录
01
数据准备与展示
仍然通过ImageDataGenerator()函数分别构造训练集数据生成器和测试集数据生成器,具体如代码示例5-14所示。
代码示例5-14:数据生成器生成训练集与测试集
from keras.preprocessing.image import ImageDataGeneratorIMSIZE=128validation_generator = ImageDataGenerator(rescale=1./255).flow_from_directory( './data_bn/CatDog/validation', target_size=(IMSIZE, IMSIZE), batch_size=200, class_mode='categorical')train_generator = ImageDataGenerator(rescale=1./255).flow_from_directory( './data_bn/CatDog/train', target_size=(IMSIZE, IMSIZE), batch_size=200, class_mode='categorical')
通过next()函数展示X和Y,其中X是图像,Y是相应的因变量。X的形状由4个数值组成,第一个200代表200张图像,剩下3个数字表示图像的分辨率是128像素×128像素,且是一个三通道的彩色照片。Y是一个200行2列的矩阵,并且已经转换成了one-hot的编码形式。具体如代码示例5-15所示。
代码示例5-15:展示X与Y
import numpy as npX,Y=next(validation_generator)print(X.shape)print(Y.shape)Y[:,0]
输出:
(200, 128, 128, 3)(200, 2)
接下来展示validation_generator输出的前10张图像,会看到很多猫和狗的照片,具体如代码示例5-16所示。
代码示例5-16:展示图像
from matplotlib import pyplot as pltplt.figure()fig,ax = plt.subplots(2,5)fig.set_figheight(6)fig.set_figwidth(15)ax=ax.flatten()for i in range(10): ax[i].imshow(X[i,:,:,:])
输出:
02
带有BN的逻辑回归模型
接下来构建带有BN的逻辑回归模型来实现猫狗分类,这时需要从Keras的layers中加载Batch Normalization模块,逻辑回归的代码大家已经非常熟悉了,只需要在input_layer下增加一行代码x=BatchNormalization()(x),即可实现BN操作,具体如代码示例5-17所示。
代码示例5-17:带有BN的逻辑回归
from keras.layers import Flatten,Input,BatchNormalization,Densefrom keras import Modelinput_layer=Input([IMSIZE,IMSIZE,3])x=input_layerx=BatchNormalization()(x)x=Flatten()(x)x=Dense(2,activation='softmax')(x)output_layer=xmodel1=Model(input_layer,output_layer)model1.summary()
输出:
代码示例5-18展示了模型拟合结果,作为示例,只运行10个epoch,从结果来看,这个分类效果并不怎么好,精度在54%左右。准确率虽然不是特别高,但是它说明最简单的逻辑回归已经在起作用了。如果把Batch Normalization从逻辑回归中去掉,就会发现预测精度马上变成50%左右。所以从这个例子中,可以看到Batch Normalization在特定的模型、特定的数据集上是有帮助的。
代码示例5-18:带有BN的逻辑回归模型与拟合
from keras.optimizers import Adammodel1.compile(loss='categorical_crossentropy',optimizer=Adam(lr=0.01),metrics=['accuracy'])model1.fit_generator(train_generator,epochs=200,validation_data=validation_generator)
输出:
5.4.3 带有BN的宽模型
作为拓展,考虑一些稍微复杂的模型,第一个模型我们把它定义为宽模型,之所以称为宽模型,是因为它用了很多个卷积核,即较深的卷积通道。具体代码和5.4.2章节的逻辑回归差不多,唯一的区别是增加了两行,一个是卷积操作,使用100个大小为2×2的卷积核进行valid卷积,另一个是池化操作,进行规格大小为16×16的最大值池化。具体如代码示例5-19所示。
代码示例5-19:带有BN的宽模型
from keras.layers import Conv2D,MaxPooling2Dn_channel=100input_layer=Input([IMSIZE,IMSIZE,3])x=input_layerx=BatchNormalization()(x)x=Conv2D(n_channel,[2,2],activation='relu')(x)x=MaxPooling2D([16,16])(x)x=Flatten()(x)x=Dense(2,activation='softmax')(x)output_layer=xmodel2=Model(input_layer,output_layer)model2.summary()
输出:
下面复习有关参数个数的计算。
(1)卷积之后,图像变为127像素×127像素的规格,每一个卷积核消耗2×2×3+1=13个参数,因为一共有100个卷积核,所以参数总个数是1 300。
(2)池化操作时,我们发现127不能被16整除,只能做7个真正有效的最大值池化,所以最后这一层的输出是7×7×100=4900的立体矩阵。将它拉直成一个长度为4 900的向量,构造全连接层,最后输出到两个节点。此时消耗的参数个数是4900×2+2=9802。
(3)总的参数个数为9802+1300+12=11114。
(4)在这11 114个参数中,有6个参数是不需要训练的,这是因为进行Batch Normalization时,每一个通道要消耗4个参数,这4个参数中的均值和方差是不需要训练的,每个通道有2个参数不需要训练,一共3个通道,因此有6个参数不需要训练,这使得最后实际上需要训练的参数总数是11 108。
前10次epoch的结果显示,在测试数据集上的预测精度可以达到78%左右,比刚才逻辑回归的结果好很多。虽然这个精度也不是特别高,但它已经代表了一个巨大的进步。具体如代码示例5-20所示。
代码示例5-20:带有BN的宽模型的编译与拟合
model2.compile(loss='categorical_crossentropy',optimizer=Adam(lr=0.01),metrics=['accuracy'])model2.fit_generator(train_generator,epochs=200,validation_data=validation_generator)
输出:
5.4.4 带有BN的深度模型
第二个模型我们考虑深度模型,这个模型中,卷积核的个数减少,但是模型的层数增加。例如,在每一层卷积后都进行一个规格大小为2×2的最大值池化操作,那么像素大小会变成原来的一半,因为输入的像素是128,它是2的7次方,这决定了最多只能做7层。这是深度模型大概的框架。
具体而言,每一层使用20个卷积核,接下来进行一个长度为7的循环,每一步要重复一个卷积和池化的基本操作,其中卷积层进行规格大小为2×2的same卷积,池化层进行规格大小为2×2的最大值池化。经过7层之后,最后的输出就变成1×1的像素矩阵。然后将其拉直并用softmax函数激活,输出到两个节点上。具体如代码示例5-21所示。
代码示例5-21:带有BN的深度模型
n_channel=20input_layer=Input([IMSIZE,IMSIZE,3])x=input_layerx=BatchNormalization()(x)for _ in range(7): x=Conv2D(n_channel,[2,2],padding='same',activation='relu')(x) x=MaxPooling2D([2,2])(x)x=Flatten()(x)x=Dense(2,activation='softmax')(x)output_layer=xmodel3=Model(input_layer,output_layer)model3.summary()
通过model.summary(),可以输出深度模型的参数概要表,如图5.13所示,可以看到最后需要训练的参数总数是10 028,比前一个宽模型稍少,但是在一个可比的范围内,这样二者的预测精度也可以保证大概可比。
图5.13 深度模型参数概要表
运行该深度模型,可以看到前10次epoch循环的预测精度在78%左右,如代码示例5-22所示。也许这就是深度带来的好处。但理论上为什么是这样,我们并不是非常的清楚,相信这仍然是一个非常值得研究的理论课题。
代码示例5-22:带有BN的深度模型编译与拟合
model3.compile(loss='categorical_crossentropy',optimizer=Adam(lr=0.01),metrics=['accuracy'])model3.fit_generator(train_generator,epochs=200,validation_data=validation_generator)
输出:
最后总结一下,Batch Normalization在很多情况下确实是帮助巨大的,但并不是对所有情况都有帮助。在什么情况下Batch Normalization能够让结果变好,在什么情况下没有帮助是不清楚的,是值得我们思考和研究的。
以上就是今天为大家介绍的VGG网络和BN技巧,下周我们继续更新其他经典的CNN模型。欢迎大家同步收看狗熊会慕课平台的深度学习课程,里面有熊大的精彩讲解!