本专栏介绍基于深度学习进行图像识别的经典和前沿模型,将持续更新,包括不仅限于:AlexNet, ZFNet,VGG,GoogLeNet,ResNet,DenseNet,SENet,MobileNet,ShuffleNet,EifficientNet,Vision Transformer,Swin Transformer,Visual Attention Network,ConvNeXt, MLP-Mixer,As-MLP,ConvMixer,MetaFormer
深度残差网络(Deep residual network, ResNet)的提出是CNN图像史上的一件里程碑事件,ResNet在2015年发表当年取得了图像分类,检测等等5项大赛第一,并又一次刷新了CNN模型在ImageNet上的历史记录。直到今天,各种最先进的模型中依然处处可见残差连接的身影,其paper引用量是CV领域第一名。ResNet的作者何恺明也因此摘得CVPR2016最佳论文奖,当然何博士的成就远不止于此,感兴趣的可以去搜一下他后来的辉煌战绩。
ResNet论文名称:Deep Residual Learning for Image Recognition
ResNet论文下载链接:https://arxiv.org/pdf/1512.03385
pytorch代码实现:https://github.com/Arwin-Yu/Deep-Learning-Classification-Models-Based-CNN-or-Attention
ResNeXt论文名称:Aggregated residual transformations for deep neural networks
ResNeXt论文下载链接:
http://openaccess.thecvf.com/content_cvpr_2017/papers/Xie_Aggregated_Residual_Transformations_CVPR_2017_paper.pdf
从经验来看,网络的深度对模型的性能至关重要,当增加网络层数后,网络可以进行更加复杂的特征提取,所以当模型更深时理论上可以取得更好的结果,例如VGG网络就证明了更深的网络可以带来更好的效果。但是更深的网络其性能一定会更好吗?
如下面,实验发现深度网络出现了退化问题(Degradation Problem):当网络深度持续增加时,网络准确度出现饱和,甚至出现下降。下图节选自ResNet原文,不管是训练阶段还是验证阶段,56层的网络比20层网络错误率更高,效果更差。究竟是什么原因导致的这一问题?
首先想到的是神经网络中的一个普遍问题:过拟合问题。但是,过拟合的主要表现是在训练阶段效果良好,但在测试阶段表现欠佳,这与上图情况是不相符的。 因此,造成网络性能下降的原因可能是梯度爆炸或梯度消失。为了理解梯度不稳定性的原因,首先回顾一下反向传播的知识。反向传播结果的数值大小不仅取决于导数计算公式,同时也取决于输入数据的大小。因为神经网络的计算本质其实是矩阵的连乘,当计算图每次输入的值都大于1,那么经过很多层回传,梯度将不可避免地呈几何倍数增长,直到不可计量,这就是梯度爆炸现象。反之,如果每个阶段的输入值都小于1,则梯度将会几何级别地下降,最终可能会趋近于0,这就是梯度消失。由于目前神经网络的参数更新基于反向传播,因此梯度不稳定性看似是一个非常重要的问题。
然而,事实并非如此简单。我们现在无论用PyTorch还是Tensorflow,都会自然而然地加上Bacth Normalization(简称BN,详见GoogLeNetV2),而BN的作用本质上也是控制每层输入的模值,因此梯度的爆炸/消失现象理论上应该在很早就被解决了,至少解决了大部分。
在模型表现退化的问题上,我们可以排除过拟合和梯度不稳定的可能性。这引出了一个困境:尽管卷积神经网络并未遇到这两个典型的问题,但随着模型深度的增加,其性能却出现了下降。在没有数学证明的情况下,这个现象显得与我们的预期相悖。
模型性能退化的现象在直觉上似乎违反了常理。通常,我们认为当模型层数增加时,其性能应该提升。例如,如果一个浅层网络已经能达到良好的效果,那么即使我们在其上添加更多层,即使这些额外的层并不进行任何操作,模型的性能理论上也不应该下降。然而,实际情况却并非如此。实际上,“什么也不做”恰恰是现有神经网络最难实现的一点。
也许赋予神经网络无限可能性的“非线性”让神经网络模型走得太远,使得特征随着层层前向传播得到完整保留(什么也不做)的可能性都微乎其微。用学术点的话说,这种“不忘初心”的品质在神经网络领域被称之为恒等映射(Identity Mapping)。因此,残差学习的初衷是为了让模型至少有一定的恒等映射能力,以确保在叠加网络的过程中,模型不会因继续叠加而产生退化。
ResNet模型解决网络退化问题的方法是设计了残差连接,最简单的残差如下图所示。图中右侧的曲线叫作残差连接(Residual Connection),通过跳接在激活函数前,将上一层(或几层)之前的输出与本层计算的输出相加,将求和的结果输入到激活函数中作为本层的输出。
顺带一提,这里一个Block中必须至少含有两个层,否则网络将退化成一个简单的神经网络: y = ( w x + b ) + x y=(w x+b)+x y=(wx+b)+x 等价于 y = w x + ( b + x ) y=w x+(b+x) y=wx+(b+x)。
前面分析得出,如果深层网络后面的层都是是恒等映射,那么模型就可以等价转化为一个浅层网络。那现在的问题就是如何得到恒等映射了。事实上,已有的神经网络很难拟合潜在的恒等映射函数H(x) = x
。但如果把网络设计为H(x) = F(x) + x
,即直接把恒等映射作为网络要学习并输出的一部分。就可以把问题转化为学习一个残差函数F(x) = H(x) - x
.
所谓残差连接指的就是将浅层的输出和深层的输出求和作为下一阶段的输入,这样做的结果就是本来这一层权重需要学习是一个对 x 到 H(x) 的映射。那使用残差链接以后,权重需要学习的映射变成了 从x -> H(x) - x 。这样在反向传播的过程中,小损失的梯度更容易抵达浅层的神经元。其实这个和循环神经网络LSTM中控制门的原理也是一样的。
最后,残差连接的代码实现如下。
1.class BasicBlock(nn.Module):
2. expansion = 1
3.
4. def __init__(self, in_channel, out_channel, stride=1, downsample=None, **kwargs):
5. super(BasicBlock, self).__init__()
6. self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel, kernel_size=3, stride=stride, padding=1, bias=False)
7. self.bn1 = nn.BatchNorm2d(out_channel)
8. self.relu = nn.ReLU()
9. self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel, kernel_size=3, stride=1, padding=1, bias=False)
10. self.bn2 = nn.BatchNorm2d(out_channel)
11. self.downsample = downsample
12.
13. def forward(self, x):
14. identity = x
15. if self.downsample is not None:
16. identity = self.downsample(x)
17.
18. out = self.conv1(x)
19. out = self.bn1(out)
20. out = self.relu(out)
21.
22. out = self.conv2(out)
23. out = self.bn2(out)
24.
25. out += identity
26. out = self.relu(out)
27.
28. return out
ResNet网络是参考了VGG19网络,在其基础上进行了修改,并通过短路机制加入了上图所示的残差链接。除此之外,变化主要体现在ResNet直接使用stride=2的卷积做下采样(取代了VGG中的池化),并且用global average pool层替换了全连接层(这样可以接收不同尺寸的输入图像),另外模型层次明显变深。相似之处是两者都是通过堆叠3X3的卷积进行特征提取。
ResNet的一个重要设计原则是:当feature map大小降低一半时,feature map的数量增加一倍,这一定程度上减轻了因减少特征图尺寸而带来的信息损失(换句话说,将输入信息的特征从空间维度提取到通道维度上)。
从下图中可以看到,ResNet相比普通卷积网络每两层间增加了残差链接,其中虚线表示feature map数量发生了改变。
在ResNet原论文中,作者给出了五个不同层次的模型结构,分别是18层,34层,50层,101层,152层。上图所示的是34层的模型结构。下图给出所有模型的结构参数:
值得注意的是: 50层,101层和152层使用的值得注意的是: 50层,101层和152层使用的残差模块与之前介绍的不同。主要原因是深层次的网络中参数量太大,为了减少参数,在3X3卷积前先通过1X1卷积对channel维度进行降维(个人感觉,其灵感来自于GoogLeNetV3中提出了网络设计准则,不了解的同学可以看我上一个博文)
如上图所示:左图是浅层模型用的残差结构;右图是深层模型用的残差结构。
注意:对于残差,只有当输入和输出维度一致时,才可以直接将输入加到输出上。但是当维度不一致时,不能直接相加。这时可以采用新的映射(projection shortcut),比如一般采用1x1的卷积对残差传递的信息做维度调整。
改进版本的残差连接的代码实现如下。
1.class Bottleneck(nn.Module):
2. """
3. 注意:原论文中,在虚线残差结构的主分支上,第一个1x1卷积层的步距是2,第二个3x3卷积层步距是1。
4. 但在pytorch官方实现过程中是第一个1x1卷积层的步距是1,第二个3x3卷积层步距是2,
5. 这么做的好处是能够在top1上提升大概0.5%的准确率。
6. 可参考Resnet v1.5 https://ngc.nvidia.com/catalog/model-scripts/nvidia:resnet_50_v1_5_for_pytorch
7. """
8. expansion = 4
9.
10. def __init__(self, in_channel, out_channel, stride=1, downsample=None,
11. groups=1, width_per_group=64):
12. super(Bottleneck, self).__init__()
13.
14. width = int(out_channel * (width_per_group / 64.)) * groups
15.
16. self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=width, kernel_size=1, stride=1, bias=False) # squeeze channels
17. self.bn1 = nn.BatchNorm2d(width)
18. # -----------------------------------------
19. self.conv2 = nn.Conv2d(in_channels=width, out_channels=width, groups=groups, kernel_size=3, stride=stride, bias=False, padding=1)
20. self.bn2 = nn.BatchNorm2d(width)
21. # -----------------------------------------
22. self.conv3 = nn.Conv2d(in_channels=width, out_channels=out_channel*self.expansion, kernel_size=1, stride=1, bias=False) # unsqueeze channels
23. self.bn3 = nn.BatchNorm2d(out_channel*self.expansion)
24. self.relu = nn.ReLU(inplace=True)
25. self.downsample = downsample
26.
27. def forward(self, x):
28. identity = x
29. if self.downsample is not None:
30. identity = self.downsample(x)
31.
32. out = self.conv1(x)
33. out = self.bn1(out)
34. out = self.relu(out)
35.
36. out = self.conv2(out)
37. out = self.bn2(out)
38. out = self.relu(out)
39.
40. out = self.conv3(out)
41. out = self.bn3(out)
42.
43. out += identity
44. out = self.relu(out)
45.
46. return out
其实残差链接有多种调整方式,比如激活函数的位置?BN的位置?连接跨越的卷积数量?等等都是可以调整的地方。因此有文献[传送门]对这些变体残差做了研究并实验。最后根据实验结果提出了一个效果最好的残差方式如下图:改进前后一个明显的变化是采用pre-activation,BN和ReLU都提前了。
值得一提的是这篇文献中的ResNet深度都是上千层的模型。之后,研究者们觉得ResNets以及足够深了, 便开始在对模块进行加宽,详见文献[传送门]。
其实残差连接可以看成一种特殊的跳跃连接,早在15年的时候,还有一篇Highway Networks【传送门】使用了类似的结构,在旁路中设置了可训练参数。如下所示:
传统的神经网络对输入 X 使用一个非线性变换 H 来得到输出output,公式如下:
y = H ( x , W H ) \mathbf{y}=H\left(\mathbf{x}, \mathbf{W}_{\mathbf{H}}\right) y=H(x,WH)
Highway network基于门机制引入了transform gate T和carry gate C,输出output是由tranform input和carry input组成, ,公式如下:
y = H ( x , W H ) ⋅ T ( x , W T ) + x ⋅ C ( x , W C ) \mathbf{y}=H\left(\mathbf{x}, \mathbf{W}_{\mathbf{H}}\right) \cdot T\left(\mathbf{x}, \mathbf{W}_{\mathbf{T}}\right)+\mathbf{x} \cdot C\left(\mathbf{x}, \mathbf{W}_{\mathbf{C}}\right) y=H(x,WH)⋅T(x,WT)+x⋅C(x,WC)
T为转换门(输出须经H处理),C为携带门(直接输出原始信息X)。 它们分别表示通过转换输入和携带输入分别产生多少输出。为了简单起见,我们设定C=1-T,也就是C=1-T。 公式变为:
y = H ( x , W H ) ⋅ T ( x , W T ) + x ⋅ ( 1 − T ( x , W T ) ) \mathbf{y}=H\left(\mathbf{x}, \mathbf{W}_{\mathbf{H}}\right) \cdot T\left(\mathbf{x}, \mathbf{W}_{\mathbf{T}}\right)+\mathbf{x} \cdot\left(1-T\left(\mathbf{x}, \mathbf{W}_{\mathbf{T}}\right)\right) y=H(x,WH)⋅T(x,WT)+x⋅(1−T(x,WT))
特别的:
y = { x , if T ( x , W T ) = 0 H ( x , W H ) , if T ( x , W T ) = 1 \mathbf{y}= \begin{cases}\mathbf{x}, & \text { if } T\left(\mathbf{x}, \mathbf{W}_{\mathbf{T}}\right)=\mathbf{0} \\ H\left(\mathbf{x}, \mathbf{W}_{\mathbf{H}}\right), & \text { if } T\left(\mathbf{x}, \mathbf{W}_{\mathbf{T}}\right)=\mathbf{1}\end{cases} y={x,H(x,WH), if T(x,WT)=0 if T(x,WT)=1
可以看到Highway Network其实就是对输入一部分进行处理(和传统神经网络相同),一部分直接通过. 其实,ResNet可以看成Highway Network的一种特殊情况。即: T ( x , W T ) T\left(\mathbf{x}, \mathbf{W}_{\mathbf{T}}\right) T(x,WT) 和 ( 1 − T ( x , W T ) ) \left(1-T\left(\mathbf{x}, \mathbf{W}_{\mathbf{T}}\right)\right) (1−T(x,WT))都等于1的情况。不过 Highway Network效果没有ResNet好,似乎就验证了保持旁路畅通无阻(恒等映射)的重要性。 高速公路网络这个名字很形象, 因为这样的结构使得特定信息可以无损地通过"高速公路"直达目的地 。
对于残差链接的有效性,除了作者提出的学习恒等映射的思想以外。我觉得有两个更加合理的解释:
第一,把残差网络展开, 相当与集成(ensemble)了多个神经网络, 这是一种典型的集成学习的思想(ensemble learning),如下图示:
左边的残差网络看似是单路的网络结果,其实如果把信息前向计算的可能路径全部画出来,即右边子图所示,此时可以看作是很多单路网络集成在一起形成的多路网络结构。另外,Deep Networks with Stochastic Depth这篇工作表明ResNet太深了,有些层是没必要的。实验方法简单解释就是通过在训练过程中随机丢掉一些层,来优化深度残差网络的训练过程。网络变得简单一点了。
具体的实现是通过Drop path :假设残差网络表达为公式y=H(x)+x ,那么Stochastic Depth Net训练时,会加入了随机变量 b(伯努利随机变量),通过y= b*H(x)+x,对 ResBlock的残差部分做了随机丢弃。如果b = 1,则简化为原始的ResNet结构;如果b = 0,则这个ResBlock未被激活,降为恒等函数。此外,Stochastic Depth Net还使用线性衰减的生存概率原则来随机丢弃整个模型中不同深度的层级结构,如下图所示,将“线性衰减规律”应用于每层的生存概率,由于较早的层会提取低级特征,而这些低级特征会被后面的层所利用,所以这些层不应该频繁地被丢弃。最终 p 生成的规则就变成了这样:
实际上,Stochastic Depth Net的Drop path 与 AelxNet中提出的Dropout大同小异;只是两者的作用对象不同:Dropout是作用在神经元级别上的;而Drop path比较高级,其是作用在网络的层级结构上的。
Drop path方法在后期的模型中被广泛应用,例如VIT, swin-Transformer等等。它与DenseNet有异曲同工之妙(这些后面会有专门的博文介绍),但是DenseNet由于需要存储中间计算结果,因而太占用内存了(即便网络参数量少),这可能也是为什么DenseNet参数少,比ResNet性能好,但是,还没有ResNet流行的原因。而Drop path就可以看成一种折中的手段,因此被后续模型不断借鉴使用。
再想一想,其实Drop path也是一种集成学习的思想,因为每次训练模型时随机失活的层级结构都不一样,等价于每次都训练了一个全新的子模型;在测试过程(推理预测)中,所有的block都将保持被激活状态,以充分利用整个长度网络的所有模型容量,这实际上是集成了训练时期的所以子模型来做推理。这个现象似乎说明,其实残差网络其实也不能说是解决了模型深度的问题,他只是因为隐含了集成神经网络的结构,所以即使在某些神经元失效时,仍然可以保证模型的整体效果。
第二,残差链接更有利于梯度传播:
z ( l ) = H ( a ( l − 1 ) ) = a ( l − 1 ) + F ( a ( l − 1 ) ) ( 1 ) z^{(l)}=H\left(a^{(l-1)}\right)=a^{(l-1)}+F\left(a^{(l-1)}\right) (1) z(l)=H(a(l−1))=a(l−1)+F(a(l−1))(1)
考虑式(1)这样的残差块组成的前馈神经网络,为了讨论简便,暂且假设残差块不适用任何激活函数,即。
a ( l ) = z ( l ) ( 2 ) \boldsymbol{a}^{(l)}=z^{(l)} (2) a(l)=z(l)(2)
考虑人意两层数 l 2 > l 1 l_2>l_1 l2>l1, 递归地展开(1)(2),
a ( l 2 ) = a ( l 2 − 1 ) + F ( a ( l 2 − 1 ) ) = ( a ( l 2 − 2 ) + F ( a ( l 2 − 2 ) ) ) + F ( a ( l 2 − 1 ) ) = … ( 3 ) \begin{gathered} \boldsymbol{a}^{\left(l_2\right)}=\boldsymbol{a}^{\left(l_2-1\right)}+\mathcal{F}\left(\boldsymbol{a}^{\left(l_2-1\right)}\right) \\ =\left(\boldsymbol{a}^{\left(l_2-2\right)}+\mathcal{F}\left(\boldsymbol{a}^{\left(l_2-2\right)}\right)\right)+\mathcal{F}\left(\boldsymbol{a}^{\left(l_2-1\right)}\right) \\ =\ldots \end{gathered} (3) a(l2)=a(l2−1)+F(a(l2−1))=(a(l2−2)+F(a(l2−2)))+F(a(l2−1))=…(3)
可以得到。
a ( l 2 ) = a ( l 1 ) + ∑ i = l 1 l 2 − 1 F ( a ( i ) ) ( 4 ) \boldsymbol{a}^{\left(l_2\right)}=\boldsymbol{a}^{\left(l_1\right)}+\sum_{i=l_1}^{l_2-1} \mathcal{F}\left(\boldsymbol{a}^{(i)}\right) (4) a(l2)=a(l1)+i=l1∑l2−1F(a(i))(4)
根据式,在前向传播时,输入信号可以从任意底层直接传播到高层。由于包含了一个天然的恒等映射,一定程度上可以解决网络退化问题。
这样,最终的损失 ϵ \epsilon ϵ, 对某低层输出的梯度可以展开为:
∂ ϵ ∂ a ( l 1 ) = ∂ ϵ ∂ a ( l 2 ) ∂ a ( l 2 ) ∂ a ( l 1 ) = ∂ ϵ ∂ a ( l 2 ) ( 1 + ∂ ∂ a ( l 1 ) ∑ i = l 1 l 2 − 1 F ( a ( i ) ) ) ( 5 ) \frac{\partial \epsilon}{\partial \boldsymbol{a}^{\left(l_1\right)}}=\frac{\partial \epsilon}{\partial \boldsymbol{a}^{\left(l_2\right)}} \frac{\partial \boldsymbol{a}^{\left(l_2\right)}}{\partial \boldsymbol{a}^{\left(l_1\right)}}=\frac{\partial \epsilon}{\partial \boldsymbol{a}^{\left(l_2\right)}}\left(1+\frac{\partial}{\partial \boldsymbol{a}^{\left(l_1\right)}} \sum_{i=l_1}^{l_2-1} \mathcal{F}\left(\boldsymbol{a}^{(i)}\right)\right) (5) ∂a(l1)∂ϵ=∂a(l2)∂ϵ∂a(l1)∂a(l2)=∂a(l2)∂ϵ(1+∂a(l1)∂i=l1∑l2−1F(a(i)))(5)
或展开写为。
∂ ϵ ∂ a ( l 1 ) = = ∂ ϵ ∂ a ( l 2 ) + ∂ ϵ ∂ a ( l 2 ) ∂ ∂ a ( l 1 ) ∑ i = l 1 l 2 − 1 F ( a ( i ) ) ( 6 ) \frac{\partial \epsilon}{\partial \boldsymbol{a}^{\left(l_1\right)}}==\frac{\partial \epsilon}{\partial \boldsymbol{a}^{\left(l_2\right)}}+\frac{\partial \epsilon}{\partial \boldsymbol{a}^{\left(l_2\right)}} \frac{\partial}{\partial \boldsymbol{a}^{\left(l_1\right)}} \sum_{i=l_1}^{l_2-1} \mathcal{F}\left(\boldsymbol{a}^{(i)}\right) (6) ∂a(l1)∂ϵ==∂a(l2)∂ϵ+∂a(l2)∂ϵ∂a(l1)∂i=l1∑l2−1F(a(i))(6)
根据式(6), 损失对某低层输出的梯度, 被分解为了两项, 前一项 ∂ ϵ ∂ a ( l 2 ) \frac{\partial \epsilon}{\partial a\left(l_2\right)} ∂a(l2)∂ϵ 表明, 反向传播时,错误信号可以不经过任何中间权重矩阵变换直接传播到低层,一定程度上可以缓解梯度弥散问题(即便中间层矩阵权重很小,梯度也基本不会消失)。
综上,可以认为残差连接使得信息前后向传播更加顺畅。
由于ResNet在神经网络里的地位实在是无可撼动,其论文的引用量在计算机视觉领域中居首位。因此,基于ResNet的变体也相继涌现,其数量达数十种之多,其中最知名的包括ResNeXt、SENet、GENet、SKNet、CBAM等。尤其是ResNeXt、SENet和SKNet备受关注。而亚马逊出品的这篇ResNeSt一经开源同样引起了不小的轰动。这一小节主要介绍变体ResNeXt 。
另外,有很多经典的网络也是受到ResNet的启发提出的,比如DenseNet、FractalNet等。
传统的模型优化方法主要依赖于增加网络的深度或宽度。然而,随着网络参数数量的增长,调整参数、设计网络和计算成本等问题也随之增加。当超参数过多时,往往很难保证所有参数都被优化到最佳状态。此外,传统模型训练出的网络对超参数的依赖性较强,使得模型在不同的数据集上需要频繁调整参数,这对模型的可扩展性造成了影响。
ResNeXt模型的出现,希望在提高准确率的同时,不增加甚至降低模型复杂度,并减少超参数的数量。其使用的分组卷积方法,提供了一种新的优化思路。
实验结果表明,ResNeXt在可扩展性、简单性、模块化设计以及超参数数量上都表现出优越性。在参数数量相同的情况下,ResNeXt的表现优于其他模型。例如,ResNeXt-101的准确率与ResNet-200相当,但计算量却减少了一半。这种优化使得ResNeXt模型在深度学习领域具有重要价值。
ResNeXt模型架构的设计理念十分简洁:它将ResNet中的3×3卷积层替换为分组卷积层。模型里增加了一个Cardinality的概念(其实就是组卷积中的Group概念),并讨论了相较于增加网络的宽度和深度,简单地增加Group会更好。
下图是正常的残差块;
下图是ResNeXt的残差块。可以看出ResNeXt先对1×1的卷积进行了一个分组(Groups=32);注意:ResNeXt残差块经过第一个1×1卷积后的维度是32×4=128,相比原始残差块64的维度来说,是上升的。由于分组卷积可以减少很多参数量,所有这里即便维度上升了,总参数量也是下降的。
ResNeXt结合了inception与resnet的优点,大家看上图就可以发现,renest优点指得自然就是残差链接;至于inception指的是多个特征图融合,值得注意的是inception里由于多个特征图是由多个不同尺寸的卷积核得到了,因此很容易就可以让人联想到它的意义----多尺度特征图的融合。那么,ResNeXt中的多个拼接的特征图是由 相同尺寸 的卷积核得到了,它的意义又是什么?见下一小节详解。
另外,上图其实本来想说的事情是经过实验验证,发现这三个小图中的操作产生的的作用基本是等效的:详细说,图(a)是先分组卷积再相加(add),最后加上残差链接;图(b)是先分组,再拼接(concat),再通过1X1卷积升维,最后再加上残差链接; 图(c)是先1X1升维,再分组卷积,再1X1升维。因此,在工程实践上直接采用最简单的(c)方式实现。
这里给出ResNeXt的详细网络结构如图:
ResNext中引入cardinality,实际上只是组卷积中的Group概念换了个名字而已。想探究ResNeXt奏效的原因,其实就要思考分组卷积的奏效原因了。
众所周知,在卷积的过程中设计多个卷积核的作用是期望每个卷积核能够学到feature map可以表征不同的特征。类比一下人类,我们在思考问题的时候,最好从多个角度思考;同理,在卷积核提取特征时,我们也希望可以从不同的角度提取特征(多个卷积核就是多个不同的角度)。为啥讲这个?因为分组卷积其实就是在以另一种方式来做这件事情。
不同的组之间实际上是不同的subspace,而他们的确能学到更多样的特征表示。这一点是有迹可循的,可以追溯到AlexNet提出的时候(AlexNet把网络分成两组,其实这样做的目的是硬件不行,通过分组以减少显存,但是却发现下面这种比较神奇的事情,也算是无心为之的发现),两组sub-networks一组倾向于学习黑白的信息(下图上半部分),而另一组倾向于学习到彩色的信息 (下图下半部分)。
AlexNet 论文中没有明确指出这是卷积组造成的这个现象,即不同的组可以学习更多样的不同特征表示。甚至连组卷积这个概念也没有提出来。其实,现在看来,相比分组卷积带来的参数量减少这一好处,分组卷积可以学习更多元的特征表示这一好处显得更加重要;因为,后面的研究发现,分组卷积即便能减少大量参数,但是其在硬件上的实际运行时间并没有很快。
多说一点,该思路沿用到Transformer里就有了multi-head attention (其实是一个思路,大家细品一下, kernels, heads和cardinality是不是本质上一样?)
由此可以看到,ResNeXt或者Multi-head Attention,相比于原始的ResNet或者Single-head Attention会具有更强的表征能力,这里引用一下Transformer原文的一句:Multi-head attention allows the model to jointly attend to information from different representation subspaces.这句话很简洁的表达了cardinality以及number of heads的作用。
ResNeXt既有残差结构(便于训练), 又对特征层进行了concat(对特征多角度理解)。这就类似于模型融合了,把具有不同优点的子模型融合在一起,效果的更好。另外,有文献表明这种分组的操作或许能起到网络正则化的作用。