具身数据

机械臂训练之前,数据到底长什么样?

从一次夹橘子的采集出发,结合 LeRobot 与 LingBot-VLA,理解设备、Episode、数据版本和训练记录之间的关系。

1. 先把“训练机械臂”换成一个具体任务

看到机械臂抓取物体的视频时,很容易把注意力放在模型上:它用了什么算法,参数有多大,能完成多少种动作。

站在 Java 后端的角度,我更想顺着数据往前看。在LeRobot 数据平台里,模型开始训练之前,平台要先接住什么?

不妨用一个具体任务来理解:让机械臂把桌上的橘子夹起来,放进旁边的盘子。

如果由人示范一次,过程中会产生摄像头画面、机器人当时的状态、下发的动作以及时间信息。一次从开始到结束的记录,可以组织为一个 Episode。

text9 行
设备与摄像头
     ↓
采集任务:把橘子放进盘子
     ↓
Episode:一次执行的连续记录
     ↓
选定数据与配置 → 数据集版本
     ↓
训练记录 → 模型产物 → 评估记录

这条链让我觉得,平台需要关心的事情远不止上传一段视频。

2. 设备、采集任务和 Episode 各自保存什么

假设同一个下午采集了多次夹橘子的过程。有的成功,有的没有夹住,有的画面被遮挡。

把整个下午当成一条数据,粒度太粗;把每一帧都当成一条需要人工管理的业务任务,又太细。

我会先把三件事拆开:

对象 关心的信息
Device / Camera 使用哪台设备、哪路相机,以及对应配置
CollectionTask 这次准备采什么、谁负责、属于哪个场景
Episode 一次实际记录的起止、数据引用和处理结果

一条采集任务可以产生多个 Episode。采集任务结束,只能说明这一轮采集安排结束了,不能推出里面每条数据都适合训练。

设备信息也不能只保存一个当前名称。相机位置或机器人配置发生变化后,旧数据仍然对应旧配置。因此,可以在采集记录中保留配置快照或引用不可变的配置版本。

这个区分很像普通后端里的订单与订单项,但需要表达的内容换成了设备、时间和连续动作。

3. LeRobot 文件和平台记录是两层东西

按 LeRobotDataset v3 的格式说明,状态、动作等表格数据放在 Parquet,视频放在 MP4,元信息负责描述特征和 Episode 的索引边界。多个 Episode 可以共享较大的文件。

这意味着平台不能简单假定:“一条 Episode,就是一个 episode_001.mp4。”

我更倾向于让 Java 管理元信息和关联关系,数据适配程序负责理解对应版本的 LeRobot 文件:

text9 行
Vue 管理页
    ↓
Spring Boot:权限、任务、版本与记录
    ├─ MySQL:业务对象和关联
    └─ 数据清单:指向固定的数据内容
                ↓
         Python 数据适配程序
                ↓
      LeRobot 表格、视频与元信息

这里没有要求把每一帧图像写进 MySQL。数据库可以保存 Episode 编号、数据来源、校验摘要和版本归属,真正的时序内容由相应的数据工具读取。

同样,collection_task_id、负责人和审核意见是平台的管理字段,不是说 LeRobot 原生就定义了这些字段。

4. 能读取,还不代表能直接训练

顺着 LingBot-VLA 的自定义数据指南往下看,数据准备还包括机器人特征映射与归一化统计。这里参考的是提交 4eb34b7,其指南使用 LeRobot v3 数据。

为什么有了数据还要这些配置?

因为一个动作数组里的第一个数,本身并不能告诉训练程序它属于哪个关节。同样,某个图像字段来自桌面相机还是手腕相机,也需要确定。

所以,导入页面至少要能显示:数据格式版本、可用特征、相机名称、状态和动作的维度,以及准备采用的机器人映射配置。

比如同一个名为 action 的字段,一批数据表达目标位置,另一批表达位置增量。即使长度相同,也不能仅凭维度一致就直接合并。

我会把“文件已经到齐”“结构能够读取”“适合这次训练任务”分成不同判断。校验发现缺失相机或未知动作含义时,保留具体原因,比只显示一个红色的失败状态更有用。

5. 数据集版本到底固定什么

假设第一次准备训练时,选用了 Episode #81 和 #84;后来又补录 #85,并修正了其中一份数据的处理方式。

如果训练记录只关联一个一直变化的 Dataset,过一段时间再打开,很难回答那次训练实际用了哪些内容。

所以可以保留 Dataset 作为逻辑名称,再由 DatasetVersion 固定一次选择:

text5 行
Dataset:橘子抓取
  ├─ 版本 v1:Episode #81、#84 的固定内容
  │     └─ TrainingJob #231
  └─ 版本 v2:Episode #81、#84、#85 的固定内容
        └─ TrainingJob #246

固定编号还不够。如果编号指向的文件可以被原地覆盖,内容照样会变。因此,版本清单还需要引用不可变的文件版本或校验摘要,并保留格式、特征映射及统计文件的确切版本。

使用 Hugging Face Hub 时,可以参考其按 revision 下载文件的方式,记录明确提交,而不是只写一个会继续变化的分支名。本地文件则需要有相应的归档和防覆盖约定。

下面是平台层的简化 Java 类型,用来表达这种关系,不是 LeRobot 的官方 API:

java7 行
public record TrainingInputRef(
    long datasetVersionId,
    String manifestUri,
    String manifestSha256,
    String robotConfigSha256,
    String normStatsSha256
) {}

这些摘要用于判断内容是否相同,不能代替内容本身。文件已经删除,再完整的摘要也无法还原它。

6. 训练成功,还要继续追到评估

LingBot-VLA 的官方仓库说明分别提供了后训练、开环评估和部署入口。对平台来说,它们也应该是可区分的记录。

训练记录回答“用了什么输入和参数,产出了什么”;评估记录回答“拿哪个模型,在什么条件下,检查了什么结果”。

text7 行
ModelVersion #7
  ├─ 来源:TrainingJob #231
  │        └─ DatasetVersion v1
  └─ Evaluation
       ├─ 使用的数据或任务场景
       ├─ 评估类型与配置
       └─ 指标、日志和失败原因

例如,开环评估用已有观测检查模型输出;真机执行后,下一次观测会受到模型动作影响。二者回答的问题不同,所以不能把开环输出误差直接放进一个叫“抓取成功率”的字段。

平台能够保存评估记录,也不代表它已经驱动机械臂完成动作。设备接入、模型执行和结果上报,需要各自的实际链路。

7. Java 在这里负责的事情很具体

如果只写“具身智能平台”,听起来很大。拆到具体请求,其实就能看见后端的工作:创建采集任务、登记数据、记录校验结果、固定数据版本、关联训练和评估。

难点在于这些请求不能各自孤立。一次训练至少要找到固定输入,一个模型要找到来源任务,一份评估要说明使用的是哪个模型。

这也是我从标注平台继续看 LeRobot 的原因。前一个项目关心“这份标签经过谁的审核”,后一个项目进一步关心“这段动作从哪里来,后来被怎样使用”。

同样是表和接口,背后需要讲清楚的因果关系更多了。

如果继续完善这条链路,我会优先检查历史记录能否读回当时的输入。数据集版本只是其中一部分,代码、基础权重、处理配置和运行环境也需要保留。记录清楚这些条件,才能进一步讨论复现;至于不同硬件下数值是否完全相同,还涉及随机性和计算行为,不能只靠一个版本编号保证。

相关阅读

← 返回文章