上周有位做工业质检的朋友抱着一台笔记本找我,说他照着网上教程把自己的数据集喂给 YOLO 目标检测模型,训练日志里的损失确实在往下掉,可一跑预测,检测框要么飘在背景上,要么整张图一个目标都框不出来,折腾了三天没找到原因。我看了他的数据目录和标签文件,问题其实很朴素:标签里存的是像素坐标而不是归一化坐标,验证集还和训练集来自同一段视频的相邻帧。这两个问题凑在一起,指标看着漂亮,模型实际上什么也没学会。这类事情我见过太多次了,所以想把自己用 YOLO 在自己的数据集上做目标检测的完整流程摊开写一遍——从数据采集、标注规范、配置文件的写法,到训练参数怎么定、预测阈值怎么调、指标怎么读、出了问题怎么排查。
这篇内容面向的是手上已经有一批自己拍的或者业务里攒下来的图片、想把它们变成能用的检测模型的人。你不需要精通深度学习,但最好跑过一次公开数据集的 demo,知道训练和推理大概是什么节奏。我会把每一步背后的“为什么”说清楚,参数取值尽量给出计算依据,也会把那些文档里不会写、只有真跑过一遍才会知道的小坑单独拎出来讲。读完你应该能独立完成一次从原始图片到可用权重的完整闭环,并且在效果不理想时知道该往哪个方向调。
1. 先想清楚:用自己的数据跑 YOLO 到底难在哪
1.1 公开数据集能跑通,不代表你的数据能跑通
很多人第一次接触目标检测,是在公开数据集上跑官方示例,几条命令下去就能在验证集上看到不错的精度,于是形成一种错觉:这套流程换个数据集照样能用。实际上公开数据集之所以“好训”,是因为它经过大量人工筛选和清洗,类别均衡、标注规范、图像质量稳定,甚至连光照分布都相对集中。而你自己采集的数据,往往是几十段视频里截出来的帧,重复度极高,前景背景比例严重失调,个别类别可能只有二三十个样本,标注还出自不同人之手,标准不统一。
我一般的判断是:在一个全新的自建数据集上,数据准备的工作量占比能到六成以上,训练和调参加起来不到三成,剩下的是推理验证和部署适配。这个比例听起来夸张,但只要你在标注上偷过懒,就一定会同意。举个具体例子,同样是“螺丝”这个类别,有人把整个螺丝头框进去,有人只框螺帽,还有人把旁边带阴影的部分也圈进来。模型学到的是框的尺寸分布,标注尺寸方差这么大,它只能学出一个折中的、谁都不准的框。
所以第一件要建立的心理预期是:这不是一个“调参问题”,而是一个“工程问题”。模型结构和超参数在很大程度上已经被开源实现调好了,你真正能拉开差距的地方,是数据质量、类别设计、划分策略,以及对训练曲线的判读能力。
1.2 版本选型:不同代际的 YOLO 该怎么挑
YOLO 这条线迭代了很多代,从早期的 anchor-based 到后来的 anchor-free,从纯卷积到引入注意力模块,每一代都有自己的性格。选型的时候我不太看论文里的精度榜单,而是看三件事:你的硬件能承载多大的输入分辨率、你需不需要导出到特定推理后端、你手上有没有靠谱的预训练权重。
近几代的主流实现,工程完备度都比较高,配置文件、训练脚本、导出脚本、验证脚本是一整套的,命令行接口也统一,用起来省心。老一些的版本(比如 v5 那一代)优势是生态成熟、社区里能搜到的现成答案多,改网络结构的人也多,缺点是有些设计在新硬件上不是最优。新版本(v8 之后)默认 anchor-free,配置更简洁,导出链路更顺,对小目标的默认处理也更好一些。
我自己的取舍逻辑是这样的:
- 如果只是想做一次验证性的实验,看看自己的数据能不能训出东西来,用最新一代的主流实现,默认配置直接跑,省掉大量摸索时间。
- 如果最终要落到边缘设备上,且算力非常紧张,可以先在新版本上训出效果,再考虑用更小的模型尺寸(n/s 级别)重训,或者做量化。
- 如果团队里已经有一套基于老版本 v5 的流水线在跑,别急着换,迁移成本往往比收益高,除非你确实卡在某个老版本解决不了的问题上。
还有一个容易被忽略的点:模型尺寸的选择比版本选择更影响最终体验。n 到 x 这几档,参数量差了一到两个数量级,在同样的数据上,大模型精度高但推理慢、显存吃紧、小数据集上还容易过拟合。我的经验是,自建数据集样本量在两三千张以内时,用 s 或者 m 就够了,上 l/x 大概率是浪费算力,还会因为过拟合导致验证指标反而更差。
1.3 硬件与时间预算的现实估算
训练目标检测模型对显存的要求,本质上是输入分辨率、批大小和模型规模的乘积关系。以 640 输入、s 尺寸模型为例,批大小 16 在 8GB 显存的卡上基本能跑起来,如果开混合精度(AMP)还能再往上加一点;换成 l 尺寸,同样的批大小大概要 16GB 起步。这个数量级关系心里要有个数,不然一上来就把 batch 设成 64,第一轮就 OOM,白等半小时。
时间预算方面,一个 2000 张图、10 个类别的数据集,在单张消费级显卡上,s 模型训 100 轮到 150 轮,大概需要几个小时。这个时间不算长,所以我的建议是先小规模试跑:抽 200 张图,训 30 轮,把整条链路(数据读取、标签解析、损失下降、验证指标输出、权重保存、推理出图)全部走通一遍。这一步能提前暴露 80% 的配置错误,比训到第 90 轮才发现标签路径写错要划算得多。
提示:试跑阶段一定要真的去看推理出来的图,不要只看指标。指标正常但预测框诡异的情况非常常见,肉眼过一遍图能省下大量排查时间。
2. 数据准备:决定模型上限的一步
2.1 采集与清洗:把“能拍到的”变成“能训的”
采集环节最常见的错误是“越多越好”的思维。有人拍了两万张图,结果全是在同一个工位、同一个光照、同一个角度下拍的,模型在这批数据上精度极高,换个场景立刻崩。目标检测模型学到的是纹理和形状的统计规律,如果你的数据里背景几乎是常量,模型很容易退化成“靠背景认类别”。
我的做法是刻意制造多样性:同一类目标,至少覆盖三种以上的光照条件(顺光、逆光、室内灯光)、三种以上的拍摄距离、三种以上的角度,以及遮挡程度不同的样本。数量上不必追求极致,一个类别有 300 到 500 个有效实例,通常就能训出可用的效果;低于 100 个实例,模型对这个类别的召回会很不稳定。
清洗比采集更费神。视频抽帧得到的数据,相邻帧之间差异极小,直接全量入训会导致严重的冗余。我一般按场景分段,每段视频按固定间隔抽帧(比如每 15 帧取一帧),保证同一条流水线上不会出现几十张几乎一样的图。另外要主动剔除模糊、过曝、欠曝、严重遮挡到看不出类别的图——这些图留着不但没用,还会污染损失。
还有一个技巧值得说:负样本(只含背景、不含任何目标的图)要保留一部分,大概占总量的 5% 到 10%。负样本能显著降低模型在空场景上的误检率。很多人只截有目标的图,结果模型在纯背景图上也要框出点什么,置信度还挺高,这就是缺少负样本训练的直接后果。
2.2 标注规范与类别设计
标注是整条链路里最容易出问题、也最难返工的环节。先说格式:YOLO 系列用的是 txt 标签文件,每行一个目标,格式是“类别编号 + 中心点 x + 中心点 y + 宽 + 高”,后四个值必须是相对图像宽高归一化到 0 到 1 之间的小数。我那位朋友的坑就在这里——他用的工具导出的是像素坐标,直接喂进去,模型看到的框位置全在图外,自然什么都学不到。
类别设计上有几条硬经验。第一,类别编号必须从 0 开始、连续、不跳号,并且和配置文件里 names 列表的顺序严格对应。中间空一个号,或者顺序写反,模型会把 A 类学成 B 类,而且指标看起来还挺正常,极难发现。第二,能合并的类别尽量合并,比如“大螺丝”和“小螺丝”,如果你的业务只要检出螺丝,就不要拆成两类,拆分只会让每类的样本变少、混淆矩阵变难堪。第三,对于边界模糊的类别,提前定好判定规则并写进标注文档,比如“遮挡超过 50% 不标”“目标宽度小于 8 像素不标”,规则定得越具体,多人标注的一致性越高。
标注完成后务必做一次可视化抽查。写个小脚本把标签画回原图上,随机抽二三十张看,重点看三件事:框是否贴合目标、有没有漏标、类别有没有标错。这一步花二十分钟,能避免后面几小时的无效训练。我见过最离谱的一次,是某批数据的标签和图片序号整体错位了一位,画出来框全在错误的位置上,肉眼一看就发现了,但如果不看,训出来的模型会表现出一种“知道有东西但位置永远偏一点”的诡异状态。
2.3 数据集目录结构与配置文件
目录结构这块,主流实现基本统一:一个 images 目录放原图,一个 labels 目录放同名 txt,各自再分 train 和 val 子目录。注意图片和标签必须同名(只是扩展名不同),并且目录层级镜像对应。一个可直接抄的结构如下:
dataset/ ├── images/ │ ├── train/ │ │ ├── 0001.jpg │ │ └── 0002.jpg │ └── val/ │ ├── 0101.jpg │ └── 0102.jpg └── labels/ ├── train/ │ ├── 0001.txt │ └── 0002.txt └── val/ ├── 0101.txt └── 0102.txt配置文件是一个 yaml,几个主流版本写法略有差异,但核心字段一致:
path: /data/dataset # 数据集根目录,建议写绝对路径 train: images/train val: images/val nc: 3 # 类别数量 names: 0: screw 1: washer 2: defect这里有几个细节值得强调。path 建议写绝对路径,相对路径在不同工作目录下跑,行为不一致,很容易出现“跑得起来但读不到图”的情况。路径里尽量不要有中文和空格,这块在某些系统的文件读取上会出玄学问题,虽然新版实现好了很多,但没必要去赌。另外 nc 和 names 的长度必须一致,多写一个少写一个,训练一开始就会报错或者直接静默出错。
标签文件里如果某张图没有任何目标(负样本),对应的 txt 文件应该是空文件而不是不存在。有些实现的容错处理不一样,空文件是最稳的做法。
2.4 数据划分与增强策略
划分训练集和验证集,最忌讳的是“随机划分”这四个字不加思考地用。如果你的数据来自视频抽帧,随机划分会让同一段视频的相邻帧同时出现在训练集和验证集里,验证指标会虚高十几个点,你以为模型泛化很好,实际上一换场景就废。正确做法是按场景、按批次、按拍摄日期做划分,保证验证集的分布和训练集有真实的差异。
比例上,八成训练两成验证是常见起点。数据量少的时候(比如总量低于 1000 张),可以考虑五折交叉验证来更充分地评估,但工程上为了省事,我一般还是固定一份验证集,只要保证它的分布足够有代表性就行。验证集一旦定下来就不要再动,否则你没法横向比较不同实验的效果。
数据增强这块,训练阶段默认会开马赛克增强(把四张图拼成一张)、随机缩放、色彩抖动、水平翻转。这些默认配置在多数场景下是有效的,但有两个例外要手动处理。一是垂直方向有明确语义的场景(比如俯拍的零件,翻转后不符合物理常识),要关掉垂直翻转。二是小目标密集的场景,马赛克增强会把目标缩得更小,反而不利于收敛,这时候可以在训练的最后十几轮关掉马赛克(多数实现提供了 close_mosaic 参数),让模型在接近真实分布的图像上做收尾微调,指标通常会有明显提升。
3. 训练配置:参数背后的取舍逻辑
3.1 从预训练权重出发的迁移学习
自建数据集几乎不应该从零开始训。预训练权重里包含的是通用的边缘、纹理、形状特征,这些特征在绝大多数视觉任务里都是通用的,拿它做初始化,能让模型在几百张图上就收敛出可用效果;从随机初始化开始,同样的数据量大概率训不出东西。这个差距在小数据集上是数量级的,不是百分比。
加载预训练权重时要注意两点。一是类别数不一致是正常的,加载时框架会自动跳过检测头的分类分支,只加载主干和颈部,日志里通常会打印“跳过某些层的权重”之类的信息,这不是错误。二是如果改了输入通道数(比如把三通道 RGB 改成单通道灰度,或者加上红外通道),主干第一层也对不上,这时候要么改配置让它重新初始化那一层,要么在数据加载阶段把图转成三通道,我一般选后者,改动最小。
还有一种情况是“增量训练”:你之前已经用自己的数据训过一个版本,现在新增了一批数据想接着训。这时候用自己训出来的权重做初始化,学习率要调小(大概是原来峰值的十分之一到五分之一),否则新数据会把旧知识冲掉,出现“学了新的忘了旧的”。这在工业场景里非常常见,值得提前规划好数据版本和权重版本的对应关系。
3.2 关键参数逐个拆解
参数不用背,但要理解每个参数在控制什么。我把最常调的几个列出来,配合取值依据:
| 参数 | 作用 | 常用取值 | 取值依据 |
|---|---|---|---|
| imgsz | 输入分辨率 | 640 / 512 / 416 | 必须是 32 的整数倍;小目标多就往上加 |
| batch | 批大小 | 8 至 32 | 显存能承载的最大值往下留一档余量 |
| epochs | 训练轮数 | 100 至 300 | 数据量越小,需要的轮数越多,但要防过拟合 |
| lr0 | 初始学习率 | 0.01(SGD)/ 0.001(Adam) | 批大小翻倍,学习率大致可翻倍 |
| patience | 早停耐心值 | 30 至 50 | 验证指标连续这么多轮不涨就停 |
| workers | 数据加载进程数 | 4 至 8 | 不超过 CPU 核心数,Windows 下建议 0 至 2 |
imgsz 这个参数最值得展开说。它必须是 32 的整数倍,因为网络在下采样过程中累计缩放了 32 倍,输入尺寸不是 32 的倍数会在特征图对齐时出问题。选多大的依据是你的目标在图中占多少像素:如果一个小目标在原图上只有 20 像素宽,你缩到 640 输入,它可能只剩 10 像素,特征图上几乎没有响应了。这种情况下要么提高输入分辨率,要么在采集阶段就靠近拍。粗暴的判断标准是:缩放到输入尺寸后,最小目标不应小于 8 到 10 像素。
batch 的取值有个隐性约束:太小会让批归一化的统计量不稳定,训练曲线抖动明显。老一些的实现在 batch 小于 8 时会给出警告,新版靠梯度累积之类的机制缓解了,但我的经验还是尽量保证在 8 以上,实在显存不够就降低 imgsz 而不是降 batch。
学习率这块,很多教程上来就写 0.01,但那是针对较大批大小和 SGD 优化器的。如果你用 Adam 系优化器,0.001 起步更稳。判断学习率是否合适,看训练前 10 轮的损失曲线:掉得太慢说明小了,前期剧烈震荡甚至出现 nan 说明大了。
一个可直接用的训练命令示例(命令行写法,各版本大同小异):
yolo detect train \ data=/data/dataset/mydata.yaml \ model=yolov8s.pt \ imgsz=640 \ batch=16 \ epochs=150 \ patience=40 \ workers=8 \ device=0 \ project=/data/runs \ name=exp_screw_v1project 和 name 这两个参数建议每次都写清楚,不然跑十几个实验之后,你会在一堆自动命名的目录里彻底迷失。我的习惯是 name 里带上数据集代号、模型尺寸和版本号,比如 exp_screw_s_v1,一眼能看出跑的是什么。
3.3 训练过程监控与中断恢复
训练启动之后,第一件要看的是数据加载是否正常。日志里会打印训练集和验证集的图片数量、类别分布,如果这个数字和你预期的不一致,立刻停下来检查路径和标签。我遇到过标签文件里有一行格式错误(少了一个字段),整个文件被静默跳过的情况,结果某个类别在统计里凭空消失了。
接下来看损失曲线的形状。正常情况是前几轮快速下降,然后进入缓慢下降的长尾阶段,中间会有小幅波动。如果损失在第 20 轮之后还在剧烈震荡,多半是学习率偏大或者数据里有大量脏标注。如果损失降得极慢,检查一下是不是冻结了主干而忘了解冻。
训练过程中最有价值的监控项其实是验证集上的 mAP 曲线,而不是训练损失。训练损失一直降但验证 mAP 持平甚至下降,就是典型的过拟合信号,这时候早停机制会帮你停下来,你也可以手动把最佳权重取出来用。
中断恢复是长训练必须要考虑的。多数实现会在每轮结束后把状态存到 last.pt 里,加上 resume=True 就能接着训。但这里有个坑:resume 依赖的是完整的训练状态,如果你中途改了配置文件或者换了数据,resume 会出问题甚至静默用错配置。我的习惯是长训练之前先把配置文件复制一份到输出目录里存档,出问题时能对照。
3.4 训练日志里的异常信号怎么读
有几个信号我总结成了“看到就查”的清单:
- 分类损失居高不下,框回归损失正常:通常是类别标签错乱,或者类别极度不均衡,某个类别样本太少。
- 框回归损失震荡且不下降:标签格式问题(归一化 vs 像素坐标)、标注框尺寸异常(宽高为 0 或为负)。
- 验证 mAP 从第一轮开始就异常高(比如 0.9 以上):几乎可以确定是数据泄漏,训练集和验证集有重复或高度相似样本。
- 训练损失远低于验证损失且差距持续扩大:过拟合,减模型尺寸、加增强、减轮数。
- 某一类别的 AP 恒为 0:该类别的标签编号写错,或者配置文件里 names 顺序错位。
这些判断没法靠工具自动给出,但你每跑一次实验把这几条过一遍,几次之后就能形成直觉。
4. 推理预测:从权重到能看的图
4.1 单图、批量、视频与摄像头的调用方式
训练完成后,权重文件(通常是 best.pt)就可以直接拿来做推理。最简单的单图预测:
from ultralytics import YOLO model = YOLO("/data/runs/exp_screw_s_v1/weights/best.pt") results = model.predict( source="/data/test/0001.jpg", imgsz=640, conf=0.25, iou=0.45, save=True, project="/data/pred", name="single" )批量预测只需要把 source 指向一个目录,框架会自动遍历。视频预测把 source 指向视频文件,逐帧推理后合成输出视频,这一点在做实际验证时特别有用,因为单帧看不出误检的时间连续性,而视频里误检往往是闪烁的,一眼就能识别。
摄像头实时推理就是把 source 设成摄像头索引,这时候要关注的不只是精度,还有帧率。我一般的做法是先在视频文件上跑,确认效果稳定之后再做实时,因为实时调试的反馈链路太短,来不及看清细节。
注意:推理时的 imgsz 要和训练时保持一致,或者至少保持同样的缩放逻辑。训练用 640、推理用 1280,虽然能跑,但特征尺度发生变化,精度通常会有可见的下降。
4.2 置信度与 NMS 阈值怎么调
conf 和 iou 这两个阈值是推理阶段最能影响观感的参数。conf 是置信度阈值,低于它的框直接丢弃;iou 是 NMS 的重叠阈值,两个框的交并比超过它就只保留分数高的那个。
调参的方向取决于你的业务更怕哪种错误。怕漏检(比如安全相关的场景),conf 往下调,0.15 到 0.2 都可以试,代价是误检变多,要靠后续规则过滤。怕误报(比如自动计数),conf 往上调到 0.4 到 0.5,宁可漏几个也别数错。iou 的调整场景窄一些,主要针对密集目标:目标挨得很近时,iou 设太大(比如 0.7)会把两个真实目标合并成一个,设太小(0.3)又会对同一个目标产生多个框。密集场景我一般用 0.4 到 0.5。
还有一个不太常用但很好使的参数是类别无关 NMS(agnostic NMS)。当两个类别在视觉上高度相似、模型经常对同一个目标同时输出两个不同类别的框时,打开它能让这两个框互相抑制,只留分数高的那个。这在“合格品/次品”这类外观差异极小的二分类任务里效果明显。
4.3 指标怎么读:别只看一个 mAP
验证输出的指标里,最常被引用的是 mAP50 和 mAP50-95。前者的意思是 IoU 阈值取 0.5 时的平均精度,后者是在 0.5 到 0.95 之间每隔 0.05 取一个阈值再平均,后者严格得多。两个数一起看才有意义:mAP50 高而 mAP50-95 低,说明模型能大概找到目标,但框的位置不够准,这种情况下如果你的应用只需要知道“有没有”,那够用;如果需要精确测量尺寸,那还差得远。
除了 mAP,我更关注三样东西。PR 曲线能看出在某个置信度下精确率和召回率的平衡点,据此定 conf 阈值比拍脑袋靠谱。混淆矩阵能暴露类别之间的系统性混淆,比如“划痕”被大量判成“污渍”,这就是特征区分度不够,要么合并类别,要么补充区分性样本。每类的 AP 分布能看出短板,整体 mAP 0.8 但某一类只有 0.4,那这一类就是下一步优化的重点。
还有一个小技巧:把验证集的预测结果全部画出来存成图,人工扫一遍。指标是统计量,会掩盖个案的荒谬错误。我曾在一次任务里发现 mAP 挺漂亮,但画出来一看,模型把所有反光点都框成了缺陷——这类问题只有看图才能发现。
4.4 导出与部署侧的速度优化
训练用的 PyTorch 权重在部署时往往不是最优选择。导成 ONNX 或者针对特定硬件做转换,推理速度通常能有显著提升。常见的路径是先导出 ONNX,再根据目标平台转成对应的推理格式。
from ultralytics import YOLO model = YOLO("/data/runs/exp_screw_s_v1/weights/best.pt") model.export(format="onnx", imgsz=640, simplify=True, opset=12)导出时有几个细节要注意。imgsz 在导出时就要定死,导出的模型输入尺寸是静态的,后面推理必须用同样的尺寸,否则会报错或输出错乱。simplify 建议打开,能消掉一些冗余算子,速度提升明显。opset 版本要和目标推理引擎兼容,不是越高越好。
如果要进一步做 INT8 量化,需要准备一批校准图(一两百张代表性的图即可),量化后精度通常会掉一两个点,速度能提升一倍左右,是否划算取决于你的场景对延迟的敏感程度。我的经验是,在边缘设备上,量化带来的收益通常值得那一两个点的精度损失;在服务器端,用 FP16 就够了。
5. 常见问题与排查实录
5.1 训练不收敛、指标不涨的排查顺序
这是我被问得最多的一类问题。排查应该从最底层往上层走,而不是一上来就调学习率。第一步,确认标签能被正确读到:把数据集配置文件里的路径打印出来,手动确认文件存在,再看训练日志里统计的实例数量和你标注的数量是否一致。第二步,把标签画回图上看,确认坐标归一化正确、类别编号正确。第三步,用 20 张图做过拟合测试——只训这 20 张,看能不能把损失压到接近 0。如果连过拟合都做不到,说明问题在数据链路或者模型配置上,不是泛化能力问题。
过拟合测试是我最推荐的一个诊断手段,因为它把问题域切得很干净:能过拟合,说明链路是通的,问题在数据量和泛化;不能过拟合,说明链路本身有错,继续调超参数是浪费时间。
5.2 显存不足与训练速度异常的排查
显存不够最直接的解法是降 imgsz 或者降 batch,但顺序有讲究:优先降 batch,imgsz 尽量保持,因为输入分辨率直接影响小目标的检出能力,而降 batch 只是影响训练稳定性。如果 batch 已经降到 4 还是不够,再考虑降 imgsz,每次降 64,降到 512、448、416。
如果开了 AMP 还 OOM,可以检查一下是不是验证阶段爆的显存——验证时也会前向传播,而且有时批大小设得比训练还大。另外,某些版本在做马赛克增强时会临时拼接大图,峰值显存比常规训练高,可以试着调小马赛克的概率。
训练速度慢则要分清是 GPU 利用率低还是数据加载慢。看 GPU 利用率,如果长期在 30% 以下波动,基本可以确定是数据加载成了瓶颈,解法是增加 workers、把数据集放到更快的磁盘上、或者开启图像缓存(把解码后的图缓存在内存里,代价是吃内存)。
提示:workers 在 Windows 上设太大容易出进程卡死的问题,表现为训练启动后长时间无输出。遇到这种情况先把 workers 设成 0 或 2 试一下,这是平台差异导致的,不是配置写错。
5.3 推理结果异常的典型原因
框位置整体偏移、大小不对,八成是训练和推理的预处理不一致。检查两边的 imgsz 是否一致、是否都做了同样的 letterbox 填充。有些自写的推理脚本忘了保持长宽比直接拉伸,导致框位置系统性偏移。
同一目标出现多个重叠框,说明 NMS 没生效或者 iou 设得过大。检查推理代码里是否正确传入了 iou 参数,以及是否在自定义后处理里重复做了 NMS。
某个类别从来检不出来,先看验证指标里这个类别的 AP 是不是接近 0,如果是,回到训练侧查标签编号和 names 顺序;如果训练侧 AP 正常,那问题在推理的类别映射表上,可能是导出模型时的类别名和推理脚本里读的不一致。
全部图都检不出东西,先看 conf 是不是设得太高,再确认输入图像的通道和归一化方式是否和训练一致。有些自定义推理代码忘了做 0 到 1 的归一化,模型输入全是很亮的像素,输出全是乱的。
5.4 一份排查速查表
| 现象 | 最可能的原因 | 优先验证方式 |
|---|---|---|
| 损失降但一个框都不出 | 标签坐标未归一化 | 把标签画回原图看位置 |
| 验证 mAP 异常高 | 训练/验证集数据泄漏 | 检查是否同源视频抽帧 |
| 某类 AP 恒为 0 | 类别编号与配置错位 | 打印数据集类别统计对比 |
| 框位置系统性偏移 | 推理预处理与训练不一致 | 核对 imgsz 和 letterbox 逻辑 |
| 密集目标被合并 | NMS 的 iou 阈值偏大 | 降到 0.4 至 0.45 重测 |
| 背景图大量误检 | 缺少负样本 | 补 5% 至 10% 纯背景图 |
| 训练一轮就 OOM | batch 或 imgsz 过大 | 先降 batch,再降 imgsz |
| GPU 利用率长期偏低 | 数据加载瓶颈 | 加 workers、开启缓存 |
6. 几个我踩过之后才记住的细节
第一个是验证集要“冻结”。我在早期项目里犯过一次错,看到验证集上某一类表现太差,就把那几张图挪回训练集重新训,指标立刻好看了,但上线后完全不是那个表现。验证集一旦用来指导决策,它就不再是客观的评估集了。后来的做法是留一份完全不动用的测试集,只在最后验收时跑一次。
第二个是权重版本管理。同一个数据集上跑十几次实验,如果没有清晰的命名和记录,你会彻底搞不清哪个权重对应哪次改动。我的做法是每次实验在输出目录里放一个说明文件,记下数据版本、模型尺寸、关键参数改动和验证指标,几行字的事,省下的时间难以估量。
第三个是小目标场景优先考虑切图推理。如果你的目标在原图上只有几十像素,而整图分辨率又很高,与其硬提输入分辨率(显存吃不消),不如把大图切成重叠的小块分别推理再合并结果。这个思路在遥感、工业检测里非常常见,效果提升比调参明显得多。
第四个是别迷信默认的 300 轮。很多配置默认给了很大的轮数,配合早停机制其实没什么问题,但如果你没有设 patience,模型会在过拟合区间里白白训很久,最后保存的 best 权重可能反而不如中间某一轮的。设上 patience 并定期看曲线,比一口气训到底要靠得住。
最后分享一个我在数据量偏少时常用的做法:先把模型在一个相近的、样本更充足的数据集上训一轮,把学到的权重作为自建数据集的初始化,再训一遍。这个“两段式”的策略在样本只有几百张时特别有效,收敛速度和最终精度都比直接用通用预训练权重更好,代价只是多花一次训练时间。