6.2 ResNet
ResNet(Residual Neural Network)是由微软研究院何恺明等人提出的,该算法获得了2015年大规模视觉识别挑战赛的冠军。不仅如此,在ImageNet Detection、ImageNet Localization、COCO Detection等多项竞赛中,该模型也都获得过冠军。截止到本书写作为止,提出ResNet的文章已有约两万次的引用,有评价说,ResNet是过去几年中计算机视觉和深度学习领域最具开创性的工作,影响了这之后深度学习在学术界和工业界的发展方向。
6.2.1
ResNet网络结构
在讲解ResNet网络结构之前,先介绍ResNet中的重要结构:残差学习模块。
01
ResNet的残差学习模块
ResNet声名鹊起的一个很重要的原因是,它提出了残差学习的思想。图6.8为ResNet的一个残差学习模块,该模块包含多个卷积层,多个卷积层对这个残差学习模块的输入数据
进行
的变化,同时原始输入信息跳过多个卷积层直接传导到后面的层中,最终将
的整体作为输入,并用激活函数激活,从而得到这个残差学习模块的输出结果。所以本质上
是输出结果和输入结果之间的差值,即残差。ResNet学习的就是
,因此ResNet又叫作残差网络。
图6.8 原论文中对残差学习模块的图解
02
残差学习模块的优势
传统的卷积神经网络或者全连接网络,在信息传递时,或多或少会存在信息丢失、损耗等问题,同时还会导致梯度消失或梯度爆炸,使得很深的网络无法训练。ResNet通过提出残差学习的思想,在一定程度上解决了这个问题。通过将输入信息X“绕道”传导到输出,极大保护了信息的完整性,整个网络只需要学习输入、输出和残差部分,即
就能简化学习的目标和难度。
以图6.9为例,最左边是19层的VGG,中间是34层的普通神经网络,最右边是34层的ResNet,将三者对比发现,ResNet与其他两个网络结构最大的区别是有很多的旁路将输入直接连接到后面的层,这种结构也被称为shortcuts,每一个shortcuts连线中间包含的是一个残差学习模块。
03
ResNet中常用的残差学习模块
图6.10所示为ResNet网络结构中常用的两种残差学习模块,左边是由两个3×3卷积网络串联在一起作为一个残差学习模块,右边是以1×1、3×3、1×1三个卷积网络串联在一起作为一个残差学习模块。ResNet大都是以这两种学习模块堆叠在一起实现的,比较常见的ResNet有50层、101层和152层。表6.1列出了ResNet不同层数的网络结构。
图6.9 3种神经网络结构的对比图
图6.10 两种残差学习模块
表6.1原论文中不同层数的ResNet结构
04
ResNet网络结构详解
下面以34层的ResNet为例,对照表6.1,详解其网络结构。
(1)conv1层,该层使用64个7×7的卷积核,步长为2,将224×224大小的彩色图像降维到112×112。
(2)conv2_x层,首先进行3×3的最大值池化,步长为2,将维度进一步降低为56×56,然后是3个残差学习模块,每一个模块都由两个卷积层组成,卷积核大小为3×3,64个通道。
(3)conv3_x层,由4个残差学习模块组成,由于conv2_3的输出结果是56×56,因此在conv3_1的某一卷积层,需要将步长调整为2,从而将conv3_4的输出维度降低到28×28。
(4)conv4_x层,由6个残差学习模块组成,同理,在conv4_1中的某一卷积层需要将步长调整为2,从而将conv4_6的输出维度降低到14×14。
(5)conv5_x层,由3个残差学习模块组成,同理,在conv5_1将步长调整为2,最后输出7×7维的图像。
(6)最后是一个全连接层,输出到1 000分类。
6.2.2
案例:花的三分类问题
01
数据准备与处理
本节将通过花的三分类问题来演示ResNet的代码实现。数据集分别存放在train和validation两个文件夹中,首先使用ImageDataGenerator将数据读入并展示其中的10张图像,具体如代码示例6-5所示。
代码示例6-5:读入数据并展示图像
from matplotlib import pyplot as pltfrom keras.preprocessing.image import ImageDataGeneratorIMSIZE=224train_generator = ImageDataGenerator(rescale=1./255).flow_from_directory( 'data_res/train', target_size=(IMSIZE, IMSIZE), batch_size=100, class_mode='categorical')validation_generator = ImageDataGenerator(rescale=1./255).flow_from_directory( './data_res/validation', target_size=(IMSIZE, IMSIZE), batch_size=100, class_mode='categorical')plt.figure()fig,ax = plt.subplots(2,5)fig.set_figheight(7)fig.set_figwidth(15)ax=ax.flatten()X,Y=next(train_generator)for i in range(10): ax[i].imshow(X[i,:,:,:])
输出:
02
ResNet代码实现
首先构建残差学习模块之前的网络结构,具体如代码示例6-6所示。
代码示例6-6:构建残差学习模块之前网络结构
from keras.layers import Inputfrom keras.layers import Activation, Conv2D, BatchNormalization, add, MaxPooling2DNB_CLASS=3IM_WIDTH=224IM_HEIGHT=224inpt = Input(shape=(IM_WIDTH, IM_HEIGHT, 3))x = Conv2D(64, (7,7), padding='same', strides=(2,2), activation='relu')(inpt)x = BatchNormalization()(x)x = MaxPooling2D(pool_size=(3, 3), strides=(2, 2), padding='same')(x)x0 = x
其中Conv2D函数设置第一个卷积层,采取64个7×7的卷积核进行same卷积,步长为2,激活函数选择Relu,接下来是BN层,然后是池化层,采取3×3的卷积核进行same池化,步长为2。X0=X表示把当前的X储存下来,之后调用原始输入信息时,可以使用X0。
接下来进入第一个残差学习模块,这里将1×1、3×3、1×1三个卷积网络串联在一起作为一个残差学习模块,参数和层数的设置如代码示例6-7所示。
代码示例6-7:残差学习模块代码
# 一个blockx = Conv2D(64, (1,1), padding='same', strides=(1,1), activation='relu')(x)x = BatchNormalization()(x)x = Conv2D(64, (3,3), padding='same', strides=(1,1), activation='relu')(x)x = BatchNormalization()(x)x = Conv2D(256, (1,1), padding='same', strides=(1,1), activation=None)(x)x = BatchNormalization()(x)# 下面两步为了把输入64通道的数据转换为256个通道,用来让x0和x维数相同,可以进行加法计算x0 = Conv2D(256,(1,1),padding='same',strides=(1,1),activation='relu')(x0)x0 = BatchNormalization()(x0)x = add([x,x0])# add把输入的x和经过一个block之后输出的结果加在一起x = Activation('relu')(x)#求和之后的结果再做一次relux0 = x
需要注意的是,ResNet的每一个残差学习模块,最后一个卷积层没有激活函数,即activation=None。由于最后一层的输出通道是256,但X0是64通道,二者维度不一样,无法直接相加,因此需要先将它们的维度统一。于是使用256个大小为1×1的卷积核对X0进行卷积,之后使用add函数完成加法运算,即X=ADD(X,X0),求和之后的结果再做一次ReLU变换,这样就完成了第一个残差学习模块的代码编写。在进入下一个残差学习模块之前,仍然需要将输出的X存入X0当中,即X0=X。
ResNet一共有17层,包含3个残差学习模块,第二个、第三个残差学习模块和第一个非常类似,此处不再赘述。
至此,ResNet的网络搭建完成,可以用model.summary查看模型参数报表,由于层数太多,无法展示全部,仅展示部分结果,具体如代码示例6-8所示。
代码示例6-8:ResNet主体部分模型结构展示
from keras.models import Modelmodel = Model(inputs=inpt,outputs=x)model.summary()
输出:
例如,输入层是224×224×3,进行64个大小为7×7的卷积操作,一个卷积核消耗7×7×3+1=148个参数,一共调用64个卷积核,因此总共消耗148×64=9472个参数,下面每一层的计算都是类似的,读者可以自己计算并与代码示例6-8输出的模型概要表中的数字进行对比。
ResNet模型的构建还没有结束,所有以上的层全部做完之后,最后的输出要通过flatten()函数拉直,然后连接一个全连接层,输出到3个节点,这一层的输出才是ResNet最后的输出,具体如代码示例6-9所示。
代码示例6-9:添加全连接层
from keras.layers import Dense, Flattenx = model.outputx = Flatten()(x)predictions = Dense(NB_CLASS,activation='softmax')(x)model_res = Model(inputs=model.input,outputs=predictions)
03
ResNet编译运行
最后编译运行模型,设定损失函数为categorical_crossentropy,在整个拟合过程中监控拟合精度,因此metrics=[‘accuracy’]。接着做50次epoch循环。可以看到,在验证数据集上的精度可以超过80%。具体如代码示例6-10所示。
代码示例6-10:ResNet模型编译与拟合
from keras.optimizers import Adammodel_res.compile(loss='categorical_crossentropy',optimizer=Adam(lr=0.001),metrics=['accuracy'])model_res.fit_generator( train_generator, steps_per_epoch=100, epochs=50, validation_data=validation_generator, validation_steps=100)
输出:
最后总结一下,ResNet最大的创新点是提出了残差学习的思想,这在一定程度上解决了梯度消失或梯度爆炸问题。ResNet将输入信息“绕道”传导到输出,极大地保护了信息的完整性,整个网络只需要学习输入、输出和残差部分,就可以简化学习的目标和难度。
以上就是今天为大家介绍的ResNet网络结构,下周我们继续更新其他经典的CNN模型。欢迎大家同步收看狗熊会慕课平台的深度学习课程,里面有熊大的精彩讲解!