> For the complete documentation index, see [llms.txt](https://openmmlabbook.gitbook.io/openmmlab-book/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://openmmlabbook.gitbook.io/openmmlab-book/image-classification/mobile/shufflenetv2.md).

# 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)**

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

![](http://static.zybuluo.com/huanghaian/koq1yvjfyak8fzwbi3mijg1o/image.png)

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

![](http://static.zybuluo.com/huanghaian/yv64a5caqrqjuzkopbidkzxo/image.png)

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

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

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

![](http://static.zybuluo.com/huanghaian/gf05fj9iqia4p0dlcbdf658n/image.png)

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

![](http://static.zybuluo.com/huanghaian/rb7b27ae7615zpjthifnkx61/image.png)

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

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

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

![](http://static.zybuluo.com/huanghaian/0o753w4ukjdpr9cj33opdjmg/image.png)

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

**G4. 元素级运算不可忽视**

![](http://static.zybuluo.com/huanghaian/1cbbnr18bb23ooioygxn7n16/image.png)

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

![](http://static.zybuluo.com/huanghaian/2yhpfoz9vwiex04obsi66rwl/image.png)

以上就是作者提出的 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。图示如下：

![](http://static.zybuluo.com/huanghaian/z9yknh3yyxg9tju06uyu1s6o/image.png)

(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 在一起后，特征图空间大小减半，但是通道数翻倍。

具体代码上非常简单，如下所示：

```python
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 基础上构造网络，如下图：

![](http://static.zybuluo.com/huanghaian/hizmp2of4hnma9musbbjijjr/image.png)

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

## 2 实验结果

![](http://static.zybuluo.com/huanghaian/cbsa35zn12p8zh174ooa8l0r/image.png)

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

![](http://static.zybuluo.com/huanghaian/uydjzgeuat8nenxjg1m2am79/image.png)

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

## 3 总结

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

## 4 参考文献

[ShuffleNet v2：轻量级 CNN 网络中的桂冠](https://zhuanlan.zhihu.com/p/48261931) [ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design](https://arxiv.org/abs/1807.11164)
