1. 一段视频里,少了什么
“让机械臂把橘子放到盘子里”,听起来是一段动作。录下来以后,它又很像一段视频。
但如果只把视频交给训练程序,它还不知道机械臂当时处于什么状态,也不知道操作者下发了什么动作。
看懂画面的人可以猜测“夹爪正在靠近橘子”。程序需要的是能够对应起来的观测、状态、动作和时间。
这就是我理解 Episode 的入口:一次从开始到结束的连续交互记录。 它可以成功,也可以失败,甚至因为中断而不完整。一次记录结束,并不自动表示任务目标已经完成。
在LeRobot 平台里,把它作为独立对象,才能讨论这一段数据是否完整、属于哪次采集,以及后来被哪一版数据集选中。
2. 把一次夹取沿时间展开
假设一次记录按 25 Hz 组织数据,那么相邻样本的名义间隔是 1 / 25 = 0.04 秒。下面只画出前三个时间点:
相对时间 0.00 s 0.04 s 0.08 s
相机画面 image_0 image_1 image_2
机器人状态 state_0 state_1 state_2
下发动作 action_0 action_1 action_2这里的频率是例子,不代表某台设备的采集结果。实际数据应读取对应元信息和时间戳。
一行可以理解为:在这个时刻看到了什么,机器人处于什么状态,以及记录了什么动作。但动作究竟对应当前位置、目标位置还是增量,需要结合采集程序和控制约定解释。
不能因为 state 和 action 都是数组,就把它们当成相同内容。也不能只按数组下标配对,就认定摄像头和机器人时钟已经同步。
如果画面晚了一小段时间,动作和图像仍可能有相同数量,却描述着不同瞬间。
3. Task、Episode、Frame 不是三个名字指同一件事
我会用下面这个层次来区分:
平台采集任务:今天采集橘子抓取
├─ Episode #81:一次夹取记录
│ ├─ Frame 0
│ ├─ Frame 1
│ └─ ...
└─ Episode #82:另一次夹取记录
└─ ...采集任务是工作安排,可以指定负责人和设备。Episode 是一次记录的边界。Frame 是连续数据中的一个采样位置。
还要留意,机器人数据里也可能有描述动作目标的 task 文本,比如“把橘子放进盘子”。它表达语义目标,不等于平台中有负责人、审批和起止状态的 CollectionTask 数据库记录。
同一句目标可以出现在多次 Episode 里。同一条平台任务也可以安排多次采集。名称很像,职责却不一样。
4. Episode 的边界不一定是文件边界
LeRobotDataset v3 的说明把逻辑访问和物理存储分开:多个 Episode 可以放进同一个 Parquet 或视频分片,读取时根据元信息找回各自的范围。
因此,看到一个 MP4 文件,不能直接把它登记为“一次采集”;看到文件变大,也不能只凭文件大小推算采集次数。
平台更稳妥的引用方式是:
数据快照
├─ 格式版本
├─ 文件与元信息的固定版本
└─ episode_index
└─ 由对应版本的读取器解析范围Episode 编号通常也需要放在数据集快照范围内解释。另一个数据集里的 Episode #81,不一定是同一条记录。
这里采用 v3 说明。碰到 v2.1 数据时,应先识别版本,再决定使用匹配的读取器或显式转换,而不是套用同一套路径拼接代码。转换后的内容也应记录为新的数据产物。
5. 我会先看元信息,再看画面
对于下载到本地的数据,可以先用一小段 Python 查看元信息,不急着把所有视频都读进内存:
import json
import sys
from pathlib import Path
root = Path(sys.argv[1])
info = json.loads((root / "meta" / "info.json").read_text())
print("format:", info.get("codebase_version"))
print("fps:", info.get("fps"))
for name, feature in info.get("features", {}).items():
print(name, feature.get("dtype"), feature.get("shape"))参数指向要检查的本地数据目录。这段代码只是查看摘要,不能凭它判断视频能否完整解码、相机是否同步或动作是否正确。
接下来才是通过对应读取器,查看所选 Episode 的范围、时间戳和样本内容。
如果相机字段缺失,就要追采集配置;如果动作维度和预期不符,就要查机器人特征映射;如果时间倒退或出现异常间隔,就要回看数据生成过程。直接丢掉“不好读”的几行,可能破坏整个动作序列。
6. 长度一样,不代表动作含义一样
看 LingBot-VLA 的机器人配置说明时,一个细节很有启发:原始状态、动作和相机字段需要映射到训练使用的特征;动作还可能涉及是否减去当前状态的选择。
这让我意识到,接口里只写 actionDimension: 6 这样的摘要是不够的。
数组中的顺序、单位、绝对值或增量语义,以及夹爪信号的含义,都影响后续解释。同样长度的两组数,未必可以互换。
这些细节应当与采集配置、映射文件和数据版本关联。页面可以显示摘要,原始配置仍然需要保存,以便回答“训练程序到底怎样理解这组数”。
不能看到公开示例使用某套双臂映射,就直接把它用于自己的单臂数据。需要根据实际设备与采集字段逐项对应。
7. 一条 Episode,可能有三种不同结论
假设 Episode #81 完整录下了机械臂松开橘子、橘子掉落的过程。这份文件可能没有缺帧,结构完全可读,但夹取没有成功。
我会把几个判断分开保存:
| 判断 | 例子 |
|---|---|
| 记录是否结束 | 录制完成、被中断 |
| 数据是否可用 | 校验通过、缺少相机、时间异常 |
| 动作目标是否完成 | 成功、失败、尚未判断 |
这些是平台可以采用的管理维度,不是宣称 LeRobot 原生带有同名状态字段。
失败轨迹也不等于永远没有用途。它是否进入某次训练,取决于训练目标和筛选规则;如果当前任务只需要成功示范,就明确将其排除,同时保留排除原因。
归档原始 Episode 与决定它是否进入某个数据版本,是两个步骤。这样以后改变筛选条件,不需要猜测之前为什么少了一段数据。
8. 看起来接近原动作,就算学会了吗
LingBot-VLA 的开环评估源码会从记录的数据中取观测,比较预测动作与记录动作,并计算误差、画出轨迹。
这能帮助发现输出范围或映射问题,但画出的曲线接近,不等于机械臂已经能够稳定完成夹取。
真机执行时,一个动作会改变物体和机器人的位置,下一帧观测也随之变化。小误差可能影响后面的整个过程。平台因此需要区分开环误差和实际任务完成情况,并记录各自的数据或场景。
另外,不能把同一 Episode 的相邻帧随意拆到训练集和验证集,随后就把结果当成对新轨迹的验证。至少要保持 Episode 边界;如果要评价新场景,还需要按场景或采集批次进一步隔离。
9. 我会怎样重新看一条 Episode
再遇到一份机器人数据,我会先找到一次记录的起止范围,再沿时间检查观测、状态和动作是否能够对应,最后结合任务目标判断它是否适合当前用途。文件能打开,只是这个过程的起点。
放回平台里,Episode 编号需要能带我找到固定的数据内容和必要配置;一次失败的动作也应该保留明确的判断原因。这样以后重新筛选数据时,才能依据原始记录做决定,而不是只能依赖一个已经说不清含义的状态值。
相关阅读
- 机械臂训练之前,数据到底长什么样?
- 从 AI 数据平台到 LeRobot:我对 Java 后端开发理解的变化
- 机器人训练数据怎样送到 GPU:Kubernetes、JuiceFS 与 Lance 的分工