YOLOv5注意力机制改进实战:SE/ECA/CBAM/CA完整嵌入与消融实验

📅 2026/8/27 20:54:20
YOLOv5注意力机制改进实战:SE/ECA/CBAM/CA完整嵌入与消融实验
在毕业设计或课题实战中给 YOLOv5 加注意力机制已经成为非常常见的“创新点”套路。但很多同学卡在同一个地方网上的教程要么只讲原理不给代码要么给了代码却不知道插到 YOLOv5 哪个文件要么好不容易跑通了做消融实验时又不知道怎么对比才规范。这篇文章就来系统解决这几个问题。本文将完整拆解 SE、ECA、CBAM、CA 四种注意力机制的原理与实现并给出它们嵌入 YOLOv5 的完整修改步骤。无论是想快速出实验对比还是想弄懂每个模块的代码细节都可以照着本文操作。适合正在做目标检测方向毕业设计、竞赛项目或者准备把注意力机制用到工业质检、遥感检测等场景的读者。1. 为什么要在 YOLOv5 中加入注意力机制1.1 注意力机制解决什么问题YOLOv5 作为单阶段目标检测器使用 CSPDarknet 作为骨干网络提取特征。骨干网络在层层提取特征时默认对所有通道、所有空间位置平等对待。但真实场景中的目标往往只占据图像中的一小块区域且不同通道对目标的贡献差异很大。例如在 PCB 缺陷检测中焊点缺陷、划痕、短路等目标通常很小背景纹理又复杂如果网络对所有位置一视同仁很容易把注意力分配到无关区域导致小目标漏检。注意力机制的核心思想是让网络自动学习“哪些通道更重要”“哪些位置更需要关注”从而把有限的计算资源集中到关键信息上。从专业角度定义注意力机制是一种动态特征加权模块它根据输入特征图自身的信息学习出一组权重对特征图进行重标定。这个权重可以是通道维度的也可以是空间维度的或者两者结合。1.2 四种注意力机制的定位与区别很多初学者容易把 SE、ECA、CBAM、CA 搞混其实它们的侧重点完全不同SESqueeze-and-Excitation只关注通道注意力。它通过全局平均池化压缩空间信息再用两个全连接层学习通道权重。核心是“哪些通道重要”。ECAEfficient Channel Attention也是通道注意力但去掉了 SE 中的全连接层改用一维卷积来捕获跨通道交互。参数量更小训练更稳定。CBAMConvolutional Block Attention Module通道注意力和空间注意力串联使用。先告诉网络“看哪些通道”再告诉网络“看哪些位置”。CACoordinate Attention把通道注意力分解成两个方向高度方向和宽度方向分别捕获长距离依赖同时保留位置信息对小目标和细长目标更友好。简单总结SE 和 ECA 是纯通道注意力CBAM 是通道加空间CA 是通道加位置信息。1.3 注意力机制插入 YOLOv5 的常见位置YOLOv5 的网络结构分为 Backbone、Neck、Head 三部分。注意力机制可以插在不同位置效果不同插入 Backbone 末尾增强骨干网络提取到的语义特征对分类和定位都有帮助。插入 Neck 部分让特征金字塔在融合不同尺度特征时更关注有效信息有利于多尺度目标检测。插入每次下采样之后可以逐层重构特征但会带来更大的计算量。本文的示例统一采用“在关键 C3 模块之后插入注意力层”的方式这也是比较稳妥、容易出效果的方案。2. 环境准备与项目结构2.1 环境说明本文代码以 YOLOv5 常见主线版本为例不同小版本之间代码结构可能略有差异但修改思路是通用的。建议环境如下操作系统Windows 10/11 或 Ubuntu 20.04Python3.8 及以上PyTorch1.8 及以上2.0 亦可CUDA根据本机显卡驱动安装对应版本YOLOv5 源码从官方仓库获取如果 PyTorch 版本过低部分代码比如nn.SiLU可能不支持。建议使用较新的稳定版本。2.2 获取 YOLOv5 源码在项目目录下进入命令行执行git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt如果网络下载慢也可以手动从仓库下载压缩包再解压。安装依赖时建议使用虚拟环境避免污染系统的 Python 环境。requirements.txt 中包含 torch、torchvision、opencv-python、numpy 等常用依赖具体版本以仓库当前要求为准。2.3 项目目录结构和本文相关的核心文件如下yolov5/ ├── models/ │ ├── common.py # 公共模块SE/ECA/CBAM/CA 要加到这里 │ ├── yolo.py # 模型解析脚本需要修改自定义模块构造逻辑 │ ├── yolov5s.yaml # 模型配置文件修改网络结构 │ └── ... ├── data/ │ └── hyp.scratch-low.yaml # 超参数配置 ├── train.py # 训练脚本 ├── val.py # 验证脚本 └── detect.py # 推理脚本后面所有代码修改都围绕models/common.py、models/yolo.py、models/yolov5s.yaml三个文件展开。3. 四种注意力机制的模块实现3.1 SE 模块原理与代码SE 模块全称 Squeeze-and-Excitation Networks由 Momenta 团队在 2018 年提出。它包含两个核心操作Squeeze通过全局平均池化把一个通道的二维特征压缩成一个实数相当于得到一个通道描述符。Excitation通过两个全连接层先降维再升维学习各通道之间的非线性关系再经过 Sigmoid 激活得到每个通道的权重。最后将权重乘回原始特征图。SE 解决的问题是“不同通道的重要性不同但网络不知道该重视谁”。在models/common.py末尾添加以下代码class SE(nn.Module): def __init__(self, c1, ratio16): super().__init__() c_hidden max(c1 // ratio, 1) self.avg_pool nn.AdaptiveAvgPool2d(1) self.fc nn.Sequential( nn.Linear(c1, c_hidden, biasFalse), nn.ReLU(inplaceTrue), nn.Linear(c_hidden, c1, biasFalse), nn.Sigmoid() ) def forward(self, x): b, c, _, _ x.size() y self.avg_pool(x).view(b, c) y self.fc(y).view(b, c, 1, 1) return x * y.expand_as(x)参数说明c1输入特征图的通道数。ratio降维比例值越大中间层越窄参数量越小。实际中常见设置为 16。3.2 ECA 模块原理与代码ECA 全称 Efficient Channel Attention是 2020 年提出的高效通道注意力模块。它发现 SE 中的全连接层降维会破坏通道之间的直接对应关系因此改用一维卷积来捕获局部跨通道交互。一维卷积的卷积核大小k通常根据通道数自适应确定但在实际工程中直接设为 3 或 5 也能获得不错的效果。在models/common.py末尾添加class ECA(nn.Module): def __init__(self, c1, k_size3): super().__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) self.conv nn.Conv1d(1, 1, kernel_sizek_size, padding(k_size - 1) // 2, biasFalse) self.sigmoid nn.Sigmoid() def forward(self, x): y self.avg_pool(x) y self.conv(y.squeeze(-1).transpose(-1, 1)).transpose(-1, 1).unsqueeze(-1) y self.sigmoid(y) return x * y.expand_as(x)参数说明k_size一维卷积核大小控制跨通道交互的范围。默认 3 即可。3.3 CBAM 模块原理与代码CBAM 全称 Convolutional Block Attention Module是 2018 年提出的一种结合通道和空间两种注意力的模块。它在通道注意力中同时使用平均池化和最大池化增强了对目标细节的响应在空间注意力中则对通道维度做平均和最大操作生成空间权重图。CBAM 的结构可以概括为先通道注意力再空间注意力。模块内部包含两个子模块ChannelAttention和SpatialAttention。在models/common.py末尾添加class ChannelAttention(nn.Module): def __init__(self, c1, ratio16): super().__init__() c_hidden max(c1 // ratio, 1) self.avg_pool nn.AdaptiveAvgPool2d(1) self.max_pool nn.AdaptiveMaxPool2d(1) self.shared_mlp nn.Sequential( nn.Conv2d(c1, c_hidden, 1, biasFalse), nn.ReLU(inplaceTrue), nn.Conv2d(c_hidden, c1, 1, biasFalse) ) self.sigmoid nn.Sigmoid() def forward(self, x): avg_out self.shared_mlp(self.avg_pool(x)) max_out self.shared_mlp(self.max_pool(x)) return self.sigmoid(avg_out max_out) class SpatialAttention(nn.Module): def __init__(self, kernel_size7): super().__init__() 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) x_cat torch.cat([avg_out, max_out], dim1) return self.sigmoid(self.conv(x_cat)) class CBAM(nn.Module): def __init__(self, c1, ratio16, kernel_size7): super().__init__() self.channel_attention ChannelAttention(c1, ratio) self.spatial_attention SpatialAttention(kernel_size) def forward(self, x): x self.channel_attention(x) * x x self.spatial_attention(x) * x return x需要说明的是CBAM 中的通道注意力使用了两个并行的池化分支因此比 SE 更能捕捉目标的显著特征。空间注意力则通过7x7卷积生成空间权重默认卷积核大小设置为 7。3.4 CA 模块原理与代码CA 全称 Coordinate Attention是 2021 年提出的注意力模块。它把通道注意力分解成两个并行的方向高度方向和宽度方向。这种设计既能捕获长距离依赖又能保留精确的位置信息特别适合小目标检测、细长目标检测等任务。CA 模块的完整实现稍微复杂一些如下所示class CA(nn.Module): def __init__(self, c1, ratio32): super().__init__() self.pool_h nn.AdaptiveAvgPool2d((None, 1)) self.pool_w nn.AdaptiveAvgPool2d((1, None)) c_hidden max(1, c1 // ratio) self.conv1 nn.Conv2d(c1, c_hidden, 1, biasFalse) self.bn1 nn.BatchNorm2d(c_hidden) self.act nn.SiLU() self.conv_h nn.Conv2d(c_hidden, c1, 1, biasFalse) self.conv_w nn.Conv2d(c_hidden, c1, 1, biasFalse) def forward(self, x): b, c, h, w x.shape x_h self.pool_h(x).permute(0, 1, 3, 2) x_w self.pool_w(x) y torch.cat([x_h, x_w], dim2) y self.conv1(y) y self.bn1(y) y self.act(y) x_h, x_w torch.split(y, [h, w], dim2) 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() return x * a_h * a_w这里有两个细节需要强调nn.AdaptiveAvgPool2d((None, 1))表示在高度维度上保持原尺寸宽度维度压缩为 1得到形状为(b, c, h, 1)的特征图。nn.AdaptiveAvgPool2d((1, None))表示在高度维度压缩为 1宽度维度保持原尺寸得到形状为(b, c, 1, w)的特征图。如果当前 PyTorch 版本不支持None作为输出尺寸参数可以考虑将两处None替换为h和w但需要动态传入代码会复杂一些。本文写法在常见 PyTorch 1.8 版本中均可正常工作。3.5 模块统一放入 common.py以上就是四种注意力机制的完整代码。将它们全部复制到models/common.py文件末尾后还需要确认文件顶部已经引入了torch和torch.nn。YOLOv5 的 common.py 默认已经包含这些导入语句因此不需要额外添加。常见写法是在文件顶部有import torch import torch.nn as nn如果发现没有则手动补充导入。4. 将注意力机制嵌入 YOLOv54.1 修改 yolo.py 适配自定义模块YOLOv5 的模型解析脚本在models/yolo.py中。parse_model函数负责解析 yaml 配置文件、创建对应网络层。在旧版 YOLOv5 中构造网络层的代码通常是这样的m_ nn.Sequential(*(m(*args, ch[f]) for _ in range(n))) if n 1 else m(*args, ch[f])这行代码把 yaml 中配置的参数args放在前面把上一层的输出通道数ch[f]放在最后。对于标准卷积、C3 等模块没有问题但对 SE、ECA、CBAM、CA 这类“第一个参数必须是输入通道数”的模块来说参数顺序不对。因此需要修改为当遇到这些自定义注意力模块时把ch[f]放在第一个位置其余超参数紧跟其后。找到parse_model函数中类似下面的代码m_ nn.Sequential(*(m(*args, ch[f]) for _ in range(n))) if n 1 else m(*args, ch[f])将其替换为if m in {SE, ECA, CBAM, CA}: m_ nn.Sequential(*(m(ch[f], *args) for _ in range(n))) if n 1 else m(ch[f], *args) else: m_ nn.Sequential(*(m(*args, ch[f]) for _ in range(n))) if n 1 else m(*args, ch[f])不同版本 YOLOv5 的这行代码可能略有差异但核心思路一致让注意力模块的第一个参数接收输入通道数。如果你的自定义模块类名不同需要同步修改集合中的类名。修改完成后parse_model函数前面的from models.common import *会自动把 SE、ECA、CBAM、CA 导入进来不需要额外处理。4.2 构造注意力改进版 YOLOv5s 模型配置本文以 YOLOv5s 为例。复制一份models/yolov5s.yaml命名为models/yolov5s_attention.yaml然后在 backbone 中选择合适的插入位置。原始 yolov5s.yaml 的 backbone 大约长这样backbone: # [from, number, module, args] [[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/4 [-1, 1, Conv, [128, 3, 2]], # 1-P2/8 [-1, 3, C3, [128]], [-1, 1, Conv, [256, 3, 2]], # 3-P3/16 [-1, 6, C3, [256]], [-1, 1, Conv, [512, 3, 2]], # 5-P4/32 [-1, 9, C3, [512]], [-1, 1, Conv, [1024, 3, 2]], # 7-P5/64 [-1, 3, C3, [1024]], [-1, 1, SPPF, [1024, 5]], # 9 ]如果要在第 4 层 C3 之后加入 SE 注意力可以在对应位置插入一行[-1, 1, SE, [16]], # SE注意力ratio16插入后的配置如下backbone: [[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/4 [-1, 1, Conv, [128, 3, 2]], # 1-P2/8 [-1, 3, C3, [128]], [-1, 1, Conv, [256, 3, 2]], # 3-P3/16 [-1, 6, C3, [256]], [-1, 1, SE, [16]], # SE注意力ratio16 [-1, 1, Conv, [512, 3, 2]], # 5-P4/32 [-1, 9, C3, [512]], [-1, 1, CA, [32]], # CA注意力ratio32 [-1, 1, Conv, [1024, 3, 2]], # 7-P5/64 [-1, 3, C3, [1024]], [-1, 1, SPPF, [1024, 5]], # 9 ]上面演示了同时插入 SE 和 CA 的情况。在实际消融实验中每个实验通常只加一种注意力不要一开始就全部加进去。对应地不同注意力在 yaml 中的写法如下模块yaml 配置参数说明SE[-1, 1, SE, [16]]16 表示 ratioECA[-1, 1, ECA, [3]]3 表示 k_sizeCBAM[-1, 1, CBAM, [16, 7]]16 表示 ratio7 表示 kernel_sizeCA[-1, 1, CA, [32]]32 表示 ratio需要注意的是CA 的默认 ratio 为 32是四种模块中相对较大的这是为了保证中间隐藏层的通道数不会太小。4.3 验证模型结构配置好 yaml 文件后不要急着直接开训。先用 YOLOv5 自带的模型打印功能验证网络结构是否正确python models/yolo.py --cfg models/yolov5s_attention.yaml如果能正常打印出模型结构且没有报错说明注意力模块已经成功嵌入。如果打印结果中能看到 SE、ECA、CBAM、CA 等层并且各层输出通道与后续层匹配就说明配置正确。如果当前版本下该命令无法直接运行也可以直接执行一次训练观察日志开头解析出来的网络结构。5. 训练与评估5.1 准备数据集YOLOv5 训练需要 YOLO 格式的数据集。目录结构建议如下datasets/ custom/ images/ train/ val/ labels/ train/ val/然后在data/目录下创建custom.yamltrain: datasets/custom/images/train val: datasets/custom/images/val nc: 3 names: [class1, class2, class3]其中nc是类别数量names是类别名称需要按自己的数据集修改。5.2 训练命令以加入 CA 注意力机制为例训练命令如下python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data data/custom.yaml \ --cfg models/yolov5s_attention.yaml \ --weights yolov5s.pt \ --name yolov5s_ca参数说明--img输入图片尺寸常用 640。--batch批次大小根据显存调整。--epochs训练轮数。--data数据集配置。--cfg模型结构配置。--weights预训练权重可以使用yolov5s.pt或自定义权重。--name实验名称输出目录在runs/train/下。如果你的显存比较紧张可以适当减小 batch size或者配合--cache参数加速数据读取。5.3 评估命令训练完成后使用验证集评估模型python val.py \ --data data/custom.yaml \ --weights runs/train/yolov5s_ca/weights/best.pt \ --img 640也可以对测试图片或视频做推理python detect.py \ --source ./test_images \ --weights runs/train/yolov5s_ca/weights/best.pt \ --img 6406. 消融实验设计6.1 什么是消融实验消融实验Ablation Study在深度学习论文中非常常见。它的核心思想是通过控制变量逐个移除或添加某个模块验证该模块对最终结果是否真的有贡献。对于 YOLOv5 注意力机制改进来说消融实验要回答的问题通常是加了 SE 比不加 SE 提升了多少SE、ECA、CBAM、CA 哪一个对当前数据集最有效多个注意力模块叠加是否一定更好6.2 对照实验方案一个规范的消融实验至少要包含以下实验组实验编号模型配置目的1原始 YOLOv5sBaseline作为基准2YOLOv5s SE验证 SE 的贡献3YOLOv5s ECA验证 ECA 的贡献4YOLOv5s CBAM验证 CBAM 的贡献5YOLOv5s CA验证 CA 的贡献6YOLOv5s 多个注意力可选验证组合效果在训练这些实验时必须严格控制变量使用相同的数据集和数据划分方式。使用相同的训练超参数包括 epochs、batch size、学习率、优化器、图片尺寸等。使用相同的数据增强策略。在相同硬件、相同 PyTorch 版本下运行。设置相同的随机种子比如在训练命令中或代码开头固定torch.manual_seed(0)。6.3 指标记录表与结果分析技巧消融实验常关注的指标包括Precision精确率。Recall召回率。mAP0.5IoU 阈值为 0.5 时的平均精度。mAP0.5:0.95更严格的平均精度指标。Params模型参数量。GFLOPs计算量。FPS推理速度。实验结果可以用下面的模板记录注意下表中的数据只是示例用于说明表格形式模型PRmAP0.5mAP0.5:0.95Params(M)GFLOPsFPSYOLOv5s0.830.780.850.617.216.562YOLOv5s SE0.840.790.860.637.316.658YOLOv5s ECA0.840.780.860.627.216.560YOLOv5s CBAM0.850.800.870.647.416.854YOLOv5s CA0.850.810.880.657.316.756在分析结果时不要只写“加了注意力机制 mAP 提升了”要结合数据集特点说明原因。例如如果 CA 效果最好可能是因为你的数据集里小目标较多CA 保留了位置信息对定位更友好。如果 CBAM 提升不明显可能是因为空间注意力在大目标较多的场景中贡献有限。如果加了注意力后参数增加但精度下降要考虑是否出现了过拟合或者注意力插入位置不合理。7. 常见问题与排查7.1 模型结构相关报错问题现象常见原因解决思路AttributeError: module models.common has no attribute SE代码没有保存或类名与 yaml 中不一致检查 common.py 是否保存核对类名TypeError: __init__() takes 2 positional arguments but 3 were givenparse_model 中参数顺序没有修改检查 yolo.py 的构造行确保自定义模块使用m(ch[f], *args)RuntimeError: Given groups1, weight of size ...注意力模块输出通道与后续层不匹配检查 yaml 中插入位置后的通道数是否一致7.2 训练过程问题问题现象常见原因解决思路训练时 loss 为 nan学习率过大、数据异常、BN 参数问题降低学习率检查数据标注是否合法尝试更小 batch显存不足CUDA out of memorybatch size 过大或输入尺寸过大调小 batch size、降低 img 尺寸、使用梯度累积训练很慢数据集加载成为瓶颈使用--cache缓存数据到内存或使用 SSD 存储加载预训练权重时结构不匹配使用原始 yolov5s.pt 加载自定义结构忽略不匹配层或使用--weights yolov5s.pt自动裁剪不匹配层7.3 消融实验对比不合理的排查如果消融实验的结果很乱优先检查以下内容是否每个实验都使用了相同的数据划分如果数据划分不同指标差异会非常大。是否固定了随机种子不固定种子两次训练即使完全相同的配置也会有几小点的波动。是否使用了相同的超参数文件有些同学实验过程中手动改了学习率或 batch size实验就失去可比性。是否在同一个版本的 YOLOv5 代码上实验不同小版本的 baseline 本身就会有差异。8. 最佳实践与工程建议8.1 注意力模块插入位置建议不要把注意力层无脑插入每一层之后。常见建议是把注意力放在特征图分辨率适中的位置例如 P3/P4 层附近。分辨率太大时空间注意力计算量大分辨率太小时特征已经高度抽象通道注意力更有意义。每加一个注意力模块都要关注参数量和 GFLOPs 的变化。如果某项指标提升很少但计算量明显增加说明性价比不高。8.2 代码管理建议使用 git 管理实验代码。每加一个注意力模块创建单独的分支或目录避免实验之间互相覆盖。模型配置文件可以按实验命名比如yolov5s_se.yaml、yolov5s_ca.yaml权重输出目录也对应命名。8.3 实验规范固定随机种子是消融实验的基本要求。可以在训练脚本或入口代码中设置import random import numpy as np import torch random.seed(0) np.random.seed(0) torch.manual_seed(0)同时建议在实验记录表中把 GPU 型号、PyTorch 版本、Python 版本也记录下来方便复现。8.4 生产环境注意事项如果改进后的模型要部署到实际项目要关注推理速度FPS和模型体积而不仅仅是 mAP。注意力模块带来的精度提升如果以大幅度牺牲速度为代价在实时检测场景中可能不可接受。部署前还需要把模型导出为 ONNX、TensorRT 或 OpenVINO 等格式不同推理引擎对某些算子的支持程度不同例如 CA 模块中的AdaptiveAvgPool2d和permute在部分引擎中可能需要额外适配。9. 扩展思路如果你已经跑通了本文的完整流程下一步可以尝试将注意力机制插入到 Neck 部分的特征融合层观察与插入 Backbone 的区别。在 PCB 缺陷检测、遥感小目标检测、安全帽检测等真实数据集上进行实验验证模块的泛化能力。把两个不同的注意力模块串联或并联使用比如 ECA CA探索组合最优策略。将训练得到的权重导出为 ONNX并结合 TensorRT 做推理加速评估部署可行性。注意力机制是目标检测改进中性价比很高的一个方向但它不是万能的。真正有价值的改进一定是基于具体数据和任务特点通过规范的消融实验验证出来的。希望这篇文章能帮你少走一些弯路把更多时间花在真正有意义的设计和分析上。