简介这份资源是面向深度学习课程设计、毕业设计与期末大作业场景的半监督YOLO目标检测框架适合已掌握PyTorch基础、希望将半监督学习落地到检测任务的学生与研究者。它解决的核心问题是在标注数据有限时如何借助大量未标注数据提升检测精度与泛化能力。压缩包共25个文件约127KB以19个Python脚本为主体涵盖训练入口、教师-学生模型、EMA、伪标签生成、损失函数与评估指标等模块另有3个yaml配置文件、1个md说明文档、1个jpg与1个gitignore分别用于参数配置、环境说明与版本管理。目录按data、models、utils、config、runs等分层组织结构清晰便于按模块阅读与二次修改。目前已有28人学习下载。读者可据此获得一套可运行的半监督检测训练流程理解网络结构调整、损失优化与训练策略制定并参考README完成环境搭建与实验复现。1. 半监督 YOLO 框架设计从标注焦虑到可复现的落地路径手里只有两千张标注图却要覆盖产线上十几种缺陷这种场景下纯监督 YOLO 的 mAP 往往卡在 0.6 上不去。半监督目标检测要解决的就是这个矛盾用少量标注数据加上大量未标注数据把检测精度拉到接近全监督的水平。这个标题里的「框架设计」不是指某个现成开源库而是指一套可复用的工程结构——数据加载、伪标签生成、一致性正则、模型导出这条链路怎么搭。适合已经跑通过 YOLO 基础训练、手里有未标注图像但标注预算有限的从业者。如果你还在纠结 YOLO 环境配置建议先把单模型训练跑通再回来否则半监督的调试复杂度会让你怀疑人生。2. 半监督 YOLO 的三种主流范式与选型依据半监督目标检测不是把未标注图直接丢进 dataloader 就完事。核心问题在于未标注数据没有框模型怎么从中学到定位能力目前工程上能跑通的主要有三条路线选哪条取决于你的标注量、算力和迭代周期。2.1 伪标签自训练最直接但最容易崩伪标签的思路很朴素先用标注数据训一个教师模型用它给未标注图打框再把置信度高的伪标签当真实标签参与训练。YOLO 生态里最常见的做法是用 Ultralytics 的预测接口批量推理然后过滤。from ultralytics import YOLO import os # 教师模型先用标注数据训好的权重 teacher YOLO(runs/detect/train/weights/best.pt) # 对未标注图像目录批量推理conf 阈值决定伪标签质量 results teacher.predict( sourcedata/unlabeled/images, conf0.5, # 低于此置信度的框直接丢弃 iou0.45, # NMS 的 IoU 阈值 save_txtTrue, # 直接输出 YOLO 格式的 txt 标签 save_confTrue, # 保留置信度后续可按置信度加权 projectpseudo_labels, nameround1 )这段代码的关键参数是conf。设太低比如 0.25伪标签里混入大量误检学生模型会被带偏设太高比如 0.8伪标签数量骤减未标注数据等于没用上。我一般从 0.5 起步观察伪标签数量和验证集 mAP 的联动——如果学生模型 mAP 比教师低超过 3 个点说明伪标签噪声过大需要提高阈值或引入置信度加权。伪标签自训练的优势是工程实现简单不需要改 YOLO 的损失函数。缺点是误差会累积教师模型的系统性偏差会通过伪标签传给学生学生再传给下一轮。常见缓解手段是每轮用学生模型重新生成伪标签而不是一直用初始教师。2.2 一致性正则让模型对扰动不敏感一致性正则的核心假设是同一张未标注图经过不同扰动后模型输出的检测结果应该一致。这里的扰动可以是数据增强颜色抖动、裁剪、翻转也可以是模型层面的 dropout 或 EMA 教师。在 YOLO 上实现一致性正则通常需要改训练循环。以 Mean Teacher 为例维护一个学生模型和一个教师模型教师参数是学生参数的指数移动平均。未标注图分别输入两个模型计算检测头输出的一致性损失。import torch import torch.nn.functional as F def consistency_loss(student_out, teacher_out, threshold0.4): student_out / teacher_out: list of tensors [cls_logits, reg_preds] 只对教师置信度高于 threshold 的位置计算一致性 cls_s, reg_s student_out cls_t, reg_t teacher_out # 教师置信度作为伪标签权重 conf_t cls_t.sigmoid().max(dim-1)[0] mask (conf_t threshold).float() # 分类一致性KL 散度 loss_cls F.kl_div( F.log_softmax(cls_s, dim-1), F.softmax(cls_t, dim-1), reductionnone ).sum(dim-1) loss_cls (loss_cls * mask).mean() # 回归一致性只对教师有高置信度目标的位置算 L1 loss_reg (F.l1_loss(reg_s, reg_t, reductionnone).sum(dim-1) * mask).mean() return loss_cls loss_reg这里threshold控制哪些位置参与一致性计算。设 0.4 意味着教师模型认为「这里可能有目标」的区域才纳入损失避免背景区域的噪声梯度。EMA 的衰减系数一般设 0.999更新太慢教师跟不上学生太快则教师失去稳定性。一致性正则比伪标签自训练更优雅但工程复杂度高需要改 YOLO 的 trainer维护两套参数还要调 EMA 衰减和损失权重。如果团队没有改训练框架的经验建议先从伪标签入手。2.3 联合训练与损失加权把未标注数据当正则项第三条路线不生成显式伪标签而是把未标注数据的损失作为正则项加入总损失。典型做法是对未标注图只计算分类损失不计算框回归因为分类的监督信号比定位更鲁棒。总损失形式为L L_sup λ * L_unsup。λ通常从 0 开始 warmup训练到一定 epoch 后线性增加到预设值。YOLO 的损失函数本身包含 box loss、cls loss、dfl loss半监督场景下一般只对未标注数据启用 cls loss。选型建议标注数据少于 500 张时优先一致性正则500 到 2000 张之间伪标签自训练性价比最高超过 2000 张标注时半监督的增益会明显收窄不如把精力放在数据增强和模型结构上。3. 搭一套可复现的半监督 YOLO 训练流水线选好范式之后落地环节的坑比算法本身多。这一章按数据组织、伪标签生成、训练配置、模型导出四个步骤拆开讲每一步给出可抄的命令和参数。3.1 数据目录结构与 YOLO 格式对齐半监督训练的数据集要同时容纳标注集和未标注集。推荐目录结构如下dataset/ ├── labeled/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ └── labels/ │ ├── train/ │ └── val/ ├── unlabeled/ │ └── images/ │ └── train/ └── data.yamldata.yaml里只需要声明标注集的路径未标注集在训练脚本里单独指定。YOLO 的标签格式是class_id x_center y_center width height坐标归一化到 0 到 1。如果原始标注是 VOC 的 XML需要先转换。# VOC 转 YOLO 格式的典型命令假设用 labelImg 标注 # 转换脚本核心逻辑解析 XML 的 bndbox除以图像宽高做归一化 python voc2yolo.py \ --voc_dir ./VOCdevkit/VOC2007 \ --output_dir ./dataset/labeled \ --classes person car dog转换时最容易翻车的是图像尺寸不一致。YOLO 训练时会把图像 resize 到imgsz但标签归一化必须基于原始尺寸。如果转换脚本读错了图像宽高框会整体偏移。验证方法是转换后随机抽几张图用cv2.rectangle画出来肉眼检查。3.2 伪标签生成与置信度过滤策略伪标签生成不是跑一次 predict 就完事。工程上要解决三个问题批量推理的内存控制、置信度阈值的动态调整、伪标签的格式校验。from ultralytics import YOLO import glob import os teacher YOLO(runs/detect/baseline/weights/best.pt) # 分批推理避免一次性加载太多图像导致显存溢出 img_list glob.glob(dataset/unlabeled/images/train/*.jpg) batch_size 16 for i in range(0, len(img_list), batch_size): batch img_list[i:ibatch_size] results teacher.predict( sourcebatch, conf0.5, iou0.45, save_txtTrue, save_confTrue, projectpseudo_labels, nameround1, exist_okTrue )生成后的伪标签要校验空标签文件没有检测到任何目标是正常的但如果空标签比例超过 80%说明阈值太高或教师模型太弱。另一个检查点是类别分布——伪标签的类别分布应该和标注集大致相似如果某个类别突然暴增可能是教师模型在该类别上过拟合。置信度过滤我一般用两阶段策略第一轮用 0.5 生成伪标签训练学生模型后在验证集上评估如果学生 mAP 比教师低不到 2 个点说明伪标签质量可接受如果低超过 5 个点把阈值提到 0.6 或 0.7 重新生成。3.3 训练配置损失权重、EMA 与 warmup半监督训练的 YOLO 配置文件需要在标准配置基础上增加几个参数。以 Ultralytics 的default.yaml为基底覆盖以下字段# semi_supervised.yaml lr0: 0.01 lrf: 0.01 epochs: 100 batch: 16 imgsz: 640 # 半监督相关 unsup_ratio: 1.0 # 未标注数据与标注数据的采样比例 unsup_loss_weight: 0.3 # 未标注损失的权重 λ ema_decay: 0.999 # EMA 教师衰减系数 pseudo_conf: 0.5 # 伪标签置信度阈值 warmup_epochs: 5 # 前 5 个 epoch 只用标注数据warmup_epochs很关键。如果一开始就把未标注损失加进来模型还没学好基础特征伪标签噪声会直接破坏训练。我一般设 3 到 5 个 epoch等标注集上的 loss 稳定后再引入未标注数据。unsup_loss_weight从 0.1 开始试逐步加到 0.3 或 0.5。权重太大时未标注损失会主导梯度模型偏向于预测「安全」的框比如大面积背景导致召回率下降。观察指标是验证集上的 precision 和 recall 曲线——如果 precision 上升但 recall 明显下降说明权重过大。3.4 模型导出与推理验证训练完成后导出 ONNX 或 TensorRT 引擎验证半监督模型的实际推理效果。导出命令# 导出 ONNX yolo export modelruns/detect/semi/weights/best.pt formatonnx imgsz640 simplifyTrue # 导出 TensorRT需要 GPU 环境 yolo export modelruns/detect/semi/weights/best.pt formatengine imgsz640 halfTrue导出后必须做一致性验证用同一张图分别跑 PyTorch 模型和 ONNX 模型对比输出的框坐标和置信度。如果差异超过 1e-3检查导出时的imgsz和simplify参数是否和训练时一致。TensorRT 的halfTrue会引入量化误差检测小目标时可能出现框偏移建议先用 FP16 验证精度损失是否可接受。推理速度方面640 分辨率下 TensorRT FP16 在 T4 上的单帧耗时大约 8 到 12 毫秒折算下来单路 1080p25 帧每秒的视频流占用不到 30% 的 GPU。如果要做多路拉流检测需要把解码和推理拆到不同进程避免 Python GIL 成为瓶颈。4. 半监督 YOLO 训练中的五个血泪坑这一章记录我在实际项目里踩过的坑每条按现象、原因、解决三段写。如果你正在调半监督模型建议对照排查。4.1 伪标签框整体偏移现象学生模型训练后验证集上的框位置系统性偏移mAP 比教师低 10 个点以上。原因伪标签生成时save_txt输出的坐标是基于推理时的图像尺寸而训练时 dataloader 会做 letterbox 填充。如果伪标签没有同步做 letterbox 逆变换框会偏移。解决用 Ultralytics 的save_txt时确认输出格式和训练时的标签格式一致。更稳妥的做法是生成伪标签后用cv2画框可视化抽查 20 张确认框贴合目标再开始训练。4.2 未标注损失不下降现象训练日志里unsup_loss始终在高位震荡不随 epoch 下降。原因未标注数据的损失计算方式有问题。如果对未标注图也计算框回归损失但伪标签的框本身不准回归损失会持续产生大梯度。解决未标注数据只启用分类损失关闭 box loss 和 dfl loss。在 YOLO 的损失函数里加一个 mask未标注样本的回归损失置零。4.3 教师模型被学生带偏现象多轮迭代后教师模型的 mAP 反而下降。原因如果每轮用学生模型更新教师而学生模型在某些类别上退化教师也会跟着退化。这是伪标签自训练的经典崩溃模式。解决教师模型始终用标注数据单独训练不参与半监督迭代。或者用 EMA 更新教师但 EMA 衰减系数设高0.999 以上让教师变化足够慢。4.4 类别不平衡被放大现象标注集里长尾类别的 AP 在伪标签训练后进一步下降。原因教师模型在长尾类别上的置信度天然偏低伪标签过滤时这些类别的框被大量丢弃学生模型见到的长尾样本更少。解决对长尾类别单独降低伪标签阈值或者用类别加权的置信度过滤。另一个做法是在损失函数里对长尾类别加 class weight。4.5 验证集指标虚高现象验证集 mAP 很高但实际推理时漏检严重。原因验证集和标注集来自同一分布而实际场景的图像分布不同。半监督模型容易在标注集分布上过拟合。解决从实际场景里抽一批图做测试集不参与任何训练。如果测试集指标明显低于验证集说明模型泛化不足需要增加未标注数据的多样性或引入更强的数据增强。5. 用置信度加权和动态阈值把半监督 YOLO 再推一档伪标签的置信度不是只有「保留」和「丢弃」两个选项。把置信度作为损失权重让高置信度伪标签贡献更大梯度低置信度伪标签贡献小但不断路这是我目前觉得性价比最高的改进。具体做法是在损失函数里给每个伪标签框乘一个权重w conf^αα取 0.5 到 1.0 之间。α0退化成硬阈值α1是线性加权。我一般从 0.5 开始试观察长尾类别的 AP 变化。def weighted_pseudo_loss(pred_cls, pseudo_cls, pseudo_conf, alpha0.5): pred_cls: 学生模型的分类预测 [N, num_classes] pseudo_cls: 伪标签的 one-hot 分类 [N, num_classes] pseudo_conf: 伪标签置信度 [N] weight pseudo_conf ** alpha loss F.cross_entropy(pred_cls, pseudo_cls.argmax(dim-1), reductionnone) return (loss * weight).mean()另一个技巧是动态阈值训练初期用高阈值0.7保证伪标签质量随着训练推进逐步降到 0.4让更多伪标签参与后期微调。实现上可以用 cosine 退火调度阈值。验证这套改进是否有效不能只看 mAP。我习惯同时看三个指标验证集 mAP、实际场景测试集的召回率、以及伪标签的类别分布熵。如果熵持续下降说明模型在往少数类别坍缩需要调低α或提高阈值。最后说一个习惯每次改半监督策略只改一个变量。同时改阈值和损失权重指标涨了你不知道是哪个起的作用指标跌了你也不知道该回退哪个。半监督的调试空间比全监督大得多控制变量是唯一的后悔药。希望帮到你。本文还有配套的精品资源点击获取