上周有个做工业质检的朋友把一段训练日志丢给我,说 mAP50 卡在 0.62 上不去了,怀疑是不是模型选小了。我打开他的数据集目录看了一眼:1200 张图里 800 张是同一台设备、同一个光照角度拍的同一种工件,剩下 400 张是从公开数据里扒来的通用图片。问题显然不在模型,而在这份数据教会了模型一件事——背景长什么样,而不是缺陷长什么样。这就是 YOLO 训练自定义模型最典型的翻车现场:命令敲得没错,参数也没填错,错在对整个流程的理解。
YOLO 训练自定义模型,说白了就是拿一个已经在海量通用图片上学会了“什么是物体、什么是边界”的检测器,用你自己的业务数据把它“掰”到你的场景里去。它解决的核心问题是:开箱即用的通用模型认识人、车、猫狗,但不认识你产线上的划痕、你货架上的特定商品、你田里的病斑叶片。这套流程适合谁?适合手上有几十到几千张带标注业务图片、想把检测能力落进实际产品里的开发者,也适合刚入门计算机视觉、想跑通第一个端到端项目的新手。我下面要讲的不是“三步跑通”的爽文,而是从数据准备到部署回归这一整条链上,真正会让你卡住的地方。
1. 先想清楚:什么情况下才该自己训一个模型
1.1 通用检测器能给你什么,给不了什么
很多人上手第一件事是下载权重、跑一张图看看效果,结果发现车能框出来、人能框出来,自己业务里的目标一个都不认识。这不奇怪,通用检测器训练时用的类别体系是固定的几十类常见物体,你的目标大概率不在这个清单里。有人会想绕过去——把“划痕”硬塞进“裂缝”这个已有类别里,指望模型泛化过去。我试过,效果非常勉强,因为两者的纹理特征、尺度分布、出现位置完全不同,模型学到的先验根本对不上。
通用检测器真正能给你的,其实是两样东西:一是骨干网络提取特征的通用能力,这部分迁移性极强,光影、边缘、纹理这些底层特征在几乎所有视觉任务里通用;二是检测头的结构先验,也就是“一个目标应该由一个框、一个类别分、一个置信度来描述”这套形式化表达。你需要做的,是保留第一样、重训第二样,而不是从零开始教一个网络认字。
判断标准很朴素:拿通用模型在你的数据上跑一批图,如果目标的召回率低于 50%,或者框的位置系统性偏移,就该自己训;如果召回率已经 80% 以上只是分类经常错,那可能只需要微调分类头或者补少量数据即可,不必大动干戈。
1.2 三个真实的触发场景
我接触过的自定义训练需求,基本可以归到三类。第一类是类别全新,比如某种特定零件、某种特定包装、某种特定农作物病害,通用模型的词表里压根没有。第二类是形态特殊,目标本身是通用类别,但在你的场景里呈现出通用模型不适应的形态——俯拍视角下的行人、只露出局部的车辆、反光表面的物体、极小尺寸的目标。第三类是精度门槛,通用模型能框个大概,但你的业务要求框得准到像素级,比如测量、计数、定位抓取。
这三类对应的训练策略其实是不一样的。第一类需要相对充足的数据把新类别“教会”;第二类重点在数据增强的设计,让模型见到它在真实场景里会遇到的各种畸变;第三类反而是最难缠的,光靠堆数据收益递减,往往要从输入分辨率、检测头尺度、后处理阈值这几个方向同时下手。
还要提醒一句:不是所有问题都该用检测模型解决。如果只是判断图里有没有某个东西,分类模型就够了,标注成本低一个数量级;如果是需要精确轮廓,分割模型更合适。检测框本质上是一种“粗略定位”,选错任务形式,后面再怎么调参都是白费。
1.3 自训练的成本账,得提前算
很多人低估的是数据成本,不是算力成本。一张图从采集到可用,标注时间通常在 30 秒到 3 分钟之间,取决于目标数量和密集程度。1000 张图的标注,一个人认真做要十几个小时。而这 1000 张图的质量,直接决定了你后面调参调到凌晨三点的意义大不大。
算力这一块反而便宜。中等规模的模型在单张消费级显卡上,640 分辨率、batch 16,一轮(epoch)千张图大概一两分钟,跑一百轮也就几个小时。云端租卡更省事。所以我的建议一贯是:把省下来的算力预算,全部投到数据质量上——多标一百张边界样本,比多跑五十轮训练有用得多。
2. 数据集才是真正的胜负手
2.1 数量、类别分布与“有效样本”
问“要多少张图才够”这个问题,答案永远是“看情况”,但我可以给一个经验区间:单类别、目标明显、背景相对固定的场景,300 到 500 张就能出一个能用的基线;目标小、背景复杂、类别多,1000 到 3000 张起步;如果类别超过 10 类,还要考虑长尾问题,每类至少保证 150 个实例。
比总数更重要的是有效样本数。什么叫无效?同一时刻连拍十张,画面几乎一样,这在统计上只算一张。同一个视频里每隔五帧抽一张,模型看到的是高度相关的数据。我见过一份数据集,三千张图实际上来自两段各五分钟的录像,模型在验证集上表现很好,一换场景就崩,原因就在这里。
还有一个容易被忽略的点:负样本(背景图)的价值。纯背景图,标签文件留空,这类样本能显著降低误检率。我一般会在数据集里掺 5% 到 10% 的纯背景图,尤其是那些“长得像目标但不是目标”的场景,比如空货架、干净的金属表面、没有缺陷的产品。这类样本不增加标注负担,收益却很实在。
2.2 标注规范:漏标比错标更致命
标注这件事有个反直觉的结论:漏标(漏框)的危害远大于错标。原因在于训练机制——目标检测的损失函数里,没被标注的正样本区域会被当作背景参与负样本损失计算。也就是说,你漏掉一个目标,等于在教模型“这个东西不是目标”。而框画歪了一点,模型只是学到一个略微松散的边界,影响有限。
所以标注规范的优先级应该是:宁可框大一点,也不要漏;宁可多标一个可疑目标,也不要少标。当然前提是类别定义清晰。我遇到过最麻烦的项目,是“划痕”和“污渍”的边界定义模糊,三个标注员标出三种结果,模型学到的是噪声。解决办法是把定义写成一句话能说清的标准,比如“长度超过 5 像素、有明确方向性的线状缺陷算划痕,其余不规则色块算污渍”,并且拿 20 张图做交叉校验,把分歧点讨论清楚再批量开工。
标注格式方面,YOLO 系列用的是归一化的 txt:每行一个目标,类别索引 中心x 中心y 宽 高,坐标都除以图片宽高,取值 0 到 1。类别索引从 0 开始,必须和数据集配置文件里的names列表顺序严格对应。这个顺序错位是新手最容易犯的错,症状是模型训练正常、指标看着不错,但推理时类别名全乱套。我一般会在训练前写个脚本统计每个类别的实例数,顺便把类别名打印出来人工核对一遍。
2.3 划分数据集时最容易踩的坑:数据泄漏
随机划分训练集和验证集是标准做法,但在某些场景下会出大问题。举个具体的:你的数据来自 20 段视频,每段视频抽 100 帧。如果用纯随机划分,几乎必然出现同一段视频的相邻帧分别落在训练集和验证集里。相邻帧的内容差异极小,模型在训练时已经“见过”验证集的近亲,验证指标自然虚高。等你上线到新场景,指标断崖式下跌。
正确的做法是按组划分:以视频、以采集批次、以设备编号、以拍摄日期为单位整组划分。比例上训练集 70% 到 80%,验证集 20% 到 30%,测试集单独留一份,从头到尾不参与任何调参决策。测试集的作用是给你一个诚实的数字,如果它和验证集差得很多,说明你的调参已经过拟合到验证集了。
另外一个小细节:验证集不能太小。我见过验证集只有 30 张图的项目,指标波动能到正负 0.1,你根本分不清是这次改动有效还是随机波动。经验值是验证集至少 100 个实例、覆盖所有类别,如果做不到,就老老实实做交叉验证。
3. 环境与配置:让版本号先对齐
3.1 依赖版本是玄学,也是纪律
深度学习环境最烦人的地方在于,同一个报错可能是三个不同原因导致的。我的原则是:不动已经跑通的环境,不追最新版本的依赖。显卡驱动、CUDA、深度学习框架、检测框架这四层之间有一张隐式的兼容表,随便升级一层就可能全盘崩掉。
实操上我推荐用虚拟环境隔离,conda 或者 venv 都行。建好环境后第一件事不是装框架,是确认显卡能用——跑一行代码检查加速库是否可用、打印一下设备名。这一步过不了,后面全是白折腾。装完之后立刻跑一次官方的最小推理示例,确认能出结果,再开始动数据。这个顺序看起来啰嗦,但能帮你把“环境问题”和“数据问题”彻底分开,排查时省下大量时间。
3.2 模型规格怎么选:不是越大越好
YOLO 系列通常提供从 nano 到 extra-large 的多个规格,参数量差几十倍。新手倾向于选中间偏大的,觉得“大一点总没错”,结果训练慢、显存爆、部署时推理延迟高。
我的选型逻辑是这样的:先确定推理侧的硬约束——你的目标设备是什么?如果是边缘设备或者移动端,直接从小规格起步,别想着先训大再压缩,因为量化和小模型化之后的精度损失不是线性的,你先训一个大模型最后压到小设备上,效果可能还不如直接训一个小模型。如果是服务端 GPU,可以从中间规格开始,训完看指标有没有必要往上加。
另外一个维度是目标尺寸。小目标检测对模型规格的敏感度低于对输入分辨率的敏感度。同样是提升精度,把输入从 640 提到 960,收益通常比换一个大一档的模型更明显。这个结论我验证过好几次,因为小目标在经过多次下采样之后,在特征图上可能只剩一个像素甚至消失,模型再大也救不回来。
3.3 显存预算的粗略换算
有人问我 8GB 显存能跑多大的 batch,这个问题必须结合分辨率和模型规格一起看。经验公式是:显存占用大致与 (batch × 分辨率² × 模型规格系数) 成正比。
举个具体的账:中等规格模型,输入 640×640,batch 16,训练时显存占用大约 6GB 上下。如果把输入提到 1280×1280,分辨率翻了 4 倍,同样的 batch 就要 24GB 以上,8GB 卡只能把 batch 压到 3 到 4。这就是为什么高分辨率训练那么贵。
显存不够时的处理顺序,我一般这样排:先降 batch(但别低于 4,太小了批归一化的统计量会不准),再开混合精度训练(能省将近一半显存,对精度影响很小),然后考虑梯度累积(用小 batch 模拟大 batch 的效果,但训练速度会变慢),最后才是降分辨率。降分辨率一定要放在最后,因为它直接改变任务难度。
4. 训练过程:参数怎么调,日志怎么看
4.1 从配置文件开始,而不是从命令行堆参数
训练命令可以写得像一长串咒语,但我不推荐。我的习惯是把数据集路径、类别名、训练轮次、增强强度这些固定项写进配置文件,命令行只覆盖这次实验想变的那个变量。这样做的好处是每次实验的配置可复现,出问题能回溯,也方便做对照。
数据集配置文件的核心字段就几个:训练集和验证集的路径(可以是目录,也可以是列出图片路径的 txt)、类别数、类别名列表。路径建议用绝对路径,相对路径在不同工作目录下跑会出幺蛾子,这种坑我踩过不止一次。
训练超参里最需要关注的三个:初始学习率、训练轮次、批次大小。学习率和批次大小是联动的,批次翻倍通常学习率也要相应上调,否则收敛会变慢。轮次设多少?先设一个偏大的值(比如 200),配合早停机制——验证指标连续若干轮不提升就自动停。这样既不会欠拟合,也不会浪费时间。
4.2 冻结骨干做小样本微调
如果你的数据量很小(几百张),全量训练很容易过拟合。这时候可以冻结骨干网络,只训练检测头部分。原理是骨干负责通用特征提取,这部分在小数据上重训反而会破坏已有的好特征;检测头负责把特征映射到你的类别上,这部分必须重训。
实际操作里,一般先冻结训练若干轮,让检测头先适应,然后解冻全部再训练一轮较小的学习率做精调。这个两段式流程在小数据场景下收益明显,我做过对比,同样是 400 张图,直接全量训练验证集指标 0.71,两段式能到 0.79 左右。
还有一种场景是增量训练:你已经有了一批数据训好的模型,后来又补了一批新场景的数据。这时候可以从已有权重继续训,但要小心灾难性遗忘——模型可能会把新数据学得很好,把老数据忘了。稳妥的做法是新老数据混在一起训,新数据占比不要超过一半,训完拿老场景的图回归测试一遍。
4.3 训练日志里真正该看的东西
日志刷屏的时候,很多人只盯着最后那个 mAP。我建议按这个顺序看:
先看分类损失和定位损失是否在下降,前 10 轮下降快、后面缓慢收敛是正常的。如果损失从头到尾抖得厉害或者直接不降,先怀疑学习率太大或标注有问题。再看验证集的精确率和召回率,这两个指标要一起看:精确率高召回率低,说明模型太保守,漏检多;召回率高精确率低,说明模型太激进,误检多。这两个此消彼长,看你业务更怕哪种错。
然后看混淆矩阵。这个图信息量很大,能直接告诉你哪两个类别在互相混淆。我遇到过一个案例,模型总是把 A 类判成 B 类,查了半天发现是这两个类在数据里经常同框出现、位置重叠,标注时边界没划清。这种问题看整体 mAP 是看不出来的。
最后看验证集的预测可视化。指标是数字,可视化是真相。我习惯每训练二十轮抽一批验证图存下来,训完翻一遍,经常能发现指标没反映出来的系统性问题,比如模型只框了目标的一半、把阴影也框进去了、对小目标完全无响应。
4.4 数据增强:开关和强度都是双刃剑
YOLO 系列默认开启了一整套增强,包括马赛克拼接、色彩抖动、随机缩放、随机翻转等。这套默认值在通用数据集上很有效,但搬到你的场景里未必合适。
马赛克增强把四张图拼成一张,能让模型在有限的轮次里见到更多组合,对提升小目标检测效果明显。但它会破坏目标的真实尺度关系,如果你的任务对尺寸敏感(比如测量类),建议在训练最后 10% 的轮次里关掉它,让模型在真实图像分布上收尾。
随机翻转要看场景。水平翻转对大部分自然场景安全,但对有方向语义的目标(文字、仪表盘刻度、特定朝向的零件)会引入错误标签。我有一次忘了关翻转,模型把倒装件的方向全学反了。色彩抖动在光照变化大的场景里有帮助,但如果你的场景光照是固定的,过强的色彩抖动反而会让模型对颜色特征不敏感。随机缩放要小心上下界,缩得太小会让原本就小的目标变成噪声。
我的建议是:第一轮训练用默认增强跑个基线,然后针对性地调。哪个增强对你的场景不合理就关掉,别一股脑全开也别全部关掉。
5. 评估、导出与上线回归
5.1 指标怎么读才不会被骗
mAP50 是 IoU 阈值 0.5 时的平均精度,比较宽松;mAP50-95 是在 0.5 到 0.95 多个阈值上取平均,严格得多。两个数字差得越大,说明框的定位精度越差——模型能框住但框不准。如果你的业务需要精确框(比如测量、抓取),必须看 mAP50-95,只看 mAP50 会给你虚假的安全感。
还有两个常被忽略的评估维度:按目标尺寸分组的指标和按类别分组的指标。整体 mAP 0.8 听起来不错,但如果拆开看发现小目标只有 0.4、某个稀有类别只有 0.3,那这个模型的可用性就要打问号了。我习惯在验收阶段把这两张拆解表打出来,逐项对业务需求。
置信度阈值也不是拍脑袋定的。精确率-召回率曲线能告诉你不同阈值下的取舍,通常做法是:先确定业务能容忍的最高误检率,然后在曲线上找对应的阈值。这个阈值必须在验证集上定,绝不能拿测试集的结果反过来调,那样测试集就失去意义了。
5.2 导出模型时的对齐细节
训练完的权重是框架私有格式,部署时要导出成通用格式,常见的是 ONNX,再往上是各种推理加速引擎。这一步最容易出的问题不是导出失败,而是导出的模型和训练时的预处理不一致。
具体说:训练时框架会对输入图像做缩放加填充(letterbox),保持长宽比、空白处填灰边,然后把像素归一化到 0 到 1,通道顺序通常是 RGB。导出后如果部署端自己做预处理,任何一个环节对不上,精度都会掉。我见过最典型的错误是部署端直接拉伸缩放(不等比),导致宽高比失真,小目标全部变形。正确做法是把训练时的 letterbox 参数(填充值、是否居中)原样搬过去。
后处理的 NMS 参数也要对齐。训练时用的 IoU 阈值和置信度阈值,部署初版建议保持一致,跑通之后再根据实际表现微调。如果换成量化模型(比如半精度或整型量化),精度会有细微变化,阈值可能需要重新标定,这一步别偷懒。
5.3 上线后的回归测试怎么做
模型上线不等于结束,我一般会建一个小规模的回归集:从真实业务数据里抽 100 到 200 张,人工标好,每次模型更新都跑一遍,记录各项指标。这个集子要定期补充新场景的样本,尤其是那些线上表现不好的案例,把它们标好加进去,下一轮训练就有了针对性。
监控方面,线上至少要记录三件事:每张图的检测数量分布、置信度分布、以及人工复核的误检漏检率。如果某天检测数量突然翻倍或者置信度整体下滑,通常意味着输入数据分布变了——换了设备、改了光照、镜头脏了。这种情况重新训练未必是最优解,先排查数据源往往更快。
我个人的经验是,一个能用的自定义检测模型,从开始标数据到上线,通常要经历两到三轮“训练-看错例-补数据-再训练”的循环。第一轮的结果不用追求完美,能跑通全链路、发现主要问题就够了。真正拉开效果差距的,是后面那几轮有明确针对性的补数据。
6. 常见问题排查速查表
6.1 训练阶段的典型症状
| 症状 | 最可能的原因 | 处理方式 |
|---|---|---|
| 损失直接变成 NaN | 学习率过大、标注坐标越界(不在 0-1 之间)、数据里有损坏图片 | 先把学习率降一个数量级,再用脚本扫一遍所有标签文件检查坐标范围,最后检查图片能否正常解码 |
| 损失完全不降 | 类别索引和 names 顺序错位、标签路径没被正确读到 | 打印数据集统计,确认每个类别都有实例被加载到 |
| 指标剧烈震荡 | batch 太小、验证集太小、学习率过大 | 提高 batch 或开梯度累积,扩充验证集 |
| 只学会一个类别 | 类别严重不平衡 | 对稀有类做重采样,或复制粘贴增强,或提高该类的采样权重 |
| 训练正常但验证很差 | 数据泄漏或训练验证分布不一致 | 按组重新划分数据集,检查两个集合的采集条件差异 |
| 显存溢出 | batch 或分辨率过大 | 按 4.3 节的顺序逐项降低 |
这张表我基本每次项目都会翻出来对一遍。里面最值得强调的是第一行和第五行:前者是硬错误,快速定位就能解决;后者是软错误,指标好看但没意义,危害更大。
6.2 效果类问题的排查思路
模型能训但效果不好,我一般按这个顺序查:先看可视化预测图,判断是漏检、误检还是框不准。漏检为主,先怀疑目标太小或者训练样本里这类目标太少;误检为主,先补背景负样本,再考虑提高置信度阈值;框不准,看 mAP50 和 mAP50-95 的差距,差距大就提升分辨率或者检查标注框是否画得太松。
小目标是重灾区,我单独说几个手段:提高输入分辨率(最直接,代价是算力)、增加一个更浅层的检测头(让高分辨率特征图也参与预测)、把大图切块推理再合并结果(适合超大分辨率图像里的密集小目标)、针对小目标做复制粘贴增强(把小目标实例抠出来贴到其他背景上)。这几个手段可以叠加,但每加一个都要重新评估,别一次全上,否则你不知道是哪个起的作用。
还有一个隐蔽问题:测试时的推理尺寸和训练时不一致。有些框架在推理时会把输入 resize 到固定尺寸,如果你训练用 640、推理默认用了 320,小目标直接消失。这个错误很低级但非常常见,排查时先确认一下。
6.3 几条来自踩坑现场的建议
第一条:永远保留一份基线结果。第一次跑通的配置、权重、指标、预测图,全部存档。后面所有改动都跟这份基线比,别凭记忆。我吃过亏,改了三版之后觉得效果不如从前,但已经不知道从前是什么样了。
第二条:一次只改一个变量。同时调学习率、增强强度、分辨率,指标涨了你也不知道是谁的功劳,指标跌了你更不知道回退哪个。我知道这很反人性,尤其是赶进度的时候,但这是唯一能让经验可复用的方式。
第三条:别迷信公开榜单上的配置。那些参数是在公开数据集上调出来的,你的数据分布、类别数量、目标尺度都不同,直接照搬大概率不如默认值。把公开配置当作起点,而不是答案。
第四条,也是我个人体会最深的一条:标注质量的收益有上限,但没有下限。一份用心标的数据,配上默认参数,往往能出 0.85 以上的结果;一份糊弄的数据,你把所有调参技巧用尽,也就在 0.6 附近晃荡。当你发现怎么调都没起色的时候,别急着换模型,回去翻翻标注文件,随机抽 50 张图对着原图看一遍框,我保证你能发现问题。
最后分享一个提高效率的小习惯:给每个实验目录起一个能自解释的名字,包含日期、改动点、关键指标,比如“0412_加分辨率960_map0.83”。三个月后回头看,你会感谢当时的自己。