YOLOv5改进系列(4)——添加ECA注意力机制
🚀一、ECA注意力机制原理
论文题目:《ECA-Net: Efficient Channel Attention for Deep Convolutional Neural Networks》
原文地址:ECA-Net
代码实现:ECA-Net: Efficient Channel Attention for Deep Convolutional Neural Networks 开源代码GitHub - BangguWu/ECANet: Code for ECA-Net: Efficient Channel Attention for Deep Convolutional Neural NetworksECA-Net: Efficient Channel Attention for Deep Convolutional Neural Networks 开源代码
1.1 ECA方法介绍
ECA是通道注意力机制的一种实现形式,是基于SE的扩展。
作者认为SE block的两个FC层之间的降维是不利于channel attention的权重学习的,并且捕获所有通道之间的依存关系是效率不高且是不必要的。权重学习的过程应该直接一一对应。
ECA 注意力机制模块直接在全局平均池化层之后使用1x1卷积层,去除了全连接层。该模块避免了维度缩减,并有效捕获了跨通道交互。并且ECA只涉及少数参数就能达到很好的效果。
ECA通过一维卷积 layers.Conv1D 来完成跨通道间的信息交互,卷积核的大小通过一个函数来自适应变化,使得通道数较大的层可以更多地进行跨通道交互。
自适应函数为:
,其中 
1.2 SE和ECA网络结构的对比

| SEblock网络结构 | ECA模块网络结构 |
| (1)global avg pooling产生1 ∗ 1 ∗ C 大小的feature maps | (1)global avg pooling产生1 ∗ 1 ∗ C 大小的feature maps |
| (2)两个fc层(中间有维度缩减)来产生每个channel的weight | (2)计算得到自适应的kernel_size |
| (3)应用kernel_size于一维卷积中,得到每个channel的weight |
1.3 ECA实现过程
(1)将输入特征图经过全局平均池化,特征图从 [h,w,c] 的矩阵变成 [1,1,c] 的向量
(2)根据特征图的通道数计算得到自适应的一维卷积核大小 kernel_size
(3)将 kernel_size 用于一维卷积中,得到对于特征图的每个通道的权重
(4)将归一化权重和原输入特征图逐通道相乘,生成加权后的特征图
🚀二、添加ECA注意力机制方法(单独加)
2.1 添加顺序
(1)models/common.py --> 加入新增的网络结构
(2) models/yolo.py --> 设定网络结构的传参细节,将ECA类名加入其中。(当新的自定义模块中存在输入输出维度时,要使用qw调整输出维度)
(3) models/yolov5*.yaml --> 新建一个文件夹,如yolov5s_ECA.yaml,修改现有模型结构配置文件。(当引入新的层时,要修改后续的结构中的from参数)
(4) train.py --> 修改‘--cfg’默认参数,训练时指定模型结构配置文件
2.2 具体添加步骤
第①步:在common.py中添加ECA模块
将下面的ECA代码复制粘贴到common.py文件的末尾
- class ECA(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, c1,c2, k_size=3):
- super(ECA, 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):
- # feature descriptor on the global spatial information
- y = self.avg_pool(x)
- 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)
如下图所示:
第②步:在yolo.py文件里的parse_model函数加入类名
首先找到yolo.py里面parse_model函数的这一行
然后把刚才加入的类ECA添加到这个注册表里面
第③步:创建自定义的yaml文件
首先在models文件夹下复制yolov5s.yaml 文件,粘贴并重命名为 yolov5s_ECA.yaml

接着修改 yolov5s_ECA.yaml ,将ECA模块加到我们想添加的位置。
注意力机制可以添加在backbone,Neck,Head等部分,常见的有两种:一是在主干的 SPPF 前添加一层;二是将Backbone中的C3全部替换。
在这里我是用第一种:将 [-1,1,ECA,[1024]]添加到 SPPF 的上一层,下一节使用第二种。即下图中所示位置:
同样的下面的head也得修改,p4,p5以及最后detect的总层数都得+1
这里我们要把后面两个Concat的from系数分别由[ − 1 , 14 ] , [ − 1 , 10 ]改为[ − 1 , 15 ],[ − 1 , 11 ]。然后将Detect原始的from系数[ 17 , 20 , 23 ]要改为[ 18 , 21 , 24 ] 。
第④步:验证是否加入成功
在yolo.py 文件里面配置改为我们刚才自定义的yolov5s_ECA.yaml

然后运行yolo.py
找到ECA这一层,就说明我们添加成功啦!
可以看到params参数这里只有3,说明参数量真的很少呀。
第⑤步:修改train.py中 ‘--cfg’默认参数
我们先找到 train.py 文件的parse_opt函数,然后将第二行‘--cfg’的 default改为'models/yolov5s_ECA.yaml',然后就可以开始训练啦~

🚀三、添加C3_CA注意力机制方法(在C3模块中添加)
上面是单独加注意力层,接下来的方法是在C3模块中加入注意力层。
刚才也提到了,这个策略是将CA注意力机制添加到Bottleneck,替换Backbone中的所有C3模块。
(因为步骤和上面相同,所以接下来只放重要步骤噢~)
第①步:在common.py中添加ECABottleneck和C3_ECA模块
将下面的代码复制粘贴到common.py文件的末尾
- class ECABottleneck(nn.Module):
- # Standard bottleneck
- def __init__(self, c1, c2, shortcut=True, g=1, e=0.5, ratio=16, k_size=3): # ch_in, ch_out, shortcut, groups, expansion
- super().__init__()
- c_ = int(c2 * e) # hidden channels
- self.cv1 = Conv(c1, c_, 1, 1)
- self.cv2 = Conv(c_, c2, 3, 1, g=g)
- self.add = shortcut and c1 == c2
- # self.eca=ECA(c1,c2)
- 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):
- x1 = self.cv2(self.cv1(x))
- # out=self.eca(x1)*x1
- y = self.avg_pool(x1)
- y = self.conv(y.squeeze(-1).transpose(-1, -2)).transpose(-1, -2).unsqueeze(-1)
- y = self.sigmoid(y)
- out = x1 * y.expand_as(x1)
- return x + out if self.add else out
- class C3_ECA(C3):
- # C3 module with ECABottleneck()
- def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
- super().__init__(c1, c2, n, shortcut, g, e)
- c_ = int(c2 * e) # hidden channels
- self.m = nn.Sequential(*(ECABottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
第②步:在yolo.py文件里的parse_model函数加入类名
在yolo.py的parse_model函数中,加入ECABottleneck,C3_ECA这两个模块
第③步:创建自定义的yaml文件
按照上面的步骤创建yolov5s_C3_ECA.yaml文件

替换4个C3模块,如下图所示
代码如下:
- # YOLOv5 🚀 by Ultralytics, GPL-3.0 license
- # Parameters
- nc: 80 # number of classes
- depth_multiple: 0.33 # model depth multiple
- width_multiple: 0.50 # layer channel multiple
- anchors:
- - [10,13, 16,30, 33,23] # P3/8
- - [30,61, 62,45, 59,119] # P4/16
- - [116,90, 156,198, 373,326] # P5/32
- # YOLOv5 v6.0 backbone
- backbone:
- # [from, number, module, args]
- [[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
- [-1, 1, Conv, [128, 3, 2]], # 1-P2/4
- [-1, 3, C3_ECA, [128]],
- [-1, 1, Conv, [256, 3, 2]], # 3-P3/8
- [-1, 6, C3_ECA, [256]],
- [-1, 1, Conv, [512, 3, 2]], # 5-P4/16
- [-1, 3, C3_ECA, [512]],
- [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
- [-1, 3, C3_ECA, [1024]],
- [-1, 1, SPPF, [1024, 5]], # 9
- ]
- # YOLOv5 v6.0 head
- head:
- [[-1, 1, Conv, [512, 1, 1]],
- [-1, 1, nn.Upsample, [None, 2, 'nearest']],
- [[-1, 6], 1, Concat, [1]], # cat backbone P4
- [-1, 3, C3, [512, False]], # 13
- [-1, 1, Conv, [256, 1, 1]],
- [-1, 1, nn.Upsample, [None, 2, 'nearest']],
- [[-1, 4], 1, Concat, [1]], # cat backbone P3
- [-1, 3, C3, [256, False]], # 17 (P3/8-small)
- [-1, 1, Conv, [256, 3, 2]],
- [[-1, 14], 1, Concat, [1]], # cat head P4
- [-1, 3, C3, [512, False]], # 20 (P4/16-medium)
- [-1, 1, Conv, [512, 3, 2]],
- [[-1, 10], 1, Concat, [1]], # cat head P5
- [-1, 3, C3, [1024, False]], # 23 (P5/32-large)
- [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
- ]
第④步:验证是否加入成功
在yolo.py 文件里面配置改为我们刚才自定义的yolov5s_C3_ECA.yaml,然后运行
这样就OK啦~
第⑤步:修改train.py中 ‘--cfg’默认参数
接下来的训练就和上面一样,不再叙述啦~
完结~撒花✿✿ヽ(°▽°)ノ✿
PS:今天训练了一下,我的评价是,ECA不如昨天的CA,mAP降了0.3