ShuffleNet v2

ShuffleNet v2 论文最大贡献不是提出了一个新的模型,而是提出了 4 条设计高效 CNN 的规则,该 4 条规则考虑的映射比较多,不仅仅 FLOPS 参数,还可以到内存使用量、不同平台差异和速度指标等等,非常全面。在不违背这 4 条规则的前提下进行改进有望设计出速度和精度平衡的高效模型。

1 算法设计

1.1 算法核心

到目前为止,前人所提轻量级 CNN 框架取得了很好效果,例如 Xception、MobileNet、MobileNetV2 和 ShuffleNet v1 等,主要采用技术手段是分组卷积 Group convolution 和逐深度方向卷积 depthwise convolution

在上述算法中,除了精度要求外,评价模型复杂度都是采用 FLOPs 指标(每秒浮点加乘运算次数),在 mobilnetv1、v2 和 ShuffleNet 都是采用该指标的。然而 FLOPs 其实是一种间接指标,它只是真正关心的直接指标(如速度或延迟)的一种近似形式,通常无法与直接指标划等号。先前研究已对这种差异有所察觉,比如 MobileNet V2 要远快于 NASNET-A(自动搜索出的神经网络),但是两者 FLOPs 相当,它表明 FLOPs 近似的网络也会有不同的速度。所以,将 FLOPs 作为衡量计算复杂度的唯一标准是不够的,这样会导致次优设计。

研究者注意到 FLOPs 仅和卷积部分相关,尽管这一部分需要消耗大部分的时间,但其它过程例如数据 I/O、数据重排和元素级运算(张量加法、ReLU 等)也需要消耗一定程度的时间。

间接指标(FLOPs)和直接指标(速度)之间存在差异的原因可以归结为两点:

  • 对速度有较大影响的几个重要因素对 FLOPs 不产生太大作用,例如内存访问成本 (MAC)。在某些操作(如组卷积)中,MAC 占运行时间的很大一部分,对于像 GPU 这样具备强大计算能力的设备而言,这就是瓶颈,在网络架构设计过程中,内存访问成本不能被简单忽视

  • 并行度。当 FLOPs 相同时,高并行度的模型可能比低并行度的模型快得多

其次,FLOPs 相同的运算可能有着不同的运行时间,这取决于平台。例如,早期研究广泛使用张量分解来加速矩阵相乘。但是,近期研究发现张量分解在 GPU 上甚至更慢,尽管它减少了 75%的 FLOPs。本文研究人员对此进行了调查,发现原因在于最近的 CUDNN 库专为 3×3 卷积优化:当然不能简单地认为 3×3 卷积的速度是 1×1 卷积速度的 1/9。

总结如下就是:

  1. 影响模型运行速度还有别的指标,例如 MAC(memory access ),并行度(degree of parallelism)

  2. 不同平台有不同的加速算法,这导致 flops 相同的运算可能需要不同的运算时间。

据此,提出了高效网络架构设计应该考虑的两个基本原则:第一,应该用直接指标(例如速度)替换间接指标(例如 FLOPs);第二,这些指标应该在目标平台上进行评估。在这项研究中,作者遵循这两个原则,并提出了一种更加高效的网络架构。

1.2 4 条高效网络设计原则

G1. 相同的通道宽度可最小化内存访问成本(MAC)

假设内存足够大一次性可存储所有特征图和参数;卷积核大小为$11$ ;输入通道有 c1 个;输出通道有 c2 个;特征图分辨率为$hw$,则在 1x1 的卷积上,FLOPS:$B = hwc1c2$,$MAC = hw(c1+c2)+c1c2$,容易推出:

MAC 是内存访问量,hwc1 是输入特征图内存大小,hwc2 是输出特征图内存大小,c1xc2 是卷积核内存大小。从公式中我们可以得出 MAC 的一个下界,即当 c1==c2 时,MAC 取得最小值。以上是理论证明,下面是实验证明:

可以看出,当 c1==c2 时,无论在 GPU 平台还是 ARM 平台,均获得了最快的 runtime。

G2. 过度使用组卷积会增加 MAC

和(1)假设一样,设 g 表示分组数,则有:

其中$B = hwc1c2/g$,当固定 c1 w h 和 B,增加分组 g,MAC 也会增加,证明了上述说法。其中 c1 w h 固定,g 增加的同时 B 复杂度也要固定,则需要把输出通道 c2 增加,因为 g 增加,复杂度是要下降的。以上是理论证明,下面是实验证明:

可以看出,g 增加,runtime 减少。

G3. 网络碎片化(例如 GoogLeNet 的多路径结构)会降低并行度

理论上,网络的碎片化虽然能给网络的 accuracy 带来帮助,但是在平行计算平台(如 GPU)中,网络的碎片化会引起并行度的降低,最终增加 runtime,同样的该文用实验验证:

可以看出,碎片化越多,runtime 越大。

G4. 元素级运算不可忽视

element-wise operations 也占了不少 runtime,尤其是 GPU 平台下,高达 15%。ReLU、AddTensor 及 AddBias 等这一类的操作就属于 element-wise operations. 这一类操作,虽然 flops 很低,但是 MAC 很大,因而也占据相当时间。同样地,通过实验分析 element-wise operations 越少的网络,其速度越快。

以上就是作者提出的 4 点设计要求,总结如下

  • 1x1 卷积进行平衡输入和输出的通道大小

  • 组卷积要谨慎使用,注意分组数

  • 避免网络的碎片化

  • 减少元素级运算

结合上述 4 点可以对目前移动端网络进行分析:

  • ShuffleNet v1 严重依赖组卷积(违反 G2)和瓶颈形态的构造块(违反 G1)

  • MobileNetV2 使用倒置的瓶颈结构(违反 G1),并且在“厚”特征图上使用了深度卷积和 ReLU 激活函数(违反了 G4)

  • 自动生成结构的碎片化程度很高,违反了 G3

1.3 算法模型

在 ShuffleNet v1 的模块中,大量使用了 1x1 组卷积,这违背了 G2 原则,另外 v1 采用了类似 ResNet 中的瓶颈层(bottleneck layer),输入和输出通道数不同,这违背了 G1 原则。同时使用过多的组,也违背了 G3 原则。短路连接中存在大量的元素级 Add 运算,这违背了 G4 原则。

基于以上缺点,作者重新设计了 v2 版本。V2 主要结构是多个 blocks 堆叠构成,block 又分为两种,一种是带通道分离的 Channel Spilit,一种是带 Stride=2。图示如下:

(a)为 v1 中的 stride=1 的 block,(b)为 v1 中的 stride=2 的 block,(c)是新设计的 v2 中的 stride=1 的 block,(d)是新设计的 v2 中的 stride=2 的 block。

在每个单元开始,c 特征通道的输入被分为两支,分别占据 c−c'和 c'个通道(一般设置 c=c'*2)。左边分支做同等映射,右边的分支包含 3 个连续的卷积,并且输入和输出通道相同,这符合 G1。而且两个 1x1 卷积不再是组卷积,这符合 G2,另外两个分支相当于已经分成两组。两个分支的输出不再是 Add 元素,而是 concat 在一起,紧接着是对两个分支 concat 结果进行 channle shuffle,以保证两个分支信息交流。其实 concat 和 channel shuffle 可以和下一个模块单元的 channel split 合成一个元素级运算,这符合原则 G4。

对于 stride=2 的模块,不再有 channel split,而是每个分支都是直接 copy 一份输入,每个分支都有 stride=2 的下采样,最后 concat 在一起后,特征图空间大小减半,但是通道数翻倍。

具体代码上非常简单,如下所示:

class InvertedResidual(nn.Module):
    def __init__(self,
                 in_channels,
                 out_channels,
                 stride=1,
                 conv_cfg=None,
                 norm_cfg=dict(type='BN'),
                 act_cfg=dict(type='ReLU'),
                 with_cp=False):
        super(InvertedResidual, self).__init__()
        self.stride = stride
        branch_features = out_channels // 2
        if self.stride > 1:
            # 当stride=2才需要
            self.branch1 = nn.Sequential(
                ConvModule(
                    in_channels,
                    in_channels,
                    kernel_size=3,
                    stride=self.stride,
                    padding=1,
                    groups=in_channels,
                    conv_cfg=conv_cfg,
                    norm_cfg=norm_cfg,
                    act_cfg=None), # 没有激活函数
                ConvModule(
                    in_channels,
                    branch_features,
                    kernel_size=1,
                    stride=1,
                    padding=0,
                    conv_cfg=conv_cfg,
                    norm_cfg=norm_cfg,
                    act_cfg=act_cfg),
            )
        # 和mobilenetv2中的反转残差块差不多,但是激活函数relu位置不一样
        self.branch2 = nn.Sequential(
            ConvModule(
                in_channels if (self.stride > 1) else branch_features,
                branch_features,
                kernel_size=1,
                stride=1,
                padding=0,
                conv_cfg=conv_cfg,
                norm_cfg=norm_cfg,
                act_cfg=act_cfg),
            ConvModule(
                branch_features,
                branch_features,
                kernel_size=3,
                stride=self.stride,
                padding=1,
                groups=branch_features,
                conv_cfg=conv_cfg,
                norm_cfg=norm_cfg,
                act_cfg=None),
            ConvModule(
                branch_features,
                branch_features,
                kernel_size=1,
                stride=1,
                padding=0,
                conv_cfg=conv_cfg,
                norm_cfg=norm_cfg,
                act_cfg=act_cfg))

    def forward(self, x):
        def _inner_forward(x):
            if self.stride > 1:
                # stride=2的模块
                out = torch.cat((self.branch1(x), self.branch2(x)), dim=1)
            else:
                # stride=1的模块,直接spilt,x1不变,x2进行逐深度可分离卷积计算
                x1, x2 = x.chunk(2, dim=1)
                out = torch.cat((x1, self.branch2(x2)), dim=1)
            out = channel_shuffle(out, 2)
            return out
        return _inner_forward(x)

以上两个部分就可以构成各种 block,在 block 基础上构造网络,如下图:

同样的有宽度因子来对通道进行缩减,主要 0.5、1.0、1.5 和 2.0 4 中配置。

2 实验结果

可以看出,和 v1 相比,在相同 FLOP 情况下,错误率更低,且和其他模型对比,复杂度明显下降。

上述结果是在 coco 目标检测上的结果,输入图像大小是 800×1200。FLOPs 行列出了输入图像大小为 224×224 时的复杂度水平。带*的 ShuffleNet v2 是变种,区别是扩大了感受野,具体为在每个 block 的第一个 pointwise 卷积的前面插入一个额外的 3x3 深度卷积。可以看出,精度提高不少,且 FLOPs 仅仅增加一点。这就留下一个疑问:如何增加网络的感受野也是一个值得探讨的问题。

3 总结

ShuffleNet v2 针对目前移动端模型都仅仅考虑精度和 FLOPS 参数,但是这两个参数无法直接反映真正高效网络推理要求,故作者提出了 4 条高效网络设计准则,全面考虑了各种能够影响速度和内存访问量的因素,并给出了设计规则。再此基础上设计了 ShuffleNet v2,实验结果表明速度和精度更加优异。

4 参考文献

ShuffleNet v2:轻量级 CNN 网络中的桂冠 ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design

Last updated