📚 《YOLO 系列训练实操·全 7 篇》——从 YOLOv5 到 YOLO26,每篇独立成文、全部可复现。
本篇为系列 5/7。承接 v9(4/7):v9 用 PGI 解决了"训练时信息保真",v10 换了个更实操的角度——把推理链路里最后的 NMS 也省掉,实现真正的端到端(E2E)。
系列进度:✅ 1/7 YOLOv5(0.46)→ ✅ 2/7 YOLOv6(0.29)→ ✅ 3/7 YOLOv8(0.68)→ ✅ 4/7 YOLOv9(0.968,2 类小数据)→ ✅ 5/7 本文(YOLOv10·0.920) → ⏳ 6/7 YOLO11 → 7/7 YOLO26。
本文所有版本号、训练指标、损失曲线均为 2026-08 真实运行输出;与 v9 同题材(normal/fall 2 类)但数据批次不同(810/192 vs 645/153),跨篇数字只做量级对照,不做严格等值比较。


1. 背景目的

衔接上一篇(v9):v9 解决的是"训练时梯度/信息保真",涨点是训练机制层面的;但 v5~v9 无论怎么改,推理时都得过一道 NMS(非极大值抑制)——重复框的贪心去重。v10(清华大学 THU-MIG 团队,2024)用端到端(E2E)双头设计把这个后处理从链路里拿掉了:推理时模型直接输出最终检测结果,不需要 NMS。这是全系列第一次在"推理后处理"这条通路上动刀,也为系列终篇(YOLO26 的无 NMS + 无 DFL)埋下伏笔。

YOLOv10 的核心贡献有三个:

  1. 无 NMS 的端到端检测:通过 one2one(一对一)标签匹配 + 双头训练,让模型在推理时直接输出"天然去重"的结果,省掉 NMS 后处理。
  2. one2many / one2one 双头训练:训练时用 one2many(一对多,梯度更丰富)监督 one2one(一一对应,推理对齐),代价是训练参数量和训练开销上升——这是"无 NMS"的成本所在。
  3. 轻量化设计:轻量解耦分类头、大核卷积等,让 n 级模型在精度不掉的同时更省算力。

本文目标:用官方 COCO 预训练权重 yolov10n.pt,在自备的 2 类数据集上完成微调训练与验证。你将看到:双头 6 条损失的收敛曲线(可复现)、无 NMS 的推理速度实测(对比上一篇 v9 的 NMS 耗时),以及一次典型的"续训崩溃"排障实录(官方框架按文件名自动切换实现的坑)。

图 1:YOLOv10 一次前向流程示意(输入 → 主干 → PAN 颈部 → 双解码头:训练两路 / 推理仅 one2one 直接输出)
在这里插入图片描述


2. 目录

  1. 背景目的
  2. 目录
  3. 环境和数据集准备
  4. 实操以及截图伴有原理解释
    • 4.1 获取官方代码与预训练权重
    • 4.2 新环境适配清单(本文排雷实录)
    • 4.3 数据配置
    • 4.4 从预训练权重开始训练
    • 4.5 训练日志与损失曲线(真实数据)
    • 4.6 深度原理:E2E 无 NMS 的代价、双头分配与轻量解耦头
    • 4.7 操作异常实录
  5. 测试验证(含无 NMS 速度实测与跨篇对照)
  6. 总结

3. 环境和数据集准备

3.1 环境搭建(通用示例)

conda create -n multi-agent-demo python=3.11 numpy
conda activate multi-agent-demo
pip install -r requirements.txt     # 在 yolov10 仓库目录内(如遇失败见 4.2)

本文实测环境(供对照):Python 3.11.15 / numpy 2.4.4 / torch 2.10.0+cu128(CUDA 可用)/ torchvision 0.25.0+cu128 / NVIDIA RTX 5060 Ti(8 GB 显存)。

3.2 数据集准备

本文使用 2 类检测数据集(normal/fall,标准 YOLO 格式,images/{train,val} + labels/{train,val}):

  • 类别:normal(0)、fall(1),nc=2
  • 规模(实测):train 810 张(jpg 664 + png 146)/ val 192 张;训练标注:normal 1568 条、fall 209 条(fall 偏少,属轻度不平衡)
  • 标注格式:每行 class x_center y_center w h(归一化);扫描无坏标签行

图 2:训练集类别标注分布——fall 仅 209 条(约为 normal 的 1/7.5),第 5 节指标中将看到少样本类别在无 NMS 架构下的表现。

在这里插入图片描述


4. 实操以及截图伴有原理解释

4.1 获取官方代码与预训练权重

git clone https://github.com/THU-MIG/yolov10
cd yolov10
  • 官方仓库 THU-MIG/yolov10(清华团队,2024);仓库是 ultralytics 的定制 fork(本地实测包版本 8.1.34 系),模型注册在 ultralytics/models/yolov10/
  • 预训练权重 yolov10n.pt约 11.4 MB,COCO 80 类)放入仓库根目录即可;官方基线:YOLOv10-N 在 COCO 上 mAP 38.5(640 分辨率,NMS-free)。
  • 结构实测(加载权重打印):385 层 / 2,775,520 参数 / 8.7 GFLOPs;fused 后 285 层 / 2,762,608 参数 / 8.6 GFLOPs——n 级仅 2.77M 参数,全系列最轻。

⚠️ 与官方文档一致:本仓库用文件名自动识别模型类型(YOLO.__init__ 只在文件名含 “yolov10” 时才走 v10 实现)。加载官方权重 yolov10n.pt 没问题;但续训加载自定义名的 best.pt 会静默落到 v8 实现并崩溃——这是本文 4.2 的重点排雷项(异常 12)。

4.2 新环境适配清单(本文排雷实录)

  1. 新 torch(≥2.6)加载 checkpoint 失败torch.load 默认 weights_only=True 拒绝官方权重(Unsupported global: ...YOLOv10DetectionModel)→ 给 ultralytics/nn/tasks.py(torch_safe_load 内 2 处)与 ultralytics/utils/torch_utils.py(strip_optimizer 内 1 处)共 3 处 torch.load(...)weights_only=False(与 v9 异常 7 同款)。
  2. numpy 2.x 移除 np.trapzval 指标计算阶段崩溃(module 'numpy' has no attribute 'trapz')→ ultralytics/utils/metrics.pycompute_ap 中改 np.trapezoid(numpy 2.0 起 trapz 更名;全仓库仅此一处)。
  3. 续训崩溃坑(本文最值钱的一条):第 1 段(加载 yolov10n.pt)正常,第 2 段(加载上一段输出的 best.pt)报 AttributeError: 'str' object has no attribute 'view'。根因:YOLOv10DetectionModel 自身没有 task_map,trainer/validator 由包装类 YOLOv10 提供;而通用 YOLO 包装类只在文件名含 “yolov10” 时切换到 YOLOv10best.pt 名字不含 → 落到 v8 的 DetectionTrainer,其 get_model 又按 v10 的 yaml 建出双头模型 → 训练时 v10 头输出 {"one2many":..., "one2one":...} 字典,被 v8 的 v8DetectionLoss 当序列迭代 → 迭代出字符串键报错。修复:代码里显式 from ultralytics import YOLOv10YOLOv10(weights);命令行 yolo train model=... 同理(权重名需含 yolov10 或改用 YOLOv10 API)。

4.3 数据配置

在仓库内新建 objdet.yaml<dataset> 为数据集根目录):

# objdet.yaml
path: <dataset>            # 数据集根目录
train: images/train
val: images/val

nc: 2
names: ['normal', 'fall']

4.4 从预训练权重开始训练

# 官方 CLI 训练(n 级,8 GB 卡推荐 batch 8 / 416)
yolo detect train data=objdet.yaml model=yolov10n.pt epochs=5 batch=8 imgsz=416 device=0
  • 输出默认到 runs/train/exp,每轮保存 best.pt / last.pt
  • 本文实测因单命令时长限制分 4 段(5×4 = 20 epochs):每段把上一段 runs/train/exp*/weights/best.pt 作为 model 继续微调。注意 4.2-3 的"文件名自动切换"限制——续训权重名必须含 “yolov10”(cp 改名即可),或改用 Python API:
from ultralytics import YOLOv10      # 显式 v10 包装类,不受文件名限制
model = YOLOv10("runs/train/exp/weights/best.pt")
model.train(data="objdet.yaml", epochs=5, batch=8, imgsz=416, device=0)
  • 训练参数:batch 8 / imgsz 416 / workers 2 / RTX 5060 Ti(8 GB)。
  • 训练耗时实测:每 5 epochs 约 2.2 分钟,20 epochs 合计约 9 分钟(n 级模型 + 416 输入,全系列最快)。

4.5 训练日志与损失曲线(真实数据)

进度条节选(真实):

      Epoch    GPU_mem     box_om     cls_om     dfl_om     box_oo     cls_oo     dfl_oo  Instances       Size
      1/1     0.776G     0.7974     0.5999     0.9223     0.8986     0.7307     0.9166          5        416

(节选自第 21 个 epoch 的续跑输出,格式与任意 epoch 一致:6 条损失 + 显存 + 实例数。)

逐 epoch 真实训练损失与验证指标(runs/train/exp*/results.csv 四段拼接,20 epochs):

epochbox_omcls_omdfl_ombox_oocls_oodfl_oomAP50mAP50-95
11.4832.0751.1581.4133.0941.0760.5660.371
21.4101.3931.1651.4112.4551.0990.6760.449
31.3081.2391.1331.3862.1561.0990.7760.551
41.2181.0811.0911.2781.8051.0550.8440.618
51.1751.0421.0721.2531.6871.0380.8530.638
61.0890.9091.0271.1761.4341.0030.8280.621
71.0790.9391.0351.1691.4031.0040.8440.627
81.1190.9541.0571.2091.3891.0310.8530.631
91.0660.8671.0331.1461.2181.0060.8600.655
101.0560.8511.0241.1621.1731.0070.8940.669
110.9520.7360.9821.0501.0120.9660.8870.651
120.9770.7930.9881.0731.0580.9710.8890.675
130.9830.7861.0011.0941.0210.9920.8690.662
140.9780.7420.9981.0690.9600.9820.8950.695
150.9880.7561.0021.0980.9460.9890.9130.683
160.8840.6570.9550.9880.8420.9470.9210.706
170.8730.6680.9520.9890.8220.9430.8660.659
180.9540.7130.9831.0610.8690.9730.8840.647
190.9520.6990.9851.0460.8320.9710.9230.697
200.9920.7141.0041.1180.8620.9880.9140.695

图 M:训练损失曲线(真实数据)——6 条损失:one2many 三件套 box 1.48→0.87、cls 2.08→0.66、dfl 1.16→0.95;one2one 三件套 box 1.41→0.99、cls 3.09→0.82、dfl 1.08→0.94。双头并行收敛,one2one 分类损失起点更高(3.09)但最终与 one2many 齐平——这正是"one2many 监督 one2one"的直观证据。

在这里插入图片描述

图 N:验证 mAP 曲线(真实数据)——mAP50 0.566→0.923(ep19),mAP50-95 0.371→0.706(ep16);ep16 后震荡收窄,20 epochs 已进入平台期。

在这里插入图片描述

4.6 深度原理:E2E 无 NMS 的代价、双头分配与轻量解耦头(本版本定制解析)

痛点 1:v5~v9 为什么离不开 NMS?v10 怎么把它拿掉的?

  • 传统检测头(v5~v9)输出的是"密集候选框":每个格点、每个尺度都会预测若干框,同一个目标会被多个相邻格点/尺度重复命中。NMS 的作用就是按 IoU 阈值做贪心去重,只留每簇分数最高的框。它既是后处理,也是"规则化"的兜底。
  • v10 的答案是把"去重"从后处理搬进训练目标:让标签分配变成 one2one(一对一)——每个真实目标只匹配唯一一个预测框。既然训练时"一个目标一个框"已经对齐,推理时模型输出的框天然不重复,NMS 自然不需要了。

痛点 2:去掉 NMS 的代价是什么?(图 K)

  • 难点:纯 one2one 匹配信号稀疏(一个目标只有一个正样本),训练不稳定、精度下降。v10 的方案是双头并行训练

图 K:双头标签分配原理——训练时 one2many 头用 TopK 匹配(每目标多个正样本,梯度丰富,是"教师");one2one 头用一一匹配(是"学生",与推理对齐)。两路损失并行回传;推理时只留 one2one 头输出。

在这里插入图片描述

  • 代价清单(本文实测可感知):① 参数量增加——one2one 需要完整复制一套解耦卷积头one2one_cv2/cv3 深拷贝自 one2many 头),n 级 fused 后 2.76M 参数;② 训练损失翻倍——6 条损失(表 4.5)比 v8 的 3 条多一倍;③ 训练时长略增(同规模数据仍比 v9-s 快很多)。

痛点 3:轻量解耦头与"端到端"的关系?(图 L)

  • 无 NMS 后,推理链路变成:前处理 → 模型 → 后处理(仅 0.1ms)。v10 同时优化了分类头(轻量解耦结构,减少卷积堆叠)与骨干(大核卷积提升感受野),使 n 级在"去 NMS"后反而更快。

图 L:推理后处理流程对比——v5~v9:候选框 → NMS(IoU 阈值去重) → 最终框;v10:候选框 → 直接输出。后处理耗时从毫秒级降到 0.1ms(图 L 与 5.1 实测)。

在这里插入图片描述

4.7 操作异常实录

本文操作中遇到的异常与解决(完整档案见异常 10~12):新 torch weights_only(3 处补丁)、numpy 2.x np.trapz 更名、以及"第 2 段续训即崩溃"的包装类按文件名自动切换问题(显式 YOLOv10() 修复)。


5. 测试验证

5.1 最终指标(独立 val,第 4 段输出的 best.pt)

实测输出(val 集 192 张 / 441 个实例):

类别实例数PRmAP50mAP50-95
normal3970.9060.7760.9130.629
fall440.8830.8860.9280.783
all4410.8940.8310.9200.706
  • 20 epochs(合计约 9 分钟)在 2 类任务上达到 mAP50 0.920 / mAP50-95 0.706;少样本类别 fall(209 条标注)mAP50 0.928、R 0.886——在无 NMS 架构下少样本类依然能学满召回。
  • 对比预训练零样本基线:mAP50 0.387 / mAP50-95 0.267(fall 类 0)→ 微调后 0.920 / 0.706,涨幅主要来自类别适配。

5.2 无 NMS 的实测价值:推理耗时拆解

环节v9(上一篇,同题材数据)v10(本文)
preprocess0.1 ms0.1 ms
inference4.3 ms0.8~2.1 ms
NMS1.4 ms—(无此环节)
postprocess0.1 ms
  • v10n 是 2.77M 参数的轻量模型(v9-s 训练态 9.6M),单张 416 推理 0.8~2.1ms;端到端链路里不再有 NMS 项,后处理仅 0.1ms——"去 NMS"在耗时表上是实打实的一行消失。
  • 诚实对照:v9 用同一题材数据(645/153 版)mAP50 0.968,本文数据(810/192 版)v10n 0.920——模型更小、速度更快、精度略低,这是"轻量端到端"与"重机制高精度"的取舍,也是系列一直强调的"框架定位差异 > 单个数字"。

5.3 结论

v10n 在 8 GB 卡上约 9 分钟即可完成 20 epochs 微调并收敛到 mAP50 0.92,推理链路无 NMS、全流程后处理 0.1ms——"轻量 + 端到端"路线的代表。


6. 总结

一句话定位:YOLOv10 是"推理链路"驱动的一代——用 one2one/one2many 双头训练把 NMS 从后处理中移除,实现端到端检测,同时用轻量设计保住速度与精度平衡,是 2024 年"去后处理"路线的代表作(也为 YOLO26 的无 NMS + 无 DFL 铺路)。

实测复盘(2 类检测数据集,20 epochs,RTX 5060 Ti)

实测一句话判断
训练损失收敛6 条损失 20 epochs 平稳下降(cls_om 2.08→0.66)双头并行训练链路健康
验证指标mAP50 0.920 / mAP50-95 0.706(P 0.894 R 0.831)轻量模型正常收敛
少样本类别 fallmAP50 0.928、R 0.886无 NMS 下少样本类不落下风
推理链路0.1ms 预处理 + 0.8~2.1ms 推理 + 无 NMS + 0.1ms 后处理全系列最短后处理链
训练耗时20 epochs ≈ 9 分钟全系列最快(n 级 416)
模型规模385 层 / 2.78M 参数 / 8.7 GFLOPs(fused 2.76M)全系列最轻

核心机制实证(对应 4.6):双头设计的证据在损失曲线里——one2one 分类损失从 3.09 的高起点一路追平 one2many(图 M),说明 one2many 的"教师"梯度确实在推着 one2one"学生"收敛;无 NMS 的证据在耗时表里——后处理 0.1ms、无 NMS 项(5.2)。

诚实局限:① 同题材数据 mAP50 0.920 低于 v9 的 0.968——轻量模型在"重机制"面前精度吃亏,且两篇数据批次不同,只宜量级对照;② 训练代价是双头 6 条损失,单卡训练时长略高于同级的 v8n;③ 官方仓库 2024 年后停更维护(本地 fork 版本号 8.1.34 系),对新 torch/numpy 需按 4.2 手动适配;④ 官方仓库的"文件名自动切换"设计对续训不友好(异常 12),工程化需封装。

决策建议:推理端到端、部署极度看重后处理链路(边缘设备/实时管线)且模型量级敏感的团队首选 v10n;追求更高精度且算力充足,v9-s 或 v8s 仍是更稳选择;新项目若无特殊端到端诉求,生态更成熟的 v8/11 系体验更省心。

踩坑速记:3 处 weights_only=False(4.2-1);np.trapznp.trapezoid(4.2-2);续训必须显式 YOLOv10()best.pt 文件名不带 “yolov10” 会自动掉进 v8 实现崩溃(4.2-3、异常 12);分段续训用上一段 best.pt 作为 model 继续微调(系列通用技巧)。

📖 下集预告(系列 6/7):YOLO11——v10 把 NMS 省了,11 又回到 NMS 阵营?作为"生产稳定版"的它到底改了什么?API 与 v8 又有什么差异?

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐