【BasicNet系列:二】ResNeXt 论文阅读解析 + pytorch 实现

2016 ImageNet Second
2017 CVPR

Aggregated ResidualTransformations for Deep Neural Networks

1、Introduce

采用 VGG 堆叠的思想和 Inception 的 split-transform-merge 思想,把convolution进化成为group convolution。

  • 网络结构简明,模块化
  • 需要手动调节的超参少
  • 与 ResNet 相比,相同的参数个数,结果更好
  • 打破或deeper,或wider的常规思路,ResNeXt引入一个新维度,称之为cardinality。
  • 因为分组了,多个分支单独进行处理,所以和原来整个一起卷积比,硬件执行效率上会低一点

2、Architecture

【BasicNet系列:二】ResNeXt 论文阅读解析 + pytorch 实现_第1张图片

32 paths 符合split-transform-merge的模式,每个paths 一模一样(采用相同的卷积参数)

【BasicNet系列:二】ResNeXt 论文阅读解析 + pytorch 实现_第2张图片

上面三个设计是等价设计, 最后一个C的结构, 极其类似ResNet的bottleneck,但是通道数却多的多。ResNeXt引入Inception结构,通过稀疏连接来approach之前的dense连接。

a是ResNeXt基本单元,如果把输出那里的1x1合并到一起,得到等价网络b拥有和Inception-ResNet相似的结构,而进一步把输入的1x1也合并到一起,得到等价网络c则和通道分组卷积的网络有相似的结构。

【BasicNet系列:二】ResNeXt 论文阅读解析 + pytorch 实现_第3张图片

ResNeXt 只能在 block 的 depth>3时使用. 如果 block 的 depth=2,则会得到宽而密集的模块.

0、卷积的范式:split-transform-merge。

借鉴GoogleNet Inception的模式:split-transform-merge。

【BasicNet系列:二】ResNeXt 论文阅读解析 + pytorch 实现_第4张图片

3、cardinality

ResNeXt 中每个分支一模一样(采用相同的卷积参数),分支的个数就是 cardinality。

借鉴

  • GoogLeNet 的 split-transform-merge
    通过在大卷积核层两侧加入 1x1 的网络层,控制核个数,减少参数个数的方式,见下图

  • VGG/ResNets 的 repeat layer
    重复相同的几层,前提条件是这几层的输出输出具有相同的维度,一般在不同的 repeat layers 之间使用 strip=2 降维,同时核函数的个数乘 2。

【BasicNet系列:二】ResNeXt 论文阅读解析 + pytorch 实现_第5张图片

C:通道数

4、Aggregated Transformations 聚合变换

  • neurons inner product

其中,
D 是 channel 输入向量; wi 是第 i 个 channel 的 filter 权重.

【BasicNet系列:二】ResNeXt 论文阅读解析 + pytorch 实现_第6张图片

Inner product 可以看作是 splitting-transforming-aggregating 的组合:

(1) Splitting:输入向量 x 被分为低维 embedding,即单维空间的 xi;
(2) Transforming:变换得到低维表示,即:wixi;
(3) Aggregating: 通过相加将所有的 embeddings 变换聚合,即

  • 将逐元素变换 wixi 替换为函数或网络.

其中,可以是任意函数,其将 x 投影到一个嵌入空间(一般是低维空间),并进行变换.
C 是待聚合的变换集的大小,即 Cardinality. 类似于 D.

对于变换函数的设计,采用策略是:所有的 Ti 拓扑结构相同.

5、Network

  • 参数量
    【BasicNet系列:二】ResNeXt 论文阅读解析 + pytorch 实现_第7张图片
    计算一下便可得:假设输入特征图为256维
    Resnet的参数量为:
    256x64+3x3x64x64+64x256=70k
    ResNeXt的参数量为:
    Cx(256xd+3x3xdxd+dx256) 当C取32,d=4时,上式也等于70k。

  • 网络结构

【BasicNet系列:二】ResNeXt 论文阅读解析 + pytorch 实现_第8张图片
ResNeXt 保留 ResNet 的堆叠 block,而是对单个 building block 改进.

6、pytorch 实现

class Block(nn.Module):
    '''Grouped convolution block.'''
    expansion = 2

    def __init__(self, in_planes, cardinality=32, bottleneck_width=4, stride=1):
        super(Block, self).__init__()
        group_width = cardinality * bottleneck_width
        self.conv1 = nn.Conv2d(in_planes, group_width, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(group_width)
        self.conv2 = nn.Conv2d(group_width, group_width, kernel_size=3, stride=stride, padding=1, groups=cardinality, bias=False)
        self.bn2 = nn.BatchNorm2d(group_width)
        self.conv3 = nn.Conv2d(group_width, self.expansion*group_width, kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(self.expansion*group_width)

        self.shortcut = nn.Sequential()
        if stride != 1 or in_planes != self.expansion*group_width:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_planes, self.expansion*group_width, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(self.expansion*group_width)
            )

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = F.relu(self.bn2(self.conv2(out)))
        out = self.bn3(self.conv3(out))
        out += self.shortcut(x)
        out = F.relu(out)
        return out


class ResNeXt(nn.Module):
    def __init__(self, num_blocks, cardinality, bottleneck_width, num_classes=10):
        super(ResNeXt, self).__init__()
        self.cardinality = cardinality
        self.bottleneck_width = bottleneck_width
        self.in_planes = 64

        self.conv1 = nn.Conv2d(3, 64, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.layer1 = self._make_layer(num_blocks[0], 1)
        self.layer2 = self._make_layer(num_blocks[1], 2)
        self.layer3 = self._make_layer(num_blocks[2], 2)
        # self.layer4 = self._make_layer(num_blocks[3], 2)
        self.linear = nn.Linear(cardinality*bottleneck_width*8, num_classes)

    def _make_layer(self, num_blocks, stride):
        strides = [stride] + [1]*(num_blocks-1)
        layers = []
        for stride in strides:
            layers.append(Block(self.in_planes, self.cardinality, self.bottleneck_width, stride))
            self.in_planes = Block.expansion * self.cardinality * self.bottleneck_width
        # Increase bottleneck_width by 2 after each stage.
        self.bottleneck_width *= 2
        return nn.Sequential(*layers)

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.layer1(out)
        out = self.layer2(out)
        out = self.layer3(out)
        # out = self.layer4(out)
        out = F.avg_pool2d(out, 8)
        out = out.view(out.size(0), -1)
        out = self.linear(out)
        return out


def ResNeXt29_2x64d():
    return ResNeXt(num_blocks=[3,3,3], cardinality=2, bottleneck_width=64)

def ResNeXt29_4x64d():
    return ResNeXt(num_blocks=[3,3,3], cardinality=4, bottleneck_width=64)

def ResNeXt29_8x64d():
    return ResNeXt(num_blocks=[3,3,3], cardinality=8, bottleneck_width=64)

def ResNeXt29_32x4d():
    return ResNeXt(num_blocks=[3,3,3], cardinality=32, bottleneck_width=4)

你可能感兴趣的:(Basic,Net)