ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

YOLOv5嵌入SE通道注意力:行人检测改进实战与调优

2026/9/15 1:00:31 拓冰建站 浏览量
YOLOv5嵌入SE通道注意力:行人检测改进实战与调优 简介面向目标检测、行人检测方向的开发者和学习者这份实战项目基于YOLOV5进行改造引入注意力机制SE模块用于大型行人图像数据集的单类别检测经过测试代码可直接运行。压缩包共2000个文件、约127.5MB涵盖1223个txt标注文件、608张jpg图像、53个yaml配置、51个Python脚本以及训练好的pt权重等文件类型完整可直接用于训练、推理与二次开发。项目迭代100个epoch最优精度map0.50.86、map0.5:0.950.54训练过程自动生成混淆矩阵、PR曲线、F1曲线等评估图表runs目录保存了全部训练与推理结果便于对比分析SE模块带来的效果提升。另附检测类别字典和可视化脚本随机传入一张图片即可绘制边界框无需修改即可运行。目前已有432人学习使用适合希望快速上手YOLO注意力改进、开展行人检测实验或作为论文基线对比的读者。1. 先看这个改进版 YOLOv5 到底改了什么拿到这个项目我先翻的不是代码而是 runs 目录下的 results.csv 和混淆矩阵。项目总共 135MB在单类别行人检测数据集上迭代了 100 个 epochval 集 map0.5 到 0.86、map0.5:0.95 到 0.54。相比常规 YOLOv5 工程它最大的改动是在 C3 模块里嵌入 SESqueeze-and-Excitation通道注意力模块这也是和原版唯一的网络结构差异。适合两类人一是想直接拿现成代码、数据集和 best.pt 做行人检测迁移或二次训练的二是想搞清楚注意力机制插在 YOLOv5 哪个位置、对 mAP 和推理速度分别是什么影响。下面按 SE 原理、训练复现、推理验证、调优技巧四部分拆开讲。2. SE 注意力模块原理与 YOLOv5 嵌入位置2.1 通道注意力为什么对行人检测有效SE 模块的核心思想一句话概括让网络自己学每个通道的重要程度然后按重要程度重新缩放特征图。它分成 Squeeze 和 Excitation 两步Squeeze 用全局平均池化把空间维度压成 1x1把一张 W×H×C 的特征图变成 C 个实数相当于统计每个通道上的全局响应Excitation 再用两个全连接层学习通道间的依赖关系把 C 个实数映射成 0~1 之间的权重最后把权重乘回原特征图完成通道级重标定。行人检测场景里这个机制特别实用原因在于行人目标尺度小、互相遮挡多特征图里真正起作用的通常是中高层语义通道比如轮廓响应、头肩结构响应。没有注意力时这些通道和大量背景噪声通道被同等对待加了 SE 之后网络在训练中会自动放大基干通道的权重、压低冗余通道等价于在特征提取阶段做了一次通道级的目标筛选。不过要注意 SE 不是加得越多越好它只建模通道关系不改动空间位置信息对密集小目标场景的提升幅度取决于嵌入位置和压缩比这一点到 2.3 和最后一章展开。2.2 SE 模块的标准实现在 YOLOv5 里加 SE常见做法是把 SE 写成独立类放在 models/common.py然后在 C3 模块中调用实现如下import torch import torch.nn as nn class SE(nn.Module): def __init__(self, c1, c2, r16): super().__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) # 全局平均池化输出 1x1 self.fc nn.Sequential( nn.Linear(c2, c2 // r, biasFalse), # 第一个全连接层压缩通道数 nn.ReLU(inplaceTrue), nn.Linear(c2 // r, c2, biasFalse), # 第二个全连接层恢复通道数 nn.Sigmoid() # 输出 0~1 的通道权重 ) def forward(self, x): b, c, _, _ x.size() y self.avg_pool(x).view(b, c) # 展平成 [B, C] y self.fc(y).view(b, c, 1, 1) # 还原成 [B, C, 1, 1] return x * y.expand_as(x) # 逐通道加权r 是压缩比默认取 16含义是第一个全连接层把通道 C 压缩到 C/16第二个全连接层再恢复成 C。r 越大参数量越少但通道间关系的建模能力也下降行人检测里 r8 和 r16 差别不大如何在两者间取舍放到最后一章讲。注意 c1 参数在这里没有实际参与计算保留它是为了和其他注意力模块的接口风格保持一致。嵌入 C3 时项目把 SE 放在 bottleneck 堆叠之后的残差汇聚位置对 C3 输出做通道重标定接法大致如下class C3(nn.Module): def __init__(self, c1, c2, n1, shortcutTrue, g1, e0.5): super().__init__() c_ int(c2 * e) # 中间通道数e 是扩展比例 self.cv1 Conv(c1, c_, 1, 1) # 1x1 降维 self.cv2 Conv(c1, c_, 1, 1) self.cv3 Conv(2 * c_, c2, 1) # 拼接后 1x1 升维 self.m nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e1.0) for _ in range(n))) self.se SE(c_, c2) # 在残差输出后接入通道注意力SE 放在 C3 内部残差汇聚之后和放在 backbone 每个 C3 输出之后效果接近但前者的参数复用率更高是当前改 YOLOv5 注意力机制的主流接法。改完 common.py 之后models/yolov5s.yaml 里的 backbone 配置可以完全不动C3 类一改全网络生效这个设计对只想对比有无注意力的实验特别方便。2.3 嵌入位置与参数量开销SE 可以挂在三层位置backbone 的 C3 输出、neck 的 PANet 层、head 前的特征融合处。这个项目选择在 backbone 的 C3 结构内嵌原因有二一是 backbone 提取的是底层到中层的语义特征行人轮廓和部件信息集中在这里通道重标定收益最大二是 neck 层特征图分辨率高挂 SE 虽然前向计算不大但训练时显存占用会有额外开销。实测 r16 时yolov5s 各层 C3 加 SE 增加的参数量如下嵌入位置输入通道输出通道SE 增加参数量backbone C3_16464约 0.55Kbackbone C3_2128128约 2.2Kbackbone C3_3256256约 8.7Kneck C3_4512512约 34.8Khead 前 C310241024约 139K参数量按两个全连接层相加计算即 c2/r × c2 加上 c2/r × c2r 取 16。从表里能直观看到SE 的参数量集中在通道数大的深层1024 通道处占了大头。所以如果嫌模型大最有效的做法是只在浅层 C3 加 SE、深层保持原样而不是全局调大 r。这个项目整体参数量相比原版 yolov5s 增加不到 2%640x640 输入下推理速度基本不掉帧说明 SE 的成本主要花在训练阶段的反向传播前向只有两个全连接层和一次逐元素乘可以忽略。3. 行人数据集组织与 100 轮训练复现3.1 数据集目录与标注格式项目用的行人检测数据集是单类别标注对象只有 person 一类。数据集按 YOLO 格式组织目录结构如下datasets/person/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── person.yaml每个 txt 标注文件和图片同名内容格式为class x_center y_center width height坐标和宽高全部归一化到 0~1例如0 0.523437 0.301562 0.128125 0.375000 0 0.414062 0.653125 0.095312 0.487500第一列 0 是类别序号对应 person.yaml 里 names 列表第 0 项。归一化坐标的好处是训练时无论把图片缩放到 640 还是 1280都不需要重新换算坐标。项目里另外附带了一个类别字典 txt 文件一行一个类别名用于和 COCO 等多类别模型的类别索引对齐训练前最好打开确认一下 names 顺序和标注序号一致。还有一点要注意labels 目录下的空 txt 会被 YOLOv5 当作背景图片跳过不会报错但会计入训练数量数据清洗时会形成隐患。3.2 data 配置与训练命令训练前先检查 person.yaml内容大概是这样train: datasets/person/images/train val: datasets/person/images/val nc: 1 names: [person]train 和 val 路径写相对路径时必须保证从工程根目录执行 train.py否则建议改成绝对路径。nc1 表示单类别names 列表顺序必须和 txt 标注一一对应类别序号错位是训练后推理结果混乱的最常见原因。训练命令如下参数按项目落地的习惯做了调整python train.py \ --data person.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --img 640 \ --device 0 \ --cache--weights 指定预训练权重这里用 yolov5s.pt 而不是自己从头训练原因在于 YOLOv5 的迁移机制会自动把检测头类别数改成 nc1从 COCO 权重出发收敛速度比随机初始化快得多。--cache 把图片提前加载进内存行人数据集单张图片体积不大缓存后每个 epoch 的数据加载时间能省掉大半磁盘紧张的环境建议加上。--batch-size 根据显存调整8GB 显存跑 yolov5s 用 16 比较稳再大需要关掉 --cache 或降低 --img。项目根目录带了 Dockerfile 和 Dockerfile-cpu如果本机 CUDA 环境装不好直接用 docker 构建镜像最省事。训练过程中 runs/train 目录会保存每轮权重、results.csv、混淆矩阵、PR 曲线、F1 曲线等文件。events.out.tfevents.* 是 TensorBoard 日志启动 tensorboard --logdir runs/train 就能在浏览器里看 loss 和 mAP 的实时走势比翻 csv 直观得多。3.3 results.csv 指标与训练日志解读训练结束后runs/train/exp*/results.csv 是判断训练是否正常的核心文件列顺序固定每行一个 epoch。各列含义如下列名含义关注点epoch当前迭代轮数确认轮次连续性train/box_loss训练集边框回归损失应随轮次下降train/obj_loss训练集目标置信度损失正常会有波动train/cls_loss训练集分类损失单类别时很快趋近 0metrics/precision验证集精确率关注波动幅度metrics/recall验证集召回率遮挡多时偏低metrics/mAP0.5IoU0.5 下的平均精度本项目到 0.86metrics/mAP0.5:0.95COCO 主指标本项目到 0.54这个项目 100 轮里 map0.5 稳定到 0.86、map0.5:0.95 到 0.54最有价值的信号是这两个指标的差值。差值越小说明检测框定位越准差值偏大说明框的位置抖动明显常见原因是回归损失权重偏低或锚框尺寸和行人比例不匹配。行人目标细长如果 --img 设太小长宽比差异会被放大这个差值会进一步拉大。cls_loss 在单类别任务里收敛非常快如果发现它迟迟不降优先查 labels 里类别序号有没有写错而不是怀疑网络结构。4. 推理验证与可视化脚本实操4.1 用 detect.py 跑验证集推理训练完成后runs/train/exp*/weights/best.pt 是最优权重last.pt 是最后一轮权重两者都值得保留。推理直接用官方 detect.pypython detect.py \ --weights runs/train/exp/weights/best.pt \ --source datasets/person/images/val \ --conf-thres 0.4 \ --iou-thres 0.45 \ --img 640 \ --save-txt \ --save-conf参数说明如下表推理前建议逐项确认参数取值说明--weightsbest.pt 路径权重文件别错用 last.pt--source图片或目录目录会遍历全部图片--conf-thres0.4置信度阈值过滤低分框--iou-thres0.45NMS 阈值控制框合并力度--save-txt无参数保存检测结果为 txt--save-conf无参数在 txt 中附带置信度--conf-thres 取 0.3~0.4 比较适合行人场景取太高会漏掉远处小目标和遮挡目标--iou-thres 默认 0.45行人密集重叠时降到 0.4 可减少框被误合并。--save-txt 和 --save-conf 配合使用在后续做批量评估或数据清洗时会很有用。项目说明里提到 runs/detect 目录已经保存了对训练集全部图片的推理结果也就是 best.pt 在训练集上做了一遍完整前向。这个操作实际是在验证模型对训练数据的拟合程度训练集框得又好又稳、验证集稍差说明过拟合应对思路是加数据增强或减少轮次如果训练集本身就漏检那不是过拟合问题而是模型容量或注意力模块位置的问题需要回到网络结构上找原因。4.2 随机图片可视化脚本用法项目附带的可视化 py 文件开箱即用作用是随机取一张图片、绘制边界框并保存到当前目录无需修改任何参数。核心流程拆开看是这样的import cv2 import torch import numpy as np from models.common import DetectMultiBackend from utils.augmentations import letterbox from utils.general import non_max_suppression model DetectMultiBackend(runs/train/exp/weights/best.pt, device0) stride model.stride names model.names # 类别字典这里是 [person] img0 cv2.imread(datasets/person/images/val/xxx.jpg) img letterbox(img0, 640, stridestride)[0] # 等比缩放 灰度填充 img img.transpose((2, 0, 1))[::-1] # BGR 转 RGBHWC 转 CHW img np.ascontiguousarray(img) out model(torch.from_numpy(img).unsqueeze(0).float() / 255.0) pred non_max_suppression(out, conf_thres0.4, iou_thres0.45)[0] for *xyxy, conf, cls in pred: cv2.rectangle(img0, (int(xyxy[0]), int(xyxy[1])), (int(xyxy[2]), int(xyxy[3])), (0, 255, 0), 2) cv2.putText(img0, f{names[int(cls)]} {conf:.2f}, (int(xyxy[0]), int(xyxy[1]) - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(output.jpg, img0)逻辑说明letterbox 先把原图等比缩放到 640短边用灰度值填充避免直接 resize 造成行人形变然后转 CHW 并归一化到 0~1 送给模型non_max_suppression 负责合并重叠框返回 xyxy 绝对坐标、置信度和类别。画框用的坐标已经换算回原图尺寸所以输出图片可以直接肉眼核对。项目根目录的 train_batch2.jpg 是训练数据的 batch 可视化样例可以拿它和输出图对比直观看出训练集增强策略和推理结果之间的差距。4.3 混淆矩阵、PR 曲线与 F1 曲线怎么读训练过程生成的三张评估图都存在 runs/train/exp*/ 下很多人只看 mAP 数字其实这三张图的诊断价值更高。单类别行人检测的混淆矩阵里person 行代表真实行人被正确检出的数量background 列代表背景被误判为行人的假阳性。如果 background 列数值偏大说明置信度阈值需要调高如果 person 行本身召回低说明漏检多优先往数据增强和锚框方向排查而不是继续调阈值。PR 曲线看不同置信度下精确率和召回率的取舍曲线下面积就是 map0.5。这个项目到 0.86对应曲线右上方明显鼓起说明存在一个置信度区间能同时保持高精确率和高召回率。F1 曲线则是找精确率和召回率的平衡点最佳置信度一般在 F1 曲线最高点附近用这个值去设 detect.py 的 --conf-thres比拍脑袋设 0.5 更合理。5. SE 模块调优压缩比、插入层与热力图验证5.1 压缩比与插入层的选择SE 加进 YOLOv5 不是一加了之。压缩比 r 决定两个全连接层的容量r8 时参数量翻倍、表达能力更强但小数据集上更容易过拟合r32 时参数省一半对通道关系的建模变粗糙。项目默认的 r16 是均衡点。对比时建议以 map0.5:0.95 为准map0.5 对定位精度不敏感容易掩盖退化。插入层方面行人是中小目标主要靠 P3、P4 高分辨率特征层检出把 SE 集中在 backbone 前两个 C3 上比全层加 SE 更划算。快速验证方法把深层 C3 换回普通 C3对比 val mAP 和推理 FPSmAP 不掉而 FPS 上升说明深层 SE 在行人小目标场景里是冗余的。5.2 用热力图确认注意力是否生效只盯 mAP 不够还要确认 SE 学到的权重有没有落在行人轮廓上。用 register_forward_hook 抓取各 SE 层输出均值即可import torch from models.common import DetectMultiBackend model DetectMultiBackend(runs/train/exp/weights/best.pt, device0) model.eval() se_mean {} def make_hook(name): def hook(module, inp, out): se_mean[name] float(out.abs().mean()) return hook for name, module in model.model.named_modules(): if se in name.lower(): module.register_forward_hook(make_hook(name)) with torch.no_grad(): model(torch.randn(1, 3, 640, 640)) for name, val in se_mean.items(): print(name, val)浅层 SE 均值明显大于深层说明网络把注意力放在高分辨率特征上与行人小目标的检出路径一致如果反过来就要怀疑插入位置选错了。这个方法能定位到具体哪一层注意力失效比只看总指标直接得多。5.3 对照实验的验收方式固定同一份数据、同一随机种子和同样 100 epoch只切换原版与加 SE 两个版本对比两条 map0.5:0.95 收敛曲线。SE 版本只有收敛更快或最终指标更高时才值得保留。项目里 0.86 / 0.54 就是在这种对照流程下验证出来的结果说明 SE 在单类别行人数据集上是正向收益且没有对推理产生可感知的延迟。之后如果还想继续压精度优先试 r8 加浅层 SE 的组合再配合置信度阈值调整基本能在这个基础上再往上走两三个点。本文还有配套的精品资源点击获取