# ResNeXt

ResNeXt 是 2017 年 FAIR 提出的一个图像分类网络。ResNeXt 是基于 ResNet 的整体结构提出的一种改进结构。 受启发于 Inception 模块“分离-变换-合并”的思路，把 ResNet 中 BottleNeck 模块中的 64 通道的卷积替换为独立的 32 路 4 通道卷积运算，并在最后按通道合并起来。 这也等价为将 ResNet 的 BottleNeck 结构中的第二个卷积层替换为分组卷积层。 这种修改在保持参数和计算量基本不变的情况下，进一步提高了网络分类精度。 ResNeXt 在 2016 年的 ILSVRC 竞赛图像分类项目中取得了第二名的成绩。

## 基本思路

ResNet 的 BottleNeck 结构如下图左图所示，其残差分支由三个卷积层串联而成，输入通道数、卷积核大小、输出通道数从左到右标注在图中。 这三个卷积层分别负责压缩特征图的通道数、扩展感受野以及恢复特征图的通道数。

![renset block vs resnext block](https://3632175219-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MJhbbglPBd1Md8dFDXQ%2Fsync%2F2eefaa110dcbcf3063e27d9cc5a016e1d9f0111c.png?generation=1609344157942342\&alt=media)

我们知道，一个卷积层所包含的参数总数为 $$N\_\text{param} = C\_\text{in} C\_\text{out} K^2$$ ，计算量正比于参数量 $$N\_\text{ops} = H\_\text{out} W\_\text{out} N\_\text{param}$$ 。 因而 BottleNeck 结构的的参数总数为

$$
256\times 64 \times 2 + 64\times 64 \times 9
$$

可以看出，中间 3x3 的卷积层占据了主要的参数量。

为了降低计算量，ResNeXt 结构对 ResNet 的 BottleNeck 结构进行了改进。 ResNeXt 将残差分支扩展到 $$C=32$$ 个，并将每个分支中中间层的输入和输出通道数降低至 $$d=4$$。 在 ResNeXt 结构中，$$C$$ 称为 cardinality，$$d$$ 称为 width。

经过这种改造后，残差分支的参数总量变为为

$$
C \cdot (2 \times  256 \times d + 9 \times d\times d)
$$

如果我们控制 $$Cd=64$$ ，则中间层的参数量仅有 ResNet 结构的 $$1/C$$ 。

如果我们控制参数量和 ResNet 相近，则可以得到如下 $$C$$ 和 $$d$$ 的配置。

![](https://3632175219-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MJhbbglPBd1Md8dFDXQ%2Fsync%2Fef201697cd201f32e332c600b518c97f3be0f6ea.png?generation=1609344158936477\&alt=media)

其中，$$(C, d)=(32, 4)$$ 是 ResNeXt 的常见配置，而 $$(C, d)=(64, 1)$$ 时退化为 ResNet 。

## 使用分组卷积实现多路残差

ResNeXt 中的多路残差结构可以由分组卷积实现。为了理解这一点，让我们先来看一下分组卷积是如何计算的。

在标准的卷积中，卷积核的通道数与输入特征图的通道数相同，我们记其为 $$C\_\text{in}$$，卷积核的记为 $$C\_\text{out}$$。 在进行卷积运算时，所有 $$C\_\text{out}$$ 个卷积核的所有 $$C\_\text{in}$$ 个通道都需要参与输入特征图的所有 $$C\_\text{in}$$ 个通道进行内积运算。 形象地说，卷积核和输入特征图在通道维度是一个*全连接*的结构，如下图所示：

![64通道的卷积](https://3632175219-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MJhbbglPBd1Md8dFDXQ%2Fsync%2F021b3bd3322b00c503a6efca99b756020431ff4e.png?generation=1609342805312340\&alt=media)

分组卷积则在通道上呈现一种*局部连接*的状态。 分组卷积将输入特征图按通道分成 $$G$$ 组，每组 $$C\_\text{in} / G$$ 个通道，再将所有卷积核的通道也由 $$C\_\text{in}$$ 缩减到 $$C\_\text{in} / G$$ 个。 在前传计算时，第 $$g$$ 个卷积核只与第 $$g$$ 组中的通道进行内积运算，如下图所示：

![64通道16组的分组卷积](https://3632175219-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MJhbbglPBd1Md8dFDXQ%2Fsync%2Fa3853e17da4e3d2808bb8e3f4c13ca77ae09a78d.png?generation=1609342804319252\&alt=media)

不难得出，分组卷积的参数量和计算量仅有普通卷积的$$1/G$$

如果我们将空间维度也考虑进去，则分组卷积的前传计算为：

$$
y\[h,w,c]=\sum\_{h',w'=0}^{K}\sum\_{c'=0}^{C\_\text{in} / G-1} x\[h+h',w+w',g\frac{C\_\text{in}}{G}+c'] \cdot k\_c\[h', w', c']
$$

其中， $$x, y$$ 分别为输入和输出的特征图，$$k\_c$$ 为第 $$c$$ 个卷积核，其形状为 $$K\times K\times \frac{C\_\text{in}}{G}$$ 。

分组卷积在 AlexNet 中就有所应用。受到当时 GPU 显存的限制，AlexNet 在实现时使用了*模型并行*的技术，将同一层的特征图按通道切分成两部分，分别放在不同的 GPU 上，即组数为 2 。 在前传时，只有中间一个卷积层和最后的两个全连接层是全连接的，需要 GPU 间进行数据通信，其余卷积层均为组数为 2 的分组卷积，即使用各自 GPU 上的通道数减半的卷积核。

接下来，我们再考察如何使用分组卷积实现 ResNeXt 的多路残差结构。 辅助论文中的图示可以更好的理解。

![](https://3632175219-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MJhbbglPBd1Md8dFDXQ%2Fsync%2F1e15be429f8c68d225fc5c44ee498fecff14743f.png?generation=1609342806230836\&alt=media)

在每个残差分支中，第一个卷积层都以同样的 256 通道特征图为输入，经由 $$d$$ 个卷积核，输出 $$d$$ 个通道的特征图。 我们可以把总共 $$Cd$$ 个卷积核集合起来，构成一个 $$Cd$$ 输出通道的卷积层。 这个合并的卷积层输出的 $$Cd$$ 通道的特征图与原结构中 $$C$$ 分支，每分支 $$d$$ 通道的特征图完全相同。 也就是说，合并的第一个卷积层与原结构中的 $$C$$ 个卷积层等价。

每个分支的第二个卷积层以第一个卷积层输出的 $$d$$ 通道特征为输入，经由 $$d$$ 个卷积核，输出 $$d$$ 通道的特征图，所有分支合计 $$Cd$$ 通道。 这等价于把合并后的 $$Cd$$ 个特征拆为 $$C$$ 组，并使用分组卷积输出 $$Cd$$ 通道的特征图。 因此，所有分支的第二个卷积层可以等效合并为一个 $$C$$ 组的分组卷积层。

每个分支的第三个卷积层以第二个卷积层输出的 $$d$$ 通道特征为输入，经由 $$Cd$$ 个卷积核，输出 256 通道的的特征图，所有分支的输出求和作为最终结果。 从输出的角度看，每个通道的特征值是 $$C$$ 个内积的和，每个内积是 $$d$$ 通道特征图和卷积核对应通道相乘求和而得。 如果我们将所有卷积核按通道拼接，将所有输入特征图按通道拼接，则一个 $$Cd$$ 通道的内积操作就可以等效替代上述分别内积再求和的操作。 因此，所有分支的第三个卷积层可以合并为一个 $$Cd$$ 输入通道的卷积层。

经过这种替换，ResNeXt 的多路残差结构就可以等效变换为一个单路结构。这也是诸多深度学习框架所采用的实现方式。

## ResNeXt 的 PyTorch 实现

由于 ResNeXt 的整体结构与 ResNet 完全相同，只要将残差分支中的普通卷积替换为分组卷积即可。由于 Pytorch 的 `Conv2d`类型直接支持了`group`参数，因此基于 ResNet 的代码实现 ResNeXt 是非常直接的。

在 torchvision 中，ResNeXt 也实现在 [resnet.py](https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py) 中，传入匹配的`group`和`width`参数给工厂函数`_resnet`，就可以实现分组卷积。

在 mmclassification 中，ResNeXt 实现在一个单独的文件 [resnext.py](https://github.com/open-mmlab/mmclassification/blob/master/mmcls/models/backbones/resnext.py) 中。与 torchvision 不同 mmclassification 通过继承 ResNet 的 `BottleNeck` 残差模块和 `ResNet` 结构，在增加了对分组卷积的实现。
