Aggregated Residual Transformations for Deep Neural Networks
深度神经网络的聚合残差变换
ResNext:ResNet和分组卷积相结合,ILSVRC 2016分类任务的第二名;
发表时间:Submitted on 16 Nov 2016 (v1), last revised 11 Apr 2017 (this version, v2)]
发表期刊/会议:Computer Vision and Pattern Recognition;
论文地址:https://arxiv.org/abs/1611.05431;
代码地址:
本文提出了一种简单的、高度模块化的图像分类网络结构。本文的网络是通过重复一个构建块来构建的,该构建块聚合了一组具有相同拓扑结构的转换(transformations)。本文设计了一个同构的多分支体系结构,只需要设置几个超参数来实现;
这种策略探索了一个新的维度,我们称之为"cardinality 基数",作为深度和宽度之外的一个重要因素;
在ImageNet-1k数据集上证明,即使在保持复杂度不变的限制条件下,增加"基数"也能提高分类精度;
此外,当我们增加容量时,增加基数比更深入或更广泛更有效。
随着超参数(通道数、filter size、stride)的增加,设计网络结构变得越来越复杂;VGG-Nets展示了一种设计有效深度网络的方法:堆积相同的block;这种策略被ResNets继承了;深度被认为是影响神经网络的重要因素之一;
Inception系列证明精心设计的拓扑能够以较低的理论复杂性实现令人信服的准确性,理论核心是拆分-转换-合并策略(比如将3×3卷积拆分成3×1和1×3卷积),但Inception架构很难适应新的数据集/任务;
本文提出一种简单的架构,采用了VGG/ResNtes的重复block策略同时以一种简单的、可扩展的利用了拆分-转换-合并策略;
在网络中的一个模块执行一组转化,每一个转换都在低维空间上,将最后的输出进行求和,求和后的转换都是相同的拓扑结构,如下图所示:
注:基数其实就是分组卷积的group size:
Multi-branch convolutional networks多分支卷积网络:
Grouped convolutions分组卷积:
Compressing convolutional networks压缩卷积网络:
Ensembling模型集成:
采用如VGG/ResNets那样的高度模块化设计;本文的网络架构也采用了残差连接,每个模块都具有相同的拓扑结构;
两个规则约束(受ResNet启发):
根据这两条规则,可以设计一个模板模块(template module),就可以据此确定一个网络中的所有模块;根据这两条规则设计了网络ResNext-50如表1所示;
ResNext-50所用的template module如图3c所示;
Pytorch实现:
# 基本的卷积模块
class BN_Conv2d(nn.Module):
"""
BN_CONV_RELU
"""
def __init__(self, in_channels, out_channels, kernel_size, stride, padding, dilation=1, groups=1, bias=False):
super(BN_Conv2d, self).__init__()
self.seq = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, stride=stride,
padding=padding, dilation=dilation, groups=groups, bias=bias),
nn.BatchNorm2d(out_channels)
)
def forward(self, x):
return F.relu(self.seq(x))
class ResNeXt_Block(nn.Module):
"""
ResNeXt block with group convolutions
"""
# 假设:cardinality = 32, group_depth = 4;
def __init__(self, in_chnls, cardinality, group_depth, stride):
super(ResNeXt_Block, self).__init__()
# in_chnls = 256
# self.group_chnls = 32 * 4 = 128 = output channel
self.group_chnls = cardinality * group_depth
# 1 * 1 卷积
self.conv1 = BN_Conv2d(in_chnls, self.group_chnls, 1, stride=1, padding=0)
# 3 * 3 卷积 分组卷积
self.conv2 = BN_Conv2d(self.group_chnls, self.group_chnls, 3, stride=stride, padding=1, groups=cardinality)
# 1 * 1 卷积 output channel * 2
self.conv3 = nn.Conv2d(self.group_chnls, self.group_chnls*2, 1, stride=1, padding=0)
self.bn = nn.BatchNorm2d(self.group_chnls*2)
self.short_cut = nn.Sequential(
nn.Conv2d(in_chnls, self.group_chnls*2, 1, stride, 0, bias=False),
nn.BatchNorm2d(self.group_chnls*2)
)
def forward(self, x):
out = self.conv1(x)
out = self.conv2(out)
out = self.bn(self.conv3(out))
out += self.short_cut(x)
return F.relu(out)
class ResNeXt(nn.Module):
"""
ResNeXt builder
"""
# cardinality = 32, group_depth = 4;
def __init__(self, layers: object, cardinality, group_depth, num_classes) -> object:
super(ResNeXt, self).__init__()
self.cardinality = cardinality
self.channels = 64
self.conv1 = BN_Conv2d(3, self.channels, 7, stride=2, padding=3)
d1 = group_depth
self.conv2 = self.___make_layers(d1, layers[0], stride=1)
d2 = d1 * 2
self.conv3 = self.___make_layers(d2, layers[1], stride=2)
d3 = d2 * 2
self.conv4 = self.___make_layers(d3, layers[2], stride=2)
d4 = d3 * 2
self.conv5 = self.___make_layers(d4, layers[3], stride=2)
self.fc = nn.Linear(self.channels, num_classes) # 224x224 input size
def ___make_layers(self, d, blocks, stride):
strides = [stride] + [1] * (blocks-1)
layers = []
for stride in strides:
layers.append(ResNeXt_Block(self.channels, self.cardinality, d, stride))
self.channels = self.cardinality*d*2
return nn.Sequential(*layers)
def forward(self, x):
out = self.conv1(x)
out = F.max_pool2d(out, 3, 2, 1)
out = self.conv2(out)
out = self.conv3(out)
out = self.conv4(out)
out = self.conv5(out)
out = F.avg_pool2d(out, 7)
out = out.view(out.size(0), -1)
out = F.softmax(self.fc(out))
return out
def resNeXt50_32x4d(num_classes=1000):
return ResNeXt([3, 4, 6, 3], 32, 4, num_classes)
model = resNeXt50_32x4d()
人工神经网络中最简单的神经元执行内积(加权和),这是由全连接层和卷积层完成的基本变换。内积可以被认为是一种聚合变换的形式:
其中, x = [ x 1 , x 2 , . . . , x D ] x = [x_1, x_2, ..., x_D] x=[x1,x2,...,xD]是神经元的一个D通道的输入向量, w i w_i wi是第 i i i个通道的filter权重;这个操作(通常包括一些输出非线性)被称为“神经元”。见图2。
上面的操作也可以被重构为拆分-转换-合并的过程:
上面对一个简单神经元的分析,我们考虑用一个更通用的函数替换初等变换( w i x i w_ix_i wixi),这个函数本身也可以是一个网络。与“Network-in-Network”[26]相反,它增加了深度维度,我们表明我们的“Network-in-Neuron”沿着一个新的维度展开。
定义aggregated transformations聚合转换如下:
其中, T i ( ) T_i() Ti()可以是任意函数,类似于一个简单的神经元, T i T_i Ti应该将x投影到一个(可选的低维)嵌入中,然后对其进行转换。
C是要聚合的转换集的大小,我们称C为基数;
在Eqn.(2)中,C的位置与Eqn.(1)中的D相似,但C不必等于D,可以是任意数。虽然宽度的维数与简单转换(内积)的数量有关,但我们认为基数的维数控制着更复杂转换的数量。通过实验表明,基数是一个基本维度,比宽度和深度维度更有效。
本文,考虑了一种设计变换函数的简单方法:所有 T i T_i Ti具有相同的拓扑结构。将单个变换 T i T_i Ti设为瓶颈型结构,第一个1×1层产生低维嵌入;
Eqn.(2)中的聚合变换作为残差函数,如下图所示, y y y是输出:
(此图对应公式3)
与Inception-ResNet的关系:
图3a的模块与图3b的模块等价;
(注:图3a就是图1右)
3(b)看起来类似于Inception-ResNet[37]块,因为它涉及到剩余函数的分支和连接。但与所有Inception或Inception- resnet模块不同的是,我们在多条路径中共享相同的拓扑结构。我们的模块需要最少的额外工作来设计每个路径。
与分组卷积的关系:
使用分组卷积[24]的符号,进行重构,图3b变得更加简洁,如图3c所示;
所有的1×1卷积都可以被一个更宽的层所取代(例如,1×1, 128-d在图3c中);**拆分本质上是由分组卷积层在将输入通道划分为组时完成的。**图3c中的分组卷积层执行32组卷积,输入和输出通道为4维。分组卷积层将它们连接起来作为该层的输出。图3c中的块与图1(左)中的原始瓶颈残块相似,只是图3c是一个较宽但连接稀疏的模块。
我们注意到,重构只在block depth≥3时才有效。如果block depth = 2,则重构会导致一个简单的宽而密集的模块。如图4所示。
在保持模型复杂性不变的情况下评估不同基数C对性能的影响。我们希望最小化对其他参数的修改;
调整瓶颈的宽度(如图1右的4-d),因为这个参数和block的输入、输出没有关系,不会影响其他超参数(block的深度、输入维度、输出维度),这样有助于我们关注基数C带来的影响;
原本的ResNet瓶颈,如图1左,有70k个参数;当 C = 32 , d = 4 C = 32, d = 4 C=32,d=4时,图1右也有70k左右的参数;
因为我们采用了第3.1节中的两个规则,所以上述近似等式在ResNet瓶颈块和我们的ResNeXt之间在所有阶段都有效(除了特征图大小变化的子采样层)。表1比较了原始的ResNet-50和我们的ResNeXt-50,它们具有相似的容量。我们注意到,复杂性只能大致保持,但复杂性的差异很小,不会影响我们的结果。
在1000类的ImageNet数据集上进行了分类任务,遵循原ResNet论文构造了ResNet-50和ResNt-101;用图 3c 的块替换ResNet-50/101中的所有块。
表1显示了基数C = 32,bottleneck width = 4d的模板构建的ResNeXt-50(图3),为了简单起见,该网络表示为ResNeXt-50(32×4d);
在计算复杂度相同的情况下,评估基数C和瓶颈宽度之间的权衡;
表2中列出了在计算复杂度的前提下,基数和宽度之间的关系;
表3top模型的模型复杂度:∼4.1 billion FLOPs;
表3bottom模型的模型复杂度:∼7.8 billion FLOPs;
表3显示了实验结果,从表中可以看出;
在ResNet-101的情况下也观察到了类似的趋势,具体见图3-bottom和图5右;
表3还表明,在保持复杂度的情况下,当瓶颈宽度较小时,以减小宽度为代价增加基数的精度变得饱和,不再增长(或增长很少)。我们认为,在这样的权衡中不断减少宽度是不值得的。因此,我们在下文中采用不小于4d的瓶颈宽度。
接下来,我们研究通过增加基数C或增加深度或宽度来增加复杂性。
以ResNet-101的模型复杂度(∼7.8 billion FLOPs)为基线,ResNext-101模型复杂度与之差不多(1 × complexity references);
同时实现了一些模型复杂度翻倍的模型(2 × complexity references),∼15 billion FLOPs:
实验结果如表4所示:
有无残差连接的影响结果如下:
这些比较表明,残差连接有助于优化,而聚合变换是更强的表示,这一事实表明,它们在有或没有残差连接的情况下的表现始终优于其对应关系。
为了简单起见,我们使用Torch内置的分组卷积实现,无需特殊优化。我们注意到,这种实现是蛮力的,不适合并行化。在NVIDIA M40的8个GPU上,表3中的32×4d ResNeXt-101训练每个小批量需要0.95秒,而具有类似FLOP的ResNet-101基线需要0.70秒。我们认为这是合理的开销。
我们预计精心设计的较低级别实现(例如,在CUDA中)将减少这一开销。我们还预计,CPU上的推理时间将减少开销。
在8个GPU上训练2×复杂度模型(64×4d ResNeXt-101)每个小批量需要1.7秒,总共10天。