犀牛鸟 2026 研究笔记 estelledc.github.io

第 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

优势:

劣势:


方案四:双分配策略——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)
要模型预测质量好 → 需要训好模型
→ 循环!

所有的分配策略都是在这个循环中找不同的突破口:

没有完美的解法——每种策略都是在”训练信号强度”和”分配质量”之间做取舍。这也是为什么正样本分配至今仍然是检测领域的活跃研究方向。


在代码中的具体位置

如果你想在源码中找到正样本分配的实现:

项目 文件路径 核心类/函数
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。分配策略决定了推理流水线的形态。


读完本章你能做什么

读完这一章,你应该能够:

下一章我们来看正样本分配的”另一面”——推理时的 NMS 后处理,以及怎么去掉它。


导读目录:README.md 上一章:第 4 章 · YOLO 家族演进 下一章:第 6 章 · NMS 与端到端检测