拓冰建站拓冰建站
首页 / 资讯中心 / 正文

YOLOv11改进策略【注意力机制篇】| 添加SE、CBAM、ECA、CA等经典注意力机制

本文基于ultralytics 8.3.13源码核对其余 8.3.x 版本步骤一致。前言注意力机制是目标检测改进中上手门槛最低、见效最快的一类方法模块本身只有几十行代码插入网络后不需要改动其他任何结构属于典型的即插即用。这篇文章把四个最经典的注意力模块——SE、CBAM、ECA、CA——一次性讲清楚原理并给出一套通用的接入流程。学会这一篇后面遇到任何新注意力模块都能照方抓药。专栏地址YOLOv11改进专栏——持续更新各方向即插即用改进一、为什么要引入注意力机制来源注意力机制的设计灵感来自人类视觉——我们看一张图时会自动聚焦于重要区域、忽略背景噪声。SENetCVPR 2018首次将这一思想系统化为可插拔的网络模块此后 CBAMECCV 2018、ECACVPR 2020、CACVPR 2021等改进接连涌现。问题检测网络的 Backbone 对所有空间位置和所有通道一视同仁地提特征但不同通道对不同目标的贡献差异很大比如检测小目标时浅层细节通道更重要同时特征图上目标所在区域只占很小一部分大量背景特征会稀释有效信息。好处显式地给通道/空间位置分配权重让网络把容量集中在有判别力的特征上参数量和计算量增加极少ECA 甚至接近零参数精度换算比高即插即用——放在任意位置都能直接训练对比是做消融实验和论文创新点组合的理想素材。二、SESqueeze-and-Excitation 通道注意力2.1 SE的原理SESqueeze-and-Excitation只做一件事让每个通道自己学一个重要性系数。Squeeze对特征图沿空间维度做全局平均池化把(B, C, H, W)压成(B, C)得到每个通道的全局描述Excitation用两层全连接先降维C → C/r再升维回C中间 ReLU最后 Sigmoid学出C个 0~1 之间的权重权重乘回原特征图重要通道被放大冗余通道被抑制。降维比例r控制复杂度常用 16。2.2 SE的实现代码import torch import torch.nn as nn class SE(nn.Module): SE通道注意力 (Hu et al., CVPR 2018, SENet)。 def __init__(self, c1, reduction16): super().__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) self.fc nn.Sequential( nn.Linear(c1, c1 // reduction, biasFalse), nn.ReLU(inplaceTrue), nn.Linear(c1 // reduction, c1, biasFalse), nn.Sigmoid(), ) def forward(self, x): b, c, _, _ x.shape y self.avg_pool(x).view(b, c) # Squeeze: (B,C) y self.fc(y).view(b, c, 1, 1) # Excitation: (B,C,1,1) return x * y.expand_as(x)三、CBAM通道 空间串联注意力3.1 CBAM的原理CBAMConvolutional Block Attention Module, ECCV 2018认为只加权通道还不够空间上看哪里同样重要于是把两个子模块串联通道注意力在 SE 基础上加强——全局平均池化和全局最大池化双路并行共享同一个多层感知机降维→ReLU→升维两路输出相加过 Sigmoid 得到通道权重空间注意力沿通道维分别取平均值和最大值得到两张单通道图拼接后用一个 7×7 卷积压缩成空间权重图Sigmoid 后逐像素乘回特征图。⚠️ 一个很多人不知道的细节ultralytics 框架其实已经内置了 CBAM在ultralytics/nn/modules/conv.py里但它是简化版——内置的ChannelAttention只有平均池化 单层不降维卷积没有论文中的双池化共享 MLP 结构。做毕业论文/期刊实验时建议使用下面的论文原版实现并在文中注明出处创新性和严谨性都更好。3.2 CBAM的实现代码注意为避免与框架内置的ChannelAttention / SpatialAttention / CBAM重名冲突这里的类名带Att后缀。class ChannelAtt(nn.Module): 通道注意力双池化 共享MLPCBAM论文原版。 def __init__(self, c1, ratio16): super().__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) self.max_pool nn.AdaptiveMaxPool2d(1) self.mlp nn.Sequential( nn.Conv2d(c1, c1 // ratio, 1, biasFalse), nn.ReLU(inplaceTrue), nn.Conv2d(c1 // ratio, c1, 1, biasFalse), ) self.sigmoid nn.Sigmoid() def forward(self, x): out self.mlp(self.avg_pool(x)) self.mlp(self.max_pool(x)) return self.sigmoid(out) class SpatialAtt(nn.Module): 空间注意力通道均值最大值拼接后7x7卷积。 def __init__(self, kernel_size7): super().__init__() assert kernel_size in (3, 7) self.conv nn.Conv2d(2, 1, kernel_size, paddingkernel_size // 2, biasFalse) self.sigmoid nn.Sigmoid() def forward(self, x): avg_out torch.mean(x, dim1, keepdimTrue) max_out, _ torch.max(x, dim1, keepdimTrue) return self.sigmoid(self.conv(torch.cat([avg_out, max_out], dim1))) class CBAMAtt(nn.Module): CBAM (Woo et al., ECCV 2018)先通道后空间。 def __init__(self, c1, ratio16, kernel_size7): super().__init__() self.ca ChannelAtt(c1, ratio) self.sa SpatialAtt(kernel_size) def forward(self, x): x self.ca(x) * x # 通道重加权 return self.sa(x) * x # 空间重加权返回完整特征图如果只想快速跑通也可以直接注册使用框架内置的CBAM类见第六步。四、ECA无降维的高效通道注意力4.1 ECA的原理ECAEfficient Channel Attention, CVPR 2020指出了 SE 的一个问题降维全连接是有害的——它切断了通道与其权重之间的直接对应关系。ECA 的做法极其简洁同样先全局平均池化但不做任何降维让C个权重经过一个一维卷积kernel 大小为k让相邻通道之间交换信息k不用手调按公式k |log₂(C)/γ b/γ|自适应计算γ2, b1取最近的奇数。参数量几乎为零只有一个 k×k 的一维卷积核效果却常与 SE 持平甚至更好。4.2 ECA的实现代码import math class ECA(nn.Module): ECA高效通道注意力 (Wang et al., CVPR 2020)。 def __init__(self, c1, k_sizeNone): super().__init__() if k_size is None: # 自适应核大小: k |log2(C)/2 1/2|取奇数 t int(abs((math.log(c1, 2) 1) / 2)) k_size t if t % 2 else t 1 self.avg_pool nn.AdaptiveAvgPool2d(1) self.conv nn.Conv1d(1, 1, kernel_sizek_size, paddingk_size // 2, biasFalse) self.sigmoid nn.Sigmoid() def forward(self, x): y self.avg_pool(x) # (B,C,1,1) y self.conv(y.squeeze(-1).transpose(-1, -2)) # (B,1,C)跨通道一维卷积 y y.transpose(-1, -2).unsqueeze(-1) # (B,C,1,1) return x * self.sigmoid(y).expand_as(x)五、CA方向感知的坐标注意力5.1 CA的原理SE 和 ECA 都只建模了通道关系丢失了位置信息CBAM 用大卷积核补位置信息但感受野受限。CACoordinate Attention, CVPR 2021的思路是把全局池化拆成方向沿高度方向池化得(H,1)、沿宽度方向池化得(1,W)分别保留某一方向上的长程依赖两路拼接 → 1×1 卷积降维 → BN HardSwish → 再拆开各自经 1×1 卷积恢复维度后 Sigmoid得到高度、宽度两组权重两组权重分别与特征图相乘相当于给每个像素位置一个行权重 × 列权重的组合既建模通道又精确定位。对小目标和长条形目标尤其友好是检测任务里涨点比较稳的一个模块。5.2 CA的实现代码class CA(nn.Module): 坐标注意力 Coordinate Attention (Hou et al., CVPR 2021)。 def __init__(self, c1, reduction32): super().__init__() self.pool_h nn.AdaptiveAvgPool2d((None, 1)) # 沿宽度池化 - (H,1) self.pool_w nn.AdaptiveAvgPool2d((1, None)) # 沿高度池化 - (1,W) mip max(8, c1 // reduction) self.conv1 nn.Conv2d(c1, mip, 1, 1, 0) self.bn1 nn.BatchNorm2d(mip) self.act nn.Hardswish(inplaceTrue) self.conv_h nn.Conv2d(mip, c1, 1, 1, 0) self.conv_w nn.Conv2d(mip, c1, 1, 1, 0) def forward(self, x): identity x n, c, h, w x.size() x_h self.pool_h(x) # (N,C,H,1) x_w self.pool_w(x).permute(0, 1, 3, 2) # (N,C,W,1) y torch.cat([x_h, x_w], dim2) # (N,C,HW,1) 共享编码 y self.act(self.bn1(self.conv1(y))) x_h, x_w torch.split(y, [h, w], dim2) x_w x_w.permute(0, 1, 3, 2) # (N,C,1,W) a_h torch.sigmoid(self.conv_h(x_h)) # 行权重 a_w torch.sigmoid(self.conv_w(x_w)) # 列权重 return identity * a_w * a_h六、添加步骤以下三步对所有即插即用模块通用以ultralytics 8.3.13为例路径相对你的项目根目录。1. 修改ultralytics/nn/modules/block.py把第二~五节中的模块类本文用到SE、ChannelAtt、SpatialAtt、CBAMAtt、ECA、CA整体粘贴到文件末尾即可。2. 修改ultralytics/nn/modules/__init__.py两处改动# ① 在 from .block import ( ... ) 列表中追加导出名 from .block import ( ... CA, CBAMAtt, ECA, SE, ) # ② 在 __all__ 元组末尾追加同样的名字 __all__ ( ... CA, CBAMAtt, ECA, SE, )3. 修改ultralytics/nn/tasks.py① 在文件顶部的from ultralytics.nn.modules import (列表里加上CBAM, CBAMAtt, CA, ECA, SECBAM是框架内置的一并注册方便对比实验注意导入的名字必须与第2步导出的名字一一对应from ultralytics.nn.modules import ( AIFI, ... CA, # 新增 CBAM, # 新增内置 CBAMAtt, # 新增 ECA, # 新增 SE, # 新增 )② 在parse_model()函数里找到最后一个分支elif m is CBFuse:在它之后、else:之前插入elif m in {SE, CBAM, ECA, CA, CBAMAtt}: # 注意力模块只需输入通道数 args.insert(0, ch[f])这一行的含义这类模块的第一个参数永远是输入通道数ch[f]其余参数如reduction、kernel_size由 yaml 中可选地给出。改完记得CBAMAtt若未粘贴到 block.py 就不要出现在集合里。七、yaml模型文件新建yolo11n-attention.yaml文件名里的n会自动匹配 scales 中的 n 尺度。这里把注意力加在最深层 C2PSA 之后新增层后所有层号都要顺移Concat 和 Detect 引用的层号已同步更新可直接复制运行# Ultralytics YOLO11n 注意力机制 (基于官方yolo11.yaml修改) nc: 80 # 类别数按你的数据集修改 scales: # [depth, width, max_channels] n: [0.50, 0.25, 1024] s: [0.50, 0.50, 1024] m: [0.50, 1.00, 512] l: [1.00, 1.00, 512] x: [1.00, 1.50, 512] backbone: # [from, repeats, module, args] - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 2, C3k2, [256, False, 0.25]] # 2 - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 2, C3k2, [512, False, 0.25]] # 4 - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 2, C3k2, [512, True]] # 6 - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 - [-1, 2, C3k2, [1024, True]] # 8 - [-1, 1, SPPF, [1024, 5]] # 9 - [-1, 2, C2PSA, [1024]] # 10 - [-1, 1, SE, []] # 11 ★新增注意力层换成CBAM/ECA/CA/CBAMAtt只需改名字 head: - [-1, 1, nn.Upsample, [None, 2, nearest]] # 12 - [[-1, 6], 1, Concat, [1]] # 13 cat backbone P4 - [-1, 2, C3k2, [512, False]] # 14 - [-1, 1, nn.Upsample, [None, 2, nearest]] # 15 - [[-1, 4], 1, Concat, [1]] # 16 cat backbone P3 - [-1, 2, C3k2, [256, False]] # 17 (P3/8-small) - [-1, 1, Conv, [256, 3, 2]] # 18 - [[-1, 14], 1, Concat, [1]] # 19 cat head P4 - [-1, 2, C3k2, [512, False]] # 20 (P4/16-medium) - [-1, 1, Conv, [512, 3, 2]] # 21 - [[-1, 11], 1, Concat, [1]] # 22 cat head P5原指向10现改为11 - [-1, 2, C3k2, [1024, True]] # 23 (P5/32-large) - [[17, 20, 23], 1, Detect, [nc]] # 24 Detect(P3, P4, P5)想尝试其他插入位置把- [-1, 1, SE, []]移到任意一层后面即可如第 2 层后的浅层特征、或 Neck 的 17 层 P3 输出后注意同步修正后续所有层号以及[[17, 20, 23], ...]这三个引用。八、成功运行结果命令行方式yolo detect train modelyolo11n-attention.yaml datacoco128.yaml epochs100 imgsz640 batch16Python 方式推荐能继承预训练权重收敛更快from ultralytics import YOLO model YOLO(yolo11n-attention.yaml).load(yolo11n.pt) # 自动加载结构匹配的层 model.train(datacoco128.yaml, epochs100, imgsz640, batch16)训练正常启动后终端会打印模型摘要能看到模型名变成了YOLO11n-attentionUltralytics 8.3.13 Python-3.11 torch-2.x CPU from n params module arguments 8 -1 1 346112 ultralytics.nn.modules.block.C3k2 [256, 256, 1, True] 9 -1 1 164608 ultralytics.nn.modules.block.SPPF [256, 256, 5] 10 -1 1 249728 ultralytics.nn.modules.block.C2PSA [256, 256, 1] 11 -1 1 8192 ultralytics.nn.modules.block.SE [256] 12 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, nearest] ... 24 [17, 20, 23] 1 464912 ultralytics.nn.modules.head.Detect [80, [64, 128, 256]] YOLO11n-attention summary: 326 layers, 2,632,272 parameters, 2,632,256 gradients, 6.6 GFLOPs Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 2/2 0G 3.948 4.064 5.077 4 320 Optimizer stripped from runs\detect\train\weights\last.pt, 5.4MB Validating runs\detect\train\weights\best.pt... YOLO11n-attention summary (fused): 245 layers, 2,590,539 parameters, 6.3 GFLOPs重点看第11层模块从C2PSA变成了我们注册的SE参数量 8192说明 yaml 解析和模块注册全部生效第 22 层的Concat引用也从[−1, 10]变成了[−1, 11]与修改后的 yaml 一致。✅ 本文代码已在ultralytics 8.3.13 Python 3.11环境完整实测模型正常构建、第 11 层为SE(256, 16)新增参数量恰为(256/16 × 256) × 2 8192总参数量 2,624,080 → 2,632,272小样本训练正常收敛并导出权重CBAM / ECA / CA / CBAMAtt 在 yaml 中互换均验证通过。欢迎在评论区晒出你在自己数据集上的对比结果。四种模块的实测开销如下640×640 输入模型Params(M)GFLOPs参数量增量说明YOLO11n 基线2.626.6-官方结构SE2.636.68,192通道注意力即插即用CBAM2.696.765,890通道空间双重加权ECA2.626.65近乎零成本的涨点方案CA2.636.66,680方向感知小目标友好提示以上为 ultralytics 8.3.13 实测值mAP 消融请在你自己的数据集上完成——同一模块在不同任务上的表现可能相反这正是消融实验的价值所在。九、总结这篇介绍了 SE、CBAM、ECA、CA 四个经典注意力模块的原理与代码并沉淀出一套贴代码 → 导出 → 注册 → 改yaml的四步通用流程——后续任何即插即用模块都是这个套路。插入位置和模块选择没有万能答案一定要在自己的数据集上做消融一般小目标多优先试 CA/CBAM算力紧张优先试 ECA。下一篇预告【YOLOv11改进策略】SimAM 无参数注意力——连权重都不用学的注意力了解一下。觉得有帮助的话点赞收藏关注三连支持一下评论区欢迎交流你的实验结果~
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门