news 2026/9/11 15:54:34

YOLOv7半监督训练源码解析:伪标签接入与训练调优

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv7半监督训练源码解析:伪标签接入与训练调优

简介:基于半监督学习训练YOLOv7的源码包,面向有一定深度学习基础、希望利用大规模未标注数据提升目标检测性能的开发者与研究人员。资源围绕伪标签生成、联合训练、一致性正则化、分阶段训练等典型半监督策略,提供了一套从数据加载、模型构建、损失函数到训练循环的完整可运行代码,非常适合用于学术实验、课程设计或工程落地参考。压缩包共34个文件,以26个Python脚本为主体,涵盖head/voc标注转换、kmeans锚框计算、semi_dataloader半监督数据读取、semi_training半监督训练、模型评估及预测等模块;另有4个xml配置、2个txt说明、1张示例jpg和工程配置文件,整体仅515KB,目录结构清晰、便于按需查阅。已有674人学习下载。通过阅读和运行源码,可深入理解伪标签的生成与更新机制、半监督损失函数的设定、如何结合有标签与无标签数据协同训练YOLOv7,并可直接基于该代码改造自己的检测任务,是学习和实践半监督目标检测的实用参考。

1. 这份源码要解决的,不只是把 yolov7 跑通

标注成本高到无法覆盖所有现场类别时,把未标注数据也塞进训练是最直接的止损思路。但市面上以“半监督学习训练 YOLOv7”为卖点的源码包,解压后往往是train.pypseudo_label.py加一组 shell 脚本,真正决定效果的并不是模型结构加了什么,而是伪标签有没有回到损失函数。很多包的实现止步于“把未标注图放进 dataloader”,训练 loss 照常下降,验证 mAP 却稳定在监督基线上不动。原因是检测输出是集合预测,伪框不经过 anchor assigner 就不会形成匹配目标,更谈不上梯度反传。这篇按伪标签接入、数据流、阈值与验证四条线,把这类源码里常见的半监督训练改法理清楚,给出一套从解压到复现 mAP 提升的完整路径。

2. 半监督学习在 YOLOv7 里的核心前提:伪标签必须走完梯度通路

2.1 为什么混入未标注图不叫半监督

半监督学习落到目标检测,最常用的不是一致性正则,而是伪标签(pseudo label)。通俗讲:先让一个已训练的 teacher 模型对无标注图做推理,输出高置信度框,把它当成标签,再和真实标注混合训练 student。这里有一个容易忽略的前提:YOLOv7 的损失由边界框回归、cls、obj 三部分组成,所有目标都必须先经过正负样本分配,把预测框和真值框做形状与 IoU 匹配,才能进入各自损失项。伪标签如果只是打印到终端、写进日志,或者像分割任务那样把输出当 mask 直接求损失,对 YOLOv7 这种 anchor-based 检测器都无效。

基于半监督学习训练 YOLOv7,本质上是给模型增加了一条“自己监督自己”的梯度通路。无标注分支的梯度路径比有标注分支更脆弱,因为模型用自己的输出监督自己,一旦置信度阈值没有过滤掉错误框,错误会顺着 box、cls、obj 三个损失项同时被放大。这也是为什么同一个源码包,别人跑能涨三到五个点,你跑反而掉点的最常见原因:不是代码有问题,而是伪标签的质量和数量关系没有被处理好。

2.2 三种能进训练循环的伪标签接入方式

伪标签要真正参与训练,不能只在推理脚本里存一份 txt。按实现路径,可以把源码包里的方案归为三类。

方式一是离线预生成:先训练一个 teacher,用它对全部无标注图做一次推理,把高置信度结果写成 YOLO 格式的 txt,和真实标注一起参与后续训练。实现最简单,显存只多出一份推理开销,对原版train.py的改动也最小。缺点是伪标签只生成一次,student 能力超过 teacher 之后,标签质量成为模型上限,因此它更适合标注数据极少的冷启动阶段。

方式二是在线周期生成:每训练 N 个 epoch 用当前权重重新推理一遍无标注集,覆盖旧的伪标签目录。这相当于在训练循环里嵌入了一条反馈回路,标签质量能跟随模型提升,但每隔几轮就要跑一次全量推理,磁盘写入和训练中断都需要设计好。

方式三是端到端 teacher-student:每个 batch 内 teacher 和 student 同时前向,teacher 用no_grad()包裹,权重通过 EMA 从 student 复制,student 的梯度不回传到 teacher。伪标签不再落盘,而是直接在内存中完成匹配和损失计算。

接入方式代码里找什么伪标签更新时机主要代价适用场景
离线预生成inference.py或导出 txt 的循环训练前一次磁盘占用冷启动、标注极少
在线周期生成write_pseudosave_pseudo每 N 个 epoch推理耗时与磁盘 IO标签质量跟随模型
端到端teacherema_decaydetach每个 step显存翻倍追求精度上限

端到端方式对显存最不友好,但伪标签新鲜度最高。需要注意它和 YOLOv7 自带的 EMA 参数平滑机制兼容良好,因为 YOLOv7 原本就用 EMA 维护推理权重,改造时可以直接复用它作为 teacher,不必额外定义模型副本。

2.3 拿到源码先做的三件事:找伪标签、追数据流、查 BN

解压源码后不要直接执行python train.py。第一步先在整个工程里搜关键词,确认半监督逻辑是否真实存在:

grep -rnE "pseudo|unlabel|teacher|ema" --include="*.py" .

输出里出现ema不代表什么,YOLOv7 原版就有 EMA 参数平滑;真正要关注的是pseudounlabel。如果整个仓库里这两个词只出现在注释里,那这份源码大概率只是把无标注图混进了训练集,基本可以认定是假半监督。

第二步追数据流,看 dataloader 对无标注图做了什么处理。常见的问题写法如下:

# 部分半监督“改造”会这样返回空目标 class UnlabeledDataset(Dataset): def __getitem__(self, idx): img, _ = self.load_image(idx) return img, torch.zeros((0, 5))

这段代码的问题在于:模型确实加载了无标注图,但传给损失函数的是一个空目标。YOLOv7 的损失函数会把“没有目标”当作负样本,模型在无标注图上学习的其实是“这张图里没有框”的先验。训练 loss 会正常下降,验证 mAP 却不会涨。真正有效的半监督实现,这里应该返回伪标签的坐标数组,或者返回伪标签索引让损失函数去查找对应结果。

第三步检查 BN 的运行状态。很多改造方案把无标注图和标注图塞进同一个 model 前向,teacher 和 student 共享 BN 层。由于无标注与标注数据往往来自不同时段或不同机位,分布存在偏差,BN 的 running stats 会被无标注分支拉偏,导致有标注分支的精度也一起下降。

提示:如果 teacher 和 student 是同一个模型实例,伪标签生成阶段记得用model.eval(),student 训练阶段用model.train(),不要让无标注前向更新 BN 统计量。

3. YOLOv7 半监督训练的数据流:目录、标注与首次启动命令

3.1 目录约定:未标注目录不能放空 txt

半监督训练的目录结构比普通训练多出无标注分支和伪标签目录。常规做法是单独维护一份伪标签输出目录,不建议直接把伪标签写回原始 label 目录,否则点数训练过程中原始标注被污染:

semi_visdrone/ ├── labeled/ │ ├── images/ │ └── labels/ # 每行: class_id cx cy w h(归一化) ├── unlabeled/ │ └── images/ └── work/ └── pseudo_labels/ # teacher 生成的伪标签 txt 全部放这里

YOLO 格式的标注文件每一行是类别编号加归一化中心点坐标和宽高。这里有一个隐蔽但破坏性很大的问题:未标注图片对应的 txt 文件,到底是“不存在”还是“存在但为空”,必须区分清楚。原版 YOLOv7 的 Dataset 在找不到对应 txt 时会跳过这张图;空 txt 则会把图片当作纯负样本来训练。半监督改造里,如果给未标注图统一生成了空 txt,模型等于在几百张图上反复学习“没有任何目标”,最终表现是验证集 recall 暴跌。

可以用一段脚本在训练前检查伪标签目录,确保既有文件也有内容:

find unlabeled/images -name "*.jpg" | while read f; do b=$(basename "$f" .jpg) if [ ! -f "work/pseudo_labels/$b.txt" ]; then echo "missing: $b" elif [ ! -s "work/pseudo_labels/$b.txt" ]; then echo "empty: $b" fi done

脚本分别检查伪标签文件不存在和文件大小为零两种情况,这两类样本都不应该进入训练。实际处理中,无标注图中确实可能有大量不含目标的背景图,如果希望它们作为负样本参与学习,也应该是显式标注为0类或加一个“背景”类别,而不是留空文件让模型去猜。

3.2 半监督入口配置与数据 YAML

源码包如果真的是按半监督学习改造过的,数据 YAML 通常会增加labeledunlabeled两段字段。常见写法如下:

# data/visdrone_semi.yaml labeled: images: labeled/images labels: labeled/labels unlabeled: images: unlabeled/images pseudo: work/pseudo_labels nc: 5 names: ['car', 'truck', 'person', 'bicycle', 'drone']

配置加载后,训练脚本需要把未标注图片的 label 根目录切换成work/pseudo_labels,而不是读取labeled/labels。这里有个容易踩的坑:原版 YOLOv7 从 YAML 读取trainval字段,半监督改造后如果源码没有兼容labeled/unlabeled段,会直接报参数缺失错误。如果遇到这种情况,需要找到dataloader初始化函数,把labeled段映射为train,同时保留unlabeled段单独初始化第二个 dataloader。

伪标签目录的路径建议用相对路径加上path:前缀,避免每次换机器都要改配置。如果源码里写的是绝对路径,训练前要确认os.path.exists能通过,否则会静默读取失败并回退到空目标。

3.3 常见训练命令参数与首次运行检查

拿到能跑的源码后,典型的启动命令长这样:

python train.py \ --data data/visdrone_semi.yaml \ --cfg cfg/training/yolov7.yaml \ --weights yolov7.pt \ --batch-size 16 \ --img 640 \ --epochs 200 \ --labeled-batch 8 \ --unlabeled-batch 8 \ --pseudo-conf 0.6 \ --pseudo-iou 0.6 \ --teacher-ema 0.999 \ --unsup-loss-weight 0.5

逐项说明这些参数的含义。--labeled-batch--unlabeled-batch共同构成总 batch size,1:1 是最常用的配比,保证梯度里始终有足够的有标注信号。显存不够时优先砍unlabeled-batch,但不要砍到 0,否则退化成纯监督训练。--pseudo-conf 0.6是伪标签保留的置信度下限,调低能提升召回但引入噪声,调高更干净但可能丢掉小目标。--pseudo-iou 0.6是伪标签生成阶段 NMS 的 IoU 阈值,用来去除重叠框,它不等于训练时 anchor assigner 的匹配阈值。--teacher-ema 0.999控制 teacher 权重的更新速度,数据量少时用 0.99,数据量大时用 0.999。--unsup-loss-weight 0.5是无标注分支损失的缩放系数,伪标签噪声大时降到 0.3,质量稳定后可升到 0.8。

运行前几轮要重点看日志里是否有两个独立的 loss 项。正常的半监督训练应该能看到类似labeled_lossunsup_loss的输出;如果日志里只有一个loss,说明无标注分支没有进入损失计算,需要回到 2.3 节检查数据流。

4. 伪标签置信度、NMS 阈值与半监督训练退化排查

4.1 conf_thres 与 iou_thres 的取值逻辑

伪标签生成阶段有两个最容易被混淆的参数:conf_thresiou_thres。前者过滤低置信度框,后者是伪标签生成阶段 NMS 的 IoU 阈值,用于去除重复框。这两个参数只影响“哪些框能成为伪标签”,不参与训练时的 anchor 正负样本分配。

阈值怎么定,取决于训练阶段。冷启动阶段 teacher 本身不强,低置信度的伪标签也有召回价值,阈值可以放低到 0.4 到 0.5;训练中后期模型已经能输出较稳定的框,再把阈值升到 0.6 或 0.7,换取更高精度的伪标签。一个不区分阶段的固定阈值,往往会在前期抹掉大量可学习的召回信号,又在后期引入不必要的噪声。

训练阶段conf_thresiou_thres目的
冷启动(前 50 epoch)0.4 - 0.50.5优先保证伪标签召回
中期(50 - 150 epoch)0.5 - 0.60.5 - 0.6召回和精度平衡
后期(150 epoch 之后)0.6 - 0.70.6保留高可信伪标签

三个阈值要特别区分:伪标签生成阶段的conf_thres、NMS 的iou_thres、以及训练阶段 anchor assigner 内部的 IoU 匹配阈值。前两个出现在源码包的推理或伪标签生成脚本里,第三个由 YOLOv7 的匹配逻辑决定,改动位置完全不同。如果伪标签生成脚本里没有 NMS 过程,iou_thres参数实际上不生效,很多源码包只是把参数列在那里。

4.2 动态阈值与 per-class 阈值

固定阈值对小目标和不常见类别很不友好。小目标本身置信度分布偏低,统一用 0.6 过滤会把大量可用的伪框丢掉;频繁出现的类别置信度普遍偏高,又会让伪标签集中在少数类上。基于半监督学习训练 YOLOv7,比较稳妥的做法是统计每个类别的平均置信度,再据此生成 per-class 阈值。

def adaptive_per_class_threshold(conf, cls_ids, epoch, base=0.6, warmup=50): if epoch < warmup: return np.full(int(cls_ids.max()) + 1, base) mean_all = conf.mean() per_cls = np.array([ conf[cls_ids == c].mean() for c in np.unique(cls_ids) ]) ratio = np.clip(per_cls / mean_all, 0.5, 1.5) return np.clip(base * ratio, 0.3, 0.85)

这段代码先算出所有伪框的平均置信度,再按类别分别计算平均置信度与全局均值的比例。置信度低于全局均值的类别,阈值自动下调以保留更多伪框;置信度偏高的类别,阈值相应上调,过滤掉低质量框。np.clip把比例限制在 0.5 到 1.5,避免某个极端类别把阈值拉得过低或过高。下限设 0.3 是在召回与噪声之间做的基本保险,低于这个值的伪框连位置都不可信,没有学习价值。

4.3 训练退化排查:mAP 不涨时看四个信号

训练 loss 正常下降但验证 mAP 不动,是半监督训练最典型的退化症状。优先排查四个信号。

第一个信号是unsup_loss是否真的非零。如果日志里压根没有这项,或者它一直为 0,说明伪标签通路是断的,回到 2.3 节查 dataloader。第二个信号是伪标注的类别分布是否极度倾斜,可以用一行命令统计:

awk '{print $1}' work/pseudo_labels/*.txt | sort | uniq -c | sort -rn

如果某个类别占比超过 80%,且真实标注里这个类本身不是多数类,说明 teacher 产生了严重的伪标签偏置。这种情况下模型会被伪标签往局部最优方向拽,mAP 涨不动是必然。

第三个信号是 labeled 与 unlabeled 的 batch 配比。如果--labeled-batch 8 --unlabeled-batch 4,无标注分支的梯度贡献占比只有三分之一,半监督收益会被稀释,可以尝试改回 1:1 或让 unlabeled 占更高比例。第四个信号是 BN 污染导致的隐式退化。

提示:一半以上的“半监督不涨点”问题,最终都落在伪标签未进损失函数和 BN 统计被污染这两项上。

5. 验证半监督训练是否生效:三个对照实验与一个最小改进

5.1 对照一:清零无标注分支损失

使用半监督源码包训练到 30 轮左右,把unsup-loss-weight临时设为 0,或者在前向计算时让无标注分支的 loss 乘以 0,继续训练 20 轮。如果 mAP 几乎不变化,说明无标注分支原本就没有贡献有效梯度;如果 mAP 明显下降,说明伪标签确实在参与学习。这个实验成本最低,也最能快速识别“假半监督”。

5.2 对照二:扰动伪标签坐标

另一个更细的验证方式,是在伪标签生成阶段对框的坐标做轻微扰动,比如把每个框的中心点随机偏移 5 到 10 个像素,或者在宽高方向上缩放 5%。刷新的目标是“只扰动训练时的目标真值,不扰动输入图像”,因为对图像做增强只会改变学习难度,不会暴露伪标签是否被真实使用。如果扰动后 mAP 不变,说明伪标签虽然是落盘了,但模型对它的依赖非常弱,大概率是阈值设置过严,伪标签大多没有匹配到预测框。

5.3 最小改进:per-class 动态阈值

如果基线已经稳定,不需要动结构,可以先做 per-class 阈值改造。把 4.2 节的adaptive_per_class_threshold集成到伪标签生成脚本中,在保存结果时按类别取阈值过滤。改进点在于:长尾类别用更低的置信度保留召回,高频类别用更高阈值控制噪声。这个改造通常能把伪标签的总数量压下来,但 mAP 反而上升,因为它让模型把注意力放在了原本被忽略的类别上。

最后注意,改完阈值必须重新生成伪标签再继续训练,不要只改pseudo-conf参数却继续用旧的 txt。源码包里如果训练脚本会自动生成伪标签,确认生成开关处于打开状态;如果不会,手动跑一次推理脚本重建work/pseudo_labels,否则新阈值不会生效。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 15:52:02

OpenProject 安装与使用指南:3 步从零搭建开源项目管理平台

OpenProject 安装与使用指南&#xff1a;3 步从零搭建开源项目管理平台 【免费下载链接】openproject OpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning…

作者头像 李华
网站建设 2026/9/11 15:51:46

2026年9月配置难题求助,企业微信问题咨询电话

2026年9月&#xff0c;不少企业在进行系统配置时遇到各类问题&#xff0c;尤其是企业微信的初始设置、功能开通与权限配置环节。本文围绕企业微信的核心能力、常见配置难点、行业应用场景以及咨询渠道展开梳理&#xff0c;帮助企业理解产品逻辑&#xff0c;减少配置过程中的反复…

作者头像 李华
网站建设 2026/9/11 15:51:26

Deepseek技术发展动态与应用场景解析

作为科研新手&#xff0c;文献检索往往是开始研究的第一道难关。面对浩如烟海的学术资源&#xff0c;如何高效、准确地找到自己所需的文献&#xff0c;避免时间浪费和信息过载&#xff0c;是每个研究生和科研人员必须掌握的基本技能。幸运的是&#xff0c;现代科技为我们提供了…

作者头像 李华
网站建设 2026/9/11 15:49:33

共享单车需求预测与智能调度:从LSTM到运筹优化实战

简介&#xff1a;这是一套面向毕业设计、期末大作业和课程案例的共享单车预测与调度实战源码包&#xff0c;系统展示如何用深度学习处理真实业务问题。项目从数据层出发&#xff0c;覆盖Geohash解码、区域划分与POI分析、多表合并、训练测试集生成等完整预处理链路&#xff1b;…

作者头像 李华
网站建设 2026/9/11 15:49:18

主流CMS系统对比与选型指南:WordPress、PageAdmin、Drupal详解

1. 内容管理系统&#xff08;CMS&#xff09;基础解析 内容管理系统&#xff08;Content Management System&#xff0c;简称CMS&#xff09;是现代网站建设的核心工具之一。简单来说&#xff0c;它就像是一个数字化的"网站工厂"&#xff0c;让不懂编程的普通人也能轻…

作者头像 李华