第 5 章 · 正样本分配——训练的隐藏指挥官
开场:考试阅卷的公平性问题
假设你是一个高考阅卷组长,负责决定”哪些答案算对、哪些算错”。听起来很简单——答案写对了就给分嘛。但现实中远比这复杂:有的学生写了部分正确的答案,给不给分?同一道题有多个正确表述,怎么判定?如果阅卷标准太严,大部分学生都得零分,老师的反馈信号太弱,学生无从知道自己哪里做得不错;如果太松,所有人都得高分,学生也分不清自己的优势在哪。
目标检测训练中的正样本分配(Label Assignment)解决的是完全一样的问题:模型在 8400 个位置都做了预测,哪些预测应该被标记为”正样本”(你的预测和真实目标匹配上了),哪些标记为”负样本”(你预测错了或者这个位置没目标)?
这个决策看似只是训练的一个细节,实际上是检测模型训练中最关键的设计选择之一——它直接决定了模型能学到什么、学得多好。
为什么正样本分配如此重要?
让我们用具体数字理解这个问题的规模。
一个 YOLOv8 模型在 640×640 输入下,三个尺度的特征图总共有 8400 个预测位置。而一张 COCO 图像平均只有约 7 个目标。这意味着:
正样本比例:每个目标分配约 10 个正样本 → 70 个正样本
负样本数量:8400 - 70 = 8330 个负样本
正负比:约 1:119
正负样本严重不平衡。 如果不加任何特殊处理,模型会迅速学到一个捷径:”什么都不检测”——因为预测全是背景时,8330/8400 = 99.2% 的预测都是”正确”的。
正样本分配策略决定了:
- 谁是正样本:哪些位置的预测和真实目标配对
- 正样本的质量:分配策略是否选出了真正有价值的训练信号
- 梯度信号的强度:正样本太少则训练信号稀疏,太多则引入低质量信号
方案一:静态分配——最朴素的方法
基于 IoU 阈值的分配
最早的方法很直接:计算每个预测框和每个真实框的 IoU,IoU 超过阈值的就是正样本。
# 静态分配的伪代码
for each prediction in 8400:
for each ground_truth in targets:
iou = compute_iou(prediction, ground_truth)
if iou > 0.5:
mark as positive sample # 正样本
elif iou < 0.4:
mark as negative sample # 负样本
else:
ignore # 忽略,不参与训练
问题:IoU 阈值怎么选?选 0.5 太松——很多质量不高的预测也被当成正样本,引入噪声。选 0.7 太严——很多位置都分不到正样本,训练信号太弱。而且同一个阈值对大目标和小目标的效果不一样——大目标很容易达到 IoU 0.7,小目标可能最好的预测 IoU 也就 0.5。
日常类比:这就像考试只有一条及格线——60 分。但如果数学题很难、英语题很简单,同一条线就不公平了。好的阈值应该对不同难度的”题目”(不同大小的目标)有不同的标准。
ATSS(Adaptive Training Sample Selection)
ATSS 是静态分配的一个改进。它根据每个真实框在不同特征层上的 IoU 分布自适应地确定阈值:计算 IoU 的均值和标准差,把阈值设为 mean + std。这样每个目标有自己的动态阈值,不再一刀切。
方案二:TAL——Ultralytics / YOLO-Master 的选择
TaskAlignedAssigner 的核心思想
TAL(Task-Aligned Assigner)是 YOLOv8 引入的正样本分配策略,也被 YOLO-Master 继承。它的核心洞察是:好的正样本应该同时在分类和定位两个任务上都表现好。
传统方法只看 IoU(定位好不好)来分配正样本,但这忽略了一个问题:有些预测框 IoU 很高(框画得很准),但分类得分很低(类别判断不确定)。把这样的样本当正样本训练,会产生矛盾——分类分支觉得这个位置”不太像是目标”,回归分支觉得”这个框很准”。
TAL 的解法是用一个联合指标:
# TAL 的核心公式
alignment_metric = cls_score ** alpha * iou ** beta
# 其中 alpha = 1.0, beta = 6.0(YOLOv8 默认值)
# 含义:
# cls_score = 分类得分(0-1),表示模型有多确定这是一个目标
# iou = 预测框和真实框的 IoU(0-1),表示框画得有多准
# alpha = 分类得分的权重
# beta = IoU 的权重(6.0,比 alpha 大得多,说明更看重定位质量)
日常类比:TAL 选正样本就像公司招聘——不是只看成绩好(分类准)也不是只看经验多(IoU 高),而是综合评分。而且 beta=6.0 说明实际操作能力(IoU)的权重远大于笔试成绩(分类分数)——这和检测的直觉一致:一个框画得极准的预测远比一个”看起来像”但框不准的预测有价值。
TAL 的完整流程
# ultralytics/utils/tal.py 简化版流程
class TaskAlignedAssigner:
def __init__(self, topk=13, alpha=1.0, beta=6.0):
self.topk = topk
self.alpha = alpha
self.beta = beta
def forward(self, pred_scores, pred_bboxes, gt_labels, gt_bboxes):
# Step 1: 计算每个预测和每个 GT 的对齐度量
align_metric = pred_scores ** self.alpha * iou ** self.beta
# Step 2: 对每个 GT,选 Top-K 个对齐度最高的预测作为候选正样本
topk_metrics, topk_idxs = align_metric.topk(self.topk, dim=-1)
# Step 3: 在候选中做位置约束
# 只有预测位置在 GT 框内部的才保留(空间先验)
is_in_gt = check_point_in_gt(pred_centers, gt_bboxes)
# Step 4: 如果一个预测位置同时被多个 GT 选中,分配给 IoU 最大的那个
# 每个预测最多只能是一个 GT 的正样本
# Step 5: 返回正样本掩码和目标标签
return target_labels, target_bboxes, fg_mask
关键细节解析:
topk=13:每个 GT 选 13 个候选正样本。这个数字不大不小——太大会引入低质量正样本,太小会导致训练信号稀疏。
空间先验:候选位置的中心点必须落在 GT 框内部。这是一个合理的假设——一个位于 GT 框外部的预测框即使 IoU 不低,也大概率是偶然匹配。
冲突解决:如果一个预测位置被多个 GT 同时选中(比如在两个目标的重叠区域),只分配给 IoU 最大的那个。这避免了同一个位置被迫同时预测两个不同的目标。
方案三:匈牙利匹配——DETR 范式的选择
一对一分配的思想
RT-DETR(以及所有 DETR 系列模型)使用匈牙利匹配实现严格的一对一分配:每个真实目标最多只分配一个预测,每个预测也最多只匹配一个目标。
TAL (一对多):
GT_1 → [pred_5, pred_12, pred_13, ..., pred_98] (13 个正样本)
GT_2 → [pred_200, pred_201, ..., pred_215] (13 个正样本)
推理时需要 NMS 去重(同一 GT 的多个正样本会产生多个检测结果)
匈牙利匹配 (一对一):
GT_1 → pred_5 (恰好 1 个正样本)
GT_2 → pred_200 (恰好 1 个正样本)
推理时不需要 NMS(天然每个目标只有一个预测)
匈牙利算法怎么工作?
匈牙利算法是一个经典的组合优化算法,解决的是”最优匹配”问题——给定 N 个预测和 M 个真实目标,找到一种一对一匹配方案,使得总匹配代价最小。
# RT-DETR/rtdetr_pytorch/src/zoo/rtdetr/matcher.py 简化版
from scipy.optimize import linear_sum_assignment
class HungarianMatcher:
def forward(self, pred_logits, pred_boxes, gt_labels, gt_boxes):
# Step 1: 计算代价矩阵 (N_pred × M_gt)
cost_class = -pred_logits[:, gt_labels] # 分类代价:预测该类别的概率
cost_bbox = torch.cdist(pred_boxes, gt_boxes, p=1) # L1 距离
cost_giou = -generalized_iou(pred_boxes, gt_boxes) # GIoU 代价
# 总代价 = 加权求和
cost = 2 * cost_class + 5 * cost_bbox + 2 * cost_giou
# Step 2: 匈牙利算法求解最优匹配
row_ind, col_ind = linear_sum_assignment(cost.cpu())
# row_ind: 被选中的预测索引
# col_ind: 匹配到的 GT 索引
return row_ind, col_ind
日常类比:匈牙利匹配就像毕业季的”双选会”——每个毕业生(预测)只能签一家公司(GT),每家公司(GT)只能录取一个人(预测)。算法的目标是找到一种匹配方案,使得所有”配对”的满意度总和最大。
匈牙利匹配的 Trade-off
优势:
- 天然一对一,推理时不需要 NMS
- 推理延迟完全确定(不受目标数量影响)
劣势:
- 训练信号极其稀疏:300 个 query 中只有 7 个(图中 7 个目标)获得正标签
- O(N^3) 的计算复杂度(虽然 N 通常只有 300,可以接受)
- 收敛更慢,需要更多训练 epoch
方案四:双分配策略——YOLOv10 的”两全其美”
核心设计
YOLOv10 观察到 TAL(一对多)和匈牙利匹配(一对一)各有优势和劣势:
TAL (一对多):
✓ 训练信号丰富(每个 GT 有多个正样本)
✗ 推理时需要 NMS
匈牙利匹配 (一对一):
✓ 推理时不需要 NMS
✗ 训练信号稀疏,收敛慢
YOLOv10 的解法是:训练时同时用两种策略,推理时只用一对一的那个。
训练时:
Backbone → 特征 → one2many 分支 (TAL, 提供丰富梯度)
→ one2one 分支 (匈牙利匹配, 学习去重)
推理时:
Backbone → 特征 → one2one 分支 → 直接输出 (无 NMS)
(one2many 分支丢弃)
为什么 one2one 分支要 detach?
# one2one 分支的输入做了 detach
one2one = self.forward_feat([xi.detach() for xi in x], ...)
如果不 detach,one2one 分支的损失会通过 backbone 反向传播,与 one2many 分支的损失冲突——因为两个分支的训练目标不同(一个要”找到所有好的预测”,一个要”只留下最好的那个”)。detach 之后,one2one 分支只训练自己的检测头参数,backbone 只受 one2many 分支的梯度影响。这就是”一致性”双分配——两个分支共享 backbone 特征,但各自优化各自的目标。
和 YOLO-Master 的关系
YOLO-Master 目前使用 TAL(一对多)分配,推理时仍然需要 NMS。但 YOLOv10 的双分配策略提供了一种可能的改进方向——在 YOLO-Master 的 MoE 骨架上叠加 one2one 分支,同时获得”自适应计算”和”NMS-free”的双重收益。这是犀牛鸟潜在的贡献方向之一。
四种策略的对比
| 维度 | 静态 IoU | TAL | 匈牙利匹配 | 双分配 |
|---|---|---|---|---|
| 分配方式 | 一对多 | 一对多 | 一对一 | 一对多 + 一对一 |
| 是否需要 NMS | 是 | 是 | 否 | 否 |
| 训练信号强度 | 中 | 强 | 弱 | 强 |
| 分配质量 | 低(固定阈值) | 高(任务对齐) | 高(全局最优) | 高 |
| 使用项目 | 早期 YOLO | YOLOv8, YOLO-Master | RT-DETR | YOLOv10 |
| 计算复杂度 | O(N) | O(N×K) | O(N^3) | O(N×K) + O(N^3) |
鸡生蛋问题:正样本分配的根本困难
正样本分配的根本困难是一个鸡生蛋问题:
要训好模型 → 需要好的正样本分配
要好的正样本分配 → 需要模型的预测质量好(TAL 依赖 cls_score 和 IoU)
要模型预测质量好 → 需要训好模型
→ 循环!
所有的分配策略都是在这个循环中找不同的突破口:
- 静态分配:完全不依赖模型预测,用固定规则——简单但不optimal
- TAL:依赖模型当前预测(cls_score, IoU),训练过程中分配策略会随着模型进步而自动调整——这其实是一种”课程学习”(curriculum learning),早期分配宽松,后期随着模型变好分配也变严格
- 匈牙利匹配:依赖全局最优匹配,也受模型预测影响,但约束更强(严格一对一)
- 双分配:用 one2many 提供稳定的训练信号,用 one2one 逐步学习去重
没有完美的解法——每种策略都是在”训练信号强度”和”分配质量”之间做取舍。这也是为什么正样本分配至今仍然是检测领域的活跃研究方向。
在代码中的具体位置
如果你想在源码中找到正样本分配的实现:
| 项目 | 文件路径 | 核心类/函数 |
|---|---|---|
| Ultralytics (YOLOv8) | ultralytics/utils/tal.py |
TaskAlignedAssigner |
| YOLO-Master | ultralytics/utils/tal.py (继承自 Ultralytics) |
TaskAlignedAssigner |
| YOLOv10 | ultralytics/utils/tal.py + nn/modules/head.py |
TaskAlignedAssigner + v10Detect |
| RT-DETR | rtdetr_pytorch/src/zoo/rtdetr/matcher.py |
HungarianMatcher |
| mmdetection | mmdet/models/task_modules/assigners/ |
ATSSAssigner, HungarianAssigner, 等 20+ 种 |
mmdetection 在这方面最丰富——它实现了 20+ 种不同的分配器,可以在配置文件里一行切换。这正是它作为”学术工具箱”的价值——方便做对比实验。
初学者常见误区
误区一:”正样本越多训练效果越好”
→ 正确理解:正样本数量和质量需要平衡。太多正样本会引入大量低质量的训练信号(IoU 很低的框也被当成正样本),导致模型学到的是”大致在这个区域”而不是”精确在这个位置”。TAL 的 topk=13 是经过大量实验确定的平衡点。
误区二:”匈牙利匹配比 TAL 更先进”
→ 正确理解:两种策略解决的是不同的问题。匈牙利匹配天然适合端到端检测(不需要 NMS),但训练信号稀疏、收敛慢。TAL 训练效率高但推理时需要 NMS。两者没有绝对的优劣,选择取决于你是否需要 NMS-free 推理。
误区三:”正样本分配只影响训练,和推理无关”
→ 正确理解:分配策略间接影响推理。TAL 训练出的模型可能在同一个目标周围产生多个高置信度预测(因为训练时一个 GT 有多个正样本),所以推理时必须用 NMS 去重。而匈牙利匹配训练出的模型学会了”一个目标只给一个预测”,推理时不需要 NMS。分配策略决定了推理流水线的形态。
读完本章你能做什么
读完这一章,你应该能够:
- 解释为什么正样本分配是检测训练中最关键的设计选择
- 用自己的话描述 TAL 的核心公式
metric = cls_score^α × IoU^β的含义 - 比较 TAL、匈牙利匹配、双分配三种策略的优缺点
- 理解”鸡生蛋问题”——为什么正样本分配是一个根本性的困难
- 知道 YOLO-Master 使用的是 TAL 策略,以及这意味着什么
下一章我们来看正样本分配的”另一面”——推理时的 NMS 后处理,以及怎么去掉它。
导读目录:README.md 上一章:第 4 章 · YOLO 家族演进 下一章:第 6 章 · NMS 与端到端检测