> 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/mobilenetv2.md).

# MobileNet v2

MobileNet v2 是 v1 的改进，随后 shufflenetv1 又对 mobilenetv1 进行了改进，并且结合 resnet 构建思想采用了 botttleneck 结构，相比之下，mobilenetv1 设计思想就显得比较陈旧了(没有 block 的概念，也没有残差连接)，故这些问题都在 MobileNet v2 中进行了改进，效果提升非常明显。主要创新点是：

* 提出线性瓶颈层 Linear Bottlenecks，也就是去掉了 1x1 降维输出层后面的非线性激活层，目的是为了保证模型的表达能力
* 提出反转残差块 Inverted Residual block，该结构和传统 residual block 中维度先缩减再扩增正好相反，因此 shotcut 也就变成了连接的是维度缩减后的 feature map

简单来说，MobileNet v2 是基于 mobilenetv1 的通道可分离卷积优势，然后再加入了残差模块功能，最后通过理论分析和实验证明，对残差模块进行了一定程度的修改，使其在移动端发挥最大性能。

## 1 算法设计

### 1.1 算法核心

mobilenetV1 主要是引入了 depthwise separable convolution 代替传统的卷积操作，实现了 spatial 和 channel 之间的解耦，达到模型加速的目的，但是整个模型非常类似 vgg，思想比较陈旧，最主流的做法应该是参考 resnet 设计思想，引入 block 和残差设计。

而且作者通过实验发现 mobilenetV1 结构存在比较大的缺陷。在实际使用的时候， 发现 Depthwise 部分的 kernel 比较容易训废掉即训完之后发现 depthwise 训出来的 kernel 有不少是空的即大部分输出为 0，这个问题在定点化低精度训练的时候会进一步放大。针对这个问题，本论文进行了深入分析。

假设经过激活层后的张量被称为兴趣流形(manifold of interest)，shape 为 HxWxD。根据前人研究，兴趣流形可能仅分布在激活空间的一个低维子空间里，或者说兴趣流形是可以用一个低维度空间来表达的，简单来说是说**神经网络某层特征，其主要信息可以用一个低维度特征来表征**。一般来说我们都会采用类似 resnet 中的 bottlenck 层来进行主要流形特征提取，在具体实现上，通常使用 1x1 卷积将张量降维，但由于 ReLU 的存在，这种降维实际上会损失较多的信息，特别是在通道本身就必须小的时候。

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

如上图所示，利用 MxN 的矩阵 T 将张量(2D，即 dim=2)变换到 M(M 可以任意设置)维的空间中，通过 ReLU 后（y=ReLU(Bx)），再用此矩阵 T 的逆恢复原来的张量。可以看到，当 M 较小时，恢复后的张量坍缩严重，M 较大(30)时则恢复较好。这意味着，**在较低维度的张量表示（兴趣流形）上进行 ReLU 等线性变换会有很大的信息损耗**。显然，当把原始输入维度增加到 15 或 30 后再作为 ReLU 的输入，输出恢复到原始维度后基本不会丢失太多的输入信息；相比之下如果原始输入维度只增加到 2 或 3 后再作为 ReLU 的输入，输出恢复到原始维度后信息丢失较多。**因此在 MobileNet V2 中，执行降维的卷积层后面不会接类似 ReLU 这样的非线性激活层，也就是所提的 linear bottleneck。**&#x603B;结如下：

* 对于 ReLU 层输出的非零值而言，ReLU 层起到的就是一个线性变换的作用
* ReLU 层可以保留输入兴趣流形的信息，但是只有当输入兴趣流形是输入空间的一个低维子空间时才有效。

如果上述不好理解的话，那么通俗理解就是：当采用 1x1 逐点卷积进行降维，如果原始降维前输入通道本身就比较小，那么经过 Relu 层后会损失很多信息，导致不断堆叠层后有效信息越来越小，到最后分类性能肯定会下降，就会表现出很多 kernel 出现全 0 的现象，特别是逐深度卷积+1x1 逐点卷积时候现象更加明显，因为逐深度卷积表达能力不如标准卷积。

### 1.2 Linear Bottlenecks

基于上述分析，为了保证信息不会丢失太多，在 Bottlenecks 层的降维后不在接 relu，图示如下：

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

蓝色块表示特征图，浅色块表示下一个 block 的开始，红色块表示卷积或者 Relu 操作，含有斜线的块表示不包括非线性层

* (a)为标准卷积
* (b)为逐深度可分离卷积
* (c)为在(b)后面加入“bottlenck layer”，即在后面接了一个不含有 relu 层的 1x1 卷积进行通道扩张，防止信息丢失过多
* 考虑到 block 是互相堆积的，调整一下视角，将“bottlenck layer”看成 block 的输入，那么这种结构也等价于(d)，block 中开始的 1x1 卷积层称为“expansion layer”，它的通道大小和输入 bottleneck 层的通道大小之比，称为扩展比（expansion ratio）。扩展层之后是 depthwise 卷积，然后采用 1x1 卷积得到 block 的输出特征，这个卷积后面没有非线性激活

### 1.3 Inverted residuals

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

(a)为标准的残差块，而(b)为所提出的反转残差块，为啥叫做反转？原始是残差块一般先采用 bottleneck layer(1x1 卷积)进行降维，最后在采用 bottleneck layer 进行扩展，而反转的做法是先采用 bottleneck layer(1x1 卷积)进行升维，最后在采用 bottleneck layer 进行降维，是相当于残差块的反向操作，其中斜线块表示在该特征图上面没有采用 relu 激活函数，通过实验表明反转残差块可以在实现上减少内存的使用，并且精度更少。

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

请注意：上图(b)应该是绘制错误，论文里面说过在 1x1 降维层不采用 relu 激活函数，故实际上应该是 1x1conv 升维度(有 relu)+3x3d conv+1x1conv 降维(没有 relu)，但是上面图明显绘制错误了。通过下面图示也可以看出来：

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

当 stride=2 的时候没有采用残差设计，代码如下：

```python
class InvertedResidual(nn.Module):
    def __init__(self,
                 in_channels,
                 out_channels,
                 stride,
                 expand_ratio,
                 conv_cfg=None,
                 norm_cfg=dict(type='BN'),
                 act_cfg=dict(type='ReLU6')):
        super(InvertedResidual, self).__init__()
        self.stride = stride
        # 扩张后通道维度
        hidden_dim = int(round(in_channels * expand_ratio))
        layers = []
        if expand_ratio != 1:
            # 1x1卷积先扩展维度，有relu6
            layers.append(
                ConvModule(
                    in_channels=in_channels,
                    out_channels=hidden_dim,
                    kernel_size=1,
                    conv_cfg=conv_cfg,
                    norm_cfg=norm_cfg,
                    act_cfg=act_cfg))
        layers.extend([
            #3x3 dw卷积，有relu6
            ConvModule(
                in_channels=hidden_dim,
                out_channels=hidden_dim,
                kernel_size=3,
                stride=stride,
                padding=1,
                groups=hidden_dim,
                conv_cfg=conv_cfg,
                norm_cfg=norm_cfg,
                act_cfg=act_cfg),
            # 1x1 pw卷积，没有relu
            ConvModule(
                in_channels=hidden_dim,
                out_channels=out_channels,
                kernel_size=1,
                conv_cfg=conv_cfg,
                norm_cfg=norm_cfg,
                act_cfg=None)
        ])
        self.conv = nn.Sequential(*layers)

    def forward(self, x):
        if self.use_res_connect:
            return x + self.conv(x)
        else:
            return self.conv(x)
```

总结一下，本文主要是提出了 linear bottleneck，和 inverted residual 两个改进，其中针对 1x1 点卷积后接 relu 会丢失大量信息，而提出了 linear bottleneck，在该改进基础上配合残差块的设计思想，最终提出了 inverted residual block。

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

图片来源：[MobileNet version 2](http://machinethink.net/blog/mobilenet-v2/)，该博客认为反转残差块的设计思想可以将 expansion layer 可以看做解压器，类似 unzip，将特征恢复到高维空间，而 depthwise layer 可以看成过滤器，筛选比较重要的特征，最后 projection layer 将其压缩到低维空间。

### 1.4 网络设计

基于 inverted residual block 就可以构建整个 MobileNet v2 了。

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

t 是扩张系数，c 是输出通道数，n 是重复次数，s 是 stride。可以看出这个基础网络都没有 pool 操作，而是采用 stride 代替。

实际上 v2 也设计了宽度因子和分辨率因子两个超参数控制网络的参数量，默认是默认宽度因子是 1.0，输入大小是 224x224，分辨率因子影响的是特征图空间大小，而宽度因子影响的是特征图 channel 大小。输入大小可以从 96 到 224，而宽度因子可以从 0.35 到 1.4。值得注意的一点是当宽度因子小于 1 时，不对最后一个卷积层的 channel 进行调整以保证性能，即维持 1280。

## 2 实验结果

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

在 imagenet 图像分类任务上面进行训练，结果如上，在同样参数大小下，MobileNet v2 比 MobileNetv1 和 ShuffleNet 要好，而且速度也更快一些。

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

上表可以看出 MobileNet V2 + SSDLite 达到了极高的性能，且速度非常块。

## 3 总结

MobileNet v2 主要是对 v1 进行针对性改进，针对 dwconv+pwconv 后经过 relu 激活层会丢失大量信息，导致 dw 很多 kernel 都失活，提出了线性瓶颈层，主要是 pw 卷积降维时候不采用 relu，接着在该基础上结合残差设计思想提出了反转残差块，通过堆叠反转残差块实现了在移动端上又快又好的模型。多个任务 benchmark 平台结果表明比 mobilenetv1 和 shufflenetv1 更加优异。

## 4 参考文献

* [MobileNet v2: Inverted Residuals and Linear Bottlenecks](https://arxiv.org/abs/1801.04381)
* [轻量级 CNN 网络之 MobileNet v2](https://zhuanlan.zhihu.com/p/52426865)
* [MobileNet version 2](http://machinethink.net/blog/mobilenet-v2/)
