ECA-Net: Efficient Channel Attention for Deep Convolutional Neural Networks论文详解

论文链接:https://arxiv.org/abs/1910.03151

代码地址:https://github.com/BangguWu/ECANet

摘要

现有的注意力方法开发更复杂的注意力模块来提升性能,增加了计算负担。本文提出了一个有效的通道注意(ECA)模块,它只涉及少量的(k<=9)参数,但带来了明显的性能增益。

通过对SENet通道注意力模块的分析,证明了避免降维和适当的跨通道信息交互对于学习有效的通道注意非常重要。文中提出了一种不降维的局部交叉通道交互策略,该策略可以通过一维卷积有效地实现。此外,我们提出了一个通道维数的函数来自适应地确定一维卷积的核大小,它代表了局域交叉通道相互作用的覆盖范围。我们以ResNets和MobileNetV2为骨干,对ECA模块在图像分类、目标检测和实例分割方面进行了广泛的评估。实验结果表明,该模块在性能上优于其他模块,具有较高的效率。

相关工作

注意机制已被证明是增强深度CNNs的潜在手段。SE-Net首次提出了一种有效的通道注意学习机制,取得了良好的效果。随后,注意模块的开发大致可以分为两个方向:(1)增强特征聚合;(2)通道与空间注意相结合。具体来说,CBAM使用平均池化和最大池化来聚合特性。GSoP引入了一个二阶池化来实现更有效的特性聚合。GE利用深度卷积来对特征进行空间扩展。scSE和CBAM利用核大小为k×k的二维卷积计算空间注意,然后将其与通道注意结合起来。双注意网络与非局部神经网络有着相似的思想,它引入了一种新的图像或视频识别关系函数,而双注意网络(DAN)和交叉网络(CCNet)同时考虑非局部通道和非局部空间注意进行语义分割。类似地,Li等人提出了一个期望最大化注意(EMA)模块用于语义分割。

方法简介

具体来说,在给定输入特征的情况下,SE块首先对每个通道单独使用全局平均池化,然后使用两个具有非线性的完全连接(FC)层,然后使用一个Sigmoid函数来生成通道权值。两个FC层的设计是为了捕捉非线性的跨通道交互,其中包括降维来控制模型的复杂性。虽然该策略在后续的通道注意模块中得到了广泛的应用,但作者的实验研究表明,降维对通道注意预测带来了副作用,捕获所有通道之间的依赖是低效且不必要的。

ECANet主要对SENet模块进行了一些改进,提出了一种不降维的局部跨信道交互策略(ECA模块)和自适应选择一维卷积核大小的方法,从而实现了性能上的提优。最近已经有很多文章在通道和空间注意力上做改进并取得了性能提升。例如SKNet,SANet,ResNeSt等等,不得不说,注意力机制真的香!

文中提出了一种不降维的局部跨信道交互策略,该策略可以通过一维卷积有效地实现。进一步,作者又提出了一种自适应选择一维卷积核大小的方法,以确定局部跨信道交互的覆盖率。

图1示:各种注意力模块SENet、CBAM的比较,以ResNets为骨干模型,在准确率、网络参数、FLOPs方面。圆的大小表示模型计算(FLOPs)。显然,我们的ECA-Net在模型复杂度较低的同时,获得了较高的精度。

图2示:SE块和(b)我们的有效通道注意力(ECA)模块的比较。给定使用全局平均池(GAP)的聚合特性,SE块使用两个FC层计算权重。与之不同的是,ECA通过执行大小为k的快速一维卷积来生成通道权值,其中k通过通道维C的函数自适应地确定。

如图 (b)所示,在不降低维数的通道级全局平均池化之后,通过考虑每个通道及其k近邻来捕获局部跨通道交互信息。通过大小为k的快速一维卷积来捕获了跨通道交互信息

ECA可以通过大小为k的快速一维卷积来有效实现,其中卷积核大小k代表了局部跨信道交互的覆盖率,即,该通道附近有多少邻居参与了这个通道的注意力预测,为了避免通过交叉验证对k进行手动调优,本文提出了一种方法来自适应地确定k,其中交互的覆盖率(即卷积核大小 k)与通道维数成正比。

如图1和表2所示,与主干模型相反,使用ECA模块的深层CNNs(称为ECA- Net)引入很少的额外参数和可忽略的计算,同时带来显著的性能提升。

 

本文贡献:

1、避免降维和适当的跨通道交互对于学习有效的深度CNNs通道注意是重要的。
2、提出了一种新型的高效通道注意(ECA),尝试为深度CNNs开发一种非常轻量级的通道注意模块,该模块增加的模型复杂度很小,但带来了明显的改进。
3、在ImageNet-1K和MS COCO上的实验结果表明,我们的方法比最先进的方法具有更低的模型复杂性,同时获得了非常有竞争力的性能。

表1:以ResNet-50为骨干的ImageNet各通道注意模块对比参数。表示每个通道注意模块所涉及的参数数。O意味着点乘。GC和C1D分别表示群卷积和1D卷积。k是C1D的核大小。

核大小k的自适应选择 

k决定了交互的覆盖范围,不同的通道数和不同的CNN架构的卷积块可能会有所不同。

k与通道维数c有关,一般认为,通道尺寸越大,长期交互作用越强,而通道尺寸越小,短期交互作用越强。换句话说,之间可能存在某种映射φk和C:

在这里插入图片描述

最优映射φ通常是未知的。基于上述分析,k与C成非线性比例,因此参数化指数函数是一个可行的选择。同时,在经典的核技巧中,作为核函数的指数族函数(如高斯)被广泛用于处理未知映射问题。因此,我们使用一个指数函数近似映射φ,如下:
在这里插入图片描述

 

此外,由于通道维C通常设置为2的整数次幂,所以我们用 在这里插入图片描述 代替在这里插入图片描述
然后,给定通道维C,自适应确定内核大小k:
在这里插入图片描述
这里 在这里插入图片描述 表示最近的奇数t。本文中将γ设置为2,b设置为1。显然,通过映射,高维通道具有较长的相互作用,而低维通道通过非线性映射具有较短的相互作用

 

代码

如下为ECA模块的代码

import torch
from torch import nn
from torch.nn.parameter import Parameter
 
class eca_layer(nn.Module):
    """Constructs a ECA module.
    Args:
        channel: Number of channels of the input feature map
        k_size: Adaptive selection of kernel size
    """
    def __init__(self, channel, k_size=3):
        super(eca_layer, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.conv = nn.Conv1d(1, 1, kernel_size=k_size, padding=(k_size - 1) // 2, bias=False) 
        self.sigmoid = nn.Sigmoid()
 
    def forward(self, x):
        # x: input features with shape [b, c, h, w]
        b, c, h, w = x.size()
 
        # feature descriptor on the global spatial information
        y = self.avg_pool(x)
 
        # Two different branches of ECA module
        y = self.conv(y.squeeze(-1).transpose(-1, -2)).transpose(-1, -2).unsqueeze(-1)
 
        # Multi-scale information fusion
        y = self.sigmoid(y)
 
        return x * y.expand_as(x)

mobilenetv2 ECA模块

from torch import nn
from .eca_module import eca_layer
 
__all__ = ['ECA_MobileNetV2', 'eca_mobilenet_v2']
 
 
model_urls = {
    'mobilenet_v2': 'https://download.pytorch.org/models/mobilenet_v2-b0353104.pth',
}
 
 
class ConvBNReLU(nn.Sequential):
    def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1):
        padding = (kernel_size - 1) // 2
        super(ConvBNReLU, self).__init__(
            nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False),
            nn.BatchNorm2d(out_planes),
            nn.ReLU6(inplace=True)
        )
 
 
class InvertedResidual(nn.Module):
    def __init__(self, inp, oup, stride, expand_ratio, k_size):
        super(InvertedResidual, self).__init__()
        self.stride = stride
        assert stride in [1, 2]
 
        hidden_dim = int(round(inp * expand_ratio))
        self.use_res_connect = self.stride == 1 and inp == oup
 
        layers = []
        if expand_ratio != 1:
            # pw
            layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1))
        layers.extend([
            # dw
            ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim),
            # pw-linear
            nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
            nn.BatchNorm2d(oup),
        ])
        layers.append(eca_layer(oup, k_size))
        self.conv = nn.Sequential(*layers)
 
    def forward(self, x):
        if self.use_res_connect:
            return x + self.conv(x)
        else:
            return self.conv(x)
 
 
class ECA_MobileNetV2(nn.Module):
    def __init__(self, num_classes=1000, width_mult=1.0):
        super(ECA_MobileNetV2, self).__init__()
        block = InvertedResidual
        input_channel = 32
        last_channel = 1280
        inverted_residual_setting = [
            # t, c, n, s
            [1, 16, 1, 1],
            [6, 24, 2, 2],
            [6, 32, 3, 2],
            [6, 64, 4, 2],
            [6, 96, 3, 1],
            [6, 160, 3, 2],
            [6, 320, 1, 1],
        ]
 
        # building first layer
        input_channel = int(input_channel * width_mult)
        self.last_channel = int(last_channel * max(1.0, width_mult))
        features = [ConvBNReLU(3, input_channel, stride=2)]
        # building inverted residual blocks
        for t, c, n, s in inverted_residual_setting:
            output_channel = int(c * width_mult)
            for i in range(n):
                if c <= 96:
                    ksize = 1
                else:
                    ksize = 3
                stride = s if i == 0 else 1
                features.append(block(input_channel, output_channel, stride, expand_ratio=t, k_size=ksize))
                input_channel = output_channel
        # building last several layers
        features.append(ConvBNReLU(input_channel, self.last_channel, kernel_size=1))
        # make it nn.Sequential
        self.features = nn.Sequential(*features)
 
        # building classifier
        self.classifier = nn.Sequential(
            nn.Dropout(0.25),
            nn.Linear(self.last_channel, num_classes),
        )
 
        # weight initialization
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out')
                if m.bias is not None:
                    nn.init.zeros_(m.bias)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.ones_(m.weight)
                nn.init.zeros_(m.bias)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                if m.bias is not None:
                    nn.init.zeros_(m.bias)
 
    def forward(self, x):
        x = self.features(x)
        x = x.mean(-1).mean(-1)
        x = self.classifier(x)
        return x
 
 
def eca_mobilenet_v2(pretrained=False, progress=True, **kwargs):
    """
    Constructs a ECA_MobileNetV2 architecture from
    Args:
        pretrained (bool): If True, returns a model pre-trained on ImageNet
        progress (bool): If True, displays a progress bar of the download to stderr
    """
    model = ECA_MobileNetV2(**kwargs)
    # if pretrained:
    #     state_dict = load_state_dict_from_url(model_urls['mobilenet_v2'],
    #                                           progress=progress)
    #     model.load_state_dict(state_dict)
    return model

 

参考:

https://blog.csdn.net/tjut_zdd/article/details/102600401

 

 

  • 2
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值