> For the complete documentation index, see [llms.txt](https://book.lianghao.work/cvkb/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://book.lianghao.work/cvkb/tong-yong-ji-suan-ji-shi-jue-suan-fa-mian-jing/tu-xiang-fen-lei-mian-shi-ti/shufflenet-v2.md).

# $ShuffleNet$ 系列算法之 $V2$

## 简介

$ShuffleNet$-$V2$，它是由旷视提出的 $V1$ 升级版本，能够在同等复杂度下，比 $ShuffleNet$-$V1$ 和 $MobileNet$-$v2$ 更准确。

主要有以下一些亮点：

* 提出了四个高效网络设计指南 $G1$、$G2$、$G3$、$G4$
* 针对 $ShuffleNet$-$V1$ 的两种 $unit$ 做了升级

## 论文前期实验

在 $ShuffleNet$-$V2$ 这篇论文中，作者先是做了两个实验来抛砖引玉，为下文提出的四个高效网络设计指南以及创新点埋下伏笔。

### 1、网络执行效率准则

目前衡量模型复杂度的一个通用指标是 $FLOPs$，具体指的是 $multiply-add$ 数量。在论文中作者一上来先提出了一个问题：

**是不是准确率和 $FLOPs$（浮点数计算的数量）就可以代表一个网络模型的性能呢？**

为此他们做了如下的实验：比较了分别以 $FLOPs$ 和 $Accuracy$ 为变量下不同平台下各个模型的推理时间。实验结果如下图 $1$ 所示。

![图 $1$](https://files.mdnice.com/user/15207/f6e561cd-18af-44e1-81bd-42acf405a247.png)

从图中我们可以看出：

$a$、在**不同平台下**同一个模型的推理速度是不同的；

$b$、**相同精度或相同 $FLOPs$** 下的模型推理时间是不同的。

作者经过分析后，认为出现这种情况的原因主要有：

* 对推理速度影响较大的因素，但没有影响到 $FLOPs$。例如：内存访问成本 $MAC$($memory$ $access$ $cost$)和并行度($degree$ $of$ $parallelism$)。
* 运行平台不同。不同的运行平台，针对卷积等操作有一定的优化，例如 $cudnn$ 等。

据此作者提出了 $2$ 个网络执行效率对比的准则：论文也是按照以下这两个准则，对多种网络（包括 $shufflenetv2$)进行评估。

**(1)使用直接度量方式如速度代替 FLOPs。**

**(2)在同一环境平台上进行评估。**

### 2、模型推理耗时

如下图 $2$ 所示，作者分别在 $GPU$ 和 $CPU$ 上对 $ShuffleNet$-$V1$，$MobileNet$-$V2$ 的推理时间进行了测试。从图中可以看出，整个推理时间被分解用于不同的操作。处理器在运算的时候，不光只是进行卷积运算，也在进行其他的运算，特别是在 $GPU$ 上，卷积运算只占了运算时间的一半左右。作者将卷积部分认为是 $FLOPs$ 操作。虽然这部分消耗的时间最多，但其他操作包括数据 $IO$、$shuffle$ 和逐元素操作（$Elemwise$，$ReLU$ 等）也占用了相当多的时间。因此，再次确认了模型只使用 $FLOPs$ 指标对实际运行时间的估计是不够准确的。

![图 $2$ $ShuffleNet$-$V1$，$MobileNet$-$V2$ 在 $GPU$ 和 $CPU$ 上的推理时间占比](https://files.mdnice.com/user/15207/4af68bc3-70cf-4429-887e-a9ad521c98cb.png)

## 四个高效网络设计指南

综上两个实验，作者提出了四个高效网络设计指南 $G1$、$G2$、$G3$、$G4$。下面让我们一起来看看这四个东东都是什么吧。

### $G1$:$Equal$ $channel$ $width$ $minimizes$ $memory$ $access$ $cost$

$G1$ 的意思为：卷积的输入输出具有相同 $channel$ 的时候，内存消耗是最小的。作者以 $1$×$1$$PW$ 卷积为例，假设输入和输出的特征通道数分别为 $c\_1$ 和 $c\_2$，特征图的大小为 $h$×$w$，则 $1$×$1$$PW$ 卷积的 $FLOPs$ 为:

$$
B=(hwc\_1*1*1\*c\_2)=hwc\_1c\_2
$$

对应的 MAC($memory$ $access$ $cost$)为：

$$
MAC=hwc\_1+hwc\_2+1*1*c\_1\*c\_2=hw(c\_1+c\_2)+c\_1c\_2
$$

并且我们知道以下均值不等式：

$$
c\_1+c\_2 ≥ 2\sqrt{c\_1c\_2}
$$

最后整理一下上面的式子可以知道：

$$
MAC≥2\sqrt{hwB}+\frac{B}{hw}
$$

那么在相同 $FLOPs$ 即当 $B$ 不变的时候，只有当 $c1$=$c2$ 的时候，$MAC$ 才能最小。如下图 $3$ 所示，作者也做了一系列的实验，从图中可看出，只有当 $c1$=$c2$ 的时候，在 $GPU$ 或者 $CPU$ 下的推理速度是最快的。

![图 $3$ 不同输入和输出下的推理速度](https://files.mdnice.com/user/15207/c256b8cd-d8c9-4c6a-a509-7dfe34850139.png)

***

### $G2$：$Excessive$ $group$ $convolution$ $increases$ $MAC$

$G2$ 意思为：过多的分组卷积操作会增大 $MAC$，从而使模型速度变慢。和前面同理，$g$ 为分组的数量，带 $group$ 操作的 $1$×$1$ 卷积的 $FLOPs$ 为：

$$
B=hw(c\_1*1*1\*c\_2)/g=hwc\_1c\_2/g
$$

其 $MAC$ 值为：

$$
MAC=hw(c\_1+c\_2)+\frac{c\_1c\_2}{g}=hwc\_1+\frac{Gg}{c\_1}+\frac{B}{hw}
$$

可以看出，在 $B$ 不变时，$g$ 越大，$MAC$ 也越大。如下图 $4$ 所示，作者也做了实验对比，从图可以看出随着 $g$ 越大，在 $GPU$ 和 $CPU$ 的推理速度也越慢。

![图 $4$ 不同 $g$ 值下 $GPU$ 和 $CPU$ 的推理速度](https://files.mdnice.com/user/15207/9a70fb64-9dc6-46ce-bd31-e5959218d945.png)

***

### $G3$：$Network$ $fragmentation$ $reduces$ $degree$ $of$ $parallelism$

$G3$ 的意思是网络内部分支操作会降低并行度。作者认为，模型中的网络结构太复杂（分支和基本单元过多）会降低网络的并行程度，模型速度越慢。文章用了一个词：$fragment$，翻译过来就是分裂的意思，可以简单理解为网络的单元或者支路数量。

为了研究 $fragment$ 对模型速度的影响，作者做了第四个实验。如图 $5$ 所示，$1$-$fragment$-$series$ 表示单个卷积层；$2$-$fragment$-$series$ 表示一个 $block$ 中有 $2$ 个卷积层串行，也就是简单的叠加；$4$-$fragment$-$parallel$ 表示一个 $block$ 中有 $4$ 个卷积层并行，类似 $Inception$ 的整体设计。图 $6$ 为测试结果，可以看出在相同 $FLOPs$ 的情况下，单卷积层（$1$-$fragment$）的速度最快。

![图 $5$](https://files.mdnice.com/user/15207/2d005608-4227-4582-bc20-62605f16be45.png)

![图 $6$ 不同分支结构下 $GPU$ 和 $CPU$ 下的推理速度](https://files.mdnice.com/user/15207/0e3954e0-4d67-4b80-a9cd-e26baca697c5.png)

***

### $G4$：$Element$-$wise$ $operations$ $are$ $non$-$negligible$

$G4$ 的意思为：$Element$-$wise$ 操作不能被忽略。如图 $2$ 所示，$IO$ 操作例如数据拷贝虽然 $FLOPs$ 非常低，但是带来的时间消耗还是非常明显的，尤其是在 $GPU$ 上。元素操作操作虽然基本上不增加 $FLOPs$，但是所带来的时间消耗占比却不可忽视。于是作者做了一个实验，采用的是 $Resnet50$ 的 bottleneck，除去跨层链接 $shortcut$ 和 $ReLU$ 之后测试其推理速度如下图 $7$ 所示，在 $GPU$ 和 $ARM$ 结构上都获得了接近 $20$% 的提速。

![图 $7$ $shortcut$ 和 $ReLU$ 的推理速度](https://files.mdnice.com/user/15207/3182354c-0d7a-43a3-8c4e-0a20d71377a9.png)

## $ShuffleNet$-$V2$ 设计

终于到了最后的部分了！这一部分，作者根据之前所提出的设计指南，在 $Shufflenet$-$V1$ 的基础上进行修改，得到了 $ShuffleNet$-$V2$。

### 1、$ShuffleNet$-$V2$ 的 $unit$ 升级

作者在遵循 G1-G4 的设计准则的条件下，对于 $ShuffleNet$-$V1$ 的 $unit$ 进行了改进，如下图 $8$ 所示为 $ShuffleNet$-$V1$ 和 $V2$ 的 $unit$ 对比。图 $8$-$a$ 和图 $8$-$b$ 分别为 $ShuffleNet$-$V1$ 中 $stride$=$1$ 和 $stride$=$1$ 的 $unit$，图 $8$-$c$ 和图 $8$-$d$ 分别为对应的改进版。

![图 $8$](https://files.mdnice.com/user/15207/86e2e720-b3a1-41b9-ae87-123f1b41e0a7.png)

作者分析了原 $ShuffleNet$-$V1$ 中违背 $G1$-$G4$ 的一些结构：

1、图 $8$-$a$ 中的逐点组卷积增加了 $MAC$ 违背了 $G2$；

2、图 $8$-$a$ 和 $8$-$b$ 中过多的分组违背了 $G3$；

3、图 $8$-$a$ 和 $8$-$b$ 中卷积输入和输出不相等违背了 $G1$;

4、图 $8$-$a$ 中使用了 $add$ 操作违背了 $G4$;

针对于以上问题，作者在 $ShuffleNet$-$V2$ 中做了以下改进：

1、对于 $stride$=$1$ 的模块来说，如图 $8$-$c$，在每个单元的开始，通过 $Channel$ $split$ 将 $c$ 特征通道的输入被分为两支，分别为 $c$−$c'$ 和 $c'$ 个通道。按照准则 $G3$，一个分支的结构仍然保持不变，另一个分支由三个卷积组成，为了满足 $G1$，令输入和输出通道相同。与 $ShuffleNet$-$V1$ 不同的是，两个 $1$×$1$ 卷积不再是组卷积($GConv$)(遵循 $G2$)，因为 $Channel$ $Split$ 分割操作已经产生了两个组。

2、卷积之后，把两个分支拼接($Concat$)起来，避免使用 $Add$ 操作(遵循 $G4$)，从而通道数量保持不变 (遵循 $G1$)。然后进行与 $ShuffleNet$-$V1$ 相同的 $Channel$ $Shuﬄe$ 操作来保证两个分支间能进行信息交流(将 shuffle 移到了 concat 之后遵循 $G3$)。

3、对于 $stride$=$2$ 的模块来说，如图 $8$-$d$，使用一个 $3$×$3$ 的 DW 卷积和 $1$×$1$ 的卷积来替代 $V1$ 当中的平均池化操作。也将原来的 $1$×$1$ 组卷积换成了普通的 $1$×$1$ 卷积(遵循 $G2$)。

如下所示为 $pytorch$ 版本 $ShuffleNet$-$V2$ 的结构单元代码：

```python
def channel_shuffle(x: Tensor, groups: int) -> Tensor:

    batch_size, num_channels, height, width = x.size()
    channels_per_group = num_channels // groups

    # reshape
    # [batch_size, num_channels, height, width] -> [batch_size, groups, channels_per_group, height, width]
    x = x.view(batch_size, groups, channels_per_group, height, width)

    x = torch.transpose(x, 1, 2).contiguous()

    # flatten
    x = x.view(batch_size, -1, height, width)

    return x


class InvertedResidual(nn.Module):
    def __init__(self, input_c: int, output_c: int, stride: int):
        super(InvertedResidual, self).__init__()

        if stride not in [1, 2]:
            raise ValueError("illegal stride value.")
        self.stride = stride

        assert output_c % 2 == 0
        branch_features = output_c // 2
        # 当stride为1时，input_channel应该是branch_features的两倍
        # python中 '<<' 是位运算，可理解为计算×2的快速方法
        assert (self.stride != 1) or (input_c == branch_features << 1)

        if self.stride == 2:
            self.branch1 = nn.Sequential(
                self.depthwise_conv(input_c, input_c, kernel_s=3, stride=self.stride, padding=1),
                nn.BatchNorm2d(input_c),
                nn.Conv2d(input_c, branch_features, kernel_size=1, stride=1, padding=0, bias=False),
                nn.BatchNorm2d(branch_features),
                nn.ReLU(inplace=True)
            )
        else:
            self.branch1 = nn.Sequential()

        self.branch2 = nn.Sequential(
            nn.Conv2d(input_c if self.stride > 1 else branch_features, branch_features, kernel_size=1,
                      stride=1, padding=0, bias=False),
            nn.BatchNorm2d(branch_features),
            nn.ReLU(inplace=True),
            self.depthwise_conv(branch_features, branch_features, kernel_s=3, stride=self.stride, padding=1),
            nn.BatchNorm2d(branch_features),
            nn.Conv2d(branch_features, branch_features, kernel_size=1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(branch_features),
            nn.ReLU(inplace=True)
        )

    @staticmethod
    def depthwise_conv(input_c: int,
                       output_c: int,
                       kernel_s: int,
                       stride: int = 1,
                       padding: int = 0,
                       bias: bool = False) -> nn.Conv2d:
        return nn.Conv2d(in_channels=input_c, out_channels=output_c, kernel_size=kernel_s,
                         stride=stride, padding=padding, bias=bias, groups=input_c)

    def forward(self, x: Tensor) -> Tensor:
        if self.stride == 1:
            x1, x2 = x.chunk(2, dim=1)
            out = torch.cat((x1, self.branch2(x2)), dim=1)
        else:
            out = torch.cat((self.branch1(x), self.branch2(x)), dim=1)

        out = channel_shuffle(out, 2)

        return out
```

### 2、$ShuffleNet$-$V2$ 的整体网络结构

![图 $9$ $ShuffleNet$-$V2$ 的整体网络结构](https://files.mdnice.com/user/15207/220e6ce8-8762-4af5-b4a4-fe0a4df0afe0.png)

$ShuffleNet$-$V2$ 的整体网络结构如图 9 所示。基本上延续了和 $V1$ 相似的结构，每个 $stage$ 都包含了若干 $stride$=$1$ 和 $stride$=$2$ 的模块，需要主要的是，每经过一个 $stage$ 通道数都会增加一倍的原因是由于 $stride$=$2$ 的模块(图 $8$-$d$)两个分支的通道数都等于输入通道，再经过 $concat$ 之后输出通道自然增加两倍。

### 2、$ShuffleNet$-$V2$ 实验结果

![图 $9$ $ShuffleNet$-$V2$ 的实验结果](https://files.mdnice.com/user/15207/24c3ec37-7be6-4649-8442-32d82d6688f3.png)

如图 $9$ 所示为 $ShuffleNet$-$V2$ 的实验结果，作者比较了 $ShuffleNet$-$V2$ 和 $MobileNet$ 系列、$DenseNet$、$Xception$ 等网络在 $GPU$ 和 $CPU$ 下的推理速度和精度。从图中可知，$ShuffleNet$-$V2$ 在精度和推理速度上都是更加杰出的。

## 总结

1、$ShuffleNet$-$V2$ 完善了网络性能对比的准则，以 $FLOPs$、推理速度和平台这三个因素来综合评价网络的性能。

2、提出了四个高效网络设计指南，并据此设计了 $ShuffleNet$-$V2$ 中的结构单元。

## 引用

* <https://arxiv.org/pdf/1807.11164.pdf>
* <https://www.jianshu.com/p/c5db1f98353f>
* <https://zhuanlan.zhihu.com/p/67009992>
* <https://zhuanlan.zhihu.com/p/48261931>
* <https://blog.csdn.net/weixin\\_44474718/article/details/91041343>
* <https://blog.csdn.net/weixin\\_43624538/article/details/86155936>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://book.lianghao.work/cvkb/tong-yong-ji-suan-ji-shi-jue-suan-fa-mian-jing/tu-xiang-fen-lei-mian-shi-ti/shufflenet-v2.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
