📒
OpenMMLab Book
  • 前言
  • Image Classification
    • 概述
      • LeNet-5
      • 数据集
      • 评价指标
    • 早期方法
      • AlexNet
      • VGGNet
      • Inception
    • 主流模型及技术
      • BN
      • ResNet
      • ResNeXt
      • SENet
    • 移动端模型
      • MobileNet
      • MobileNet v2
      • ShuffleNet
      • ShuffleNet v2
  • Object Detection
    • 概述
      • 问题定义
      • 朴素方法
      • 一些概念
      • 算法评价
    • 早期方法
      • RCNN
      • YOLO
    • 两阶段方法
      • Fast R-CNN
      • RPN
      • Faster R-CNN
      • Mask R-CNN
    • FPN
    • 单阶段方法
      • SSD
      • YOLO v2
      • YOLO v3
      • RetinaNet
    • 新的方向
      • Cascade R-CNN
      • FCOS
  • Semantic Segmentation
    • 概述
    • FCN
    • PSPNet
Powered by GitBook
On this page
  • 模块设计
  • 使用 SENet 改造已有网络
  • SENet 的 Pytorch 实现

Was this helpful?

  1. Image Classification
  2. 主流模型及技术

SENet

PreviousResNeXtNext移动端模型

Last updated 4 years ago

Was this helpful?

SENet 并不是一个完整的网络结构,而是一个可以插入任意网络结构的模块。 与以往的网络设计思路不同,SENet 显式考虑了特征图的通道之间的关系, 使用了一种 self-attention 机制,从特征图中学习出不同通道的重要性,并对特征图的每个通道重新加权。 SENet 于 2017 年由 Momenta 公司和牛津大学视觉组提出,并获得了当年 ILSVRC 竞赛图像分类项目的冠军。

模块设计

SENet 模块的设计并不复杂,整体结构如下图所示。

这两个全连接层可以表示一个相对复杂的非线性映射。而通道数先减再增的瓶颈结构可以一定程度上降低计算量。

使用 SENet 改造已有网络

SENet 的使用相对自由,可以插入到任何一个已有网络的任何一个卷积层之后(具体而言是非线性激活层之后),实现对网络的改造。 SENet 原始论文中给出了 Inception 以及残差网络 ResNet 和 ResNeXt 的改进方式,如下图所示。

对于 Inception 网络,我们可以将其直接插入到 Inception 结构之后。 对于 ResNet 和 ResNeXt,我们可以将其插入到残差分支的基本模块或瓶颈模块之后,并保留直通分支不变。

SENet 的 Pytorch 实现

由于 SENet 是一个插入模块。因此对于通过工厂函数生成的 ResNet 和 ResNeXt 模型,只需要重新定义 SEBottleNeck ,在残差分支的最后加入 SELayer 就可以实现 SE-ResNet 和 SE-ResNeXt。

class SELayer(nn.Module):
    def __init__(self,
                 channels,
                 ratio=16,
                 conv_cfg=None,
                 act_cfg=(dict(type='ReLU'), dict(type='Sigmoid'))):
        super(SELayer, self).__init__()
        if isinstance(act_cfg, dict):
            act_cfg = (act_cfg, act_cfg)
        assert len(act_cfg) == 2
        assert mmcv.is_tuple_of(act_cfg, dict)
        self.global_avgpool = nn.AdaptiveAvgPool2d(1)
        self.conv1 = ConvModule(
            in_channels=channels,
            out_channels=int(channels / ratio),
            kernel_size=1,
            stride=1,
            conv_cfg=conv_cfg,
            act_cfg=act_cfg[0])
        self.conv2 = ConvModule(
            in_channels=int(channels / ratio),
            out_channels=channels,
            kernel_size=1,
            stride=1,
            conv_cfg=conv_cfg,
            act_cfg=act_cfg[1])

    def forward(self, x):
        out = self.global_avgpool(x)
        out = self.conv1(out)
        out = self.conv2(out)
        return x * out

可以看到,该模块的前传函数由一个全局平均每池化层和两个 1x1 的卷积层串联而成。 池化层即为压缩过程,两个卷积层则等价实现了通道维度的两个全连接层,即激发过程。 在这里,通道的压缩比率默认为 16 。 最后一个带广播的乘法完成了加权操作。

图中,Ftr\mathbf{F}_{tr}Ftr​ 表示被插入 SENet 模块的网络中的某个卷积层,可以是任意网络结构中的任意一层,U\mathbf{U}U 即为网络输出的特征图。 U\mathbf{U}U 到 X~\mathbf{\tilde{X}}X~ 的部分为 SENet 模块。

SENet 是 Squeeze and Excitation Network 的缩写。从这个名字中可以看出,这个模块中包含一个压缩过程和一个激发过程,分别对应图中的 Fsq(⋅)F_{sq}(\cdot)Fsq​(⋅) 和 Fex(⋅,W)F_{ex}(\cdot, \mathbf{W})Fex​(⋅,W) 部分。

压缩过程 Fsq(⋅)F_{sq}(\cdot)Fsq​(⋅) 是一个简单的全局平均池化层(Global Average Pooling)。 它可以将特征图 U\mathbf{U}U 压缩成一个 1×1×C1\times 1\times C1×1×C 的向量。 向量中的每一个元素是同通道全部空间位置的神经元的均值, 因而一定程度上包含了特征图 U\mathbf{U}U 全部空间位置的信息。

激发过程 Fex(⋅,W)F_{ex}(\cdot, \mathbf{W})Fex​(⋅,W) 由两个全连接层构成,是一个可学习的模块。 第一个全连接层将 CCC 维度的全局特征映射到低维空间,并使用 ReLU 函数激活。 第二个全连接层再将低维向量映射回 CCC 维,并使用 Sigmoid 激活。 激发过程的具体构造如下图所示,其中 rrr 为通道的压缩系数。

最后,再用激发后的特征向量(以下简称激发态向量,记为 s\mathbf{s}s )对原特征图 U\mathbf{U}U 的每个通道进行加权。 由于激发过程最后以 Sigmoid 函数作为激活函数,因此所有通道的权重介于 0 到 1 之间。 一个通道的重要性越低,它所对应的系数就越接近 0,在最终输出的特征图 X~\mathbf{\tilde{X}}X~ 中就会被削弱。

mmclassification 在 中实现了加入了 SENet 模块的 SE-ResNet 和 SE-ResNeXt。

SELayer 即为 SENet 核心逻辑的实现,定义在在 中,代码如下:

backbone
se_layer.py
SE block
SE Excitation
SE Mod