VLA 系统学习第 11 课:机器人 Dataset 到底应该怎么划?——从 Episode Split 到真正的 Generalization
第十课标准答案
-
Generalization 是模型把训练数据里学到的规律,用到没参与训练的新数据上的能力。不是“把训练集做得很好”,而是面对新的 Observation、Episode 或任务条件仍然有效。
-
Train、Validation、Test 的核心职责分别是:
\[ Train\rightarrow更新模型参数 \]\[ Validation\rightarrow训练过程中选模型、调超参数 \]\[ Test\rightarrow开发完成后的最终独立评价 \]
-
Validation 虽然通常不参与
backward(),但我们会根据 Validation 结果决定网络结构、Learning Rate、Epoch、Checkpoint 等。因此开发者已经间接利用了 Validation 信息,所以还需要 Test 做最后独立评价。 -
Overfitting,过拟合,典型现象是:
\[ Train\ Loss\downarrow \]
但是到某个阶段:
\[ Validation\ Loss\uparrow \]
模型越来越适合训练数据,却越来越不适合新数据。
-
Underfitting,欠拟合,就是连训练集本身都没有学好。通常表现为 Train Loss 和 Validation Loss 都比较高。
-
Robot Dataset 如果随机按 Frame 划分,容易出现:
Episode 7 Frame 100 → Train
Episode 7 Frame 101 → Test
而相邻帧几乎一样。这样 Test 看似“没参与训练”,实际上模型已经见过几乎完全相同的数据,造成 Data Leakage。
-
Episode-level split 更合理,因为一整个 Episode 要么属于 Train,要么属于 Validation/Test,避免同一条轨迹的相邻帧跨集合泄漏。
-
即使按 Episode 划分,也不能自动说明泛化强。因为 Train 和 Test Episode 仍可能具有相同物体、相同背景、相同任务、相同初始位置。真正的泛化要看你究竟改变了什么条件。
-
Test Loss 很低但 Rollout 仍可能失败,因为 Test Loss 是在专家 Dataset 提供的 Observation 上离线预测 Action;Rollout 时模型自己的 Action 会改变后续 Observation,因此会发生 Distribution Shift 和误差累积。
-
model.eval()和torch.no_grad()不一样。model.eval()把 Module 切换到评估模式,影响 Dropout、BatchNorm 等层的行为;torch.no_grad()是关闭梯度跟踪,减少显存和计算开销。二者经常一起用,但作用完全不同。 -
Validation 阶段如果执行普通的:
val_loss.backward()optimizer.step()
就等于让 Validation 数据参与 Parameter Update,它就失去了“未参与训练数据”的意义。
- 如果 Epoch 60 的 Validation 最好,而 Epoch 100 只是 Train Loss 更低,通常更倾向保存 Epoch 60。因为我们关心的是泛化,而不是单纯把 Train Set 拟合到最低。
VLA 系统学习第 11 课:机器人 Dataset 到底应该怎么划?——从 Episode Split 到真正的 Generalization
上一课我们知道:
\[ Train\neq Validation\neq Test \]
但在普通图片分类里,“随机把图片分成 80% / 10% / 10%”有时还能工作。
Robot Learning 没这么简单。
因为机器人数据不是彼此独立的一堆图片,而是一条条具有强时间相关性的轨迹:
\[ \text{Episode} = (o_0,a_0,o_1,a_1,\ldots,o_T,a_T) \]
所以真正的问题不是:
“Train 占多少比例?”
而是:
什么东西允许同时出现在 Train 和 Test?什么东西必须彻底隔离?
这会直接决定你最后测出来的结果到底有没有意义。
一、为什么 Frame-level Random Split 很危险?
假设我们有一个抓取 Episode:
Frame 100
Frame 101
Frame 102
Frame 103
控制频率比较高,因此相邻两帧可能只相隔:
\[ 0.05s \]
于是:
\[ I_{100} \]
和:
\[ I_{101} \]
几乎一样。
Robot State 也可能只是:
\[ q_{101}=q_{100}+\Delta q \]
其中 \(\Delta q\) 非常小。
现在如果把所有 Frame 打乱:
random.shuffle(all_frames)
然后随机切分,可能得到:
Frame 100 → Train
Frame 101 → Test
Frame 102 → Train
模型在 Test 看到 Frame 101 时,实际上已经在 Train 中见过几乎一样的 Frame 100 和 102。
于是 Test Loss 很漂亮。
但它真正证明的可能只是:
模型会对已经见过附近状态进行插值。
它并没有证明:
模型能面对一条全新的任务轨迹。
所以这里产生:
\[ \boxed{Data\ Leakage} \]
数据泄漏。
二、Episode-level Split 如何解决这个问题?
更合理的一种做法是:
假设:
\[ 100\ Episodes \]
直接按 Episode 编号划分:
\[ Episode\ 0\sim79\rightarrow Train \]\[ Episode\ 80\sim89\rightarrow Validation \]\[ Episode\ 90\sim99\rightarrow Test \]
那么:
Episode 91 Frame 0
Episode 91 Frame 1
...
Episode 91 Frame 200
全部属于 Test。
Train 中不会出现 Episode 91 的前一帧、后一帧。
这样测试变成:
给模型一条训练阶段没有出现过的新轨迹,它还能不能预测合理 Action?
这比随机 Frame split 严格得多。
因此在 Robot Learning 中,一看到 Dataset Split,我们就应该先问:
\[ \boxed{ Split\ unit到底是什么? } \]
是:
\[ Frame? \]
还是:
\[ Episode? \]
这个问题往往比“80/20 还是 90/10”重要得多。
三、但 Episode-level Split 仍然只是第一层泛化
假设所有 100 个 Episode 都这样采集:
同一个桌子
同一个红色方块
同一个目标盒
方块几乎总在同一位置
相机位置不变
任务始终相同
你确实按 Episode 划分了:
\[ 80/10/10 \]
但是 Test Episode 和 Train Episode 的实际条件仍然极其相似。
于是模型 Test 表现很好,只能说明:
它对“同类条件下的新轨迹”有一定泛化能力。
却不能说明它能处理:
新物体
新位置
新场景
新任务
新语言
所以:
\[ \text{Generalization} \]
不是只有“泛化/不泛化”两个状态。
我们必须明确:
到底对什么东西泛化?
四、从 Episode Generalization 继续往外扩
假设机器人任务是:
把物体拿起来放进容器。
我们可以设计不同难度的 Test。
最简单的测试:
Train:
Episode 0~79
Test:
Episode 80~99
物体、场景、任务都一样,只是具体轨迹不同。
这是比较弱的一层泛化。
再严格一点,可以让:
\[ Train \]
中的物体位置主要在:
\[ x\in[-0.1,0.1] \]
而 Test 使用一些没有见过的初始位置。
那么在问:
Policy 能不能泛化到新的初始状态?
再进一步:
Train:
红色方块
蓝色方块
Test:
绿色方块
这时测试的是:
新物体条件下是否有效。
再比如:
Train:
桌面场景 A
Test:
桌面场景 B
是在测 Scene Generalization。
如果进一步:
Train:
pick up the red block
Test:
put the block into the box
那任务本身都发生变化,测试难度又不同。
所以以后看到论文写:
“Our model generalizes well.”
第一反应不能是:
好厉害。
而应该问:
\[ \boxed{ Generalize\ to\ what? } \]
五、Seen 和 Unseen 到底是什么意思?
以后论文和 Benchmark 中会频繁看到:
Seen
和:
Unseen
先从最简单例子理解。
如果训练数据里出现过:
red block
测试还是:
red block
可以称某种意义上的:
Seen Object
如果训练数据完全没有:
green mug
测试第一次出现:
green mug
那么可能称:
Unseen Object
但“Unseen”一定要看论文具体定义。
因为它可能表示:
- unseen object;
- unseen scene;
- unseen instruction;
- unseen task;
- unseen environment configuration。
不能看到:
unseen
就自己默认是“模型从未见过这个任务”。
六、现在引入 IID:训练和测试来自“同一种数据规律”
这里需要一个以后非常常见的概念:
IID
全称:
Independent and Identically Distributed
先不深挖概率论。
当前只抓住其中最重要的:
Identically Distributed
同分布。
可以粗略理解:
Train 和 Test 虽然不是同一批数据,但来自大致相同的数据生成规律。
例如:
Train:
物体位置在桌面上随机采样
Test:
物体位置仍按同样规则随机采样
具体位置不一样。
但是数据生成方式差不多。
这就比较接近:
\[ IID\ Evaluation \]
它回答的是:
在和训练环境相似的新样本上,模型怎么样?
七、OOD 又是什么?
OOD:
Out-of-Distribution
分布外。
意思是:
Test 数据在某些关键方面已经超出了训练数据主要覆盖的范围。
例如 Train:
\[ x\in[-0.1,0.1] \]
而 Test:
\[ x\in[0.3,0.4] \]
模型训练阶段从来没见过这么偏的位置。
或者:
Train:
方块
Test:
杯子
再或者:
Train:
白色背景
Test:
复杂厨房背景
这些都可能形成某种 OOD 情况。
所以:
\[ IID \]
更像:
同类条件的新数据。
而:
\[ OOD \]
更像:
数据条件本身发生了明显变化。
八、为什么不能简单说“OOD 一定比 IID 高级”?
因为 OOD 有很多不同种类。
比如:
新颜色
可能很容易。
而:
新任务逻辑
可能非常难。
所以不能只看:
OOD
三个字母。
必须继续问:
\[ \boxed{ 到底哪个Distribution发生了变化? } \]
例如:
Object OOD
Scene OOD
Task OOD
Language OOD
Pose OOD
这些难度和意义完全不同。
九、Dataset Split 实际上是在定义“考试题”
可以用考试类比。
训练集:
平时给你的练习题。
Validation:
模拟考试。
Test:
最终考试。
但真正决定考试难度的,并不是:
考试有多少道题。
而是:
最终考试和练习题到底有多像。
例如 Train:
1 + 1
2 + 2
3 + 3
Test:
4 + 4
属于同一种规律。
但如果 Test 突然变成:
求微积分
就是完全不同的 Generalization。
Robot Dataset 也是一样。
所以评估一个模型之前,必须先看:
\[ \boxed{Split\ Protocol} \]
也就是:
数据到底按照什么规则被划成 Train / Validation / Test。
十、为什么论文之间的 Success Rate 不能只看数字?
假设论文 A:
\[ Success\ Rate=95\% \]
论文 B:
\[ Success\ Rate=82\% \]
不能立刻说:
A 比 B 强。
因为 A 可能测试的是:
Seen Object
Seen Scene
Seen Task
New Episode
而 B 测试:
Unseen Object
Unseen Scene
后者显然更困难。
甚至两篇论文可能使用完全不同:
- Dataset;
- Robot;
- Task;
- Split;
- Evaluation Protocol。
所以以后比较 Robot Learning / VLA 模型必须同时看:
\[ \boxed{ Metric + Dataset + Split + Evaluation\ Protocol } \]
单独一个 Success Rate 没有足够意义。
十一、代码层面 Dataset Split 会在哪里出现?
真实代码里可能看到:
train_dataset = ...val_dataset = ...test_dataset = ...
也可能看到:
train_episodes = [...]val_episodes = [...]
或者配置:
train_episodes:
- ...
val_episodes:
- ...
再或者 Dataset 类内部通过:
split="train"
加载不同集合。
所以以后进入真实仓库时,不能只找到:
Dataset
然后就结束。
还要继续问:
Dataset 的 Train / Val / Test 到底在哪里定义?
以及:
是按 Frame、Episode、Task 还是其他单位切分?
这将成为我们以后代码审读的固定检查项。
十二、把这一课重新接回模型训练
现在完整流程已经不是简单:
\[ Dataset \rightarrow Training \]
而应该变成:
\[ Dataset \rightarrow Split\ Protocol \rightarrow \begin{cases} Train\\ Validation\\ Test \end{cases} \]
Train:
\[ Observation \rightarrow Policy \rightarrow Loss \rightarrow Backward \rightarrow Update \]
Validation:
\[ Observation \rightarrow Policy \rightarrow Metric \]
但:
\[ \text{No Parameter Update} \]
Test:
\[ \text{Final Evaluation} \]
最终如果是 Robot Policy,还要继续:
\[ Checkpoint \rightarrow Rollout \rightarrow Task\ Success \]
所以真正意义上的模型评价链现在变成:
\[ \boxed{ Train\ Loss \rightarrow Validation \rightarrow Test \rightarrow Rollout } \]
越往后,越接近真正任务表现。
第十一课自测
-
为什么 Robot Dataset 随机按 Frame 划分 Train/Test 特别容易造成 Data Leakage?
-
Episode-level split 解决了什么问题?
-
为什么 Episode-level split 做完以后仍然不能自动宣称模型“泛化很好”?
-
Seen Object和Unseen Object的核心区别是什么? -
IID Test 和 OOD Test 可以分别怎样直观理解?
-
如果 Train 中物体位置始终:
\[ x\in[-0.1,0.1] \]
Test 使用:
\[ x\in[0.35,0.45] \]
这更接近 IID 还是 OOD?为什么?
-
为什么论文 A 的 Success Rate = 95%,不能仅凭这个数字认为它一定强于 Success Rate = 85% 的论文 B?
-
以后阅读 Robot Learning Repository 时,除了 Dataset Class 本身,还必须继续检查哪个信息?
下一课:第 12 课——为什么机器人不能只看“一帧”?从时间序列到 Observation History 和 Action Chunk
这一课之后我们就会明显向 ACT 靠近。
因为现在的数据我们一直写成:
\[ (o_t,a_t) \]
也就是:
看当前 Observation,预测当前 Action。
但真实动作具有时间连续性。
下一课我们会自然把它扩展成:
\[ [o_{t-k},\ldots,o_t] \]
以及:
\[ [a_t,a_{t+1},\ldots,a_{t+K-1}] \]
真正讲清:
- 为什么需要时间信息;
- Observation History 是什么;
- Sequence Tensor 为什么会出现;
- 为什么 Shape 开始变成 \[ [B,T,D] \]
- 单步 Action 和 Action Sequence 有什么区别;
- 为什么“一次预测一串动作”会自然导向 Action Chunking。
这一步完成后,ACT 里的第一个核心词 Action Chunking 就不会再是突然冒出来的名词;之后我们就可以开始为 Attention / Transformer 做最后一段必要准备。
更多推荐


所有评论(0)