YOLOv5改进系列(3)——添加CA注意力机制
🚀一、CA注意力机制原理
论文题目:《Coordinate Attention for Efficient Mobile Network Design》
1.1 SE和CBAM方法的不足
- SE(Squeeze-and-Excitation):SE注意力机制在编码过程中只考虑编码通道间信息而忽略了位置信息的重要性
- CBAM(CBAM: Convolutional Block Attention Module):使用卷积计算空间注意力来利用位置信息,但卷积只能捕获局部关系,而无法捕捉的长距离依赖关系。
1.2 Coordinate Attention Block

Coordinate Attention通过精确的位置信息对通道关系和长期依赖性进行编码,具体操作分为 Coordinate信息嵌入 和 Coordinate Attention生成 2个步骤。
1.2.1 Coordinate Information Embedding:Coordinate信息嵌入
将 CxHxW 形状的输入特征图逐通道进行平均池化,使用(H,1)和(1,W)的池化核分别按X和Y轴方向进行池化,产生 CxHx1 和 Cx1xW 形状的特征图。

通过这种方式所产生的一对方法感知特征图,可以使 CA 注意力能够在一个通道内捕获长距离的依赖关系,并且还有助于保留精确的位置信息,使网络能够更加准确的定位对象。
1.2.2 Coordinate Attention Generation :Coordinate Attention生成
(1)将上述所提取到的特征图按空间维度进行拼接,拼接成 x1x(W+H) 形状的特征图,其中 r 用于控制块的减小率和SE中的作用相同。
(2)再将特征图经过F1卷积变换函数(1x1卷积)和非线性激活函数产生 f 中间特征图。然后将 f 按空间维度拆分成两个张量 fh 和 fw ,形状分别为xH 和 xW,接着分别进行 Fh 和 Fw 卷积变换函数(1x1卷积)和 Sigmoid 激活函数,得到 gh 和 gw 坐标注意力。
(3)最后将gh 和 gw 与原输入进行相乘,得到与输入相同形状的输出。

1.3 CA的优势
- 不仅考虑了通道信息,还考虑了方向相关的位置信息。
- 足够的灵活和轻量,能够简单的插入到轻量级网络的核心模块中。
- 可以作为预训练模型用于多种任务中,如检测和分割,均有不错的性能提升。
🚀二、添加CA注意力机制方法(单独加)
2.1 添加顺序
(1)models/common.py --> 加入新增的网络结构
(2) models/yolo.py --> 设定网络结构的传参细节,将CA(CoordAtt
)类名加入其中。(当新的自定义模块中存在输入输出维度时,要使用qw调整输出维度)
(3) models/yolov5*.yaml --> 新建一个文件夹,如yolov5s_CA.yaml,修改现有模型结构配置文件。(当引入新的层时,要修改后续的结构中的from参数)
(4) train.py --> 修改‘--cfg’默认参数,训练时指定模型结构配置文件
2.2 具体添加步骤
第①步:在common.py中添加CA模块
将下面的CA代码复制粘贴到common.py文件的末尾
- # CA
- class h_sigmoid(nn.Module):
- def __init__(self, inplace=True):
- super(h_sigmoid, self).__init__()
- self.relu = nn.ReLU6(inplace=inplace)
- def forward(self, x):
- return self.relu(x + 3) / 6
- class h_swish(nn.Module):
- def __init__(self, inplace=True):
- super(h_swish, self).__init__()
- self.sigmoid = h_sigmoid(inplace=inplace)
- def forward(self, x):
- return x * self.sigmoid(x)
- class CoordAtt(nn.Module):
- def __init__(self, inp, oup, reduction=32):
- super(CoordAtt, self).__init__()
- self.pool_h = nn.AdaptiveAvgPool2d((None, 1))
- self.pool_w = nn.AdaptiveAvgPool2d((1, None))
- mip = max(8, inp // reduction)
- self.conv1 = nn.Conv2d(inp, mip, kernel_size=1, stride=1, padding=0)
- self.bn1 = nn.BatchNorm2d(mip)
- self.act = h_swish()
- self.conv_h = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0)
- self.conv_w = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0)
- def forward(self, x):
- identity = x
- n, c, h, w = x.size()
- #c*1*W
- x_h = self.pool_h(x) # N*C*H*1
- x_w = self.pool_w(x).permute(0, 1, 3, 2) # N*C*1*W
- y = torch.cat([x_h, x_w], dim=2)
- #C*1*(h+w)
- y = self.conv1(y)
- y = self.bn1(y)
- y = self.act(y)
- x_h, x_w = torch.split(y, [h, w], dim=2)
- x_w = x_w.permute(0, 1, 3, 2)
- a_h = self.conv_h(x_h).sigmoid()
- a_w = self.conv_w(x_w).sigmoid()
- out = identity * a_w * a_h
- return out
如下图所示:

第②步:在yolo.py文件里的parse_model函数加入类名
首先找到yolo.py里面parse_model函数的这一行

然后把刚才加入的类CoordAtt添加到这个注册表里面

第③步:创建自定义的yaml文件
首先在models文件夹下复制yolov5s.yaml 文件,粘贴并重命名为 yolov5s_CA.yaml

接着修改 yolov5s_CA.yaml ,将CA模块加到我们想添加的位置。
注意力机制可以添加在backbone,Neck,Head等部分, 常见的有两种:一是在主干的 SPPF 前添加一层;二是将Backbone中的C3全部替换。
在这里我是用第一种:将 [-1,1,CA,[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_CA.yaml


然后运行yolo.py

找到CA这一层,就说明我们添加成功啦!
第⑤步:修改train.py中 ‘--cfg’默认参数
我们先找到 train.py 文件的parse_opt函数,然后将第二行‘--cfg’的 default改为'models/yolov5s_CA.yaml',然后就可以开始训练啦~

🚀三、添加C3_CA注意力机制方法(在C3模块中添加)
上面是单独加注意力层,接下来的方法是在C3模块中加入注意力层。
刚才也提到了,这个策略是将CA注意力机制添加到Bottleneck,替换Backbone中的所有C3模块。
(因为步骤和上面相同,所以接下来只放重要步骤噢~)
第①步:在common.py中添加CABottleneck和C3_CA模块
将下面的代码复制粘贴到common.py文件的末尾
- # CA
- class h_sigmoid(nn.Module):
- def __init__(self, inplace=True):
- super(h_sigmoid, self).__init__()
- self.relu = nn.ReLU6(inplace=inplace)
- def forward(self, x):
- return self.relu(x + 3) / 6
- class h_swish(nn.Module):
- def __init__(self, inplace=True):
- super(h_swish, self).__init__()
- self.sigmoid = h_sigmoid(inplace=inplace)
- def forward(self, x):
- return x * self.sigmoid(x)
- class CABottleneck(nn.Module):
- # Standard bottleneck
- def __init__(self, c1, c2, shortcut=True, g=1, e=0.5, ratio=32): # 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.ca=CoordAtt(c1,c2,ratio)
- self.pool_h = nn.AdaptiveAvgPool2d((None, 1))
- self.pool_w = nn.AdaptiveAvgPool2d((1, None))
- mip = max(8, c1 // ratio)
- self.conv1 = nn.Conv2d(c1, mip, kernel_size=1, stride=1, padding=0)
- self.bn1 = nn.BatchNorm2d(mip)
- self.act = h_swish()
- self.conv_h = nn.Conv2d(mip, c2, kernel_size=1, stride=1, padding=0)
- self.conv_w = nn.Conv2d(mip, c2, kernel_size=1, stride=1, padding=0)
- def forward(self, x):
- x1 = self.cv2(self.cv1(x))
- n, c, h, w = x.size()
- # c*1*W
- x_h = self.pool_h(x1)
- # c*H*1
- # C*1*h
- x_w = self.pool_w(x1).permute(0, 1, 3, 2)
- y = torch.cat([x_h, x_w], dim=2)
- # C*1*(h+w)
- y = self.conv1(y)
- y = self.bn1(y)
- y = self.act(y)
- x_h, x_w = torch.split(y, [h, w], dim=2)
- x_w = x_w.permute(0, 1, 3, 2)
- a_h = self.conv_h(x_h).sigmoid()
- a_w = self.conv_w(x_w).sigmoid()
- out = x1 * a_w * a_h
- # out=self.ca(x1)*x1
- return x + out if self.add else out
- class C3_CA(C3):
- # C3 module with CABottleneck()
- 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(*(CABottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
第②步:在yolo.py文件里的parse_model函数加入类名
在yolo.py的parse_model函数中,加入CABottleneck,C3_CA这两个模块

第③步:创建自定义的yaml文件
按照上面的步骤创建yolov5s_C3_CA.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_CA, [128]],
- [-1, 1, Conv, [256, 3, 2]], # 3-P3/8
- [-1, 6, C3_CA, [256]],
- [-1, 1, Conv, [512, 3, 2]], # 5-P4/16
- [-1, 3, C3_CA, [512]],
- [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
- [-1, 3, C3_CA, [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_CA.yaml,然后运行

这样就OK啦~
第⑤步:修改train.py中 ‘--cfg’默认参数
接下来的训练就和上面一样,不再叙述啦~
完结~撒花✿✿ヽ(°▽°)ノ✿
PS:今天训练的结果和比昨天CBAM的mAP增长了1.1,说明CA效果还是很不错的~