ResNet代码(含详细的使用说明):
https://github.com/GarsonWw/resnet-garson.git
当谈到深度学习中的卷积神经网络时,ResNet(Residual Network)是一个备受赞誉且引人注目的架构。ResNet的出现在2015年引发了巨大的影响,并在图像分类、目标检测和语义分割等领域取得了令人瞩目的成果。
ResNet之所以引人注目,是因为它提出了一个独特的解决方案来应对深度卷积神经网络面临的问题,即梯度消失和模型难以优化的问题。在传统的卷积神经网络中,随着网络层数的增加,梯度往往会逐渐减小并消失,导致网络的收敛变得困难。而ResNet通过引入残差连接(residual connection),成功地解决了这一问题。由于梯度可以在残差连接中直接传播,ResNet可以轻松地训练非常深的网络,在原论文的介绍中甚至超过100层。
在ResNet的学习过程中,我们必须了解其网络结构。ResNet特殊的残差网络结构正是其解决梯度消失和模型难以优化的关键。
我们通过这张图可以发现:无论是18、34、50、101、152层的网络, 他们的网络框架都是基本一样的。
首先通过一个7x7的卷积层,再通过一个3x3的最大池化下采样。而后通过各自对应的conv2_x、conv3_x、conv4_x、conv5_x 残差结构。最后再跟上一个平均池化下采样,以及全连接输出层。
需要注意的是,下图的意思是需要2个3x3的卷积层,其卷积核的个数为64。
在我们了解ResNet的网络结构后,我们就可以通过代码将其结构呈现出来。
ResNet为了实现特征的降维和尺寸的匹配,分为两种残差结构 1.实线残差结构 2.虚线残差结构。需要注意的是ResNet18/34使用的两种残差结构与ResNet50及往上所使用的不同。如下图所示:
那首先让我们搭建ResNet18、ResNet34对应的残差结构。
下方为对应的Python实现代码,不要着急,我们将其慢慢讲解。
1、expension指的是卷积核的个数不发生变化,我们可以从ResNet的网络结构图种发现,ResNet18、34,其第一层和第二层的卷积核是不发生变化的,所以此时的expansion为1
2、根据图2.1可知,我们需要先经过stride步距为1、3x3卷积核、输入矩阵大小为56x56x64的卷积处理,所以我们需要定义一个conv1作为第一个卷积层。
3、而后我们跟随了一个批归一化处理(Batch Normalization,简称bn),归一化处理的好处有很多,可以加速网络训练,适应高学习率等,再次不过多赘述。其输入的参数是输出矩阵的深度。
4、 根据图2.1可知,经过conv1之后,我们需要经过一个ReLU函数。
5、 根据图2.1可知,ReLU函数之后,我们需要再经过一个卷积层,其步骤和第二点一致。但需要注意的是,无论是 图2.1 的实线残差结构,还是 图2.2 的虚线残差结构,他们第二步卷积的步距都为1,所以stride设为1。
6、 再经过一个批归一化处理(Batch Normalization,简称bn)层和一个下采样层。注意此时下采样层默认为None。下采样层通常位于残差块的第一个卷积层之前,用于将输入特征图的尺寸减小,以便与残差块的输出进行相加操作。
7、接下来的forward函数是一个正向传播的过程。我们输入特征矩阵的值为x,如果下采样函数为None时,对应的是我们的实线残差结构,那我们可以不进行下采样,此时x直接为identity(捷径分支的输出)。但如果不为None时,输入特征矩阵就需要经过下采样层函数得到identity(捷径分支的输出)。
8、接下来我们根据 图2.1 来使用前几步定义好的函数,来拼装成我们的残差结构。每一层的输出矩阵是下一层的输入矩阵。
至此resnet18/34残差结构讲解完毕!
使用ResNet50以及更大层数时,我们使用的残差结构是虚线残差结构。我们了解过实线残差结构是如何搭建之后,虚线残差结构就不难理解了。
1、因为是虚线残差结构,根据 图1.1 可知,ResNet50以上,第三层卷积层其卷积核的个数是等于前两层的4倍的(如图256是64的四倍,512是128的四倍...),所以此时expansion设为4。
2、卷积层的初始化与ResNet18/34基本一致,需要注意的是,其第二层卷积层的stride需要根据输入的stride进行调整。
3、最需要注意的是,我们的第三层卷积核的个数是第一层和第二层的4倍,所以在这里我们的outchannel输入矩阵就需要*4倍。同样的,输出改变了,那我们的bn的输出矩阵深度也需要改变。
4、根据图2.3,拼装残差结构。每一层的输出矩阵是下一层的输入矩阵。
至此resnet50+残差结构讲解完毕!
本节代码需要参考图1.1搭建。
1、初始化模型,其中:
block:网络模块(Bottleneck/BasicBlcok)
blocks_num:模块的数量(残差结构个数列表)
num_classes:训练集分类个数
include_top:用于基于resnet搭建更加复杂的网络
2、 输入矩阵的值,从 图1.1 发现所有第一个输入矩阵的值都为64。
3、搭建第一层卷积层。padding设为3可以让图像原来的高和宽缩减为原来的一半。
4、经过bn、ReLU、maxpool层。注意,为了让图像原来的高和宽缩减为原来的一半,padding为1。
5、每一层layer对应的下图中的每一层残差结构。
6、block对应卷积模块,channel对应输入卷积核的个数,block_num包含了多少个残差结构。
判断是否使用下采样函数,如果步距不为1或者有expansion时则需要使用。需要注意的是,ResNet50+层的layer1种对应的虚线残差结构只需要改变特征矩阵的深度,所以判断语句种的stride是等于传入的stride的。而对于其他的虚线残差结构,我们既要改变特征矩阵的深度也要改变特征矩阵的高度和宽度。
layers.apend先将第一层残差结构添加,再循环将每一个层残差结构都添加进去。
7、再进行整个残差网络结构的正向传播。
至此定义ResNet网络模型讲解完毕!