做目标检测这几年,我经手过的调优需求少说也有几十个。大部分人来问的时候,问题都不是 "模型能不能跑",而是 "模型跑起来了,但 mAP 卡在 0.6 上不去" 或者 "离线指标挺好看,一上产线漏检一堆"。高效调优目标检测模型这件事,本质不是调参玄学,而是一套有先后顺序的工程流程:先把目标和基线立住,再把数据看透,然后才轮到网络结构、损失函数、学习率这些大家最爱聊的东西。顺序错了,你在学习率上折腾一周,最后发现是标注框松紧不一致,这种亏我吃过不止一次。这篇内容适合三类人看:刚跑通第一个检测模型、想把它调到自己数据集上的新手;卡在瓶颈期、指标怎么都上不去的中间层;以及要把模型塞进边缘设备、必须在精度和延迟之间做取舍的工程同学。下面我按自己实际干活的顺序,把每个环节的判断依据、参数取值和踩过的坑都摊开讲。
1. 调优之前先明确一件事:你要优化的是哪个数
动手改任何一行配置之前,我会先跟需求方确认一件事:最终被谁看、按什么标准验收。这句话听起来像流程废话,但它决定了后面所有技术选择的走向。检测模型的指标太丰富了,mAP50、mAP50-95、每类 AP、小目标 AP、召回率、误检率、单帧延迟、显存占用,任何两个指标之间都可能是互相拉扯的关系。你按 mAP50-95 去调,模型会倾向于把框贴合得更紧;你按产线漏检率去调,模型会倾向于多吐框,哪怕误检变多。这两个方向的参数调整几乎是相反的。
1.1 把业务语言翻译成模型指标
我习惯做一张对照表,把需求方的口语描述落成可以计算的量。这一步做完,后面每次实验的结论才有地方落。举个常见的例子,某工业质检场景提的要求是 "小的划痕不能漏,大的脏污误报无所谓"。翻译过来就是:小尺度目标的召回率优先,置信度阈值可以往下压,NMS 的 IoU 阈值要相对保守以免把相邻小目标合并掉,评估时不能只看整体 mAP,要单独拉出面积小于 32×32 的那一档 AP 来看。
下面这张表是我常用的翻译模板,你可以直接套:
| 业务说法 | 对应指标 | 直接影响的操作 |
|---|---|---|
| 不许漏检 | Recall / 小目标 AP | 降 conf 阈值、提高正样本分配下限、加强小目标增强 |
| 误报太多 | Precision / FP 数量 | 提高 conf 阈值、收紧 NMS、清理困难负样本 |
| 框要贴得准 | mAP75 及以上 | 换更精细的回归损失、减小输入缩放比例 |
| 要跑在低算力设备 | 延迟 / 显存 | 降分辨率、剪枝蒸馏、量化 |
| 类别之间别搞混 | 混淆矩阵对角线 | 检查标注一致性、调分类损失权重 |
注意:验收指标一旦定下来,就不要在中途换。我见过一个项目前期按 mAP50 调,调到最后两周突然改成按 mAP50-95 验收,结果之前为了提升召回做的所有增强全部变成了负资产。
1.2 建立一条谁都能复现的基线
调优最怕的不是指标低,而是不知道自己改的那一步到底有没有用。所以我做的第一件事永远是搭一条 "傻瓜基线":用官方默认配置、默认增强、默认学习率,完整跑一遍训练到收敛,把这组结果、日志、权重、数据划分文件全部存档。这条基线的意义在于它是后续所有对比实验的锚点。没有它,你改完一个参数看到 mAP 涨了 0.3,根本判断不出是参数起了作用,还是这次随机种子刚好比较友好。
基线还要保证可复现的三件事:固定随机种子、固定数据划分文件、固定环境版本。我一般是把种子设为 0,然后在训练脚本开头把 Python、NumPy、深度学习框架三处的种子都设一遍,同时打开确定性算子开关。这样做会牺牲一点点速度,但换来的是每次实验的差异都真实来自你的改动。
import os, random import numpy as np import torch def set_seed(seed=0): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) os.environ["PYTHONHASHSEED"] = str(seed) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False环境这块我建议直接用一份锁死的依赖清单,别偷懒。同一个配置在不同版本的框架上差出 1 个点 mAP 的情况我遇到过两次,排查过程极其痛苦,因为你会发现所有参数都一样,结果就是对不上。
2. 数据决定了天花板,模型只是逼近它
这句话在检测任务里比在分类任务里更成立。检测的标注成本高,标注质量参差不齐,一个数据集里同时存在框太松、框太紧、漏标、重复标、类别定义模糊这五种问题太正常了。我个人的经验是,一个中等规模的自建数据集(几千到几万张),把标注问题清理干净带来的收益,通常比换一个更强的骨干网络更大。所以调优流程里,数据的排查优先级要排在所有模型改动之前。
2.1 数据体检:先看分布,再看标签
体检我一般分三步走。第一步看尺寸分布和长宽比分布,把所有图的宽高画成散点图,你会立刻发现数据里混进了多少分辨率异常的图。这些异常图如果不处理,在统一缩放的预处理里会被拉得变形,模型学到的东西就是错的。第二步看每类目标的实例数量和目标面积分布,这一步直接决定你后面要不要做类别重采样、要不要针对小目标单独开增强。第三步才是逐张抽查标签,我通常随机抽 200 张可视化,再专门抽 200 张目标数量最多和最少的那批看。
标签层面的问题有几个高频项,我列出来对照检查:
- 框贴得太松,把背景大量框进去,模型学到的回归目标偏大,推理时框会普遍偏大
- 框贴得太紧,只框住了目标的一部分,模型对小目标的定位会漂
- 同一目标被标了两次,训练时正样本重复计数,评估时会被算成一个 TP 一个 FP
- 该标没标,这类问题最隐蔽,因为损失函数不会报错,模型只是默默学会了 "这个位置不该有目标"
- 类别边界模糊,比如 "破损" 和 "污渍" 到底怎么分,不同标注员理解不一致
第三类问题我有个笨但有效的办法:用当前基线模型对训练集做一次推理,把高置信度但和标注不匹配的框挑出来人工看。如果模型很确信那里有东西而标注里没有,大概率是漏标。这个方法一次能捞出一批漏标样本,效率比纯人眼扫高得多。
2.2 数据划分里的隐形炸弹
这一条我单独拎出来讲,因为它造成的指标虚高最容易被忽略。如果训练集和验证集里存在同一段视频的相邻帧、同一场景的连拍图、或者同一张原图经过不同增强得到的副本,那么验证集指标会明显偏高,而你据此做出的所有调优决策都建立在虚假的精度上。等模型上了真实场景,性能会断崖式下跌,那时候再回头查划分,成本已经很高了。
我的做法是按 "源" 分组划分,而不是按图片随机划分。同一个拍摄批次、同一个工件、同一段连续采集的数据,整组只进训练集或只进验证集。工业场景尤其要注意这一点,因为同一个工件在不同角度拍几十张是常态。
2.3 增强策略:不是越多越好
增强这块,新手最容易犯的错是把能开的增强全开满。实际上一组增强只有在 "它模拟了真实场景中会出现的变换" 时才是有益的。你在室内固定机位场景里开大幅度的随机旋转和透视变换,模型学到的是不存在的分布,指标反而会掉。
我常用的增强组合和它们的实际作用是这样的:颜色抖动(亮度、对比度、饱和度、色调)用来对抗光照变化,是性价比最高的一项;随机缩放加随机裁剪用来提升尺度鲁棒性,但要控制裁剪后目标被截断的比例,我一般限制在 20% 以内;水平翻转几乎无副作用,除非你的场景有明确的左右语义(比如文字、方向性零件);马赛克增强对提升小目标和上下文利用很有效,但它会让训练初期的收敛变慢,所以通常只在训练前 80% 到 85% 的轮次开启,最后十几轮关掉让模型回归真实分布。
这里有个细节值得说:关闭马赛克的那最后十几轮,往往是你 mAP 提升最明显的阶段。很多人训到一半看曲线平了就停了,恰恰错过了这段。我一般把总轮次的最后 10% 到 15% 设为关闭增强的 "收敛期",学习率也已经降到很低,这一段几乎每次都能稳定带来 0.5 到 1.5 个点的提升。
3. 训练配置怎么定:学习率、批量与损失的联动
模型结构改动牵扯大、验证周期长,而训练配置的调整成本低、见效快,是我在项目中期最常动的地方。但配置之间是联动的,单独改一个参数往往会失效甚至变差,这一点必须理解透。
3.1 批量大小和学习率必须一起动
学习的本质是沿着梯度方向走一步,批量大小决定了这一步梯度估计的噪声水平,学习率决定了步长。批量变大,梯度噪声变小,理论上可以用更大的步长。这就是所谓的线性缩放规则:批量翻倍,学习率翻倍。但这个规则只在批量不是特别大的区间内成立,超过某个规模之后梯度已经足够稳定了,再线性放大学习率会导致训练发散。
我的实际取值经验是分两档。批量在 8 到 64 之间时,用 SGD 的话基准学习率按lr = 0.01 × batch / 64来算,比如批量 16 就取 0.0025;用 AdamW 这类自适应优化器的话基准学习率取 1e-4 到 2e-4,批量翻倍时学习率的放大系数给 1.2 到 1.5 就够了,不要线性放大。批量超过 128 我一般就不再用 SGD 了,改自适应优化器,然后用梯度累积来凑出等效批量,而不是真的去开大显存。梯度累积有个副作用要注意:批归一化的统计量是按实际前向批量算的,累积并不会改变它,所以当实际批量小于 8 时,批归一化的统计会很不稳定,这时候要么换分组归一化,要么冻结归一化层。
显存估算也顺手说一下,因为你经常需要通过它来反推能开多大批量。粗略公式是:显存占用 ≈ 权重和梯度显存 + 激活值显存。前半部分等于参数量乘以 4 字节再乘以一个系数(含梯度和优化器状态,AdamW 大概是 3 到 4 倍);后半部分与批量、输入分辨率面积成正比。这意味着把输入从 640 降到 512,激活显存大约降到原来的 64%,几乎能让你把批量翻一番。
# 训练配置片段(数值为我常用的起点) epochs: 120 batch: 16 imgsz: 640 optimizer: SGD lr0: 0.0025 # 对应 batch=16 的线性缩放结果 lrf: 0.01 # 末期学习率 = lr0 * lrf momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3 warmup_momentum: 0.8 warmup_bias_lr: 0.1 box_loss_gain: 7.5 cls_loss_gain: 0.5 dfl_loss_gain: 1.5 close_mosaic: 15 # 最后 15 轮关闭马赛克增强3.2 预热和指数滑动平均这两个小开关
预热的作用是在训练最开始、权重还完全随机的时候,用很小的学习率慢慢把模型 "扶上马"。如果直接上目标学习率,前几个批次的巨大梯度会把权重冲乱,表现出来的现象是损失曲线开头剧烈震荡,甚至出现 NaN。预热轮次我一般给总轮次的 3% 到 5%,短训任务(30 轮以内)就给 1 到 2 轮,别给多了,给多了等于浪费训练预算。
指数滑动平均(EMA)是另一个几乎零成本、但收益稳定的技巧。它维护一份模型权重的滑动平均副本,用这个副本来做评估和最终导出。衰减系数取 0.9998 到 0.9999 之间,训练轮次越多可以取得越接近 1。它的价值在于平滑训练后期的抖动,我实测下来在验证指标上通常能白捡 0.3 到 1 个点,尤其在小批量、训练后期损失震荡比较厉害的时候效果更明显。
提示:EMA 衰减系数和训练步数要匹配。如果你总共只有几百步训练,衰减系数取 0.9999 会导致平均副本几乎跟不上主权重,评估出来的结果会明显偏低。步数少的时候把衰减系数调到 0.99 到 0.995 这个区间。
3.3 损失权重的调整逻辑
检测损失一般是三块的加权和:分类损失、框回归损失、以及分布焦点损失这类定位质量相关的项。默认权重通常是分类 0.5、回归 7.5、质量项 1.5 这个量级。如果你发现模型的框位置大致对但分类老出错,说明分类分支欠拟合,可以把分类权重往上提到 0.8 到 1.0,同时观察回归指标有没有下降;反过来,如果类别判断很准但框总是偏,就加大回归权重。
这里有个反直觉的点:回归权重不能无限加大。回归损失本身数值量级就比分类损失大(默认配比 7.5 : 0.5 已经差 15 倍),再往上加会让梯度主要来自定位分支,分类分支学不动,最后模型会退化成 "位置对了但类别全猜"。我在一个三分类的小数据集上就踩过这个坑,把回归权重提到 12 之后 mAP50 掉了 3 个点,回到 7.5 立刻恢复。
另一块是正负样本分配策略。动态分配(按预测质量挑正样本)比静态的 IoU 阈值分配效果好,尤其在密集小目标场景。但动态分配有个参数是每个真值分配的正样本数量上限,这个值我一般给 10 到 13。目标密集时给大一点(13),目标稀疏且尺度差异大时给小一点(6 到 8),因为给小目标硬塞 13 个正样本会导致它们的特征学得过于激进。
4. 锚框和输入分辨率:影响最大的两个 "结构参数"
这两个参数共同决定了模型能 "看见" 什么尺度的东西,改动它们的效果往往比换损失函数明显得多。但它们也是最容易被无脑沿用的,因为默认值就写在配置文件里,很多人根本没想过要动。
4.1 锚框要用你自己的数据重新聚类
通用数据集上的锚框尺寸和你自己的数据集大概率不匹配。目标检测里锚框本质上是 "先验框",它决定了每个位置初始预测的尺度和长宽比。如果你的目标平均是 20×60 这种细长形状,而锚框都是接近正方形的,那模型需要花大量容量去修正这个先验偏差,收敛慢且精度受限。
重新聚类的方法很直接:取出训练集所有真值框的宽高,做 k-means 聚类,距离度量用1 - IoU而不是欧氏距离,因为我们要的是框形状的相似度。聚类中心数量取 9(三个尺度各三个锚框)是通用配置,如果你的目标只有一个尺度区间,可以减到 6。
import numpy as np def iou_wh(boxes, clusters): # boxes: (N, 2), clusters: (K, 2) b = boxes[:, None, :] # N,1,2 c = clusters[None, :, :] # 1,K,2 inter = np.minimum(b, c).prod(-1) union = b.prod(-1) + c.prod(-1) - inter return inter / (union + 1e-9) def kmeans_anchors(wh, k=9, iters=100, seed=0): rng = np.random.default_rng(seed) clusters = wh[rng.choice(len(wh), k, replace=False)].copy() for _ in range(iters): d = 1.0 - iou_wh(wh, clusters) # N,K assign = d.argmin(1) for j in range(k): if (assign == j).any(): clusters[j] = wh[assign == j].mean(0) return clusters[np.argsort(clusters.prod(1))]聚类完成后不要直接信,先看一眼每个锚框覆盖了多少真值框。如果某个锚框只覆盖不到 1% 的真值,说明它是冗余的,可以考虑去掉或者合并。聚类结果还要按面积排序后分别分配到不同尺度的检测头上,通常是小的给高分辨率特征图,大的给低分辨率特征图,这个分配不能搞反。
4.2 分辨率不是越大越好,要算清楚代价
提高输入分辨率的收益主要在小目标上,因为小目标在低分辨率下经过多次下采样后可能只剩不到一个像素,特征图上直接消失了。但代价是显存和计算量按面积平方增长。从 640 提到 1280,像素数变成 4 倍,推理延迟大概变成 3 到 4 倍(考虑算子效率,不是严格 4 倍)。
我的取舍方法是:先算出你的目标在输入分辨率下的最小像素尺寸。假设你的最小目标在原图上大概是 16×16 像素,那么输入尺寸至少要保证它在缩放后仍有 8×8 以上,同时考虑到网络最后一个检测头的下采样倍率是 32,你要保证这个目标在最低层特征图上至少占 1 个格子。如果算下来 640 已经够用(小目标缩放后仍有 20 像素以上),那就别升分辨率,把算力花在别处。
一个更聪明的做法是保持训练时用较大分辨率、推理时降回小分辨率,或者用多尺度训练(每若干个批次随机换一个分辨率,范围取 0.5 到 1.5 倍)。多尺度训练能让模型对尺度变化更鲁棒,代价只是训练慢一点,几乎不增加推理成本。这个技巧我在实际项目里用得很频繁,尤其是目标尺度跨度大的场景,通常能提升 1 到 2 个点,而且推理端不用付任何账。
5. 把模型跑起来的最后一段:推理后处理与部署侧调优
很多人调优只调训练侧,觉得训练指标上去了任务就完成了。实际经验是,产线上感知到的 "模型不好用" 里,有相当一部分是后处理和部署环节的参数没调对。这一块投入产出比非常高,往往改两个数字就能解决一大批投诉。
5.1 置信度阈值和 NMS 阈值要分开调
置信度阈值决定了多少框被保留下来,NMS 的 IoU 阈值决定了重叠框怎么合并。这两个参数的作用方向不同,必须分开做实验。评估模型能力时,置信度阈值要压到极低(比如 0.001)去算 mAP,因为 mAP 关心的是整条 PR 曲线;但部署时阈值要按业务调,通常是取 PR 曲线上 F1 分数最高的那一点,或者按 "召回优先" 往前推一段。
NMS 阈值我常用的起点是 0.45 到 0.5。如果场景里目标经常紧挨在一起或者互相遮挡,就要往下调到 0.3 到 0.4,否则相邻目标会被合并掉一个,表现出来就是 "两个东西只检出一个"。反过来,如果同类目标几乎不可能重叠,把阈值提到 0.6 到 0.7 能减少误删。对于密集场景,我强烈建议试试软性抑制或者按类别分开做 NMS,后者对 "多类别混在一起" 的场景提升明显,因为不同类别的框本来就不该互相抑制。
# 部署前扫一遍阈值组合,挑 F1 最高的那一组 import itertools best = None for conf in [0.15, 0.2, 0.25, 0.3, 0.35, 0.4]: for iou in [0.3, 0.4, 0.45, 0.5, 0.6]: tp, fp, fn = evaluate(detector, val_loader, conf=conf, iou=iou) p = tp / (tp + fp + 1e-9) r = tp / (tp + fn + 1e-9) f1 = 2 * p * r / (p + r + 1e-9) if best is None or f1 > best["f1"]: best = dict(conf=conf, iou=iou, f1=f1, p=p, r=r) print(best)注意:这个扫描一定要在独立的验证集上做,绝对不能在训练集上扫。我见过有人直接在训练集上扫出 conf=0.15、IoU=0.5 的组合,上线后发现误检泛滥,原因是训练集上的置信度分布被过拟合了,普遍偏高。
5.2 量化与导出:别让精度在最后一步掉光
模型要部署到边缘设备,量化是绕不过去的。我的建议是先做导出侧的验证:把训练好的模型导出成中间格式,用同一批图像分别跑一遍原始框架和导出后的模型,逐张比对输出的框坐标和分数,确认数值误差在可接受范围(一般框坐标误差小于 0.5 像素,分数误差小于 1e-3)。这一步不做,后面量化出问题时你根本分不清是导出本身的误差还是量化引入的误差。
训练后量化我通常先试动态量化,它对精度影响小、改动少;如果延迟还不达标,再上静态量化,需要准备 200 到 500 张有代表性的校准图。校准集的选择很关键,一定要覆盖各个类别和各种光照条件,不能随手抓一批空场景图。我遇到过一次精度掉 6 个点的案例,排查半天发现校准集里 90% 是同一时段的画面,量化参数明显偏了,换成语料均衡的校准集后只掉 1.2 个点。
量化之后必须要做的验证是按类别看 AP,而不是只看整体。整体掉 1 个点可能某一类掉了 8 个点,只是被其他类别平均掉了。另外,量化对定位精度的影响通常比对分类的影响小,如果你发现框位置漂得厉害,那多半是量化范围没设好,检查一下是否有异常大的激活值把量化区间撑爆了。
5.3 模型蒸馏:用小模型追大模型的一条捷径
如果精度和延迟实在没法兼顾,蒸馏是很划算的一招。做法是先训一个大模型当教师,再训一个目标小模型,学生模型除了学真实标签,还要模仿教师的输出。检测任务的蒸馏一般落在两个地方:分类头的软标签和回归头的位置分布。分类那部分用温度参数软化,温度取 2 到 4;回归部分可以直接对学生和教师在正样本位置上的预测做一致性约束。
我这边的经验是,蒸馏通常能让小模型拿到大模型 70% 到 85% 的差距回收,成本只是多训一个教师模型。但有个前提:教师和学生要在同一套数据预处理和同样的输入分辨率下工作,否则一致性约束会对不上。
6. 常见问题与排查实录
调优过程中真正耗时间的是排查,而不是调参数。我把这些年记录下来的症状和对应处理整理成一张速查表,基本覆盖了八成以上的情况。
6.1 问题速查表
| 症状 | 最可能的原因 | 优先验证的动作 |
|---|---|---|
| 训练损失正常下降,验证 mAP 从第一轮就不涨 | 数据划分泄露或验证集太小 | 按源重新划分,验证集至少 500 张 |
| mAP50 高但 mAP50-95 很低 | 框贴合度差,回归分支欠拟合 | 提高回归损失权重,检查标注框松紧 |
| 某一类 AP 明显低于其他类 | 该类样本少或类别边界模糊 | 统计实例数,重采样,复核标注定义 |
| 训练后期损失突然发散 | 学习率末期过低或数据异常批次 | 检查异常图像,看学习率调度曲线 |
| 小目标几乎检不出 | 输入分辨率不足或正样本分配阈值过严 | 提高分辨率,放宽小目标分配下限 |
| 相邻目标只检出一个 | NMS 阈值过高 | 降到 0.35 到 0.4,或改按类别 NMS |
| 推理结果比验证结果差很多 | 预处理不一致(归一化、通道顺序、缩放方式) | 逐张比对两端预处理输出 |
| 置信度普遍偏高 | 过拟合或验证集和训练集同分布 | 检查划分,加增强,加权重衰减 |
| 多卡训练比单卡还慢 | 数据加载成瓶颈或通信开销占比过大 | 提高工作进程数,开启预取,检查带宽 |
用这张表的时候我想强调一点:永远从最便宜的检查项开始。数据加载、预处理一致性、划分问题,这些检查成本低、命中率高;而换骨干网络、改损失函数这些成本高的操作放到最后。我见过太多人一上来就换骨干,换了三个网络指标还是那样,最后发现是验证集里混进了训练图。
6.2 我踩过的三个坑
第一个坑是盲目相信增强。有一次在一个目标尺度本来就很大的数据集上,我沿用了小目标场景的增强配置,开了大幅随机缩放和马赛克。结果是模型学到的都是被裁得残缺的目标,验证时 mAP 掉了 4 个点。后来把缩放范围从 0.5 到 1.5 收到 0.8 到 1.2,马赛克关掉,指标立刻回来了。这件事让我形成了一个习惯:每加一组增强,都单独跑一次消融,确认它在这个数据集上真的是正收益。
第二个坑是忽略推理侧的数字。有个项目训练指标达标,交付后反馈 "有时候会把一个东西框成一个大的整块"。查了很久,发现是模型的一个检测头在小目标上输出了大量低分框,NMS 的时候这些框被合并成了一个覆盖全图的大框。解决办法很简单:给每个检测头的输出加面积上限过滤,超过图像面积 40% 的框直接丢掉。这个改动只花了十分钟,解决了一个看起来像是模型能力的问题。
第三个坑是评估口径不一致。训练框架算 mAP 时用的 IoU 阈值序列和产线验收脚本用的不一致,一个是 0.5 到 0.95 步长 0.05,一个只看了 0.5。这个差异导致双方对同一个模型的判断差了将近 8 个点,白开了两次会。从那以后我要求所有项目在开始阶段就先对齐评估脚本,把这个脚本作为交付物的一部分固定下来。
6.3 一份可以直接照搬的调优顺序
最后把我现在实际执行的顺序列一遍,这套流程在多个项目上验证过,能避免大部分无效折腾:
- 固定随机种子、环境、数据划分文件,跑出一条默认配置的基线并完整存档
- 数据体检:尺寸分布、类别分布、面积分布,随机抽 200 张看标签质量
- 用基线模型对训练集推理,把高分但与标注不匹配的样本挑出来人工复核,修漏标
- 用聚类重算锚框,检查覆盖率
- 按业务指标确定输入分辨率,能不动就不动;不能确定就先试两档做对比
- 调批量与学习率,跑一组 3 次的重复实验确认稳定性
- 加 EMA、调预热,确认收敛曲线更平滑
- 逐组加增强,每组单独消融,负收益的直接删掉
- 调整损失权重,重点看分类和回归两块的平衡
- 最后 10% 到 15% 的轮次关闭强增强,做收敛
- 导出模型,比对数值误差,再进量化流程
- 在独立验证集上扫置信度和 NMS 阈值,按业务指标挑组合
这套顺序的核心思想是:越靠前的步骤改动成本越低、影响越大,越靠后的步骤越精细但收益越局部。很多人反过来做,先调最后一步的阈值,再回头改数据,等于在建好的房子上换地基。
7. 我个人在长期调优中沉淀的几点判断
真正做久了会发现,调优这件事最难的从来不是知道有哪些技巧,而是判断 "什么时候该停"。我给自己定了一条线:如果连续三组实验的指标波动都在 0.5 个点以内,说明已经进入噪声区间了,继续调参数的收益是负的,该转向数据或者换思路了。很多项目卡住不是技术不够,是没人喊停,一直在同一个维度上打转。
另一个体会是关于实验记录。我要求自己每次实验都记三样东西:改了什么、为什么改、结果如何。这三行字看着简单,但当你做到第 40 组实验时,没有记录你根本记不清哪些组合试过了。我现在用一个极简的表格维护,一行一次实验,字段就是编号、改动描述、mAP50、mAP50-95、单帧延迟、结论。这个习惯帮我省下的时间,比我学过的任何一个调优技巧都多。
最后一个建议是,不要一开始就上最复杂的方案。检测领域的新方法层出不穷,但大多数项目的瓶颈并不在方法本身,而在数据质量和工程一致性上。我做过的一个项目,最终交付的模型配置和第一次跑通的配置比,只改了四个地方:重算的锚框、降下来的输入分辨率、关掉的一组有害增强、以及后处理的阈值组合。四个改动,mAP50-95 从 0.41 提到 0.53,推理延迟还降了 30%。那些看起来不酷的活儿,往往才是真正拿分的地方。