1 深度残差网络

 随着CNN的不断发展,为了获取深层次的特征,卷积的层数也越来越多。一开始的 LeNet 网络只有 5 层,接着 AlexNet 为 8 层,后来 VggNet 网络包含了 19 层,GoogleNet 已经有了 22 层。但仅仅通过增加网络层数的方法,来增强网络的学习能力的方法并不总是可行的,因为网络层数到达一定的深度之后,再增加网络层数,那么网络就会出现随机梯度消失的问题,也会导致网络的准确率下降。以下实验结果也表明确实出现了该现象,论文中称为网络退化现象,注意这和网络过拟合是两种情况。

1.1 什么是梯度爆炸、梯度消失?

上图是一个四层的全连接的网络,包括输入层、隐层(中间除了输入层跟输出层的总和)、输出层,假设每层网络激活后的输出为fi(x),其i表示第i层,x指的是第i层的输入,也就是第i-1层的输出,f是作为激活函数,那么就,反向传播算法是基于梯度下降,以目标负梯度方向对参数进行调整,参数的更新为,如果要更新第二层隐藏层的权值信息,根据链式求导法则:其实类似于就是对激活函数进行求导,如果所求导结果大于1,那么随着层数的增加,求出的梯度的更新将以指数形式相应增加,发生前文所提到的梯度爆炸;如果小于1,求出的梯度的更新将以指数形式相应减少,就会发生梯度消失。

1.2 如何解决这个问题?

 为了解决这一问题,传统的方法是采用数据初始化和正则化的方法,这解决了梯度消失的问题,但是网络准确率的问题并没有改善。而残差网络的出现可以解决梯度问题,而网络层数的增加也使其表达的特征也更好,相应的检测或分类的性能更强,再加上残差中使用了 1×1 的卷积,这样可以减少参数量,也能在一定程度上减少计算量。

 ResNet 网络的关键就在于其结构中的残差单元,如下图所示,在残差网络单元中包含了跨层连接,图中的曲线可以将输入直接跨层传递,进行了同等映射,之后与经过卷积操作的结果相加。假设输入图像为 x,输出为H(x),中间经过卷积之后的输出为F(x)的非线性函数,那最终的输出为H(x) = F(x) + x,这样的输出仍然可以进行非线性变换,残差指的是“差”,也就是F(x),而网络也就转化为求残差函数F(x) = H(x) - x,这样残差函数要比 F(x) = H (x) 更加容易优化。

 Resnet可以理解为三个人之间传话的游戏,第一个人传给第二个人,第二个人传给第三个人,那么第三个人收到的信息可能会由于第二个人理解错误,接收的信息受到影响,增加了传错的概率,所以Resnet的作用可以直跳过第二个人,直接把话给到第三个人。这个就是对于Resnet的简单理解。

1.3 什么是残差?

 ResNet提出了两种mapping:一种是identity mapping,指的就是图中”弯弯的曲线”,另一种residual mapping,指的就是除了”弯弯的曲线“那部分,所以最后的输出是 H(x) = F(x) + x,identity mapping顾名思义,就是指本身,也就是公式中的x,而residual mapping指的是“差”,也就是F(x) = H(x)−x,所以残差指的就是F(x)部分,这里有解释了一遍F(x) = H(x)−x。

2 Resnet50 网络

 Resnet50网络中包含了49 个卷积层,外加一个全连接层(FC)。如下图所示,Resnet50网络结构可以分成七个部分,第一部分不包含残差块,主要对输入进行卷积、正则化、激活函数、最大池化的计算。第二、三、四、五部分结构都包含了残差块,图中的绿色图块不会改变残差块的尺寸,只用于改变残差块的维度。在Resnet50网络结构中,残差块都有三层卷 积,那网络总共就是有1+3 × (3+4+6+3) = 49个卷积层,加上最后的全连接层总共是50层,这也是Resnet50名称的由来。网络的输入为224×224×3,经过前五部分的卷积计算,输出为7×7×2048,池化层会将其转化成一个特征向量,最后分类器会对这个特征向量进行计算并输出类别概率。

下面这张图片是Resnet文章所展示的结构图,文章会对其进行剖析,让人好理解。 Resnet50的另外一种理解。

2.1 ResNet50整体结构

上图可划分为左、中、右3个部分,三者内容分别:Resnet50整体结构;Resnet50各个stage具体结构;Bottleneck具体结构。 整体结构部分分为5个stage,其中stage 0 的结构是比较简单,可以视其为对INPUT图像的预处理,后4个Stage都由Bottleneck组成,结构较为相似。Stage 1包含3个Bottleneck,剩下的3个stage分别包括4、6、3个Bottleneck。

stage 0 阶段

(3,224,224) 指输入INPUT的通道数(channel)、高(height)和宽(width),即(C,H,W)。现假设输入的高度和宽度相等,所以用(C,W,W)表示。 该stage中第1层包括3个先后操作: 1.CONV CONV是卷积(Convolution)的缩写,7×7指卷积核大小,64指卷积核的数量(即该卷积层输出的通道数),/2指卷积核的步长为2。 2.BN BN是Batch Normalization的缩写,即常说的BN层。 3.RELU RELU指ReLU激活函数。 该stage中第2层为MAXPOOL,即最大池化层,其卷积核大小为3×3、步长为2。 (64,56,56)是该stage输出的通道数(channel)、高(height)和宽(width),其中64等于该stage第1层卷积层中卷积核的数量,56等于224/2/2(步长为2会使输入尺寸减半)。

总体来讲,在Stage 0中,形状为(3,224,224)的输入先后经过卷积层、BN层、ReLU激活函数、MaxPooling层得到了形状为(64,56,56)的输出。

stage 1 阶段

与stage 0类似,stage1输入的形状为(64,56,56),输出的形状为(64,56,56)。

Bottleneck具体结构

现在要介绍2种Bottleneck的结构。“BTNK”指的是BottleNeck的缩写。

2种Bottleneck分别对应了2种情况:输入与输出通道数相同(BTNK2)、输入与输出通道数不同(BTNK1),这一点可以结合ResNet原文。

BTNK2

BTNK2有2个可变的参数C和W,即输入的形状(C,W,W)中的c和W。

令形状为(C,W,W)的输入为x,令BTNK2左侧的3个卷积块(以及相关BN和RELU)为函数F(x),两者相加(F(x)+x)后再经过1个ReLU激活函数,就得到了BTNK2的输出,该输出的形状仍为(C,W,W),即上文所说的BTNK2对应输入x与输出F(x)通道数相同的情况。

BTNK1

BTNK1有4个可变的参数C、W、C1和S。

与BTNK2相比,BTNK1多了1个右侧的卷积层,令其为函数G(x)。BTNK1对应了输入x与输出F(x)通道数不同的情况,也正是这个添加的卷积层将x变为G(x),起到匹配输入与输出维度差异的作用(G(x)和F(x)通道数相同),进而可以进行求和F(x)+G(x)。

总结

ResNet后4个stage中都有BTNK1和BTNK2。

4个stage中BTNK2参数规律相同,4个stage中BTNK2的参数全都是1个模式和规律,只是输入的形状(C,W,W)不同。stage 1中BTNK1参数的规律与后3个stage不同,然而,4个stage中BTNK1的参数的模式并非全都一样。具体来讲,后3个stage中BTNK1的参数模式一致,stage 1中BTNK1的模式与后3个stage的不一样,这表现在以下2个方面: 1.参数S:BTNK1左右两个1×1卷积层是否下采样:  stage 1中的BTNK1:步长S为1,没有进行下采样,输入尺寸和输出尺寸相等。  后3个stage的BTNK1:步长S为2,进行了下采样,输入尺寸是输出尺寸的2倍。 2.参数C和C1:BTNK1左侧第一个1×1卷积层是否减少通道数  stage 1中的BTNK1:输入通道数C和左侧1×1卷积层通道数C1相等(C=C1=64),即左侧1×1卷积层没有减少通道数。  后3个stage的BTNK1:输入通道数C和左侧1×1卷积层通道数C1不相等(C=2*C1),左侧1×1卷积层有减少通道数。

代码实现

class Bottleneck(nn.Module):

expansion = 4 # 卷积核是前一个卷积核的四倍

def __init__(self, in_channel, out_channel, stride=1, downsample=None):

super(Bottleneck, self).__init__()

self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,

kernel_size=1, stride=1, bias=False) # squeeze channels

self.bn1 = nn.BatchNormalization(out_channel)

# -----------------------------------------

self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel, kernel_size=3, bias=False,

stride=stride, padding=1)

self.bn2 = nn.BatchNormalization(out_channel)

# -----------------------------------------

self.conv3 = nn.Conv2d(out_channels=out_channel * self.expansion, kernel_size=1, stride=1,

bias=False)

self.bn3 = nn.BatchNormalization(out_channel * self.expansion)

# -----------------------------------------

self.relu = nn.ReLU(inplace=True)

self.downsample = downsample

def forward(self, x):

identity = x

if self.downsample is not None: # 为None的话对应实线的残差结构,如果不为None则是虚线的

identity = self.downsample(x)

out = self.conv1(x)

out = self.bn1(out)

out = self.relu(out)

out = self.conv2(out)

out = self.bn2(out)

out = self.relu(out)

out = self.conv3(out)

out = self.bn3(out)

out = self.add([out, identity])

out = self.relu(out)

return out

引入Resnet50网络之后的效果

参考:

https://zhuanlan.zhihu.com/p/353235794哔哩哔哩—霹雳吧啦https://arxiv.org/abs/1512.03385

精彩链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: