但事实并非如此,人们创造随着网络深度的增加,模型精度并不总是提升,并且这个问题显然不是由过拟合(overfitting)造成的,由于网络加深后不仅测试偏差变高了,它的演习偏差竟然也变高了。
作者提出,这可能是由于更深的网络会伴随梯度消逝/爆炸问题,从而阻碍网络的收敛。
作者将这种加深网络深度但网络性能却低落的征象称为退化问题(degradation problem)。

ResNet及其变种的结构梳理、有效性分析与代码解读_卷积_收集 智能助手

Is learning better networks as easy as stacking more layers? An obstacle to answering this question was the notorious problem of vanishing/exploding gradients [1, 9], which hamper convergence from the beginning.

Unexpectedly, such degradation is not caused by overfitting, and adding more layers to a suitably deep model leads to higher training error.

文中给出的实验结果进一步描述了这种退化问题:当传统神经网络的层数从20增加为56时,网络的演习偏差和测试偏差均涌现了明显的增长,也便是说,网络的性能随着深度的增加涌现了明显的退化。
ResNet便是为理解决这种退化问题而出身的。

图1 20层与56层传统神经网络在CIFAR上的演习偏差和测试偏差

二、ResNet怎么办理网络退化问题

随着网络层数的增加,梯度爆炸和梯度消逝问题严重制约了神经网络的性能,研究职员通过提出包括Batch normalization在内的方法,已经一定程度上缓解了这个问题,但依然不敷以知足需求。

This problem,however, has been largely addressed by normalized initialization [23, 9, 37, 13] and intermediate normalization layers[16], which enable networks with tens of layers to start converging for stochastic gradient descent (SGD) with backpropagation [22].

作者想到了构建恒等映射(Identity mapping)来办理这个问题,问题办理的标志是:增加网络层数,但演习偏差不增加。
为什么是恒等映射呢,我是这样子想的:20层的网络是56层网络的一个子集,56层网络的解空间包含着20层网络的解空间。
如果我们将56层网络的末了36层全部短接,这些层进来是什么出来也是什么(也便是做一个恒等映射),那这个56层网络不就等效于20层网络了吗,至少效果不会比较原来的20层网络差吧。
同样是56层网络,不引入恒等映射为什么就弗成呢?由于梯度消逝征象使得网络难以演习,虽然网络的深度加深了,但是实际上无法有效演习网络,演习不充分的网络不但无法提升性能,乃至降落了性能。

There exists a solution by construction to the deeper model: the added layers are identity mapping, and the other layers are copied from the learned shallower model. The existence of this constructed solution indicates that a deeper model should produce no higher training error than its shallower counterpart.

图2 残差学习基本单元

那怎么构建恒等映射呢?大略地说,原来的网络输入x,希望输出H(x)。
现在我们改一改,我们令H(x)=F(x)+x,那么我们的网络就只须要学习输出一个残差F(x)=H(x)-x。
作者提出,学习残差F(x)=H(x)-x会比直接学习原始特色H(x)大略的多。

三、ResNet网络构造与代码实现

ResNet紧张有五种紧张形式:Res18,Res34,Res50,Res101,Res152;

如下图所示,每个网络都包括三个紧张部分:输入部分、输出部分和中间卷积部分(中间卷积部分包括如图所示的Stage1到Stage4共计四个stage)。
只管ResNet的变种形式丰富,但都遵照上述的构造特点,网络之间的不同紧张在于中间卷积部分的block参数和个数存在差异。
下面我们以ResNet18为例,看一下全体网络的实当代码是若何的。

图3.1 ResNet构造总览

网络整体构造

我们通过调用resnet18( )函数来天生一个详细的model,而resnet18函数则是借助ResNet类来构建网络的。

class ResNet(nn.Module): def forward(self, x): # 输入 x = self.conv1(x) x = self.bn1(x) x = self.relu(x) x = self.maxpool(x) # 中间卷积 x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) # 输出 x = self.avgpool(x) x = x.view(x.size(0), -1) x = self.fc(x) return x# 天生一个res18网络def resnet18(pretrained=False, kwargs): model = ResNet(BasicBlock, [2, 2, 2, 2], kwargs) if pretrained: model.load_state_dict(model_zoo.load_url(model_urls['resnet18'])) return model

在ResNet类中的forward( )函数规定了网络数据的流向:

(1)数据进入网络后先经由输入部分(conv1, bn1, relu, maxpool);

(2)然后进入中间卷积部分(layer1, layer2, layer3, layer4,这里的layer对应我们之前所说的stage);

(3)末了数据经由一个均匀池化和全连接层(avgpool, fc)输出得到结果;

详细来说,resnet18和其他res系列网络的差异紧张在于layer1~layer4,其他的部件都是相似的。

网络输入部分

所有的ResNet网络输入部分是一个size=7x7, stride=2的大卷积核,以及一个size=3x3, stride=2的最大池化组成,通过这一步,一个224x224的输入图像就会变56x56大小的特色图,极大减少了存储所需大小。

self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False) self.bn1 = nn.BatchNorm2d(64) self.relu = nn.ReLU(inplace=True) self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)

输入层特色图数据变革

网络中间卷积部分

中间卷积部分紧张是下图中的蓝框部分,通过33卷积的堆叠来实现信息的提取。
红框中的[2, 2, 2, 2]和[3, 4, 6, 3]等则代表了bolck的重复堆叠次数。

ResNet构造细节

刚刚我们调用的resnet18( )函数中有一句 ResNet(BasicBlock, [2, 2, 2, 2], kwargs),这里的[2, 2, 2, 2]与图中红框是同等的,如果你将这行代码改为 ResNet(BasicBlock, [3, 4, 6, 3], kwargs), 那你就会得到一个res34网络。

残差块实现

下面我们来详细看一下一个残差块是怎么实现的,如下图所示的basic-block,输入数据分成两条路,一条路经由两个33卷积,另一条路直接短接,二者相加经由relu输出,十分大略。

basic_blockclass BasicBlock(nn.Module): expansion = 1 def __init__(self, inplanes, planes, stride=1, downsample=None): super(BasicBlock, self).__init__() self.conv1 = conv3x3(inplanes, planes, stride) self.bn1 = nn.BatchNorm2d(planes) self.relu = nn.ReLU(inplace=True) self.conv2 = conv3x3(planes, planes) self.bn2 = nn.BatchNorm2d(planes) self.downsample = downsample self.stride = stride def forward(self, x): identity = x out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) if self.downsample is not None: identity = self.downsample(x) out += identity out = self.relu(out) return out

bascic_block 数据走向

代码比较清晰,不做剖析了,紧张提一个点:downsample,它的浸染是对输入特色图大小进行减半处理,每个stage都有且只有一个downsample。
后面我们再详细先容。

网络输出部分

网络输出部分很大略,通过全局自适应平滑池化,把所有的特色图拉成11,对付res18来说,便是1x512x7x7 的输入数据拉成 1x512x1x1,然后接全连接层输出,输出节点个数与预测种别个数同等。

self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) self.fc = nn.Linear(512 block.expansion, num_classes)

至此,整体网络构造代码剖析结束,更多细节,请看torchvision源码。

四、Bottleneck构造和11卷积

ResNet50起,就采取Bottleneck构造,紧张是引入1x1卷积。
我们来看一下这里的1x1卷积有什么浸染:

对通道数进行升维和降维(跨通道信息整合),实现了多个特色图的线性组合,同时保持了原有的特色图大小;比较于其他尺寸的卷积核,可以极大地降落运算繁芜度;如果利用两个3x3卷积堆叠,只有一个relu,但利用1x1卷积就会有两个relu,引入了更多的非线性映射;

Basicblock和Bottleneck构造

我们来打算一下11卷积的打算量上风:首先看上图右边的bottleneck构造,对付256维的输入特色,参数数目:1x1x256x64+3x3x64x64+1x1x64x256=69632,如果同样的输入输出维度但不该用1x1卷积,而利用两个3x3卷积的话,参数数目为(3x3x256x256)x2=1179648。
大略打算下就知道了,利用了1x1卷积的bottleneck将打算量简化为原有的5.9%,收益超高。

五、ResNet的网络设计规律

全体ResNet不该用dropout,全部利用BN。
此外,回到最初的这张细节图,我们不难创造一些规律和特点:

受VGG的启示,卷积层紧张是3×3卷积;对付相同的输出特色图大小的层,即同一stage,具有相同数量的3x3滤波器;如果特色舆图大小减半,滤波器的数量更加以保持每层的韶光繁芜度;(这句是论文和现场演讲中的原话,虽然我并不理解是什么意思)每个stage通过步长为2的卷积层实行下采样,而却这个下采样只会在每一个stage的第一个卷积完成,有且仅有一次。
网络以均匀池化层和softmax的1000路全连接层结束,实际上工程上一样平常用自适应全局均匀池化 (Adaptive Global Average Pooling);

从图中的网络构造来看,在卷积之后全连接层之前有一个全局均匀池化 (Global Average Pooling, GAP) 的构造,这个构造最早出自经典论文:https://arxiv.org/abs/1312.4400

In this paper, we propose another strategy called global average pooling to replace the traditional fully connected layers in CNN. The idea is to generate one feature map for each corresponding category of the classification task in the last mlpconv layer. Instead of adding fully connected layers on top of the feature maps, we take the average of each feature map, and the resulting vector is fed directly into the softmax layer. One advantage of global average pooling over the fully connected layers is that it is more native to the convolution structure by enforcing correspondences between feature maps and categories. Thus the feature maps can be easily interpreted as categories confidence maps. Another advantage is that there is no parameter to optimize in the global average pooling thus overfitting is avoided at this layer. Futhermore, global average pooling sums out the spatial information, thus it is more robust to spatial translations of the input.

We can see global average pooling as a structural regularizer that explicitly enforces feature maps to be confidence maps of concepts (categories).This is made possible by the mlpconv layers, as they makes better approximation to the confidence maps than GLMs.

总结如下:

比较传统的分类网络,这里接的是池化,而不是全连接层。
池化是不须要参数的,比较于全连接层可以砍客岁夜量的参数。
对付一个7x7的特色图,直接池化和改用全连接层比较,可以节省将近50倍的参数,浸染有二:一是节省打算资源,二是防止模型过拟合,提升泛化能力;这里利用的是全局均匀池化,但我以为大家都有疑问吧,便是为什么不用最大池化呢?这里阐明很多,我查阅到的一些论文的实验结果表明均匀池化的效果略好于最大池化,但最大池化的效果也差不到哪里去。
实际利用过程中,可以根据自身需求做一些调度,比如多分类问题更适宜利用全局最大池化(道听途说,不作担保)。
如果不愿定话还有一个更保险的操作,便是最大池化和均匀池化都做,然后把两个张量拼接,让后续的网络自己学习权重利用。

六、如何改造得到自己的ResNet?

我举一个大略的例子

from torchvision.models.resnet import def get_net(): model = resnet18(pretrained=True) model.avgpool = nn.AdaptiveAvgPool2d((1, 1)) model.fc = nn.Sequential( nn.BatchNorm1d(5121), nn.Linear(5121, 你的分类种别数), ) return model

代码大略解读一下:

首先,通过torchvision导入干系的函数通过resnet18( )实例化一个模型,并利用imagenet预演习权重将均匀池化修正为自适应全局均匀池化,避免输入特色尺寸不匹配修正全连接层,紧张是修正分类种别数,并加入BN1d

这样子,不仅可以根据自己的需求改造网络,还能最大限度的利用现成的预演习权重。
须要把稳的是,这里的nn.BatchNorm1d(5121)是很必要的,初学者可以考试测验删除这个部件感想熏染一下差异。
在我曾经的实验里面,loss会直接爆炸。

七、ResNet的常见改进

改进一:改进downsample部分,减少信息流失落。
前面说过了,每个stage的第一个conv都有下采样的步骤,我们看左边第一张图左侧的通路,input数据进入后在会经历一个stride=2的11卷积,将特色图尺寸减小为原来的一半,请把稳1x1卷积和stride=2会导致输入特色图3/4的信息不被利用,因此ResNet-B的改进便是便是将下采样移到后面的3x3卷积里面去做,避免了信息的大量流失落。
ResNet-D则是在ResNet-B的根本年夜将identity部分的下采样交给avgpool去做,避免涌现1x1卷积和stride同时涌现造成信息流失落。
ResNet-C则是另一种思路,将ResNet输入部分的7x7大卷积核换成3个3x3卷积核,可以有效减小打算量,这种做法最早涌如今Inception-v2中。
实在这个ResNet-C 我比较迷惑,ResNet论文里说它借鉴了VGG的思想,利用大量的小卷积核,既然这样那为什么第一部分依旧要放一个7x7的大卷积核呢,不知道是出于若何的考虑,但是现在的多数网络都把这部分改成3个3x3卷积核级联。

ResNet的三种改进

改进二:ResNet V2。
这是由ResNet原班人马打造的,紧张是对ResNet部分组件的顺序进行了调度。
各种魔改中常见的预激活ResNet便是出自这里。

ResNet V2

原始的resnet是上图中的a的模式,我们可以看到相加后须要进入ReLU做一个非线性激活,这里一个改进便是砍掉了这个非线性激活,不难明得,如果将ReLU放在原来的位置,那么残差块输出永久是非负的,这制约了模型的表达能力,因此我们须要做一些调度,我们将这个ReLU移入了残差块内部,也便是图e的模式。
这里的细节比较多,建议直接阅读原文:Identity Mappings in Deep Residual Networks ,就先先容这么多。

八、1.从模型集成角度理解ResNet的有效性

ResNet 中实在是存在着很多路径的凑集,全体ResNet类似于多个网络的集成学习,证据是删除部分ResNet的网络结点,不影响全体网络的性能,但是在VGG上做同样的事请网络急速崩溃,由此可见比较其他网络ResNet对付部分路径的缺失落不敏感。
更多细节详细可拜会NIPS论文:Residual Networks Behave Like Ensembles of Relatively Shallow Networks ;

模型集成假说

毁坏性实验

八、2.从梯度反向传播角度理解ResNet的有效性

残差构造使得梯度反向传播时,更不易涌现梯度消逝等问题,由于Skip Connection的存在,梯度能畅通无阻地通过各个Res blocks,下面我们来推导一下 ResNet v2 的反向传播过程。

原始的残差公式是这样子的,函数F表示一个残差函数,函数f表示激活函数,:

ResNet v2 利用恒等映射,且相加后不该用激活函数,因此可得到:

递归得到第L层的表达式:

反向传播求第l层梯度:

我们从这个表达式可以看出来:第l层的梯度里,包含了第L层的梯度,普通的说便是第L层的梯度直接通报给了第l层。
由于梯度消逝问题紧张是发生在浅层,这种将深层梯度直接通报给浅层的做法,有效缓解了深度神经网络梯度消逝的问题。

八、3.其他假说汇总

(1)差分放大器假说

残差构造可以放大输入中眇小的扰动,因此更加灵敏;

(2)自适应深度(https://www.zhihu.com/question/293243905/answer/484708047)

传统的conv模块是很难通过学习变成恒等的,由于大家学过旗子暗记与系统都知道,恒等的话filter的冲激相应要为一个冲激函数,而神经网络实质是学概率分布 局部一层不太随意马虎变成恒等,而resnet加入了这种模块给了神经网络学习恒等映射的能力。
以是我个人理解resnet除了减弱梯度消逝外,我还理解为这是一种自适应深度,也便是网络可以自己调节层数的深浅,不须要太深时,中间恒等映射就多,须要时恒等映射就少。
当然了,实际的神经网络并不会有这么强的局部特性,它的演习结果基本是一个整体,并不一定会涌现我说的某些block便是恒等的情形

九、总结

ResNet是当前打算机视觉领域的基石构造,是初学者无法绕开的网络模型,仔细阅读论文和源码并进行实验是极有必要的。

末了,强烈推举大家看一下何凯明的现场演讲,有助于更好地理解ResNet。

https://zhuanlan.zhihu.com/p/54072011?utm_source=com.tencent.tim&utm_medium=social&utm_oi=41268663025664

参考资料:

https://zhuanlan.zhihu.com/p/31852747

https://zhuanlan.zhihu.com/p/40050371

https://zhuanlan.zhihu.com/p/28124810

https://zhuanlan.zhihu.com/p/37820282

https://zhuanlan.zhihu.com/p/47766814

作者 | Pascal - 知乎

版权声明

本文版权归《Pascal》,转载请自行联系