Phase 2:什么算 ground truth
· 约 4 分钟阅读
Phase-1 概览 收尾时说「Phase 2 是把人工校验回灌进模型的部分」。这是 Phase 2 的第一块拼图:决定哪部分人工校验你真的当标签信。
陷阱
LS 的导出包含 LS 知道的所有东西——包括:
- 模型写的、没人动过的预标注
- 标注员开始标然后取消的草稿
- 标注员提交的标注
- 因为标注员决定不了被跳过的任务
如果全部拿去微调,两件事会发生:
- 「没动过」的预标注就是模型自己的预测。 拿这些训练 = 没有误差信号的自蒸馏。模型对它本来就错的地方变得更自信。
- 被跳过的任务恰好是难例。 把它们排除在外,模型就永远见不到它最需要学的那些例子。
两个都把模型往错的方向拉。第一个比第二个更狠。
该跑的过滤
任何一帧成为训练样本之前,先过:
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 步骤:
- 读 Project A/B/C 的 LS 导出(按 project 划分的 JSON,跟
aggregate的产出同形)。 - 跑上面的
is_ground_truth过滤。 - Project A 的段级标注扩展成逐帧标签,加一列权重(中段 1.0,边界降到 0.5)。
- Project B/C 把存在的双人复核 join 起来、算像素分歧、按质量分层(
hi、med、lo)发逐帧标签。 - 写到带版本号的训练集 slice:
training_sets/v_N/{a,b,c}/*.parquet。
Phase 1 的 layout 模块扩出来知道这些路径。版本号 v_N 进 EpisodeLayout,下游微调可以确定性地读回去。
总结
- LS 导出里大多数东西不是 ground truth。过滤到「已提交、未取消、未跳过、整任务已标」的部分。
- Project A 的边界是糊的;段内中段权重比两头高。
- 双人一致性(有的话)是最便宜的标签质量信号。
harvest步骤是这些决定在代码里的落点。Phase-1 的路径纪律直接迁过来用。