CNN模型之ResNet

深度残差网络(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-ImageNet分类Top5误差
1 深度网络的退化问题

从经验来看,网络的深度对模型的性能至关重要,当增加网络层数后,网络可以进行更加复杂的特征模式的提取,所以当模型更深时理论上可以取得更好的结果,从图一也可以看出网络越深而效果越好的一个实践证据。但是更深的网络性能一定会更好吗?实验发现深度网络出现了退化问题(Degradation problem):网络深度增加时,网络准确度出现饱和,甚至出现下降。这个现象可以从图二中直观看出来:56层的网络比20层网络的效果还要差。这不会是过拟合问题,因为56层网络的训练误差同样高。我们知道深层网络存在着梯度爆炸或者梯度消失的问题,这使得深度学习模型很难训练。但是现在已经存在一些技术手段如BatchNorm来解决这个问题,因此出现深度网络的退化问题是非常令人诧异的。
2-20层与56层网络在CIFAR-10上的误差
2 残差学习

2.1 残差块
深度网络的退化问题至少说明深度网络不容易训练。但是我们考虑这样一个事实:现在你有一个浅层网络,你想通过向上堆积新层来建立深层网络,一个极端情况是这些增加的层什么也不学习,仅仅复制浅层网络的特征,即这样新层是恒等映射(Identity mapping)。在这种情况下,深层网络应该至少和浅层网络性能一样,也不应该出现退化现象。好吧,你不得不承认肯定是目前的训练方法有问题,才使得深层网络很难去找到一个好的参数。
这个有趣的假设让何博士灵感爆发,他提出了残差学习来解决退化问题。对于一个堆积层结构(几层堆积而成)当输入为x时其学习到的特征记为H(x),现在我们希望其可以学习到残差F(x) = H(x) - x,这样其实原始的学习特征是F(x) + x。之所以这样是因为残差学习相比原始特征直接学习更容易。当残差为0时,此时堆积层在输入特征基础上学习到新的特征,从而拥有更好的性能。残差学习的结构如图三所示,这有点类似于电路中的“短路”,所以是一种短路连接(shortcut connection)。

3-残差学习单元
为什么残差学习相对更容易,从直观上看残差学习需要学习的内容少,因为残差一般会比较小,学习难度小点,一个残差块可以表示成如图四:
4-残差块
图中的Weight在卷积网络中是指卷积操作,addition是指单位加操作。
从数学的角度来分析这个问题,残差单元可以表示为
残差块数学表示
其中Xl和Xl+1分别表示的是第l个残差单元的输入和输出;h(xl) = xl表示恒等映射,反映在图四是左边的灰色部分;F是残差函数,表示学习到的残差,它一般由两三个卷积操作构成,即图四中右侧包含卷积的部分;f是ReLU激活函数。

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 残差网络的背后原理

残差块一个更通用的表示方式是

现在我们先不考虑升维或者降维的情况,那么在上式中,我们首先给出两个假设,h是直接映射,f是激活函数,一般使用ReLU,也是直接映射。基于上式,我们求得从浅层l到深层L的学习特征为:
这个公式反映了L层可以表示为任意一个比它浅的l层和他们之间残差部分之和。
利用BP中使用的链式规则,可以求得反向过程的梯度:
上面公式反映了残差网络的两个属性:
1,在整个训练过程中,对F(xi,Wi)对xl的偏导(即括号里的那个公式)不可能一直为-1,也就是说在残差网络中不会出现梯度消失的问题
2,损失函数对xL求得偏导(即上式第一个等式后的第一个公式)表示L层的梯度可以直接传递到任何一个比它浅的l层。
通过分析残差网络的正向和反向的两个过程,我们发现,当残差块满足上面两个假设时,信息可以非常畅通地在高层和低层之间相互传导,说明这两个假设是让残差网络可以训练深度模型的充分条件
那么这两个假设是必要条件吗?
3.1 直接映射是最好的选择
对于假设1,我们采用反证法,假设h(xl) = axl,那么这时候,残差块表示为(图中的系数就是a):
对于更深层的L层,
为了简化问题,我们只考虑公式的左半部分,忽略加号之后的部分,损失函数对xl求偏微分得
上面公式反映了两个属性:
1,当系数a>1时,很有可能发生梯度爆炸;
2,当系数a<1时,梯度变成0,会阻碍残差网络信息的反向传递,从而影响残差网络的训练。
所以系数a必须为1。同理,其他常见的激活函数都会产生和上面的例子类似的阻碍信息反向传播的问题。
对于其他不影响梯度的h(),例如LSTM中的门机制,或者Dropout以及用于降维的1*1卷积也许会有效果,

作者采用了实验的方法进行验证,实验结果表明,在所有的变异模型中,依旧是直接映射的效果最好。
3.2 激活函数的位置
上面提出的残差块可以详细展开如下图a,即在卷积之后使用BN做归一化,然后在和直接映射单位加之后使用ReLU作为激活函数。
激活函数在残差网络中的应用
作者通过调整ReLU和BN的使用位置得到了几个变种,即d中的ReLU-only pre-activation和5.d中的 full pre-activation。作者通过对照试验对比了这几种变异模型,结果表明将激活函数移动到残差部分可以提高模型的精度。
该网络一般就在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
4 残差网络与模型集成

Andreas Veit等人的论文[3]指出残差网络可以从模型集成的角度理解。如下图所示,对于一个3层的残差网络可以展开成一棵含有8个节点的二叉树,而最终的输出便是这8个节点的集成。而他们的实验也验证了这一点,随机删除残差网络的一些节点,网络的性能变化较为平滑,而对于VGG等stack到一起的网络来说,随机删除一些节点后,网络的输出将完全随机。
残差网络展开成二叉树

你可能感兴趣的:(CNN模型之ResNet)