你的 ResNet 是时候更新了 —— ResNeSt 来也!
作者简介
CW,广东深圳人,毕业于中山大学(SYSU)数据科学与计算机学院,毕业后就业于腾讯计算机系统有限公司技术工程与事业群(TEG)从事Devops工作,期间在AI LAB实习过,实操过道路交通元素与医疗病例图像分割、视频实时人脸检测与表情识别、OCR等项目。
目前也有在一些自媒体平台上参与外包项目的研发工作,项目专注于CV领域(传统图像处理与深度学习方向均有)。
赶在4月末,终于有时间写文了,最近工作上需求比较急,抽不出时间来更文,但我心早已狂热!在我敲上这行字的过程中,真的很开心,因为真心很享受这种静静地码字向别人分享知识的时光(虽然不知道有没有人看..)。如今就为大家奉上这个新鲜出炉的新品 —— ResNeSt!
文末可获取相关Paper&源码链接
你没看错,是 ResNeSt 而不是 ResNet 哟!这是张航、李沐等大佬创造的 ResNet 改进版,在参数量没有显著增加的情况下显著提升了性能,并且可以很方便地如 ResNet 般集成到现有算法框架中。通过本文,我们就一起来看看它有多香吧!
Outline
I. 主要思想
II. 分组的通道注意力机制:Split-Attention
III. 从代码出发,知行合一
SplAtConv2d
这部分我们来详谈分组的通道注意力是怎样一种操作,作者论述到可能的实现方式有多种,这里我先谈谈其中一种。
了解 ResNeXt 的朋友们都知道,其引入了 Cardinality 的概念,代表分组的组数,为方便叙述,这里记为 K;ResNeSt 则在此基础上进一步分组,称为 split 操作,同时引入一个超参 Radix,代表将 K 个组中的每一个进一步划分的组数,这里记为 R。这里的分组都是在通道这个维度上进行,由此看来,就是将输入在通道这个维度划分为 KxR 个组。
分组完毕后,对每个组实施不同的特征变换(Conv+Bn+Relu 等),然后将它们分成 R 份,这样每份就包含原来的 K 个组,对每一份应用投票机制形成注意力(Softmax or Sigmoid),接着将这 R 份注意力与特征图对应相乘(element-wise multiply),最后将这 R 份结果加起来(element-wise sum)形成输出,输出相当于对应了原来的 K 个组。
梳理下,可以知道注意力在是分了 K 个组后再分R个组上执行的,记 R 中的每一份为 r,K 中的每一份为k,那么每个 r 上得到的注意力是不同的,即每个 k split 下的每个 r 上的注意力不同,而同一个 r 下对应的不同 k 的注意力是一致的。
看过 paper 和源码的朋友们可能会一头雾水,paper 中展示的结构图和代码实现的有出入,一开始 CW 也是如此,看了几篇文但总感觉自己理解得依旧不那么清晰,于是乎亲自把代码手撸一遍,并结合画图理解,最终眼前的迷雾也就散开了。
我国古代优秀大佬王阳明推崇知行合一,虽然凡事不一定硬要知行结合,但是吾以为有了认知才有“行”的方向,“行”了才能加深认知或者说真正认知,这是一个循环,最终达到合二为一的高手境界。
接下来进入正题~
作者在源码中对于 split attention 的实现有两种方式,分别对应两个类,其中一个类为 SplAtConv2d,对应于上一部分展示的图中结构;另一个类为 RadixMajorNaiveImp,对应下图中的结构。
RadixMajorNaiveImp
RadixMajorNaiveImp
结合上图和代码,先来看看 RadixMajorNaiveImp 具体如何实现。
首先将输入分为 KxR 个组,然后依次对K中的每个 k 执行注意力机制,具体做法是取出同一个 k 下的所有 r,然后把它们加起来,输入全局平均池化层和两层全连接层。
RadixMajorNaiveImp (i)
接着令通道这个维度等于 R,在这个维度上生成注意力权重,同时,将同一 k 下的所有 r 在通道这个维度上拼接起来,与注意力权重相乘,相乘后的结果分为 R 份,将这 R 份结果加起来形成这一个 k 的输出,最终将K组中所有 k 的结果在通道数这个维度上拼接起来。
RadixMajorNaiveImp (ii)
总的来说,这种方式就是依次对 K 组中的每份 k 进行处理,每份 k 进一步 split 成 R 份,其中每份 r 生成不同的注意力,K 组中的每份 k 都结合完注意力后,再将它们的结果在通道上拼接起来。
SplAtConv2d
接下来看看 SplAtConv2d 的实现方式。
SplAtConv2d
仔细观察上图,我们可以发现,这种实现方式是将输入分为 R 份,其中的每份 r 包含了 K 个组,每份 r 生成的注意力不同(对应上图中的虚线框),上一节便说到了,同一 k 下 不同的 split r 上形成的注意力不一致,但不同的 k 对应相同的 r 上形成的注意力却是一致的。
再回顾下 RadixMajorNaiveImp 的实现方式,同一 k 下 不同的 split r 上形成的注意力也是不一致,但不同 k 的注意力是独立生成的,它们之间并没有联系,这就是两种实现方式的最大差别了。
一起来瞄瞄代码~
SplAtConv2d (i)
这里提醒大家注意下,训练过程中在测试这个模块时,记住把 batch size 设置大于1,由于使用了 global average pooling,输出特征的大小变为1x1,因此其后接 bn 的话(上图中 self.bn1)就要求每个通道上多于一个元素,而如果 batch size 为1的话就会报错了:
ValueError: Expected more than 1 value per channel when training
bn 是在每个通道上(channel-wise)做归一化的,如果通道上只有1个元素,那么归一化就无意义了,所以在训练过程中, bn 要求每个通道上必须多于1个元素。
SplAtConv2d (ii)
另外,SplAtConv2d 这种实现方式不需要依次对 K 组中的每份进行处理,而是直接对 K 个组同时进行处理,相比于 RadixMajorNaiveImp 的方式更加简洁些。
作者在 paper 和 github 源码中也给出了两者等价性的证明,源码可以查看《SplAtConv2d 和 RadixMajorNaiveImp 的等价性证明》,链接:https://github.com/zhanghang1989/ResNeSt/blob/master/tests/test_radix_major.py#L100
对于 ResNeSt, 初次接触时往往会感觉其代码实现和paper描述得有出入,因此要把它讲述明白,自己本身一定要理解得透彻。如果没有亲自敲过一遍代码,就很难做到。对于其它算法模型也一样,能真正掌握的办法就是亲自上阵实践一番,所谓知而不行,乃是未知。
【参考】
源码链接:
https://github.com/zhanghang1989/ResNeSt
https://github.com/zhanghang1989/ResNeSt/blob/master/tests/test_radix_major.py#L100
Paper:
ResNeSt: Split-Attention Networks