深度残差网络(Deep Residual Network,ResNet)的提出是CNN图像史上的里程碑事件。ResNet在ILSVRC和COCO 2015上的取得了五项第一,ResNet的作者何凯明也因此摘得CVPR2016最佳论文奖(链接:https://arxiv.org/pdf/1512.03385.pdf)
ResNet主要解决深度CNN网络难训练的问题,从图一可以看出,14年的VGG才19层,而15年的ResNet多达152层,虽然ResNet确实好像是靠深度取胜,但是ResNet还有架构上的trick,这才使得网络的深度发挥出作用,这个trick就是残差学习(Residual learning)。下面详细讲述ResNet的理论及实现。1 深度网络的退化问题
从经验来看,网络的深度对模型的性能至关重要,当增加网络层数后,网络可以进行更加复杂的特征模式的提取,所以当模型更深时理论上可以取得更好的结果,从图一也可以看出网络越深而效果越好的一个实践证据。但是更深的网络性能一定会更好吗?实验发现深度网络出现了退化问题(Degradation problem):网络深度增加时,网络准确度出现饱和,甚至出现下降。这个现象可以从图二中直观看出来:56层的网络比20层网络的效果还要差。这不会是过拟合问题,因为56层网络的训练误差同样高。我们知道深层网络存在着梯度爆炸或者梯度消失的问题,这使得深度学习模型很难训练。但是现在已经存在一些技术手段如BatchNorm来解决这个问题,因此出现深度网络的退化问题是非常令人诧异的。2 残差学习
2.1 残差块
深度网络的退化问题至少说明深度网络不容易训练。但是我们考虑这样一个事实:现在你有一个浅层网络,你想通过向上堆积新层来建立深层网络,一个极端情况是这些增加的层什么也不学习,仅仅复制浅层网络的特征,即这样新层是恒等映射(Identity mapping)。在这种情况下,深层网络应该至少和浅层网络性能一样,也不应该出现退化现象。好吧,你不得不承认肯定是目前的训练方法有问题,才使得深层网络很难去找到一个好的参数。
这个有趣的假设让何博士灵感爆发,他提出了残差学习来解决退化问题。对于一个堆积层结构(几层堆积而成)当输入为x时其学习到的特征记为H(x),现在我们希望其可以学习到残差F(x) = H(x) - x,这样其实原始的学习特征是F(x) + x。之所以这样是因为残差学习相比原始特征直接学习更容易。当残差为0时,此时堆积层在输入特征基础上学习到新的特征,从而拥有更好的性能。残差学习的结构如图三所示,这有点类似于电路中的“短路”,所以是一种短路连接(shortcut connection)。
从数学的角度来分析这个问题,残差单元可以表示为
2.2 残差网络
残差网络的搭建分为两步
1:使用VGG公式搭建Plain VGG
2:在Plain VGG的卷积网络之前插入Identity Mapping,注意需要升维或者降维的时候加入1*1卷积
在实现过程中,一般是直接stack残差块的方式。
def resnet_v1(x):
x = Conv2D(kernel_size=(3,3), filters=16, strides=1, padding='same', activation='relu')(x)
x = res_block_v1(x, 16, 16)
x = res_block_v1(x, 16, 32)
x = Flatten()(x)
outputs = Dense(10, activation='softmax', kernel_initializer='he_normal')(x)
return outputs
2.3 为什么叫残差网络
在统计学中,残差和误差是两个很容易混淆的概念。误差是衡量观测值(模型的输入)和真实值之间的差距,残差是指预测值(模型的输出)和观测值之间的差距。对于残差网络的命名原因,作者给出的解释是,网络的一层通常可以看做 y=H(x) ,而残差网络的一个残差块可以表示为 H(x) = F(x) + x,也就是 F(x) = H(x) -x,在单位映射中,y = x 便是观测值,而 H(x) 是预测值,所以 F(x) 便对应着残差,因此叫做残差网络。
3 残差网络的背后原理
残差块一个更通用的表示方式是
利用BP中使用的链式规则,可以求得反向过程的梯度:
1,在整个训练过程中,对F(xi,Wi)对xl的偏导(即括号里的那个公式)不可能一直为-1,也就是说在残差网络中不会出现梯度消失的问题
2,损失函数对xL求得偏导(即上式第一个等式后的第一个公式)表示L层的梯度可以直接传递到任何一个比它浅的l层。
通过分析残差网络的正向和反向的两个过程,我们发现,当残差块满足上面两个假设时,信息可以非常畅通地在高层和低层之间相互传导,说明这两个假设是让残差网络可以训练深度模型的充分条件。
那么这两个假设是必要条件吗?
3.1 直接映射是最好的选择
对于假设1,我们采用反证法,假设h(xl) = axl,那么这时候,残差块表示为(图中的系数就是a):
1,当系数a>1时,很有可能发生梯度爆炸;
2,当系数a<1时,梯度变成0,会阻碍残差网络信息的反向传递,从而影响残差网络的训练。
所以系数a必须为1。同理,其他常见的激活函数都会产生和上面的例子类似的阻碍信息反向传播的问题。
对于其他不影响梯度的h(),例如LSTM中的门机制,或者Dropout以及用于降维的1*1卷积也许会有效果,
作者采用了实验的方法进行验证,实验结果表明,在所有的变异模型中,依旧是直接映射的效果最好。
3.2 激活函数的位置
上面提出的残差块可以详细展开如下图a,即在卷积之后使用BN做归一化,然后在和直接映射单位加之后使用ReLU作为激活函数。
该网络一般就在resnet_v2,keras实现如下:
def res_block_v2(x, input_filter, output_filter):
res_x = BatchNormalization()(x)
res_x = Activation('relu')(res_x)
res_x = Conv2D(kernel_size=(3,3), filters=output_filter, strides=1, padding='same')(res_x)
res_x = BatchNormalization()(res_x)
res_x = Activation('relu')(res_x)
res_x = Conv2D(kernel_size=(3,3), filters=output_filter, strides=1, padding='same')(res_x)
if input_filter == output_filter:
identity = x
else: #需要升维或者降维
identity = Conv2D(kernel_size=(1,1), filters=output_filter, strides=1, padding='same')(x)
output= keras.layers.add([identity, res_x])
return output
def resnet_v2(x):
x = Conv2D(kernel_size=(3,3), filters=16 , strides=1, padding='same', activation='relu')(x)
x = res_block_v2(x, 16, 16)
x = res_block_v2(x, 16, 32)
x = BatchNormalization()(x)
y = Flatten()(x)
outputs = Dense(10, activation='softmax', kernel_initializer='he_normal')(y)
return outputs