ResNext:Aggregated Residual Transformations for Deep Neural Networks

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;

代码地址:


0 摘要

本文提出了一种简单的、高度模块化的图像分类网络结构。本文的网络是通过重复一个构建块来构建的,该构建块聚合了一组具有相同拓扑结构的转换(transformations)。本文设计了一个同构的多分支体系结构,只需要设置几个超参数来实现;

这种策略探索了一个新的维度,我们称之为"cardinality 基数",作为深度和宽度之外的一个重要因素;

在ImageNet-1k数据集上证明,即使在保持复杂度不变的限制条件下,增加"基数"也能提高分类精度;

此外,当我们增加容量时,增加基数比更深入或更广泛更有效。


1 简介

随着超参数(通道数、filter size、stride)的增加,设计网络结构变得越来越复杂;VGG-Nets展示了一种设计有效深度网络的方法:堆积相同的block;这种策略被ResNets继承了;深度被认为是影响神经网络的重要因素之一;

Inception系列证明精心设计的拓扑能够以较低的理论复杂性实现令人信服的准确性,理论核心是拆分-转换-合并策略(比如将3×3卷积拆分成3×1和1×3卷积),但Inception架构很难适应新的数据集/任务;

本文提出一种简单的架构,采用了VGG/ResNtes的重复block策略同时以一种简单的、可扩展的利用了拆分-转换-合并策略;

在网络中的一个模块执行一组转化,每一个转换都在低维空间上,将最后的输出进行求和,求和后的转换都是相同的拓扑结构,如下图所示:

图1右:ResNext模块(基数 = 32)

注:基数其实就是分组卷积的group size:

2 相关工作

Multi-branch convolutional networks多分支卷积网络:

  • Inception;
  • ResNet;
  • 决策森林;

Grouped convolutions分组卷积:

  • AlexNet——将模型分布在两个GPU上;
  • 据我们所知,几乎没有证据表明可以利用分组卷积来提高准确性。分组卷积的一种特殊情况是通道卷积(depthwise),其中分组的数量等于通道的数量。

Compressing convolutional networks压缩卷积网络:

Ensembling模型集成:


3 方法

3.1 Template

采用如VGG/ResNets那样的高度模块化设计;本文的网络架构也采用了残差连接,每个模块都具有相同的拓扑结构;

两个规则约束(受ResNet启发):

  • block内; 特征图不变,卷积核个数不变;
  • block之间:特征图减半,卷积核个数增加一倍(确保所有块的计算复杂度大致相同);

根据这两条规则,可以设计一个模板模块(template module),就可以据此确定一个网络中的所有模块;根据这两条规则设计了网络ResNext-50如表1所示;


ResNext-50所用的template module如图3c所示;

图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()

3.2 Revisiting Simple Neurons重温简单神经单元

人工神经网络中最简单的神经元执行内积(加权和),这是由全连接层和卷积层完成的基本变换。内积可以被认为是一种聚合变换的形式:


其中, 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。


上面的操作也可以被重构为拆分-转换-合并的过程:

  • 拆分::将向量 x x x切片为低维嵌入,其中为一维子空间 x i x_i xi
  • 转换:对低维表示进行变换,在上面将其简单地缩放为 w i x i w_ix_i wixi
  • 合并:所有嵌入中的变换通过 ∑ i = 1 D \sum _{i=1}^{D} i=1D聚合;


3.3 Aggregated Transformations聚合转换

上面对一个简单神经元的分析,我们考虑用一个更通用的函数替换初等变换( 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)

图1左:残差连接

与Inception-ResNet的关系

图3a的模块与图3b的模块等价;
(注:图3a就是图1右)


3(b)看起来类似于Inception-ResNet[37]块,因为它涉及到剩余函数的分支和连接。但与所有Inception或Inception- resnet模块不同的是,我们在多条路径中共享相同的拓扑结构。我们的模块需要最少的额外工作来设计每个路径。

图: Inception-ResNet-A模块示意图

与分组卷积的关系:
使用分组卷积[24]的符号,进行重构,图3b变得更加简洁,如图3c所示;

所有的1×1卷积都可以被一个更宽的层所取代(例如,1×1, 128-d在图3c中);**拆分本质上是由分组卷积层在将输入通道划分为组时完成的。**图3c中的分组卷积层执行32组卷积,输入和输出通道为4维。分组卷积层将它们连接起来作为该层的输出。图3c中的块与图1(左)中的原始瓶颈残块相似,只是图3c是一个较宽但连接稀疏的模块。

我们注意到,重构只在block depth≥3时才有效。如果block depth = 2,则重构会导致一个简单的宽而密集的模块。如图4所示。

图4:重构导致一个宽而密集的模块

3.4 Model Capacity

在保持模型复杂性不变的情况下评估不同基数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,它们具有相似的容量。我们注意到,复杂性只能大致保持,但复杂性的差异很小,不会影响我们的结果。


4 Implementation details实现细节

  • ImageNet数据集上,输入图像是224×224;
  • 使用GoogLeNet实现的数据增强,从调整大小的图像中随机裁剪输入图像224×224;
  • shortcut:恒等映射identity mapping;
  • 用stride = 2 的卷积进行下采样;
  • 在8个GPU(每个GPU 32个)上使用最小批量大小为256的SGD;
  • weight decay = 0.0001;
  • momentum = 0.9;
  • init learning rate = 0.1;
  • 模型通过图3(c)的形式实现,在图3(c)中的卷积之后立即执行批量归一化(BN)+ReLU;


5 实验

5.1 Experiments on ImageNet-1K

在1000类的ImageNet数据集上进行了分类任务,遵循原ResNet论文构造了ResNet-50和ResNt-101;用图 3c 的块替换ResNet-50/101中的所有块

5.1.1 符号说明:

表1显示了基数C = 32,bottleneck width = 4d的模板构建的ResNeXt-50(图3),为了简单起见,该网络表示为ResNeXt-50(32×4d)

5.1.2 基数 vs. 宽度:

在计算复杂度相同的情况下,评估基数C和瓶颈宽度之间的权衡;
表2中列出了在计算复杂度的前提下,基数和宽度之间的关系;


表3top模型的模型复杂度:∼4.1 billion FLOPs;
表3bottom模型的模型复杂度:∼7.8 billion FLOPs;

表3显示了实验结果,从表中可以看出;

  • 与ResNet-50相比,ResNeXt-50(32×4d)的验证误差为22.2%,比ResNet基线的23.9%低1.7%;
  • 在保持计算复杂度不变的前提下,随着基数C从1增加到32,错误率不断降低;
  • ResNeXt (32×4d) 的训练误差也比ResNet的训练误差低得多,这表明增益不是来自正则化,而是来自更强的表示(图5左);

在ResNet-101的情况下也观察到了类似的趋势,具体见图3-bottom和图5右;


表3还表明,在保持复杂度的情况下,当瓶颈宽度较小时,以减小宽度为代价增加基数的精度变得饱和,不再增长(或增长很少)。我们认为,在这样的权衡中不断减少宽度是不值得的。因此,我们在下文中采用不小于4d的瓶颈宽度。


5.1.3 增加基数vs.更深/更宽:

接下来,我们研究通过增加基数C或增加深度或宽度来增加复杂性。

以ResNet-101的模型复杂度(∼7.8 billion FLOPs)为基线,ResNext-101模型复杂度与之差不多(1 × complexity references);

同时实现了一些模型复杂度翻倍的模型(2 × complexity references),∼15 billion FLOPs:

  • 模型变得更深:ResNet-200;
  • 增加瓶颈宽度:ResNet-101, wider;
  • 增加基数C:
    • ResNeXt-101,64 × 4d;
    • ResNeXt-101,2 × 64d;

实验结果如表4所示:

  • 与ResNet-101基线(22.0%)相比,将复杂性增加2倍会持续减少误差,但当深入(ResNet-200,0.3%)或更宽(ResNet-101 wider,0.7%)时,改善很小;
  • 增加基数C比更深入或更宽显示出更好的结果:
    • 2×64d ResNeXt-101(即在1×64d的ResNet-101基线上加倍C并保持宽度)将前1位误差降低1.3%至20.7%。
    • 64×4d ResNeXt101(即在32×4d的ResNeXt-101上加倍C且保持宽度)使前1位的误差降低20.4%。
  • 32×4d ResNet-101(21.2%)的性能优于更深的ResNet-200和更宽的ResNet101,尽管其复杂度仅为~50%。这再次表明基数是比深度和宽度维度更有效的维度。

5.1.4 残差连接

有无残差连接的影响结果如下:

  • 从ResNeXt-50中删除快捷方式会将错误增加3.9个百分点至26.1%。
  • 从ResNet-50中删除快捷方式要糟糕得多(31.2%)。

这些比较表明,残差连接有助于优化,而聚合变换是更强的表示,这一事实表明,它们在有或没有残差连接的情况下的表现始终优于其对应关系。


5.1.5 性能

为了简单起见,我们使用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天。

5.1.6 与最新结果的比较


5.2 Experiments on ImageNet-5K



5.3 Experiments on CIFAR



5.4 Experiments on COCO object detection


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值