【深度学习】ResNet解读及代码实现
简介
ResNet是何凯明⼤神在2015年提出的⼀种⽹络结构,获得了ILSVRC-2015分类任务的第⼀名,同时在ImageNet
detection,ImageNet localization,COCO detection和COCO gmentation等任务中均获得了第⼀名,在当时可谓是轰动⼀时。
ResNet⼜名残差神经⽹络,指的是在传统卷积神经⽹络中加⼊残差学习(residual learning)的思想,解决了深层⽹络中梯度弥散和精度下降(训练集)的问题,使⽹络能够越来越深,既保证了精度,⼜控制了速度。
出发点
随着⽹络的加深,梯度弥散问题会越来越严重,导致⽹络很难收敛甚⾄⽆法收敛。梯度弥散问题⽬前有很多的解决办法,包括⽹络初始标准化,数据标准化以及中间层的标准化(Batch Normalization)等。但是⽹络加深还会带来另外⼀个问题:随着⽹络加深,出现训练集准确率下降的现象,如下图,
很多同学第⼀反应肯定是“这不是过拟合了吗”。其实,这不是由于过拟合引起的。过拟合通常指模型在训练集表现很好,在测试集很差。凯明⼤神针对这个问题提出了残差学习的思想。
残差学习指的是什么?
残差学习的思想就是上⾯这张图,可以把它理解为⼀个block,定义如下:
残差学习的block⼀共包含两个分⽀或者两种映射(mapping):
1. identity mapping,指的是上图右边那条弯的曲线。顾名思义,identity mapping指的就是本⾝的映射,也就是⾃⾝;
2. residual mapping,指的是另⼀条分⽀,也就是部分,这部分称为残差映射,也就是。
为什么残差学习可以解决“⽹络加深准确率下降”的问题?
对于⼀个神经⽹络模型,如果该模型是最优的,那么训练就很容易将residual mapping优化到0,此时只剩下identity mapping,那么⽆论怎么增加深度,理论上⽹络会⼀直处于最优状态。因为相当于后⾯所有增加的⽹络都会沿着identity mapping(⾃⾝)进⾏信息传输,可以理解为最优⽹络后⾯的层数都是废掉的(不具备特征提取的能⼒),实际上没起什么作⽤。这样,⽹络的性能也就不会随着深度的增加⽽降低了。
⽹络结构
⽂中提到了⼀个名词叫“Shortcut Connection”,实际上它指的就是identity mapping,这⾥先解释⼀下,免的⼤家后⾯会confu。针对不同深度的ResNet,作者提出了两种Residual Block:
对上图做如下说明:
1. 左图为基本的residual block,residual mapping为两个64通道的3x3卷积,输⼊输出均为64通道,可直接相加。该block主要使⽤在相对浅层⽹络,⽐如ResNet-34;
2. 右图为针对深层⽹络提出的block,称为“bottleneck” block,主要⽬的就是为了降维。⾸先通过⼀个1x1卷积将256维通道(channel)降到64通道,最后通过⼀个256通道的1x1卷积恢复。
通过上⾯的介绍我们知道,residual mapping和identity mapping是沿通道维度相加的,那么如果通道维度不相同怎么办?
作者提出在identity mapping部分使⽤1x1卷积进⾏处理,表⽰如下:
其中,指的是1x1卷积操作。
下图为VGG-19,Plain-34(没有使⽤residual结构)和ResNet-34⽹络结构对⽐:
对上图进⾏如下说明:
1. 相⽐于VGG-19,ResNet没有使⽤全连接层,⽽使⽤了全局平均池化层,可以减少⼤量参数。VGG-19⼤量参数集中在全连接层;
2. ResNet-34中跳跃连接“实线”为identity mapping和residual mapping通道数相同,“虚线”部分指的是两者通道数不同,需要使⽤1x1卷积调整通道维度,使其可以相加。
论⽂⼀共提出5种ResNet⽹络,⽹络参数统计表如下:
代码实现
本节使⽤keras实现ResNet-18。
from keras.layers import Input
from keras.layers import Conv2D, MaxPool2D, Den, BatchNormalization, Activation, add, GlobalAvgPool2D dels import Model
from keras import regularizers
from keras.utils import plot_model
from keras import backend as K
def conv2d_bn(x, nb_filter, kernel_size, strides=(1, 1), padding='same'):
"""
conv2d -> batch normalization -> relu activation
"""
x = Conv2D(nb_filter, kernel_size=kernel_size,
strides=strides,
padding=padding,
kernel_regularizer=regularizers.l2(0.0001))(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
return x
def shortcut(input, residual):
"""
shortcut连接,也就是identity mapping部分。
"""
input_shape = K.int_shape(input)
residual_shape = K.int_shape(residual)
stride_height = int(round(input_shape[1] / residual_shape[1]))
stride_width = int(round(input_shape[2] / residual_shape[2]))
equal_channels = input_shape[3] == residual_shape[3]
identity = input
# 如果维度不同,则使⽤1x1卷积进⾏调整
if stride_width > 1 or stride_height > 1 or not equal_channels:
identity = Conv2D(filters=residual_shape[3],
kernel_size=(1, 1),
strides=(stride_width, stride_height),
padding="valid",
kernel_regularizer=regularizers.l2(0.0001))(input)
return add([identity, residual])
def basic_block(nb_filter, strides=(1, 1)):
"""
基本的ResNet building block,适⽤于ResNet-18和ResNet-34.
"""
def f(input):
conv1 = conv2d_bn(input, nb_filter, kernel_size=(3, 3), strides=strides)
residual = conv2d_bn(conv1, nb_filter, kernel_size=(3, 3))
return shortcut(input, residual)
return f
def residual_block(nb_filter, repetitions, is_first_layer=Fal):
"""
构建每层的residual模块,对应论⽂参数统计表中的conv2_x -> conv5_x
"""
def f(input):
for i in range(repetitions):
strides = (1, 1)
if i == 0 and not is_first_layer:
strides = (2, 2)
input = basic_block(nb_filter, strides)(input)
return input
return f
def resnet_18(input_shape=(224,224,3), nclass=1000):
"""
build resnet-18 model using keras with TensorFlow backend.
:param input_shape: input shape of network, default as (224,224,3)
:param nclass: numbers of class(output shape of network), default as 1000 :return: resnet-18 model
"""
input_ = Input(shape=input_shape)
conv1 = conv2d_bn(input_, 64, kernel_size=(7, 7), strides=(2, 2))
pool1 = MaxPool2D(pool_size=(3, 3), strides=(2, 2), padding='same')(conv1)
conv2 = residual_block(64, 2, is_first_layer=True)(pool1)
conv3 = residual_block(128, 2, is_first_layer=True)(conv2)
conv4 = residual_block(256, 2, is_first_layer=True)(conv3)
conv5 = residual_block(512, 2, is_first_layer=True)(conv4)
pool2 = GlobalAvgPool2D()(conv5)
output_ = Den(nclass, activation='softmax')(pool2)
model = Model(inputs=input_, outputs=output_)
model.summary()
return model
if __name__ == '__main__':
model = resnet_18()
plot_model(model, 'ResNet-18.png') # 保存模型图