先说一段我在训练YOLO系检测模型时踩出来的感悟一直以为收敛慢是显卡不够大、batch太小、学习率没调好后来才发现很多时候瓶颈特别朴素——我们一遍遍把模型早就滚瓜烂熟的图片重新喂进去而那些真正学不会的难样本反而被稀释了。Anti-Forgetting Sampling反遗忘采样这个思路简单说就是让模型“跳过已学会的图片”把算力集中在还没搞定的样本上。听起来很像偷懒但我实测下来训练时间大概缩短了三分之一最终mAP不但没掉还涨了零点几个点。这篇文章就把这个训练小技巧从原理到实现完整拆开适合已经在用YOLO训练模型、觉得收敛太慢又不想动模型结构的人参考。1. 先想明白训练时“炒冷饭”为什么拖垮收敛1.1 均匀采样时谁在主导梯度方向默认情况下YOLO的训练循环会对整个数据集的图片做随机打乱然后每个epoch把所有图片都过一遍。这个流程从工程上无可挑剔但从梯度更新的角度看非常浪费。一张纯色背景、目标又大又清晰的图片模型可能在第2个epoch就能把它分类到0.99置信度检测框也稳得不行。而另一张目标被遮挡、光照诡异的图片训了20个epoch还在反复横跳。问题在于训练时每个样本对损失的贡献并不相同。简单样本的损失值通常已经压得很低难样本的损失值一直居高不下。当数据集里简单样本占大头时每一轮梯度更新都被大量“低损失样本”主导难样本梯度的占比被压缩。最后的表现就是从曲线看损失在正常下降但到后期loss降不动了mAP也卡在瓶颈。这个现象跟人复习考试很像已经滚瓜烂熟的题反复做每做一遍能提升的分数微乎其微反而是一直做错的难题如果不定期回顾下次还是会错。均匀采样等于强制所有人每天做全套试卷不管你是已经考了99分还是只考了40分。1.2 遗忘机制比“重复学习”更隐蔽的杀手跳过已学会的图片最直接的好处是节省算力。但如果真的把所有简单样本全部扔掉新的问题马上出现遗忘。神经网络训练是一个用新batch覆盖旧知识的过程。当某个类别的图片在一段时间内完全没有出现在训练集中模型对这个类别的决策边界就会逐渐漂移。这种漂移通常以两种形式出现一是分类置信度整体下降二是检测框回归精度变差。我去翻过不少训练日志最典型的情况是某个稀有类别的样本大约10个epoch没被采样到之后再验证时这类别的mAP直接掉了两三个点。所以Anti-Forgetting Sampling的核心不是“删数据”而是“按需复习”。学会的图片不是永远消失而是降低它的出现频率容易遗忘的难样本和稀有类别样本反而要有更高的回放概率。这个机制跟持续学习领域的“经验回放”是同一套思路只不过我们把它搬到了常规的目标检测训练里。1.3 和难例挖掘、课程学习的区别很多人第一次听到这个思路会以为它就是难例挖掘。两者确实有血缘关系目标都是把算力往难样本倾斜但出发点不同。难例挖掘比如OHEM、Focal Loss的视角是“我应该更关注哪些难样本”它的操作是给难样本更高权重或强制高频率出现。Anti-Forgetting Sampling的视角是“哪些样本可以少出现甚至暂时跳过”它比难例挖掘多了一层遗忘保护。所以它并不是简单地把低分样本拎出来反复练而是维护了一个动态的样本分数表然后根据分数决定不同样本的采样概率。还有一个容易混的概念是课程学习先学简单、再学难。Anti-Forgetting Sampling没有这种固定的课程顺序它完全由模型当前的学习状态动态决定某张图是否需要出现。模型的判断力越强被跳过的图就越多整个采样过程是自适应的。2. 判断“已学会”的三种信号置信度、间隔分数与损失值2.1 置信度阈值最直觉的方案但有一个盲区最容易想到的判据是模型对这张图的检测置信度已经很高了说明学会了。于是设定一个置信度阈值比如预测得分超过0.95这张图下一轮就不进训练集。实操时这个方案有个致命盲区。YOLO的置信度分数由两部分组成目标存在性objectness和分类置信度class score。两者都会偏高但都不直接反映边框回归质量。也就是说模型可能已经能稳定判断“这里有猫、是猫”但这个猫的框始终偏大或偏小几个像素。只看置信度分数你会误以为它学得很好实际边框精度一直在原地踏步。如果你想用置信度做判据我建议把判断对象从“单张图的置信度”换成“整张图所有目标的平均置信度”并且单独把回归损失拎出来看。否则很容易出现框不准却被当成“已学会”的情况。2.2 top-1/top-2间隔分数比置信度更靠谱的“真学会”信号我在实际项目中更推荐用分类间隔分数来做主要判据。具体做法对某个检测框的分类输出取最大概率类别的得分top1和第二大概率类别的得分top2间隔 top1 - top2。间隔分数体现的是模型在决策边界上的裕度。top1 0.6、top2 0.4和top1 0.98、top2 0.01置信度完全不同但比例上后者明显更“确定”。间隔越大说明决策边界离这个样本越远这个样本越不容易被遗忘扰动。反之如果间隔很小即使top1分数不低模型在这个样本上依然是摇摆状态属于需要高频复习的区间。我把这个信号作为主要筛选项之后“框不准但分类很自信”的误判问题缓解了不少。想要更严格的话还可以把边框的IoU预测分支如果框架里带了或者回归损失项一并纳入打分公式。2.3 损失值作为滞后指标只能当辅助还有一个候选信号是单张图片的损失值。它的优点是直接反映模型对这张图的“不满程度”缺点是比较滞后。因为损失值在batch内是平均值同一批图里难易样本会互相平均单张图的损失信号被稀释得很厉害。如果你想用损失值做判据需要推batch内的逐样本损失工程上比推置信度麻烦一些。我目前的推荐组合是先记录每个样本的top1-top2平均间隔分数再记录该样本的平均回归损失。两张图同时满足高间隔、低回归损失就进入“已学会”名单只要回归损失还偏高就算分类间隔再大也继续正常出现。下面是我在项目里试过的几种“已学会”判据对比。判据计算成本误判风险对定位精度敏感度我的评价平均置信度阈值低较高分类自信不代表框准差只能做初筛top1-top2间隔低中等但更接近决策边界状态中等推荐做主判据逐样本损失值中高相对准确但工程麻烦好适合做辅助间隔 回归损失组合中低好最稳强烈推荐3. Anti-Forgetting Sampling的工程实现跳过不是目的回放才是核心3.1 维护一个带分数的样本表工程实现的第一步是把每个训练样本的“学习状态”记下来。实践中我用一个简单结构就能搞定给每张图片分配一个dict记录当前间隔分数、平均回归损失、最近一次被采样的epoch、以及累计被跳过次数。每一轮epoch结束后对整个数据集的样本表做一次计算。根据最新的模型权重推理一次肯定不现实我的做法是采用“滞后更新”在训练过程中模型本来就要对每个batch做前向计算顺手把逐样本得分记录下来每隔一定轮数汇总一次。这样不需要额外的前向推理额外开销几乎为零。3.2 采样策略高分段跳过低分段回放中分段随机拿到所有样本的分数之后不能直接一刀切。我用的策略是把样本分成三个区间高分段分类间隔高且回归损失低累计得分超过阈值进入“可跳过区”下一轮出现概率设为0.1~0.2低分段分类间隔低或回归损失高属于“重点复习区”下一轮强制出现并且可以在一个epoch内重复采样1~2次中分段分数在中间摇摆说明模型还没完全确定按正常概率出现。这其实是对经验回放思路的一种变体。低分段重复采样是主动加练高分段降低频率是为了腾出算力但保留0.1~0.2的概率是为了抵抗遗忘漂移。完全删掉的方案我试过大概5~8个epoch后分数段可能会重新洗牌那些被完全跳过的类别会重新变成高损失反而两头不讨好。3.3 核心流程伪代码参考下面这个流程可以直接嵌入到YOLO训练循环里。# 伪代码Anti-Forgetting Sampling 核心流程 for epoch in range(epochs): # 训练前动态构建本epoch采样列表 train_indices [] for img_id, record in sample_table.items(): score record[gap] - 0.5 * record[reg_loss] if score high_thresh and record[last_epoch] 0: # 高分段只保留概率 p_low 出现防止完全遗忘 if random.random() p_low: train_indices.append(img_id) elif score low_thresh: # 低分段必定出现并可重复1~2次 train_indices.append(img_id) if score very_low_thresh: train_indices.append(img_id) else: # 中分段正常出现 train_indices.append(img_id) # 用新的索引列表替换原始DataLoader的采样器 sampler CustomBatchSampler(train_indices, batch_size) # 正常训练 for batch in data_loader_with_sampler(sampler): loss, detections model(batch) # 记录逐样本gap和回归损失延迟更新到sample_table update_sample_scores(batch, detections)这里我把每个epoch当成一次重新采样的单位。实际操作时也可以每2000个iteration重采样一次粒度更细但要注意额外的采样和排序时间数据量大时反而更慢。3.4 接入YOLO训练循环时需要注意的两个工程坑第一个坑是DataLoader的兼容性。YOLO系框架最常使用的是COCO格式数据集和默认的随机采样器。直接改数据集索引列表时要小心collate_fn和Mosaic增强的配合。Mosaic会把四张图拼成一张如果你从样本表里单独挑了四张图拼完之后其实已经变成了一个新的图像不能再按原始单张图的分数回溯。我的处理方式是在开启Mosaic增强的epoch跳过这些“可跳过区”的样本只在中后段用正常Mosaic继续训练等Mosaic关闭之后再应用采样策略。第二个坑是多尺度训练。YOLO类模型普遍带多尺度输入同一张图每隔几个iteration会被缩放到不同尺度。样本的难度其实跟尺度有关小目标在低分辨率下会变得更难。所以你在记录样本分数时要约定一个固定的参考尺度或者按尺度段分别维护分数否则会出现同一张图分数忽高忽低的情况。3.5 是否需要配合损失加权有些读者可能觉得硬跳过太粗暴想用损失加权代替给高分段样本乘一个0.1的权重给低分段样本乘1.5。这个思路在Focal Loss里已经被验证过但实操中我发现硬跳过加有限的低概率回放效果比权重更干净。原因在于权重法对梯度方向的改变不如直接采样改变来得明显而且YOLO的损失包括cls、box、obj三部分统一加权很容易破坏各部分之间的平衡。如果你还是想用加权方案建议只对分类损失做加权box回归和obj损失保持原样。从我的测试结果看这种“半加权”方案收敛速度不如硬跳过方案但训练过程更平滑偶尔用于防止剧烈波动也算一个可用选项。4. 实测复盘加速了三分之一精度不降反升但阈值翻过车4.1 实验设置我在一个自建的安防场景检测数据集上做过完整对比测试。这个数据集大约有12000张图片20个类别类别分布不算均匀其中三四个类别的样本数量明显偏少。模型用的是YOLO系中等规模的检测网络输入尺寸640优化器SGD初始学习率0.01总共训练100个epoch。对照组用默认的均匀采样训练实验组在前5个epoch保持均匀采样让模型先建立基础的判断能力第6个epoch开始启用Anti-Forgetting Sampling。配置对照组实验组图片数量1200012000Epoch100100启用策略时间无第6个epoch开始高分段跳过概率无0.15低分段强制重复次数无2总训练时长约20小时约13小时4.2 收敛速度与最终精度的变化实验组的收敛速度在前半段就非常明显。打到第30个epoch时对照组mAP大约为0.52实验组已经到了0.57。分析训练日志可以发现实验组每个batch里无效样本的比例变少了损失下降得更快尤其是box回归损失在中后期几乎每轮都在刷新。到第100个epoch时对照组mAP大约是0.622实验组到了0.628。严格说涨幅也不算很大但关键是总训练时长从20小时降到13小时左右省下来接近35%的算力。如果把这省下来的算力用来多训几十个epoch精度还能继续往上走这是它真正的价值把训练预算换成精度。4.3 阈值参数过高的翻车事故这个策略最需要提防的是高分段阈值设得太松或太紧。我最初把高分段阈值定得很高想让模型“只跳过完美掌握的图”结果高分段几乎没有任何样本能进入跳过区策略形同虚设。后来我把阈值放到0.8即间隔分数在0.8以上、回归损失低于一定数值时跳过学习速度快了很多但跑了一段时间发现另一个问题。有一个稀有类别本身样本数量就少图片特征和另一个类很像。阈值调松之后这个类别的样本被大量判定为“已学会”并进入低概率区大约8个epoch之后模型在这个类别上的mAP出现了明显回退。后来我把评分逻辑改成按类别分组统计每组单独设置阈值的分位数。在统计时边缘类别如果某一张图被判定为高分段还需要满足一个额外条件该图片所在类别最近的验证精度不能低于该类别的历史均值。加了这个保护之后稀有类别回退的坑就填上了。4.4 分epoch开启 vs 全程开启前几个epoch别偷懒还有一个很值得强调的经验前几个epoch不要开启这个策略。原因很简单模型在冷启动阶段对所有样本都没有足够区分度所谓的“间隔分数”没什么判别力此时开启只会提前丢掉一些暂时看起来简单、但后续可能需要细化的样本。我一般设定的规则是第1~5个epoch完全均匀采样第6~15个epoch只开启低分段重复采样不开启高分段跳过第16个epoch开始完整启用三段式采样策略。这种渐进式开启的收益比全程开启更高。从另一组对比看全程开启的实验组比渐进式开启的实验组最终mAP低了约0.8个百分点主要就是早期被误伤的样本太多。5. 和大厂调参党常玩的几个“偷懒”方案放一起比一比5.1 随机裁剪图片/随机丢弃图片简单但不可控有人会觉得既然要偷懒干脆每个epoch随机丢一部分图片不就完了。这确实能省时间但只要丢的比例稍微超过10%mAP就会出现肉眼可见的下降。它跟Anti-Forgetting Sampling的本质区别在于随机丢弃是没有方向性的它不管这张图是简单的还是难的只是均匀地删数据。丢掉的可能是关键难例反而让模型得不到充分训练。算力节省的上限很低基本在10%以内就必须打住了。5.2 OHEM式难例挖掘前期猛后期飘难例挖掘在检测里也经常被提到做法是每个batch只挑损失最高的K个样本参与梯度回传。它的优点很明显训练前期收敛极快几乎每个batch都在啃最硬的骨头。缺点是到了中后期为了让模型保持稳定你必须保留一部分正常样本。如果你把难例比例拉得太高模型会对难例过拟合最终精度反而不如均匀采样。我自己试过OHEM它在variance很大的数据集上波动很厉害验证loss像过山车一样忽高忽低。Anti-Forgetting Sampling和OHEM的一个关键区别是前者保留了低概率回放机制而OHEM则倾向于把所有“非难例”直接踢出计算。前者更稳适合作为长时间训练的主策略后者更适合短时间快速试探数据集的上限。5.3 EMA打分方案更平滑但更慢很多训练框架会通过EMA指数移动平均来维护每个样本的得分避免单次前向计算的噪声。这套方案确实比每轮直接重新打分更平滑也不容易被某个异常batch带偏。但它的问题是EMA带来的平滑会让得分滞后也就是“模型已经学会了一段时间分数还没来得及更新”。在训练节奏快的任务里这种滞后会导致已经学会的样本迟迟不进跳过区加速效果打折扣而遗忘的样本又因为分数滞后迟迟不进低分段复习不够及时。我把EMA和直接打分两种方式并排对比过EMA版本的训练过程更稳但总时长只节省了大约20%低于直接打分版本的30%~35%。如果你的数据集噪声很大、单次前向计算很不稳定EMA值得保留样本本身就比较干净的话直接打分更香。5.4 实际工程里的组合姿势做了这么多对比之后我目前在项目里的默认组合是这样的数据集加载层面用Anti-Forgetting Sampling做动态采样损失函数层面不做额外加权让Focal Loss按原样发挥验证环节每5个epoch跑一次完整验证及时把类别精度回退的苗头抓出来。如果发现某个类别精度明显下滑我会把这个类别的所有样本临时标记为低分段强制下一轮密集出现。这套组合跑下来的体感是除了收敛更快、精度微涨之外训练过程比默认配置更让人安心因为你随时知道哪些样本在“放养”、哪些在“特训”不再是一个黑盒里盲目下注。最后分享一个小教训Anti-Forgetting Sampling不是所有任务都能稳赚不赔。我后来把它挪到一个类别极度不均衡、且数据只有3000多张的小数据集上结果就没那么理想因为样本总量太少了所谓“跳过”很容易把仅有的支撑样本给跳没。如果你也要在小数据集上用建议把高分段出现的概率从0.15提到0.3同时把“低分段强制重复”改为“低分段只出现一次”先求稳再求快。另外一个体会是这类训练策略优化效果往往不是靠一个精妙的设计点完成的而是靠把“每张图是否该出现”这件事从固定流程变成动态决策。多想一想样本层面的调度很多时候比调学习率、换优化器见效得更实在。希望这个思路也能帮你的YOLO训练少踩一点弯路。
阅读完成 · 觉得有帮助?