第九课标准答案
上一课的核心其实只有一条链:
\[ \text{Action Representation} \rightarrow \text{Model Output} \rightarrow \text{Loss} \rightarrow \text{Gradient} \rightarrow \text{Parameter Update} \]
所以答案也沿这条链来收。
1. 为什么不能直接把正负误差相加?
假设两个维度的误差分别是:
\[ -2,\qquad +2 \]
如果直接相加:
\[ -2+2=0 \]
看起来好像模型完全没错。
但实际上两个维度都预测错了。
所以 Loss 必须避免“正负误差互相抵消”。
MSE 采用平方:
\[ (-2)^2+(2)^2=8 \]
L1 采用绝对值:
\[ |-2|+|2|=4 \]
都能避免这个问题。
2. MSE 中 Mean、Squared、Error 分别是什么意思?
MSE:
Mean Squared Error
对应:
\[ L= \frac{1}{N} \sum_{i=1}^{N} (\hat y_i-y_i)^2 \]
其中:
Error:预测和正确答案之间的误差
\[ \hat y_i-y_i \]Squared:把误差平方
\[ (\hat y_i-y_i)^2 \]Mean:把所有平方误差求平均
\[ \frac1N\sum \]
所以中文就是:
均方误差。
3. 专家 Action 为
\[ a=[1,3] \]
预测:
\[ \hat a=[2,5] \]
求 MSE
两个误差:
\[ 2-1=1 \]\[ 5-3=2 \]
平方:
\[ 1^2=1 \]\[ 2^2=4 \]
求平均:
\[ MSE=\frac{1+4}{2} \]
所以:
\[ \boxed{MSE=2.5} \]
4. 为什么 MSE 比 L1 更容易受到异常大误差影响?
因为 MSE 会平方。
假设误差:
\[ e=10 \]
那么 L1:
\[ |e|=10 \]
而平方误差:
\[ e^2=100 \]
如果误差进一步变成:
\[ 20 \]
L1:
\[ 20 \]
平方误差:
\[ 400 \]
所以误差越大,MSE 的惩罚增长得越快。
因此极端异常值会对 MSE 产生更强影响。
5. 为什么连续 Action 通常属于 Regression?
因为连续 Action 的目标通常是预测一个连续数值。
例如:
\[ \Delta x=0.013 \]
模型预测:
\[ 0.012 \]
这时候我们关心:
预测值和真实值之间差多少?
这正是 Regression,回归问题。
例如:
\[ [\Delta x,\Delta y,\Delta z] \]
中的每一维都可能是连续值。
6. 为什么离散 Action 不能用“类别编号的数字距离”衡量?
假设:
0 → Left 1 → Right 2 → Open Gripper 3 → Close Gripper类别编号只是为了方便编码。
不能因为:
\[ |0-3|=3 \]
就说:
Left 和 Close Gripper 的动作差距是 3。
这个数字没有真正的动作几何意义。
所以分类问题关心的是:
正确类别的概率是否足够高。
而不是:
类别编号之间差几个数字。
7.logits.shape = [32,10]中 32 和 10 是什么?
\[ 32 \]
表示:
Batch Size
一次有 32 个样本。
\[ 10 \]
表示:
Class Number
每个样本对 10 个类别分别产生一个原始分数。
所以单个样本:
\[ logits:[10] \]
一个 Batch:
\[ logits:[32,10] \]
8. 为什么 Cross Entropy 可以理解成“提高正确类别概率”?
对于正确类别 \(y\),核心形式可以写成:
\[ L=-\log p_y \]
如果模型给正确类别概率:
\[ p_y=0.9 \]
Loss 很小。
如果:
\[ p_y=0.01 \]
Loss 很大。
所以为了降低 Loss,模型自然会被训练成:
\[ p_y\uparrow \]
也就是:
给正确类别更大的概率。
9. 为什么nn.CrossEntropyLoss()通常直接接 logits?
因为 PyTorch 的:
nn.CrossEntropyLoss()内部已经组合了适合计算 Cross Entropy 的相关操作,并采用了数值更稳定的实现。
所以通常应该:
loss = loss_fn(logits, target)而不是先:
prob = softmax(logits)loss = loss_fn(prob, target)后者反而可能把接口用错。
因此看到:
nn.CrossEntropyLoss()时应该先想到:
输入通常是原始 logits。
10. Action 一个维度范围[-0.05,0.05],另一个范围[-180,180],为什么直接 MSE 有问题?
因为数值尺度差距太大。
位置误差可能是:
\[ 0.01 \]
平方:
\[ 0.0001 \]
角度误差可能是:
\[ 10 \]
平方:
\[ 100 \]
于是总 Loss 几乎被角度维度控制。
模型训练时就会更关心:
把大数值尺度的那一维预测好。
而位置维度即使很重要,在 Loss 中也可能几乎没有存在感。
11. 为什么 Action Normalization 会直接影响训练?
因为它改变的不只是“数据长得好不好看”,而是改变每个维度进入 Loss 时的数值尺度。
例如:
\[ a'_i= \frac{a_i-\mu_i}{\sigma_i} \]
归一化以后,不同 Action Dimension 可能进入相近的范围。
于是:
\[ Action\ Scale \rightarrow Loss\ Contribution \rightarrow Gradient\ Magnitude \rightarrow Parameter\ Update \]
所以 Normalization 会真正改变模型训练过程。
12. 如何理解:
\[ Action\ Representation \rightarrow Model\ Output \rightarrow Loss \rightarrow Gradient \rightarrow Policy \]
如果 Action 是连续值:
\[ [\Delta x,\Delta y,\Delta z] \]
那么模型输出通常也是连续数值。
因此可能使用 Regression Loss。
如果 Action 是离散 Token:
\[ 0,1,2,\ldots,C-1 \]
那么模型可能输出:
\[ logits:[B,C] \]
再使用 Classification Loss。
Loss 决定“什么叫预测错误”。
Loss 再决定:
\[ \nabla_\theta L \]
也就是参数应该怎样变化。
参数变化以后,Policy 的行为发生变化。
所以:
\[ \boxed{ Action怎么定义 } \]
最终会一直影响到:
\[ \boxed{ Policy最后学成什么样 } \]
这也是以后读 VLA 时为什么一定先查 Action Representation。
VLA 系统学习第 10 课:为什么训练 Loss 很低,模型仍然可能什么都没学会?
到目前为止,我们已经完整建立了一条训练链:
\[ Dataset \rightarrow Batch \rightarrow Forward \rightarrow Loss \rightarrow Backward \rightarrow Optimizer \rightarrow Parameter\ Update \]
如果不断训练,通常会看到:
\[ Training\ Loss\downarrow \]
这时候很容易产生一个误解:
Loss 越低,模型就一定越好。
实际上完全不一定。
因为我们真正关心的并不是:
模型能不能把已经看过的数据做对。
而是:
面对没有参与训练的新情况,它还能不能做对。
这就自然引出了这一课最重要的概念:
\[ \boxed{Generalization} \]
泛化能力。
一、先看一个最极端的例子:把答案全背下来
假设我们有 100 个训练样本:
\[ (x_1,y_1), (x_2,y_2), \ldots, (x_{100},y_{100}) \]
模型非常复杂。
训练很久以后,它可能做到:
\[ x_1\rightarrow y_1 \]\[ x_2\rightarrow y_2 \]
一直到:
\[ x_{100}\rightarrow y_{100} \]
训练集上:
\[ Loss\approx0 \]
看起来非常厉害。
但现在给它一个从未见过:
\[ x_{101} \]
模型完全预测错误。
那么这个模型其实没有真正掌握规律。
它只是:
记住了训练样本。
这就是我们后面要说的:
Overfitting
过拟合。
二、什么叫 Generalization?
Generalization 可以理解成:
模型把训练数据里学到的规律,迁移到没见过的新数据上的能力。
例如训练时看到:
红色方块位于左侧 红色方块位于中间 红色方块位于右侧测试时换成:
红色方块位于稍微不同的位置模型仍然知道该怎么做。
这说明:
模型不是单纯记住某个固定像素或者固定状态,而是学到了某种更一般的 Observation → Action 关系。
因此真正希望得到的是:
\[ \boxed{ Training\ Performance好 + Unseen\ Data\ Performance也好 } \]
三、那怎么知道模型对“没见过的数据”表现怎么样?
最直接的方法就是:
不要把全部数据都拿去训练。
假设 Dataset 有:
\[ 10000 \]
个样本。
我们可以把它划分成:
\[ Train\ Set \]\[ Validation\ Set \]\[ Test\ Set \]
例如:
\[ 8000 \]
用于训练,
\[ 1000 \]
用于 Validation,
\[ 1000 \]
用于 Test。
这里具体比例没有统一规定,重点不是死记 80/10/10。
真正重要的是三者职责不同。
四、Train Set 是干什么的?
Train Set:
真正用来更新模型参数的数据。
也就是:
Train Batch ↓ Forward ↓ Loss ↓ Backward ↓ Optimizer.step()模型参数:
\[ \theta \]
会直接从 Train Set 中学习。
所以:
\[ \boxed{ Train\ Set \rightarrow 更新Parameter } \]
五、Validation Set 又是什么?
Validation Set 用于:
训练过程中检查模型对未参与参数更新的数据表现如何。
假设每训练一段时间,我们在 Validation Set 上算:
\[ Validation\ Loss \]
但是:
Validation 数据通常不会拿来执行
backward()和optimizer.step()。
也就是:
Validation Data ↓ Forward ↓ Validation Loss ↓ 观察结果但不会:
Backward ↓ Update Parameter六、那为什么还需要 Test Set?
你可能会想:
已经有 Validation Set 了,为什么还要 Test?
原因是:
我们训练过程中会不断根据 Validation 结果做决定。
例如发现:
Validation Loss太高于是你可能:
- 改 Learning Rate;
- 改网络结构;
- 改 Batch Size;
- 改训练 Epoch;
- 换 Optimizer;
- 改数据处理。
然后再看 Validation。
这意味着虽然没有直接对 Validation 做 Backward,但是你这个“人”已经通过 Validation 结果不断调整模型设计。
于是模型开发过程实际上也间接利用了 Validation 信息。
所以最终还需要一份真正尽量没有参与开发决策的数据:
Test Set
用于最后评估。
因此可以理解:
\[ \boxed{ Train \rightarrow 模型学习 } \]\[ \boxed{ Validation \rightarrow 开发过程中选模型、调超参数 } \]\[ \boxed{ Test \rightarrow 最终评价 } \]
七、把整个训练流程写出来
现在一个更完整的机器学习过程是:
Dataset ↓ Split ├── Train Set ├── Validation Set └── Test Set训练:
Train Set ↓ Forward ↓ Loss ↓ Backward ↓ Optimizer ↓ Update Parameters一段时间后:
Validation Set ↓ Forward Only ↓ Validation Loss / Metric ↓ 判断模型是否真的在泛化最终开发完成:
Test Set ↓ Final Evaluation这才是完整的模型训练评估流程。
八、Overfitting 到底长什么样?
假设训练过程中:
| Epoch | Train Loss | Validation Loss |
|---|---|---|
| 1 | 1.20 | 1.30 |
| 10 | 0.70 | 0.78 |
| 20 | 0.40 | 0.50 |
| 30 | 0.20 | 0.42 |
| 40 | 0.08 | 0.55 |
| 50 | 0.03 | 0.76 |
你会发现一开始:
\[ Train\ Loss\downarrow \]
同时:
\[ Validation\ Loss\downarrow \]
说明模型确实在学规律。
但是后面:
\[ Train\ Loss \]
继续下降,
而:
\[ Validation\ Loss \]
反而开始上升。
这就是一个非常典型的信号:
\[ \boxed{Overfitting} \]
九、为什么会发生 Overfitting?
可以把它理解成:
模型一开始学习:
数据里真正稳定的规律。
继续训练以后,模型越来越努力拟合 Train Set 的细枝末节。
例如:
- 某些偶然噪声;
- 某个相机角度下的特殊背景;
- 某些示范者的操作习惯;
- 某个固定物体位置;
- 某一批数据里的特殊误差。
于是:
\[ Train\ Performance \]
越来越好。
但新数据没有这些偶然细节。
所以:
\[ Validation\ Performance \]
反而下降。
十、一个机器人例子会更直观
假设训练 Demonstration 中,物体总是在:
桌子中央偏左背景总是:
同一个桌面光照也完全一样。
模型可能学会:
某个固定像素区域出现红色,就执行某组 Action。
训练 Rollout 可能非常好。
但只要测试时把物体往右移动:
10 cm模型就失败。
这说明模型并没有真正学会:
找到物体并根据物体位置控制动作。
而可能只是记住了:
训练数据中特定视觉模式。
这就是机器人任务中的 Overfitting。
十一、Underfitting 又是什么?
Overfitting 是:
Train 很好,但新数据差。
而 Underfitting:
欠拟合
是:
连 Train Set 自己都学不好。
例如:
| Epoch | Train Loss | Validation Loss |
|---|---|---|
| 1 | 1.5 | 1.6 |
| 10 | 1.3 | 1.4 |
| 50 | 1.2 | 1.3 |
训练很久:
\[ Train\ Loss \]
仍然很高。
这可能意味着:
- 模型能力太弱;
- 训练时间不够;
- Learning Rate 不合适;
- 输入信息不足;
- 数据质量差;
- Loss 设计有问题。
所以:
\[ \boxed{ Underfitting = 训练数据本身都没学好 } \]
十二、用一张关系图理解三种情况
理想情况:
Train表现好 Validation表现也好 → Generalization较好Underfitting:
Train差 Validation也差 → 模型连训练规律都没学会Overfitting:
Train非常好 Validation变差 → 模型过度贴合训练数据最重要的不是:
Train Loss 有多低。
而是:
\[ \boxed{ Train和Unseen Data之间的差距 } \]
十三、机器人 Dataset 有一个比普通分类更危险的问题:相邻 Frame 太像了
现在进入这一课对 Robot Learning 最重要的一点。
假设一个 Episode:
Frame 0 Frame 1 Frame 2 Frame 3 ... Frame 100机器人控制频率比较高。
那么:
\[ Frame\ 50 \]
和:
\[ Frame\ 51 \]
通常非常相似。
相机可能只变化一点点:
Frame 50 夹爪位置 x = 0.312 Frame 51 夹爪位置 x = 0.314图片几乎一样。
十四、如果我们随机按 Frame 划 Train/Test 会怎样?
假设我们把所有 Frame 混在一起:
random_split(all_frames)可能出现:
Episode 7, Frame 50 → Train Episode 7, Frame 51 → Test Episode 7, Frame 52 → Train这时候 Test Set 看起来没有直接参与训练。
但 Test Frame:
\[ 51 \]
和训练里的:
\[ 50,52 \]
几乎一模一样。
于是模型在 Test 上表现很好。
你可能得到:
\[ Test\ Loss \]
非常低。
但这个结果并不能真正说明:
模型对新的任务执行有很强泛化能力。
因为它几乎已经看过 Test 数据前后相邻的画面。
这叫:
Data Leakage
数据泄漏的一种典型形式。
十五、为什么机器人 Dataset 更应该按 Episode 划分?
一种更合理的思路是:
Episode 0~79 → Train Episode 80~89 → Validation Episode 90~99 → Test这样整个:
\[ Episode\ 90 \]
从来没有出现在训练里。
测试时模型面对的是一整条真正没参与过训练的轨迹。
于是评估更接近:
面对一次新的任务执行,Policy 是否仍然能工作。
所以:
\[ \boxed{ Robot\ Dataset 通常更应该关注Episode-level split } \]
而不是把所有 Frame 完全随机混合后再切。
十六、但“按 Episode 切”也不自动等于真正泛化
这是更进一步的重要认识。
假设:
Train Episode 和 Test Episode虽然是不同 Episode,
但所有 Episode 都有:
- 同一个物体;
- 同一个位置;
- 同一个背景;
- 同一个光照;
- 同一种任务。
那么 Test 仍然很接近 Train。
所以你必须继续问:
我到底想测试哪种 Generalization?
这就涉及:
新初始位置? 新物体? 新背景? 新语言指令? 新任务? 新相机视角?不同论文的 Generalization 定义可能完全不同。
以后看 Benchmark 时,必须查清楚。
十七、为什么 Robot Learning 最终还要做 Rollout Evaluation?
即使 Test Dataset 上:
\[ Loss \]
很好,仍然不能完全证明 Policy 能完成任务。
因为离线 Test Loss 仍然是在问:
给定 Dataset 中正确的 Observation,模型能不能预测正确 Action?
但真正 Rollout:
Observation ↓ Predicted Action ↓ Execute ↓ New Observation ↓ Predicted Action ↓ ...模型自己的动作会改变后续 Observation。
因此最终必须测试:
连续闭环执行一整个任务,会不会成功?
于是常见最终指标可能是:
\[ Success\ Rate \]
例如:
\[ \text{Success Rate} = \frac{\text{Successful Rollouts}} {\text{Total Rollouts}} \]
假设测试 50 次:
\[ 37 \]
次成功,那么:
\[ \frac{37}{50}=0.74 \]
即:
\[ 74\% \]
十八、所以 Loss 和 Success Rate 在回答不同问题
Test Loss:
对离线专家数据,模型的 Action 预测有多准?
Success Rate:
模型真正闭环执行整个任务,最终完成了多少次?
这两者相关,但不是同一回事。
完全可能:
\[ Test\ Loss很低 \]
但是:
\[ Success\ Rate不高 \]
原因可能还是我们之前讲过的:
Distribution Shift
所以现在几章终于连上了:
\[ Training\ Loss \]
只是训练问题。
\[ Validation/Test \]
开始检查离线泛化。
\[ Rollout \]
进一步检查真实闭环行为。
十九、Validation 时为什么通常要model.eval()?
现在第一次自然遇到:
model.eval()注意:
eval()不是“开始计算测试指标”的函数。
它的作用是:
把模型切换到 Evaluation Mode。
为什么需要这个模式?
因为有些层在训练和测试时行为不同,例如之后会学习的:
- Dropout;
- BatchNorm。
所以训练:
model.train()Validation / Test:
model.eval()告诉这些层:
现在不是训练阶段,请使用评估行为。
但:
model.eval()不会自动关闭梯度计算。
这是另一个概念。
二十、为什么 Validation 还常出现torch.no_grad()?
因为 Validation 不需要:
\[ Backward \]
也不需要:
\[ Gradient \]
所以代码可能写:
model.eval()with torch.no_grad(): pred = model(obs) loss = loss_fn(pred, action)这里:
torch.no_grad()表示:
这段计算不需要构建用于梯度计算的 Autograd 图。
这样可以:
- 减少显存占用;
- 减少一些计算开销。
所以:
model.eval()和:
torch.no_grad()不是一回事。
前者:
改变某些 Module 的训练/评估行为。
后者:
关闭这段代码中的梯度跟踪。
这一点以后非常高频。
二十一、一个完整 Training + Validation Loop 开始成形
现在我们已经可以读懂这种结构:
for epoch in range(num_epochs): model.train() for batch in train_loader: obs = batch["observation"] action = batch["action"] optimizer.zero_grad() pred = model(obs) loss = loss_fn(pred, action) loss.backward() optimizer.step() model.eval() with torch.no_grad(): for batch in val_loader: obs = batch["observation"] action = batch["action"] pred = model(obs) val_loss = loss_fn(pred, action)现在不要把它看成两段孤立代码。
它是:
Train Set ↓ Forward ↓ Backward ↓ 更新模型然后:
Validation Set ↓ Forward ↓ 检查当前模型泛化 ↓ 不更新模型二十二、为什么不能在 Validation 上optimizer.step()?
因为一旦这么做:
val_loss.backward()optimizer.step()Validation Set 就参与了参数训练。
它就不再是:
未参与训练的数据。
于是它失去了验证泛化能力的意义。
所以最基本原则:
\[ \boxed{ Validation/Test 不用于普通Parameter Update } \]
二十三、Validation 会参与“模型选择”
假设训练 100 Epoch。
你发现:
| Epoch | Validation Loss |
|---|---|
| 20 | 0.30 |
| 40 | 0.20 |
| 60 | 0.15 |
| 80 | 0.19 |
| 100 | 0.25 |
最好的 Validation 是:
\[ Epoch\ 60 \]
于是最终可能保存:
\[ Epoch\ 60 \]
的模型,而不是 Epoch 100。
这就是:
Model Selection
模型选择。
所以 Validation 虽然:
不直接通过 Backward 修改参数,
但会影响我们:
最终选择哪个模型。
这就是为什么 Test 还应该再独立出来。
二十四、Early Stopping 是怎么自然出现的?
刚才:
Epoch 60 Validation最好之后 Validation 越来越差。
说明可能开始 Overfitting。
那么一种策略就是:
Validation 连续一段时间没有改善,就停止训练。
这叫:
Early Stopping
早停。
它的逻辑就是:
Train Loss继续下降 但Validation不再改善 ↓ 继续训练可能只是在过拟合 ↓ 停止这不是所有训练都必须用,但概念上非常重要。
二十五、现在重新理解“一个模型训练成功了”
不能只说:
代码跑通了也不能只说:
Training Loss降到了0.01至少应该依次问:
\[ \boxed{ Training\ Loss怎么样? } \]
↓
\[ \boxed{ Validation/Test怎么样? } \]
↓
\[ \boxed{ Rollout怎么样? } \]
↓
\[ \boxed{ 真正任务Success Rate怎么样? } \]
这才逐渐接近:
Policy 真的有效。
二十六、第十课整章主线回看
从 Dataset 开始:
\[ \mathcal D \]
不能全部直接拿去训练并用同一批数据证明自己很好。
而应该建立:
\[ \mathcal D \rightarrow \{ D_{train}, D_{val}, D_{test} \} \]
其中:
\[ D_{train} \]
负责:
\[ \theta\text{更新} \]\[ D_{val} \]
负责:
开发过程中的模型选择与超参数判断。
\[ D_{test} \]
负责:
最终独立评估。
如果:
\[ Train\ Loss高 \]
可能是 Underfitting。
如果:
\[ Train\ Loss低 \]
而:
\[ Validation\ Loss高 \]
可能是 Overfitting。
而机器人数据还必须特别注意:
\[ \boxed{ 不要因为相邻Frame高度相似 而造成Train/Test数据泄漏 } \]
因此评估时经常应该考虑:
Episode-level split
最终还要通过:
\[ Rollout \]
检查闭环任务表现。
第十课自测
Generalization 到底是什么意思?
Train、Validation、Test 三者最核心的职责分别是什么?
为什么 Validation 没有参与
backward(),却仍然不能完全替代 Test Set?什么叫 Overfitting?它的 Train Loss 和 Validation Loss 通常会呈现怎样的关系?
什么叫 Underfitting?
为什么 Robot Dataset 如果随机按 Frame 划分 Train/Test,容易产生 Data Leakage?
为什么按照 Episode 划分通常比随机 Frame 划分更合理?
按 Episode 划分以后,为什么仍然不能直接宣称拥有很强的 Generalization?
Test Loss 很低,为什么 Rollout Success Rate 仍然可能不高?
model.eval()和torch.no_grad()有什么区别?为什么 Validation 阶段不能执行普通的
optimizer.step()?如果 Epoch 60 的 Validation 最好,而 Epoch 100 的 Train Loss 更低,你更可能保存哪个 Checkpoint?为什么?