zoukankan      html  css  js  c++  java
  • 经典backbone——ResNet

    ResNet——Residual Network

    Deep Residual Learning for Image Recognition

    Identity Mappings in Deep Residual Networks

    Wide Residual Network

    最原始的ResNet

    卷积神经网络(Convolutional Neural Network)正不断朝着“Deep”这个方向发展,最早期的LeNet只有5层,后来VGG把深度增加到19层,而我们即将要介绍的ResNet,更是超过了100层。

    于是我们的 only true god-何恺明提出一个问题:

    Is learning better networks as easy as stacking more layers?

    答案是否定的,单纯地增加网络层数,提高深度,不仅不会得到更好的效果,反而会使误差增大!

    img

    img

    多个datasets的测试表明,仅仅只是简单地堆叠卷积层,并不能让网络训练得更好。

    img

    按理来说,如果网络加深,training acc应该增大,而testing acc减小,但是上图并不是这么回事,于是乎,Kaiming He提出了Deep Residual Learning的架构。

    关于残差模块(residual block)

    恺明老师首先引入了一个残差模块(residual block)的概念

    img

    输入为(x),需要拟合的结果为(H(x)),此时我们将输出差分为(x+y),也就是(H(x)=x+y)

    再令(y=F(x)),即通过正常的卷积产生的结果,最后两者相加即为残差块输出。

    img

    因为(x)本身就是输出,所以我们只需要拟合(F(x))即可

    如上图,原始的plain架构,我们用两层卷积层来模拟函数(g(cdot)),而在residual block中,我们用两层卷积层来模拟函数(F(cdot))

    举个例子:

    输入(x=2.9),经过拟合后的输出为(H(x)=3.0),那么残差就是(F(x)=H(x)-x=0.1)。如果我们拟合的是恒等变换,即输入(x=2.5),输出还是(H(x)=2.5),那么残差就是(F(x)=H(x)-x=0.0)

    如上图所示,假设(x)(2.9)经过两层卷积层(cov)后变为(3.1),普通网络的变化率为(igtriangleup =frac{3.1-3.0}{3.0}=3.3%),而残差模块的变化率为(igtriangleup =frac{0.2-0.1}{0.1}=100%)

    残差的引入去掉了主体部分,从而突出了微小的变化

    网络结构

    有了残差模块(residual block)这个概念,我们再来设计网络架构,
    架构很简单,基于VGG19的架构,我们首先把网络增加到34层,增加过后的网络我们叫做plain network,再此基础上,增加残差模块,得到我们的Residual Network

    preview

    img

    虚线部分,维度增加,亦即卷积核(filters)数目增加的过程,这个时候(F(x)+x)就会出现维度不匹配现象,所以就出现后面用(1 imes 1)的conv来提高维度,然后再进行相加的操作.

    关于bottle neck

    论文中有两种residual block的设计,如下图所示:

    preview

    在训练浅层网络的时候,我们选用前面这种,而如果网络较深(大于50层)时,会考虑使用后面这种(bottle neck),这两个设计具有相似的时间复杂度。

    conv虽然写的是(3 imes 3),但实际上应该是(64(上一层channel数) imes 3 imes 3(conv filter的大小) imes 64(输出的channel数目))

    同样举个例子:
    对于ImageNet而言,进入到第一个residual block的input为((56,56,64)),采用左侧的两个(3 imes 3)conv的卷积层:参数量为((3 imes 3 imes 64) imes 64+(3 imes 3 imes 64) imes 64),化简一下:(params:(18 imes 64) imes 64=73728)
    采用右侧的的bottle neck:参数量为((1 imes1 imes64) imes 256+(3 imes3 imes 64) imes64+(1 imes1 imes 256) imes 64) ,化简一下:(params:(10 imes 64)+2 imes 64 imes256=53248)

    可以看到它们的参数量属于同一个量级,
    但是这里bottleneck占用了整个network的「三层」,而原本只有「两层」,
    所以这样就节省了大量的参数,
    在大于50层的ResNet中,他们使用了bottle neck这种形式。

    具体细节如图所示:

    img

    ResNet 改进 Identity mapping(恒等变换)

    我们来仔细分析一下Residual Block, 在这篇paper中也被叫为Residual Unit.

    对于原始的 Residual Unit(block),我们有如下计算:

    [egin{matrix}y_l=h(x_l)+mathcal{F}(x_l,mathcal{W}_l)\x_{l+1}=f(y_l)end{matrix} ]

    (x_l)表示第(l)个Residual Unit的输入, (y_l)则代表第(l)个Residual Unit的输出,(h)代表的某个变换,在此处是恒等变换,(mathcal{F})代表residual function,(f)代表某种操作,在这里是ReLU 。

    所以可以写成如下形式:

    [egin{matrix}y_l=x_l+mathcal{F}(x_l,mathcal{W}_l)\x_{l+1}=ReLU(y_l)end{matrix} ]

    img

    也就是Residual Unit一开始的做法了。

    那如果,我们(f)是恒等变换呢,即(x_l+1equiv y_l),那么有:

    [x_{l+1}=y_l=x_l+mathcal{F}(x_l,mathcal{W}_l) ]

    对于任意一层,我们都能用这个公式来表示:

    [X_L=x_l+sum^{L-1}_{i=l}mathcal{F}(x_i,mathcal{W}_i) ]

    img

    这便是这篇paper的改进,把原本的ReLU,放到Residual Unit的conv前面去,而不是放在addition之后。

    img

    可以看到,在上图作者对cifar10进行的多组实验中,使用full pre-activation这种Residul Unit效果最佳,个人认为这张表格还是挺重要的,我们简单分析一下!

    • (a)original:原始的结构
    • (b)BN after addition:这是在做相反的实验,本来我们的目的是把ReLU移到旁路上去,这里反而把BN拿出来,这进一步破坏了主路线上的恒等关系,阻碍了信号的传递,从结果也很容易看出,这种做法不ok
    • (c)ReLU before addition:将(f)变为恒等变换,最容易想到的方法就是将ReLU直接移动到BN后面,但这会出现一个问题,一个(mathcal{F})(残差函数)的输出应该可以是((-infty,infty)) ,但是经过ReLU之后就会变为((0,infty))​,这种做法的结果也比(a)要差。

    直接提上来似乎不行,但是问题反过来想, 在addition之后做ReLU,不是相当于在下一次conv之前做ReLU吗?

    • (d)ReLU-only pre-activation:根据刚才的想法,我们把ReLU放到前面去,然而我们得到的结果和(a)差不多,原因是什么呢?因为这个ReLU层不与BN层连接使用,因此无法共享BN所带来的好处。
    • (e)full pre-activation:啊,那要不我们也把BN弄前面去,惊喜出现了,我们得到了相当可观的结果,是的,这便是我们最后要使用的Unit结构!!!

    Wide Residual Network

    img

    原始的ResNet如(a)和(b)所示,(b)是使用了bottleneck的residual block,而(c)和(d)便是WRN这篇paper的作者提出的架构。他认为一味的增加深度并不是最有效的方法,residual block的宽度对网络性能的提升更有帮助。所以他考虑增加每一层的宽度(身材修长固然好,但是太瘦便是营养不良,稍微长结实点,也许会更好,貌似家长是这个观点)。

    About block type

    1. (B(3,3))- original <<basic>> block
    2. (B(3,1,1))- with one extra (1 imes 1) layer
    3. (B(1,3,1))- with the same dimensionality of all convolutions,<<straightened>> bottle neck
    4. (B(1,3))- the network has alternating (1 imes 1sim 1 imes 3) convolutions everywhere
    5. (B(3,1))- similar idea to the previous block
    6. (B(3,1,1))- Network-in-Network style block

    (B(3,3))(B(1,3,1))是原始paper中有实验的block,(B(3,1,1))类似于NIN的block。
    对以上这些residual block,作者使用了(k=2)来进行测试,结果表明(B(3,3))最好。

    img

    所以作者后来实验的baseline使用的是resnet最原始的两个conv3x3的residual block,上图中右侧的(k),就是增加宽度的倍数,如果(k=1) ,就是原本的ResNet。

    img

    Experimental results

    从实验的结果来看,也是一目了然,WRN的40-4架构就可以和ResNet的1001层相比,当然,训练时间的话还是WRN更少。
    另外可以看出,深度和宽度都会为网络的性能提高带来好处。

    img

    About Dropout

    网络深度和宽度的加深,会增加训练的参数量,导致过拟合,作者提出的另一个idea便是在residual block里面加入dropout,也是就开头讲到的(d)

    img

    从实验结果看,也能说明,当网络层(depth)数较浅,或者宽度(k)较小时,网络还不需要加dropout,但是当层数增加,宽度增加,参数量指数增大时,加入dropout可以有效防止model的overfitting。

    ResNeXt

    很多人认为ResNeXt没什么创新点,原因如下:

    1. 结构照搬Inception
    2. 实现只加了个分组卷积,变化微乎其微

    因此乏善可陈。但这反而是ResNeXt的闪光点,只是这闪光点略有些眩目而已。因为若仔细想想,“照搬Inception”必然导致模型复杂,而实现起来却又“变化微乎其微”,这两者之间有巨大的矛盾。

    但随着阅读论文/梳理思路/实现模型,再根据模型重新阅读论文梳理思路,进而修改实现这么循环下来,“吃透了”ResNeXt,才发现它的趣味所在。

    通过“同构”的简洁,四两拨千斤地击败复杂的Inception,还含蓄地动摇了Inception的出发点。

    Abstract

    • 借鉴Inception的“分割-变换-聚合”策略,却用相同的拓扑结构组建ResNeXt模块。
    • 简洁:同构多分枝,因此有更少的超参数
    • 引入“基数”(cardinality),基数增加可提高模型效果。比变深or变宽还好使。

    Introduction

    现在的研究方向已经从早年寻找各类手工设计特征(如SIFT和HOG)的“特征工程”(feature engineering)到寻找高效网络结构的“网络工程”(network engineering)发展。但由于大家设计网络的骚操作五花八门,搞得超参数越来越多,所以现在设计结构也变得越来越难了……

    VGG则是个清流,它采用了非常简练的设计原则:堆叠相同形状的模块(blocks)。这种思考方法ResNet用了都说好,只不过ResNet采用相同形状的“残差块”(Residual Blocks)。

    这样做不仅满足了广大处女座强迫症患者,最大的好处在于网络结构有关的超参数只涉及深度和宽度,那么调参就不用那么疯狂了。

    要知道如果超参数太多,调起来麻烦不说,有时候自己都不知道到底哪个参数解决了问题,碰到新问题可能原来调好的模型就又歇逼了。(存在一组过于精细的超参数只对特定数据集有效的风险)。

    而VGG同期反其道而行之的Inception,则使用了一套精心设计的网络结构,通过卷积层之间的稀疏连接达到了很好的效果。Inception将输入通过(1*1)conv分到几个低维嵌入。然后用一套特殊的卷积核处理,最后接在一起合成。这就是它特有的“分割-变换-融合”策略。

    注:稀疏表示指每个block中有几个不同的分支,相互独立互不影响,只在最后把他们的输出拼接在一起(concatenation)。

    这么做的好处是,这个Block所能表示的解空间,实际上是一个更大卷积核的严格子空间。但本身复杂度很小。

    说人话:Inception用了小卷积核的计算量干了大卷积核的事情,效果还差不多。

    Inception的作者认为,Inception的优势更多在于通过精心设计的复杂Block结合多个感受野的特征。但这就正中了一开始分析的下怀:针对问题尽心设计的精巧结构往往会失去足够的泛化性。

    那么问题来了,Inception的能力之所以优越,究竟是因为它引以为傲的复杂结构,还是别的什么?

    比如,“分割-变换-融合”策略(稀疏连接)?

    从“分割-变换-融合”到聚合变换

    “分割-变换-融合”这技术看起来唬人,但实际上却也一直在我们身边。

    1.重新思考神经元

    img

    很久很久以前,在CNN还没有大行其道,人们还管全连接层叫神经网络的时候。最常见也是最简单的运算就是输入向量与权重的内积了,而这本来就是一种聚合变换。

    [underset{i=1}{overset{D}{sum}}w_ix_i ]

    其中(X=[x_1,x_2,...,x_D])是D通道的输入向量,(w_i)则是对应(i)通道的权重。现在我们用之前“分割,变换,聚合”的角度重新描述一下内积过程:

    1. 分割:向量(X)被分成了D个低维嵌入,内积中则被直接分成了一个个1维子空间(x_i)
    2. 变换:模型对低维表示进行变换,这里只是简单的乘了一个数,变成了(w_ix_i)
    3. 聚合:把全部低维嵌入聚合到一块,这里就是(underset{i=1}{overset{D}{sum}})

    2.聚合变换与NeXt

    上面的变换是神经元对输入只进行一次简单内积,现在我们把(w_ix_1)换成更一般的函数,当然这个函数也可以是一个子网络。

    也就是说,之前是在一个神经元中的变换从内积变成了一堆子网络

    如果Inception是“网络中的网络”(Network-in-Network),那么这里就是“神经元中的网络”(Network-in-Neuron)

    [mathcal{F}(x)=underset{i=1}{overset{C}{sum}}mathcal{T}_i(X) ]

    其中(mathcal{T}_i) 是能把(X)投影到(低维)子空间,并进行变换的任意函数。ResNeXt中,(mathcal{T}_i)是一个自带瓶颈的网络结构((1*1)conv降维→(3*3)conv→(1*1)conv升维)

    (C)就是传说中的“基数”了,类比神经元,就和通道数(D)差不多。论文发现,基数本身可以作为继卷积滤波器宽度(通道数),网络深度(卷积层数)之后的第三个基本超参数(那种理应越大越好的超参数)。特别是当增长深度与宽度对模型的收益边际递减时,基数的增加另辟蹊径解决了这一问题。

    在深度和宽度外找到了第3个维度:基数。模型也因找到了下一个调参维度,而被称为“NeXt”。

    最后,加上我们喜闻乐见的恒等映射,就做好ResNeXt的基本结构啦

    [y=X+underset{i=1}{overset{C}{sum}}T_i(X) ]

    3.为何要强调“聚合变换”?

    很多人看到ResNeXt的介绍/论文时,直到这里都是懵的。(以后看到很多人,自动替换成当年的我就好了)

    懵了后只想看图,觉得就像作者吹了半天牛,其实只是把Inception的idea直接照抄过来。

    其实不然,作者写这么多非但不是为了把Inception的想法照抄,而是想把Inception从立脚点开始到性能都吊起来打一顿

    img

    上图左边是Incepytion,右边是ResNeXt。观察两个Block结构,能发现二者有何差别?(除了上下颠倒,除了一个彩色一个黑白……)

    最本质的差别,其实是Block内每个分支的拓扑结构,Inception为了提高表达能力/结合不同感受野,每个分支使用了不同的拓扑结构。

    而ResNeXt则使用了同一拓扑的分支,即ResNeXt的分支是同构的!

    分支是同构的!

    分支是同构的!

    同构的分支才是问题所在,它从根本上动摇了一个独步江湖几年的经典模型的根基。

    同构

    因为ResNeXt是同构的,因此继承了VGG/ResNet的精神衣钵:维持网络拓扑结构不变。主要体现在两点:

    1. 特征图大小相同,则涉及的结构超参数相同
    2. 每当空间分辨率/2(降采样),则卷积核的宽度*2(我在Res中写作深度,但还是宽度比较严谨,避免和层数的“深度”产生歧义)。

    只不过,ResNeXt通过分析得出的(mathcal{T}_i),拓展了VGG设计原则:从重复相同大小的层,到重复相同拓扑的滤波器组。

    除了更加简洁的设计语言,更简单的调参过程,成品模型迁移中更强的鲁棒性外,同构网络在实现时也很有优势。

    1.”过于简单“的实现

    现在已经有了无数人的结论,以及很多优秀的开源代码,仔细观察这些“成品”,不难发现ResNeXt的实现意外地简洁,至少远比Inception简洁。

    简洁到只要给ResNet加个参数,两个卷积换成3个卷积,每个Block里的(3*3)conv换成分组卷积就好了。

    如下代码快所示,比较需要注意的就是第8行中的 groups=cardinality

    class ResNeXtBottleneck(nn.Module):
     def __init__(self, in_channels, out_channels, stride, cardinality, base_width, widen_factor):
     super(ResNeXtBottleneck, self).__init__()
            width_ratio = out_channels / (widen_factor * 64.)
            D = cardinality * int(base_width * width_ratio)
            self.conv_reduce = nn.Conv2d(in_channels, D, kernel_size=1, stride=1, padding=0, bias=False)
            self.bn_reduce = nn.BatchNorm2d(D)
            self.conv_conv = nn.Conv2d(D, D, kernel_size=3, stride=stride, padding=1, groups=cardinality, bias=False)
            self.bn = nn.BatchNorm2d(D)
            self.conv_expand = nn.Conv2d(D, out_channels, kernel_size=1, stride=1, padding=0, bias=False)
            self.bn_expand = nn.BatchNorm2d(out_channels)
     
            self.shortcut = nn.Sequential()
     if in_channels != out_channels:
                self.shortcut.add_module('shortcut_conv',
                                         nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, padding=0,
                                                   bias=False))
                self.shortcut.add_module('shortcut_bn', nn.BatchNorm2d(out_channels))
    

    但,这推导到现在的结构比起来,是不是太简单了点?

    2.同构ResNeXt Block的等价形式

    在引入等价形式之前,我们再看一下推导聚合变换时的式子:

    [mathcal{F}(x)=underset{i=1}{overset{C}{sum}}mathcal{T}_i(X) ]

    还有内积时的式子:

    [mathcal{F}(x)=underset{i=1}{overset{D}{sum}}w_ix_i ]

    不知到大家看出什么不同了吗?在于第一个式子的自变量。通项公式中写的是向量(X),但内积中求和的却是向量(X)其中一个通道(x_i)

    而论文中也明确指出(mathcal{T}_i), 的作用是:“先把输入分发到(低维)嵌入中,再对第i个嵌入变换”,最后对基数C个变换进行汇总。根据这种最开始的想法,我们能得到第一个ResNeXt Block的结构示意图:

    img

    也就是说,如果只看每个Block中单独的支路(Branch),则其只是一个常见的“降维→变换→升维”的Bottleneck结构。但是,若以“分割-变换-聚合”的角度考虑,那么第一个(1*1)conv的“降维”,实际上也是把输入分给基数(这里C=32)个低维嵌入的过程。

    而在内积中,第一个分割成低维嵌入的过程实际上只是挨个取元素,所以被我们直观忽略了。

    img

    OK,罗嗦这个意义何在?

    意义在于让我们知道,分割/汇聚不一定是个显式过程,必要时完全可以等加成更简单的表达。就好比Inception-ResNet形式表示的ResNeXt Block:

    img

    32个输入256通道,输出4通道的(1*1)conv加起来,和32个4通道拼在一起变成128通道,再过一个输入128通道输出256通道的(1*1)conv有多大区别呢?

    我们用MobileNet中计算标准卷积参数量的公式来计算:

    [D_Kcdot D_Kcdot Mcdot N ]

    img

    从参数量上:前者为(32*(1*1*4*256)=32768)

    后者为(1*1*128*256=32768)

    显然,两者的参数量一样。而Inception早已说明了Block所能表示的解空间,实际上是一个更大卷积核的严格子空间。也就是说,先汇聚后一个大的标准卷积,能表达的范围只会比一堆小的标准卷积之组合更大。因此,这种后者对前者的等价没问题。

    那么同理,在最后的32个conv能用上述方法等效成大的conv,那最前面的conv也同理可得一个等效的大conv。那么类似于我在ResNet中提到的滑稽情况(若ResNet的恒等映射中间只有一层权重时,恒等映射加了等于没加)又出现了:

    img

    如上图所示,如果一个ResNeXt Block中只有两层conv,前后都可等效成一个大的conv层,那聚合变换和没聚一样。

    如果看到这里,你已经完成了对这么个看似很“Inception”,看似很“高大上”的模型祛魅的过程。

    因为这种反面例子同样告诉了我们,抛弃重重理论,ResNeXt最核心的地方只存在于被最上最下两层卷积夹着的,中间的部分。和汉堡一样,两边都是面包,中间的肉最值钱。

    为什么呢,因为第一个分开的conv其实都接受了一样的输入,各分支又有着相同的拓扑结构。类比乘法结合律,这其实就是把一个conv的输出拆开了分掉。(相同输入,不同输出)

    而最后一个conv又只对同一个输出负责,因此就可以并起来用一个conv处理。(不同输入,相同输出

    唯一一个输入和输出都不同的,就是中间的(3*3)conv了。它们的输入,参数,负责的输出都不同,无法合并,因此也相互独立。这才是模型的关键所在。

    最终模型可以被等效为下图所示的最终形态:

    img

    而很巧的是,AlexNet的分组卷积实际上干了一样的事。只不过Alex当时这么做是形势所逼。ResNeXt却是主动选择,还成为了提高模型效果的手段。

    img

    到这里,同构更大的优势就体现出来了:因为同构,所以经过层层等价,ResNeXt的模型远比Inception来的简洁优雅,易于实现。

    模型容量 被遗忘的d

    到这里,模型的亮点和特色也差不多了。但是,很多博文都会遗忘掉模型中的另外一个超参数D

    self.conv_conv = nn.Conv2d(D, D, kernel_size=3, stride=stride, padding=1, groups=cardinality, bias=False)
    

    就是这段代码的D(论文里为d),它指代C个分支的bottleneck中,降维得到低维嵌入的维度。因此,我们得到最终一个ResNeXt模块的参数量:

    [Ccdot(Inchannelcdot d+3cdot 3cdot dcdot d+dcdot Outchannel) ]

    可以看出,若d的取值足够小,则模型本身相较ResNet的参数量也不会很大。这就是论文所说的,在保证计算复杂性不变的同时,取得更优结果。

    本文来自博客园,作者:甫生,转载请注明原文链接:https://www.cnblogs.com/fusheng-rextimmy/p/15468611.html

  • 相关阅读:
    windows中android SDK manager安装更新sdk很慢,或者出现Done loading packages后不动甚至没有任何可用包
    实用小函数
    UML类图几种关系的总结
    TextView自动换行
    Android launchMode=singleInstalce 与onActivityResult
    红黑树
    二叉查找树相关算法
    添加google账户时无法与服务器建立连接
    ubuntu 下eclipse svn更改用户
    ubuntu adb: command not found
  • 原文地址:https://www.cnblogs.com/fusheng-rextimmy/p/15468611.html
Copyright © 2011-2022 走看看