Jack Pan

Phase 2:什么算 ground truth

· 约 4 分钟阅读

Phase-1 概览 收尾时说「Phase 2 是把人工校验回灌进模型的部分」。这是 Phase 2 的第一块拼图:决定哪部分人工校验你真的当标签信。

陷阱

LS 的导出包含 LS 知道的所有东西——包括:

  • 模型写的、没人动过的预标注
  • 标注员开始标然后取消的草稿
  • 标注员提交的标注
  • 因为标注员决定不了被跳过的任务

如果全部拿去微调,两件事会发生:

  1. 「没动过」的预标注就是模型自己的预测。 拿这些训练 = 没有误差信号的自蒸馏。模型对它本来就错的地方变得更自信。
  2. 被跳过的任务恰好是难例。 把它们排除在外,模型就永远见不到它最需要学的那些例子。

两个都把模型往错的方向拉。第一个比第二个更狠。

该跑的过滤

任何一帧成为训练样本之前,先过:

def is_ground_truth(annotation, task):
    if annotation.was_cancelled:
        return False
    if not annotation.submitted_at:
        return False                       # 草稿、没提交
    if not task.is_labeled:
        return False                       # 任务整体没标完
    if task.was_skipped:
        return False                       # 标注员主动放弃
    return True

上面的陷阱由这一层解决。更难的过滤是双人一致性。

信任梯度

不同 project 产出的标签质量不一样,即使同一个标注员:

Project标的内容单帧信任度失败模式
A动作时间线(视频)段边界前后糊 2-3 帧
B手部关键点 + 物品 bbox(逐帧)多数是接受 / 拖一个点
C人体骨骼(逐帧)同上

Project B、C 的修正是密集的像素级工作。标注员在修一帧、独立看这一帧。单帧信任度高。

Project A 的修正稀疏。一段 2 分钟视频大概 8 个动作段;「approach 从 423 帧开始」是每段一次决定。边界糊 是真实存在的——不同标注员在同样的 approach 上选 421 vs 425 是常态。边界帧上的单帧信任度只能算中等。

怎么处理:

  • Project A 训练时,给每段中段 的权重比边界帧高。「帧 423-512 = approach」这条标签中间基本对、两头糊。
  • Project B/C 训练时,按帧本身的样子收。信号密集、逐帧。

双人一致性是标签质量信号

Project B/C 如果做双人复核(两人各修同一帧),分歧率就是直接测出的标签质量:

  • 关键点像素差 < 5px → 高质量,两份都留(或取平均)当训练样本
  • 差 5-20px → 中等,留一份但打标记抽查
  • 差 > 20px → 标签质量问题,整帧丢

20px 的分歧通常意味着 (a) 帧本身有歧义(运动模糊、遮挡)或 (b) 一个标注员标错了。两种情况下这帧都是糟糕的训练样本。

这暗示你想在至少一部分采样上做双人复核。不是每帧——那把标注成本翻倍——但够估出按标注员、按 task 类型的分歧率。

我会先写什么

一个 harvest.py 步骤:

  1. 读 Project A/B/C 的 LS 导出(按 project 划分的 JSON,跟 aggregate 的产出同形)。
  2. 跑上面的 is_ground_truth 过滤。
  3. Project A 的段级标注扩展成逐帧标签,加一列权重(中段 1.0,边界降到 0.5)。
  4. Project B/C 把存在的双人复核 join 起来、算像素分歧、按质量分层(himedlo)发逐帧标签。
  5. 写到带版本号的训练集 slice:training_sets/v_N/{a,b,c}/*.parquet

Phase 1 的 layout 模块扩出来知道这些路径。版本号 v_NEpisodeLayout,下游微调可以确定性地读回去。

总结

  • LS 导出里大多数东西不是 ground truth。过滤到「已提交、未取消、未跳过、整任务已标」的部分。
  • Project A 的边界是糊的;段内中段权重比两头高。
  • 双人一致性(有的话)是最便宜的标签质量信号。
  • harvest 步骤是这些决定在代码里的落点。Phase-1 的路径纪律直接迁过来用。