Aggregated Residual Transformations for Deep Neural Networks

论文链接: https://arxiv.org/abs/1611.05431
在这里插入图片描述
废话不多说,先上图。上图左侧为A block of ResNet,右侧即为本文章所提出的新结构:A block of
ResNeXt with cardinality = 32,与左侧的结构复杂度相似,框中的数字含义分别为 (# in channels,filter size, # out channels).
在本篇论文中,作者提出了一个简单的体系结构,它采用了VGG/ResNets的重复层策略,同时以一种简单、可扩展的方式利用split-transform-merge策略。网络中的一个模块设置一组 transformations,每个 transformations都基于低维嵌入,模块的输出通过summation来进行拼接。作者将这个想法的进行了一个简单实现——被拼接的 transformations都是相同的拓扑结构(上图右)。这种设计允许我们扩展到任何大量的transformations,而无需专门的设计。
在这里插入图片描述
以上的模型结构有其他两种等效形式(如上图)。上图中的(b)中的重新表述与InceptionResNet模块连接多个路径相似,但是本文论文所提出的模块不同于所有现有的Inception模块,本论文所提出的模块所有的路径共享相同的拓扑结构,因此路径的数量可以很容易地切割为一个被研究的因素。此模块可以通过Krizhevsky等人的分组卷积进行更简洁的重构。一些张量操作表明,上图(a)等价于上图(b),图(b)与Inception-ResNet块相似,在残差函数中涉及分支和连接。但与所有Inception或Inception- resnet模块不同的是,本文多个路径中共享相同的拓扑结构。来实现最小的额外努力来设计每个路径。使用分组卷积的表示法,上面的模块变得更加简洁这个重新表述如上图©所示。所有的低维嵌入(第一个1×1层)都可以被一个单一的、更宽的层(如图上©中的1×1、128-d)所替代。分裂本质上是由分组卷积层在将输入信道分成组时完成的。上图©中的分组卷积层进行了32组卷积,其输入输出通道为4维。分组的卷积层将它们连接起来作为该层的输出。上图©中的块与图1(左)中原始的瓶颈残留块相似,但图©是一个更宽切稀疏连接的模块。

即使在保持计算复杂度和模型大小的限制条件下,split-transform也优于原始ResNet模块,图1(右)的设计是为了保持图1(左)的FLOPs复杂度和参数数量。虽然通过增加容量(用更深或更宽的网络)来提高准确性相对容易,但在文献中,既提高准确性又保持(或减少)复杂性的方法很少。
经过研究表明,除了宽度和深度之外,基数(transformations的大小)是一个具体的、可测量的维度,具有中心重要性。实验表明,增加基数比对模型进行加深或加宽更有利于提高准确性,特别是当深度和宽度对现有模型的收益开始递减时。
一、多分支卷积网络
Inception models是成功的多分支架构,其中每个分支都被仔细地定制。ResNets可以被认为是两个分支网络,其中一个分支是恒等映射。深度神经决策森林是具有学习分裂函数的树型多分支网络。
二、分组卷积
分组卷积的使用可以追溯到AlexNet论文。Krizhevsky et al.给出的动机是为了将模型分配到两个gpu上。Caffe、Torch和其他库都支持分组卷积,这主要是为了兼容AlexNet。几乎没有证据表明利用分组卷积来提高精度。分组卷积的一种特殊情况是基于通道的卷积,其中分组的数量等于通道的数量。通道方向的卷积是中可分离卷积的一部分。
三、压缩卷积网络
分解(空间和/或通道)是一种广泛采用的降低深度卷积网络冗余和加速/压缩的技术。Ioannou et al.提出了一种用于减少计算量的“根”模式网络,根中的分支通过分组卷积实现。这些方法以较低的复杂性和较小的模型尺寸显示了精确的折衷。本论文的方法不是压缩,而是通过经验展示出更强表示能力的架构。
在这里插入图片描述
(左)ResNet-50,(右)ResNeXt-50和32×4d模块(使用图2©中的重新表述)。框架是残差块的形状,框架外是堆叠块的数量。“C=32”表示分组卷积有32个组。这两种模型的参数和FLOPs数量相似。
这些模块具有相同的拓扑,本模块受VGG / ResNets的影响有两个条件限制:
(1)如果产生相同大小的空间特征图,这个模块块共享相同的超参数(宽度和卷积核的大小)
(2)每次当空间特征图向下采样2倍时,模块的宽度乘以2的倍数。
第二个规则是为了确保计算复杂度,以FLOPs(浮点运算,在# of multiply- added中)来说,对所有块来说大致相同。有了这两条规则,我们只需要设计一个模板模块,网络中的所有模块就可以相应确定。因此,这两条规则大大缩小了设计空间,并允许我们专注于几个关键因素。由这些规则构建的网络如上表所示。
四、参数对比在这里插入图片描述
上表显示基数C和瓶颈宽度d之间的关系。
在图1(左)中,原始ResNet瓶颈块具有 256 ∗ 64 + 3 ∗ 3 ∗ 64 ∗ 64 + 64 ∗ 256 ≈ 70 k 256*64 + 3*3*64*64 + 64*256≈70k 25664+336464+6425670k参数和比例FLOPs(在相同的feature map大小上)。瓶颈宽度d,我们在图1(右)中的模块有如下参数和FLOPs:
C*(256d + 33dd + d256)
当C = 32, d = 4时,方程n
(4)≈70k。
五、实验结果
在这里插入图片描述
ImageNet-1K的训练曲线。(左):保留复杂性的ResNet/ResNeXt-50(约41亿次FLOPs,约2500万个参数);(右):保留复杂性的ResNet/ResNeXt-101(约78亿次FLOPs,约4400万个参数)。
在这里插入图片描述
ImageNet-1K的消融实验。(顶部):ResNet50保持复杂性(约41亿次);(下)ResNet-101具有保存的复杂性(约78亿次FLOPs)。错误率是在224×224像素的单个作物上评估的。
在这里插入图片描述
当浮点数增加到ResNet-101的2倍时,在ImageNet-1K上进行比较。错误率是在224×224像素的单个作业上评估的。突出显示的C是增加复杂性的因素。
在这里插入图片描述
ImageNet-1K验证集上最先进的模型(单作物测试)。ResNet/ResNeXt的测试大小是224×224和320×320,就像在[15]中一样,而Inception模型的测试大小是299×299。
在这里插入图片描述
ImageNet-5K实验。模型在5K集上进行训练,并在原始的1K验证集上进行评估,绘制为一个1K路分类任务。ResNeXt和ResNet对等物具有类似的复杂性。
在这里插入图片描述
ImageNet-5K错误(%)。模型在ImageNet-5K上进行训练,并在ImageNet-1K val集上进行测试,测试时将其视为一个5K-way分类任务或一个1K-way分类任务。ResNeXt和ResNet对等物具有类似的复杂性。错误是在224×224像素的单一作物上评估的。
在这里插入图片描述
CIFAR的测试误差(%)和模型尺寸。结果是10次运行的平均值。
在这里插入图片描述
CIFAR-10上的测试误差与模型大小。运行10次计算结果,用标准误差条显示。标签显示模块的设置。
在这里插入图片描述
对COCO的目标检测结果进行最小集。ResNeXt和ResNet对等物具有类似的复杂性。
相关代码如下:

'''
New for ResNeXt:
1. Wider bottleneck
2. Add group for conv2
'''

import torch.nn as nn
import math

__all__ = ['ResNeXt', 'resnext18', 'resnext34', 'resnext50', 'resnext101',
           'resnext152']
# 定义一个三乘三的卷积
def conv3x3(in_planes, out_planes, stride=1):
    """3x3 convolution with padding"""
    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
                     padding=1, bias=False)

# 定义一个基础的模块
class BasicBlock(nn.Module):
    expansion = 1
	"""3*3conv-bn-rele-3*3的分组卷积-bn,后加跳连结构"""
    def __init__(self, inplanes, planes, stride=1, downsample=None, num_group=32):
        super(BasicBlock, self).__init__()
        self.conv1 = conv3x3(inplanes, planes*2, stride)
        self.bn1 = nn.BatchNorm2d(planes*2)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(planes*2, planes*2, groups=num_group)
        self.bn2 = nn.BatchNorm2d(planes*2)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        residual = 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:
            residual = self.downsample(x)
# 跳连结构
        out += residual
        out = self.relu(out)

        return out

# 定义一个标准的瓶颈模块
class Bottleneck(nn.Module):
"""此模块的结构即为前文中的图二c"""
    expansion = 4

    def __init__(self, inplanes, planes, stride=1, downsample=None, num_group=32):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(inplanes, planes*2, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes*2)
        self.conv2 = nn.Conv2d(planes*2, planes*2, kernel_size=3, stride=stride,
                               padding=1, bias=False, groups=num_group)
        self.bn2 = nn.BatchNorm2d(planes*2)
        self.conv3 = nn.Conv2d(planes*2, planes * 4, kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(planes * 4)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        residual = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)

        if self.downsample is not None:
            residual = self.downsample(x)

        out += residual
        out = self.relu(out)

        return out


class ResNeXt(nn.Module):

    def __init__(self, block, layers, num_classes=1000, num_group=32):
        self.inplanes = 64
        super(ResNeXt, self).__init__()
        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)
        self.layer1 = self._make_layer(block, 64, layers[0], num_group)
        self.layer2 = self._make_layer(block, 128, layers[1], num_group, stride=2)
        self.layer3 = self._make_layer(block, 256, layers[2], num_group, stride=2)
        self.layer4 = self._make_layer(block, 512, layers[3], num_group, stride=2)
        self.avgpool = nn.AvgPool2d(7, stride=1)
        self.fc = nn.Linear(512 * block.expansion, num_classes)

        for m in self.modules():
            if isinstance(m, nn.Conv2d):
            # 对conv层参数进行初始化
                n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
                m.weight.data.normal_(0, math.sqrt(2. / n))
            elif isinstance(m, nn.BatchNorm2d):
            # 对bn层参数进行初始化
                m.weight.data.fill_(1)
                m.bias.data.zero_()

    def _make_layer(self, block, planes, blocks, num_group, stride=1):
        downsample = None
        # 如果输出与输出维数不对应,或者步长不为1,则需要对跳连结构进行下采样
        if stride != 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2d(self.inplanes, planes * block.expansion,
                          kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(planes * block.expansion),
            )

        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample, num_group=num_group))
        self.inplanes = planes * block.expansion
        for i in range(1, blocks):
            layers.append(block(self.inplanes, planes, num_group=num_group))

        return nn.Sequential(*layers)

    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


def resnext50(**kwargs):
    """Constructs a ResNeXt-50 model.
    """
    model = ResNeXt(Bottleneck, [3, 4, 6, 3], **kwargs)
    return model
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值