论文名称:ECA-Net: Efficient Channel Attention for Deep Convolutional Neural Networks
论文地址:https://arxiv.org/abs/1910.03151
代码: https://github.com/BangguWu/ECANet
这是一篇CVPR2020上对通道注意力进行改进的文章---ECANet,ECANet主要对SENet模块进行了一些改进,提出了一种不降维的局部跨信道交互策略(ECA模块)和自适应选择一维卷积核大小的方法,从而实现了性能上的提优。
目录
实验效果
创新点
pytorch代码实现
实验效果
创新点
- 针对SEBlock的步骤(3),将MLP模块(FC->ReLU>FC->Sigmoid),转变为一维卷积的形式,有效减少了参数计算量(我们都知道在CNN网络中,往往连接层是参数量巨大的,因此将全连接层改为一维卷积的形式)。
- 一维卷积自带的功效就是非全连接,每一次卷积过程只和部分通道的作用,即实现了适当的跨通道交互而不是像全连接层一样全通道交互。
首先回顾一下SENet中的通道注意模块。具体来说,在给定输入特征的情况下,SE块首先对每个通道单独使用全局平均池化,然后使用两个具有非线性的完全连接(FC)层,然后使用一个Sigmoid函数来生成通道权值。两个FC层的设计是为了捕捉非线性的跨通道交互,其中包括降维来控制模型的复杂性。虽然该策略在后续的通道注意模块中得到了广泛的应用,但作者的实验研究表明,降维对通道注意预测带来了副作用,捕获所有通道之间的依赖是低效的,也是不必要的。
因此,本文提出了一种针对深度CNN的高效通道注意(ECA)模块,将SENet中的两个先降维后升维的卷积,替换为更有效的连接方式,提高准确率的同时也减少了参数量。该模块避免了降维,有效捕获了跨通道交互的信息。如下图2所示:
ECA模块的思想是非常简单的。去除了原来SE模块中的FC层,直接在GAP--全局平均池化(Global Average Pooling)之后的聚合特征[C,1,1] 上通过一个可以权重共享的1D卷积进行学习,其中1D卷积涉及到超参k,就是卷积核尺寸,它代表了局部跨通道交互的覆盖率,通过通道维度C的映射自适应地确定。本人一开始在看ECA模块的结构图时是有些不解的,特别是看到一开始的5条连线对应后边的第3个神经元,为什么不是对应第1个神经元?下图是2020-CVPR-通道注意力超强改进-ECANet - 简书作者在理解ECA思想后所修改的,这里边涉及到padding问题,所以红框才是k=5(其中红框里的2个元素来自int(5/2))是GAP特征实际的样子,不然就说不通了。作者的这个使用1D卷积代替FC层的方法,也是受到了分组卷积思想的启发,如(6)所示。
1D卷积最初的特征矩阵如上(6)所示,可以看到1D卷积的权重之间是交错的,即是相互跨通道的,同时又是一组一组存在的,一组中权重的多少取决于卷积核k的大小。作者为了进一步提升网络性能,使用了卷积的共享权重的方法,即每一组的权重完全一样,这就极大地减少了参数量,从原来地k·C(其中C为通道数)缩减到k。
正如之前提到的1D卷积核k是一个超参,对于不同数量地通道数C应有不同的大小变化才行,所以本文作者进一步提出了一种自适应选择1D卷积核大小地方法。作者认为k和C之间有一种映射:
,其中是2的次方考虑到的是通道数量的设计一般都是以2的次方设计的,这样对本文模块的k的计算就有好一些。最后通过下面的公式就能对不同的通道数量自适应选择卷积核大小了。
其中, k表示卷积核大小,
表示距离t最近的奇数;C表示通道数;本文作者γ和b分别取2和1,这是实验结果。
【如何理解通道C自适应确定卷积核大小:当通道数多的时候,我需要卷积核k稍大一点,当通道数少的时候,我需要卷积核k稍微小一点,这样能充分融合部分通道间的交互】
pytorch代码实现
import math
import torch
import torch.nn as nn
class ECABlock(nn.Module):
def __init__(self, channels, gamma = 2, b = 1):
super(ECABlock, self).__init__()
kernel_size = int(abs((math.log(channels, 2) + b) / gamma))
kernel_size = kernel_size if kernel_size % 2 else kernel_size + 1
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.conv = nn.Conv1d(1, 1, kernel_size = kernel_size, padding = (kernel_size - 1) // 2, bias = False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
v = self.avg_pool(x)
v = self.conv(v.squeeze(-1).transpose(-1, -2)).transpose(-1, -2).unsqueeze(-1)
v = self.sigmoid(v)
return x * v
if __name__ == "__main__":
features_maps = torch.randn((8, 54, 32, 32))
model = ECABlock(54, gamma = 2, b = 1)
model(features_maps)
这里对比了两个论文的代码实现,可以看到,只是把MLP更换为了一维卷积。
# SEBlock 采用全连接层方式
def forward(self, x):
b, c, _, _ = x.shape
v = self.global_pooling(x).view(b, c)
v = self.fc_layers(v).view(b, c, 1, 1)
v = self.sigmoid(v)
return x * v
# ECABlock 采用一维卷积方式
def forward(self, x):
v = self.avg_pool(x)
v = self.conv(v.squeeze(-1).transpose(-1, -2)).transpose(-1, -2).unsqueeze(-1)
v = self.sigmoid(v)
return x * v
参考链接:
通道注意力超强改进,轻量模块ECANet来了!即插即用,显著提高CNN性能|已开源 - 知乎
2020-CVPR-通道注意力超强改进-ECANet - 简书