通过引入所谓的“恒等快捷连接”(identity shortcut connections),ResNet能够建立比以往更深的网络结构而不会受到梯度消失问题的影响。
退化现象与对策
在介绍ResNet之前首先需要理解,为何我们需要这样一个结构。传统的DCNN在层数加深时会面临梯度消失的问题,即随着层数的增加,梯度在反向传播过程中逐渐减小,导致深层网络参数难以更新。ResNet通过引入快捷连接来缓解这个问题,这些连接可以让一些层直接传递较低层的特征到较高层,从而减轻梯度消失的影响。
ResNet网络架构
如果我们考虑ResNet的单个层作为一个操作F(x) + x,其中x是输入,这个层试图学习残差函数 F(x) = H(x) - x,H(x)是期望的映射。这里可以这么理解:在H(x)是输出值,将其看作真值,在原先值的基础上加上真值和假设值之间的差值即残差,模型的训练主要训练残差值,使的输入的x更加贴近H(x)。同时,在方向传播过程中由于添加了原始参数,继而使得梯度理论上不会消失。
按照这个思路,ResNet团队分别构建了带有“快捷连接(Shortcut Connection)”的ResNet构建块、以及降采样的ResNet构建块,区降采样构建块的主杆分支上增加了一个1×1的卷积操作。左图对应的是浅层网络,而右图对应的是深层网络。对于短路连接,当输入和输出维度一致时,可以直接将输入加到输出上。但是当维度不一致时(对应的是维度增加一倍),这就不能直接相加。有两种策略:(1)采用zero-padding增加维度,此时一般要先做一个downsamp,可以采用strde=2的pooling,这样不会增加参数;(2)采用新的映射(projection shortcut),一般采用1x1的卷积,这样会增加参数,也会增加计算量。短路连接除了直接使用恒等映射,当然都可以采用projection shortcut。
! [在这里插入图片描述] (https://i-blog.csdnimg.cn/blog_migrate/9934969b057e650eea79e87639352159.png)
需要注意的是当神经网络达到一定层数时,模型还是会存在退化的现象。
这里可能的原因有:
- 虽然残差结构可以缓解梯度消失问题,但当网络非常深时,模型变得更加复杂,它的参数空间也更加庞大和复杂,导致优化算法难以找到良好的最小值。
- 简单的残差块可能无法充分提取复杂的特征,导致模型在面对复杂任务时表现不佳。
- 过深的网络更容易过拟合训练数据,导致模型在测试集上的泛化能力下降。
- 虽然残差连接可以部分解决梯度消失问题,但随着网络层数的增加,信号在经过多个残差块后仍然会发生衰减,导致信息丢失,影响模型的学习能力。
基于Tensorflow的代码块
import tensorflow as tf
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Conv2D, BatchNormalization, Activation, Add
def resnet_identity_block(input_tensor, kernel_size, filters, stage, block):
filters1, filters2, filters3 = filters
conv_name_base = 'res_stage' + str(stage) + '_block' + str(block) + '_branch'
bn_name_base = 'bn_stage' + str(stage) + '_block' + str(block) + '_branch'
x = Conv2D(filters1, (1, 1), name=conv_name_base + '2a')(input_tensor)
x = BatchNormalization(name=bn_name_base + '2a')(x)
x = Activation('relu')(x)
x = Conv2D(filters2, kernel_size, padding='same', name=conv_name_base + '2b')(x)
x = BatchNormalization(name=bn_name_base + '2b')(x)
x = Activation('relu')(x)
x = Conv2D(filters3, (1, 1), name=conv_name_base + '2c')(x)
x = BatchNormalization(name=bn_name_base + '2c')(x)
x = Add()([x, input_tensor])
x = Activation('relu')(x)
return x
# 示例用法:
input_tensor = Input(shape=(224, 224, 3))
x = resnet_identity_block(input_tensor, kernel_size=3, filters=[64, 64, 256], stage=2, block='a')
model = Model(inputs=input_tensor, outputs=x)
model.summary()