PAYATHON 2026

PyTorch Mask R-CNN 的 RPN 为什么几乎没有正样本?

数据阿明

结论

现有信息还不足以确定唯一根因。需要先区分 RPN 的正样本 anchor 和 RPN 生成的 proposals:训练时,RPN 根据 anchor 与真实框的 IoU 分配正负标签,再计算 objectness 和回归损失。经过解码、裁剪、分数筛选和 NMS 后留下的 proposals,则属于后续阶段的结果。因此,分数低或最终 proposal 数量少,不能单独证明训练阶段没有正 anchor。

在常见的 torchvision RPN 匹配逻辑中,低质量匹配会尝试让每个真实框至少匹配一个最高 IoU 的 anchor。如果训练统计显示正标签接近零,应先确认实际送入模型的目标框和匹配结果,再检查是否使用了自定义匹配或损失逻辑。

先核对训练目标和图像变换

数据集摘要中的 zero_area_count: 0 和 bad_coord_count: 0 能提供线索,但要确认检查对象是经过数据增强和模型变换后实际送入 RPN 的框。常见问题包括:

  • 框坐标仍是原图像素,但图像已经缩放;或图像缩放后,框没有同步变换。
  • COCO 的 [x, y, width, height] 格式没有转换为 xyxy,或转换时把宽高误当成右下角坐标。
  • 框超出图像边界、坐标顺序颠倒,或增强后出现零宽高框。
  • 模型收到的 target["boxes"] 不是浮点型 Tensor[N, 4],或类别标签包含背景 0。检测模型的目标标签通常应使用从 1 开始的前景标签。

可以在训练循环中抽查变换后的目标框:

for image, target in zip(images, targets):
    boxes = target["boxes"]
    height, width = image.shape[-2:]

    assert boxes.ndim == 2 and boxes.shape[-1] == 4
    assert torch.isfinite(boxes).all()
    assert (boxes[:, 2] > boxes[:, 0]).all()
    assert (boxes[:, 3] > boxes[:, 1]).all()
    assert (boxes[:, 0] >= 0).all() and (boxes[:, 1] >= 0).all()
    assert (boxes[:, 2] <= width).all()
    assert (boxes[:, 3] <= height).all()

这项检查应放在与模型内部图像变换一致的位置。如果在变换前运行,它只能验证原始样本,无法排除缩放或增强过程中的问题。

检查 FPN 层级与 anchor 匹配

当前配置为五个 FPN 层级分别使用边长 8、16、32、128、512 的 anchor,每个位置有三个宽高比。先确认 RPN 实际收到五个特征图,并核对 anchor 的层级和尺寸顺序是否符合模型的特征图顺序。当前每个位置仍有 3 个 anchor;如果只替换了 anchor_generator,没有改变 anchor 数量,通常不会因为数量不同而与 RPN head 的输出通道不匹配。

8、16、32、128、512 中缺少 64,且数据集中的框尺寸跨度较大,因此需要测量 anchor 与真实框的匹配情况。不过,单看框尺寸分位数,不能断定这些 anchor 一定不合适。关键指标是每个真实框的最高 IoU,以及按实际 matcher 阈值分配后正样本、负样本和忽略样本的数量。

可以用经过图像变换的真实框和模型实际生成的 anchors 统计最高 IoU。anchors 应取自当前 FPN 特征图对应的 anchor 生成结果,不能自行假设图像尺寸或特征层步长:

# boxes: [N, 4],模型变换后的真实框
# anchors: [A, 4],当前图像对应的全部 anchors
ious = box_iou(boxes, anchors)
best_iou_per_gt = ious.max(dim=1).values
print("每个 GT 的最高 anchor IoU:", best_iou_per_gt)

同时,在 RPN 匹配阶段记录匹配标签,并分别统计匹配后和正负样本采样后的数量。如果 GT 的最高 IoU 很低,应检查框坐标尺度、FPN 层级对应关系和 anchor 尺寸。如果最高 IoU 尚可,但采样后没有正样本,则应检查 matcher 阈值、采样配置,以及项目是否改写了 assign_targets_to_anchors 或采样逻辑。具体阈值应以模型实例的实际配置为准,不能只根据构造参数名推定。

如何解读现有训练日志

第 0 个 epoch 的 rpn_cls_loss 约为 0.659,rpn_loc_loss 约为 0.034。这两个数值本身不能证明 RPN 没有正样本:分类损失可能主要来自背景 anchor,回归损失则会受到正样本数量、损失归约方式和具体实现的影响。更直接的证据是每张图像的正 anchor 数量,以及训练前后 RPN objectness logits 是否发生变化。

日志还显示了大量自定义损失项,包括 focal、GIoU、Lovász 和额外的 mask loss。应确认这些改动实际接入了哪个模块。如果它们只作用于 RoI 或 mask 分支,就无法解释 RPN 匹配结果;如果替换了 RPN 的损失或目标分配逻辑,则需要检查标签定义、正负样本掩码和损失归约方式。日志中的 score_thresh 和 nms_thresh 会影响预测筛选,无法修复错误的目标坐标或 anchor 标签分配。

建议排查顺序

  1. 打印变换后图像的高宽、每张图像的 GT 数量、框坐标范围和前几个标签,确认图像与框使用同一坐标系。
  2. 统计每个 GT 的最高 anchor IoU,并检查匹配及采样后的正负标签数量。
  3. 确认 FPN 特征图数量、anchor 层级顺序,以及替换生成器后 RPN 使用的配置是否已更新。
  4. 用一个小批次关闭自定义损失和额外训练逻辑,观察标准 RPN 的正样本计数和损失是否变化。这有助于缩小问题范围,但不能代替对目标格式和坐标的检查。
  5. 确认类别映射与 84 个模型类别的设置一致。现有映射示例只展示前五项,无法据此验证全部标签。不过,类别映射通常影响 RoI 分类,不直接决定 RPN 正样本匹配。

数据统计、anchor 配置和损失曲线可以提供排查线索。目前还缺少变换后的目标框、实际匹配标签和 FPN anchor 对应关系,因此无法据此断定某个具体设置就是根因。

备注:内容仅供参考。