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 标签分配。
建议排查顺序
- 打印变换后图像的高宽、每张图像的 GT 数量、框坐标范围和前几个标签,确认图像与框使用同一坐标系。
- 统计每个 GT 的最高 anchor IoU,并检查匹配及采样后的正负标签数量。
- 确认 FPN 特征图数量、anchor 层级顺序,以及替换生成器后 RPN 使用的配置是否已更新。
- 用一个小批次关闭自定义损失和额外训练逻辑,观察标准 RPN 的正样本计数和损失是否变化。这有助于缩小问题范围,但不能代替对目标格式和坐标的检查。
- 确认类别映射与 84 个模型类别的设置一致。现有映射示例只展示前五项,无法据此验证全部标签。不过,类别映射通常影响 RoI 分类,不直接决定 RPN 正样本匹配。
数据统计、anchor 配置和损失曲线可以提供排查线索。目前还缺少变换后的目标框、实际匹配标签和 FPN anchor 对应关系,因此无法据此断定某个具体设置就是根因。
备注:内容仅供参考。
