Xiaomi-Robotics-U0 管统一表征、从采集到训练这条链路得先跑通

文章导读
链路串起来失败,多数不是某一段写错了,而是段与段之间的接口对不上:采集写出的字段、清洗脚本假设的字段、打包时的 dtype 和维度顺序、训练时读的归一化统计,只要有一处不一致,日志往往只报一句 key 找不到,或者干脆静默丢掉样本,看起来每段都能跑。要判断这条链路是否真的跑通,先把五段各自的产物文件固定下来,再用一次性小批量从头串到尾,确认能出 loss 和 checkpoint,再去讨论数据质量
📋 目录
  1. 壹 为五段链路各定义一个可观测产物
  2. 贰 在采集端核对元数据字段是否齐全
  3. 叁 清洗脚本先做只读扫描再动数据
  4. 肆 对合成输出做抽样人工核对
  5. 伍 训练前用一次性小批量跑通并留日志
A A

链路串起来失败,多数不是某一段写错了,而是段与段之间的接口对不上:采集写出的字段、清洗脚本假设的字段、打包时的 dtype 和维度顺序、训练时读的归一化统计,只要有一处不一致,日志往往只报一句 key 找不到,或者干脆静默丢掉样本,看起来每段都能跑。要判断这条链路是否真的跑通,先把五段各自的产物文件固定下来,再用一次性小批量从头串到尾,确认能出 loss 和 checkpoint,再去讨论数据质量和模型效果。

这套做法适用于自采数据、自己维护预处理脚本的机器人策略训练项目:采集、清洗、合成、统一表征打包、训练五段各留一个可校验产物,接口字段以 schema 文件为准。判断链路是否跑通的验证方式是——任意一次全链路运行结束后,只看产物文件就能指出断在哪一段;边界是它只证明链路连通,不证明数据质量与模型效果。

为五段链路各定义一个可观测产物

五段按顺序是:采集、清洗、合成或增广、统一表征打包、训练。每段都要有一个“跑完了就一定会出现”的文件,以及一条能单独执行的校验命令。约定好之后,“跑通了”就不再是感觉,而是一次 `ls` 加一次 `jq` 能回答的问题。

段可观测产物生成条件校验命令示例
采集`raw/manifest.jsonl`,一行一个 episode,含文件清单每个 episode 正常停止并落盘`ls -d raw/*/ | wc -l` 与 `wc -l raw/manifest.jsonl` 数量一致
清洗`clean/manifest.jsonl`(每行带 status 与 reason)+ `reports/clean_report.json`只读扫描完成且判定写入`jq -r '.status' clean/manifest.jsonl | sort | uniq -c`
合成`synth/manifest.jsonl`,每条带 source_episode_id 与 transform合成任务结束且写入索引`jq -r 'select(.source_episode_id==null) | .episode_id' synth/manifest.jsonl` 应为空
表征打包`dataset/schema.json` + 分片文件 + `dataset/stats.json`打包脚本正常退出`jq '.keys | length' dataset/schema.json`,并核对 stats 里每个 key 都有 min/max/mean
训练`runs//resolved_config.yaml`、`metrics.jsonl`、checkpoint至少完成一次反向传播并保存`head -n1 runs//metrics.jsonl` 能看到有限 loss

串联失败时的排查方向是从后往前看:最后一个成功产物在哪一段,问题就落在它的下一段。比如 `clean/manifest.jsonl` 有内容但 `dataset/` 没生成,先怀疑打包脚本读取的字段名与清洗输出不一致,而不是回去怀疑采集。

在采集端核对元数据字段是否齐全

缺字段最典型的后果不是报错,而是丢数据:清洗阶段按 fps 对齐时间轴,字段为空就整条跳过;打包阶段按 action_dim 建张量,字段缺失就写出一个维度不同的分片。所以元数据核对要放在采集端收工之前做,而不是等训练报 shape 不匹配。

字段用途缺失时的典型后果
episode_id全链路主键清洗与合成无法回溯来源
task_instruction语言条件、任务分层无法做按任务抽样与分桶
robot_model / config_version区分机身与配置不同构型数据被混进同一 schema
fps / timestamp_unit时间轴对齐时间戳单位误判,动作与图像错位
state_dim / action_dim / control_mode张量形状与语义关节与末端位姿混用,训练静默学错
cameras(名称、内外参版本)多相机对齐拼接顺序错,画面与动作不对应
calib_version标定可追溯标定变更后新旧数据无法区分
success / 结果标记筛选与评估只能靠人工回看,抽样成本高
files(相对路径与校验和)完整性校验半截文件被当成正常样本
# tools/check_meta.py 用法: python tools/check_meta.py raw/manifest.jsonl
import json, sys
REQUIRED = ['episode_id','task_instruction','robot_model','config_version',
            'fps','timestamp_unit','state_dim','action_dim','control_mode',
            'cameras','calib_version','success','files']

def check(path):
    bad = []
    ok = 0
    for ln, line in enumerate(open(path, encoding='utf-8'), 1):
        if not line.strip():
            continue
        rec = json.loads(line)
        lack = [k for k in REQUIRED if rec.get(k) in (None, '', [], {})]
        if lack:
            bad.append({'line': ln, 'episode_id': rec.get('episode_id'), 'missing': lack})
        else:
            ok += 1
    print(json.dumps({'ok': ok, 'bad': len(bad)}, ensure_ascii=False))
    for b in bad:
        print(json.dumps(b, ensure_ascii=False))
    return 1 if bad else 0

sys.exit(check(sys.argv[1]))

补齐方式按优先级来:先从 episode 目录里的 sidecar 文件和采集日志回填;回填不了就整条标 drop 并写 reason;不建议给 fps、control_mode 这类字段填全局默认值,默认值会把错位藏起来,后面更难查。

Xiaomi-Robotics-U0 管统一表征、从采集到训练这条链路得先跑通

清洗脚本先做只读扫描再动数据

清洗是最容易把原始数据改坏的一段。建议拆成两个入口:扫描只读、只产出报告;写入必须显式开关,且写到新目录,不原地覆盖。

# 1) 只读扫描,不改任何原始文件
python tools/clean_scan.py `--input` raw `--out` reports/clean_scan.json `--dry-run`

# 2) 记录扫描前的校验和,作为事后比对基线
sha256sum raw/*/data.npz > reports/raw_sha256.before

# 3) 人工看过报告,确认无误后再打开写入开关
python tools/clean_apply.py `--input` raw `--out` clean \
    `--report` reports/clean_scan.json `--only-status` keep `--apply`

# 4) 写入后核对原始文件没被碰过
sha256sum -c reports/raw_sha256.before

扫描报告至少输出这些字段:episode_id、status(keep/drop/review)、reason_code、frames、duration_s、dt 抖动、state_dim、action_dim、NaN 计数、动作绝对值上限、多相机帧同步差、是否被规则命中。`--apply` 默认关闭,脚本在没有该参数时只打印“将写入 N 条”,不落盘。写入目录与输入目录不同,是保证可回溯的最低成本做法。

对合成输出做抽样人工核对

合成或增广阶段常出现机器指标正常、动作语义不合理的情况:数值全在范围内,但夹爪开合与物体是否在位对不上,或者时间轴上出现大量静止帧。这类问题只能靠人眼看。

抽样规则建议分层:按任务指令、按机器人配置、按时长分位各抽一遍,每个分层至少抽到一条,总量以覆盖完所有任务与所有相机配置为准,覆盖不到就说明分层还不够细。抽样时把帧序列和对应的动作曲线画在同一张时间轴上,比单纯看视频更容易发现错位。

Xiaomi-Robotics-U0 管统一表征、从采集到训练这条链路得先跑通
核对字段看什么
指令与动作是否对应描述的任务和实际轨迹是否一致
动作维度顺序与 schema 声明的顺序是否一致
量纲与范围关节角、末端位姿、夹爪量是否在同一量纲
时间对齐图像切换与动作变化是否同步
静止段与异常帧是否存在大段无变化或明显跳变
结论通过 / 复采 / 丢弃,附复核人

不合格样本的处理路径要固定:先在 manifest 打 flag 并记录原因,再回到原始 episode 重新导出;如果原始数据本身就缺帧或缺标定,就整条剔除并保留记录。不建议在合成阶段“修正”原始动作来凑合格,这会破坏可回溯性。

训练前用一次性小批量跑通并留日志

正式训练前先跑一次小批量,目的是让字段名、维度、归一化、checkpoint 保存这些接口问题在几分钟内暴露,而不是在长时间训练里才发现 loss 一直不动。

# configs/smoke.yaml(字段名按自己的实现替换)
dataset:
  manifest: dataset/manifest.jsonl
  schema: dataset/schema.json
  stats: dataset/stats.json
  max_episodes: 8
loader:
  batch_size: 2
  num_workers: 0
  shuffle: false
train:
  max_steps: 20
  seed: 0
  log_every: 1
  save_dir: runs/smoke
# 跑通并留日志,日志与解析后的配置一起存档
python train.py `--config` configs/smoke.yaml 2>&1 | tee runs/smoke.log

关键日志行大致是这几类,字段名可以不同,但信息必须齐:数据集解析到的 episode 数与 schema key 数、第一个 batch 的 obs 与 action 形状和 dtype、stats 里动作范围与 NaN 计数、first step 的 loss 是否为有限值、checkpoint 落盘路径。log 里只要有这五行,链路是否连通就不必再靠猜。

失败时的分段回退顺序:训练报 key 错或形状错,回到表征打包看 schema 与分片;打包读不到样本,回到合成看 manifest 是否写入;合成缺源,回到清洗看 status 与 reason;清洗没有产出,回到采集看 missing 字段。每次回退前先把当前日志留在 runs 目录里,避免同一问题被反复从头跑。