先说一段我在训练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训练少踩一点弯路。
先说一段我在训练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训练少踩一点弯路。