美文网首页神经网络与深度学习
你的 ResNet 是时候更新了 —— ResNeSt 来也!

你的 ResNet 是时候更新了 —— ResNeSt 来也!

作者: CW不要无聊的风格 | 来源:发表于2020-05-01 02:50 被阅读0次

    Date: 2020/04/30 夜晚 ~ 2020/05/01 凌晨

    Author: CW

    前言:

    赶在月末,终于有时间写文了,最近工作上需求比较急,抽不出时间来简书更文,但我心早已狂热!在我敲上这行字的过程中,真的很开心,因为真心很享受这种静静地码字向别人分享学习心得的时光(虽然不知道有没有人看..)。OK,废话不多说,如今就为大家奉上这个新鲜出炉的新品 —— ResNeSt

    你没看错,是 ResNeSt 而不是 ResNet 哟!这是张航、李沐等大佬创造的 ResNet 改进版,在参数量没有显著增加的情况下显著提升了性能,并且可以很方便地如 ResNet 般集成到现有模型中。通过本文,我们就一起来看看它有多香吧!

    此处附上 Paper & 源码

    2020/05/05更新:

    今天把 ResNeSt 用在了OCR场景上,在训练过程中发现源码有个bug,在 github 上提交了 issue(https://github.com/zhanghang1989/ResNeSt/issues/46),详见以下第III部分。


    Outline

    I. 主要思想

    II. 分组的通道注意力机制:Split-Attention

    III. 从代码出发,知行合一


    主要思想

    ResNeSt 很好懂,不复杂,说白了就是结合了 ResNeXt 的分组卷积和 SE-Net 的通道注意力机制,将通道进行分组,对每组运用注意力机制,同时保留了 ResNet 的残差结构。


    分组的通道注意力机制:Split-Attention

    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 的注意力是一致的。

    很奇妙,对于分得的K个组,每个组内切分R份分配不同的注意力,但不同组依次对应的这R份注意力却分别是一致的,是谓同又不尽全同!


    从代码出发,知行合一

    看过 paper 和源码的朋友们可能会一头雾水,paper 中展示的结构图和代码实现的有出入,一开始 CW 也是如此,看了几篇文但总感觉自己理解得依旧不那么清晰,于是乎亲自把代码手撸一遍,并结合画图理解,最终眼前的迷雾也就散开了。

    我国古代优秀大佬王阳明推崇知行合一,虽然凡事不一定硬要知行结合,但是吾以为有了认知才有“行”的方向,“行”了才能加深认知或者说真正认知,这是一个循环,最终达到合二为一的高手境界。

    (⊙o⊙)… sorry,装b装过头了,接下来进入正题。

    作者在源码中对于 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)

    #2020/05/05更新

    不知大家有无注意,训练过程中以上代码会产生一个小问题,由于使用了 global average pooling,输出特征的大小变为1x1,因此之后如果再使用 bn 的话就会报错:

    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 的等价性证明

    另外还可参考 Amusi (CVer) 的这篇文:

    ResNeSt 实现有误?


    最后:

    对于 ResNeSt, 如果将 paper 和源码独立分开来看的话确实简单明了,但是若要结合在一起,并且向他人讲述明白,还需费一定心思。对基于 coding 实现的技术来说,如果没有亲自敲过一遍代码,还真不敢说你把这东西搞懂了,所以,最有效掌握知识的办法就是亲自上阵实践一番,所谓知而不行,乃是未知。

    本打算赶在4月末最后一晚写完这篇文,没想到被夜班工作上的事情拖着了,晚了几小时,遗憾呐!

    相关文章

      网友评论

        本文标题:你的 ResNet 是时候更新了 —— ResNeSt 来也!

        本文链接:https://www.haomeiwen.com/subject/ggxxghtx.html