第八课标准答案
上一课我们解决的是:Backward 已经算出了梯度以后,参数到底怎么更新。
先把 10 个问题完整收掉,然后直接进入第 9 课。今天下一步要解决的是训练链里一直没有真正展开的:
\[ loss = loss\_fn(pred\_action, action) \]
也就是——Loss 到底在衡量什么。
1. Gradient 和 Learning Rate 分别回答什么问题?
Gradient:
\[ \nabla_\theta L \]
主要回答:
参数朝哪个方向变化,会让 Loss 发生怎样的变化。
而 Learning Rate:
\[ \eta \]
主要回答:
知道方向以后,这次参数到底走多大一步。
所以可以记成:
\[ \boxed{ Gradient\rightarrow方向与局部变化趋势 } \]\[ \boxed{ Learning\ Rate\rightarrow更新步长 } \]
最终二者共同参与:
\[ \theta_{new} = \theta_{old} - \eta\nabla_\theta L \]
2. 已知
\[ w=2 \]\[ \frac{\partial L}{\partial w}=5 \]\[ \eta=0.1 \]
最基础梯度下降:
\[ w_{new} = w_{old} - \eta\frac{\partial L}{\partial w} \]
代入:
\[ w_{new} = 2-0.1\times5 \]
所以:
\[ \boxed{w_{new}=1.5} \]
梯度为正,因此为了降低 Loss,参数向更小的方向更新。
3. 为什么 Learning Rate 太大会导致震荡甚至发散?
因为虽然 Gradient 给出了局部方向,但这个方向只描述:
当前参数附近,Loss 大概怎么变化。
如果:
\[ \eta \]
特别大,那么一次更新:
\[ \Delta\theta=-\eta\nabla_\theta L \]
可能直接跨过较好的区域。
于是可能出现:
最低点在中间 左边 ↓ 一次更新太大 ↓ 直接跳到右边很远 ↓ 下一次又跳回左边参数不断来回跨越低 Loss 区域。
严重时数值越来越大,最终可能出现:
loss = inf或者:
loss = nan4. 为什么 Learning Rate 太小会训练很慢?
因为每次:
\[ \Delta\theta = -\eta\nabla_\theta L \]
都非常小。
即使 Gradient 的方向完全正确:
当前参数 ↓ 移动极小一点 ↓ 再移动极小一点 ↓ 再移动极小一点需要大量 Training Step 才能到达较好的参数区域。
所以不是:
模型完全没学。
而可能是:
学得太慢。
5. Mini-batch SGD 中为什么不同 Batch 的 Gradient 不一样?
因为不同 Batch 包含的训练样本不同。
例如:
\[ B_1 \]
里的样本更容易,而:
\[ B_2 \]
里可能包含更难的数据。
它们对应的 Loss 不同:
\[ L_1(\theta)\neq L_2(\theta) \]
因此:
\[ \nabla_\theta L_1 \]
和:
\[ \nabla_\theta L_2 \]
也可能不同。
所以 Mini-batch Gradient 可以理解为:
利用一小批数据,对整个 Dataset 的更新方向进行一次估计。
这也是训练 Loss 经常上下轻微波动的原因之一。
6. Momentum 比最基础 SGD 多利用了什么?
最基础 SGD 主要看:
\[ 当前Gradient \]
Momentum 还会考虑:
过去若干次更新形成的方向趋势。
因此不会因为当前某一个 Batch 的 Gradient 稍微偏了一点,就立刻完全改变更新方向。
直觉上:
SGD 只看现在往哪里走 Momentum 现在的方向 + 之前积累的惯性所以 Momentum 经常能:
- 减少部分震荡;
- 加速稳定方向上的移动。
7. Adam 中的 \(m_t\) 和 \(v_t\) 怎么理解?
先用直觉理解即可。
\[ m_t \]
是梯度的一阶移动平均,可以理解为:
最近 Gradient 大致朝哪个方向。
而:
\[ v_t \]
是梯度平方的移动平均,可以理解为:
最近这个参数的 Gradient 通常有多大。
所以 Adam 同时考虑:
往哪里走? + 这一维梯度尺度有多大?再决定实际更新量。
8. Adam 会自动消除 Learning Rate 吗?
不会。
Adam 虽然会根据历史 Gradient 调节不同参数的有效更新尺度,但仍然需要一个基础:
\[ \eta \]
例如:
optimizer = torch.optim.Adam( model.parameters(), lr=1e-4)这个:
\[ 10^{-4} \]
仍然非常重要。
所以:
\[ \boxed{ Adam\neq不需要Learning\ Rate } \]
更准确地说:
Adam 在基础 Learning Rate 之上,再根据 Gradient 历史进行自适应调整。
9. 为什么恢复 Adam 训练时还要保存optimizer.state_dict()?
因为 Adam 自己也在学习过程中保存状态,例如:
\[ m_t \]\[ v_t \]
以及当前 Step 等信息。
假设已经训练:
\[ 50000 \]
步。
如果只恢复:
model.state_dict()模型参数恢复了。
但 Adam 之前积累的历史 Gradient 信息没了。
于是 Optimizer 相当于:
参数已经训练很久,但优化器却从“第一天”重新开始。
所以如果目的是真正继续训练,通常需要同时保存:
model.state_dict()和:
optimizer.state_dict()10. 为什么optimizer.step()不能简单解释成“执行梯度下降”?
因为:
optimizer.step()只是 PyTorch 提供的统一接口。
具体怎么更新,取决于 Optimizer 类型。
例如 SGD:
\[ \theta_{t+1} = \theta_t-\eta g_t \]
Momentum SGD 会加入历史趋势。
Adam 又会维护:
\[ m_t,\quad v_t \]
因此:
optimizer.step()更准确的解释应该是:
根据当前 Optimizer 的更新规则,利用已经计算好的 Gradient 修改它所管理的 Parameter。
VLA 系统学习第 9 课:Loss 到底在告诉模型什么?——从 MSE、L1 到 Cross Entropy
现在我们已经把训练链走到了:
\[ Observation \rightarrow Policy \rightarrow Predicted\ Action \]
然后:
\[ Predicted\ Action + Expert\ Action \rightarrow Loss \]
再:
\[ Loss \rightarrow Backward \rightarrow Gradient \rightarrow Optimizer \]
但是到目前为止,loss_fn一直是一个黑盒。
例如:
loss = loss_fn(pred_action, action)这里实际上发生的是一件非常重要的事情:
你通过 Loss 告诉模型:什么叫“错”,以及错多少算严重。
所以 Loss 不是训练代码中随便选的一个数学公式。
它实际上定义了:
\[ \boxed{ 模型到底应该努力变成什么样 } \]
一、先回到 Behavior Cloning
假设专家 Action:
\[ a= [0.10,-0.05] \]
模型预测:
\[ \hat a= [0.07,-0.01] \]
现在模型已经给出了结果。
但它自己不知道:
这个结果究竟好不好?
所以我们必须构造:
\[ L(\hat a,a) \]
把:
\[ 预测值 \]
和:
\[ 正确答案 \]
之间的差距转换成一个数字。
例如:
\[ L=0.0025 \]
之后 Backward 才能回答:
为了让这个数字下降,各 Parameter 应该怎么改变?
因此 Loss 是:
\[ Prediction \]
和:
\[ Gradient \]
之间的桥梁。
完整链是:
\[ \boxed{ Prediction \rightarrow Loss \rightarrow Gradient \rightarrow Parameter\ Update } \]
二、连续 Action 最直接的方法:MSE
假设机器人 Action 是连续值。
例如:
\[ a= [\Delta x,\Delta y] \]
专家:
\[ a=[0.10,-0.05] \]
预测:
\[ \hat a=[0.07,-0.01] \]
一种最自然的想法就是:
每一维预测值离正确答案到底有多远?
差值:
\[ \hat a-a \]
得到:
\[ [-0.03,0.04] \]
但是这里有一个问题。
如果直接相加:
\[ -0.03+0.04=0.01 \]
两个方向的错误可能互相抵消。
明明模型错了两次,最后看起来误差却很小。
所以一种常见方法就是:
把误差平方。
于是:
\[ (-0.03)^2=0.0009 \]\[ (0.04)^2=0.0016 \]
它们都变成正数。
这就是 MSE 的基础。
三、MSE 到底是什么?
MSE:
Mean Squared Error
均方误差。
如果有 \(N\) 个预测数字:
\[ \hat y_1,\hat y_2,\ldots,\hat y_N \]
对应正确答案:
\[ y_1,y_2,\ldots,y_N \]
那么:
\[ L_{MSE} = \frac{1}{N} \sum_{i=1}^{N} (\hat y_i-y_i)^2 \]
这里:
Mean
表示求平均。
Squared
表示误差平方。
Error
就是预测误差。
四、刚才的 Action 算一次 MSE
专家:
\[ a=[0.10,-0.05] \]
预测:
\[ \hat a=[0.07,-0.01] \]
两个平方误差:
\[ 0.0009 \]
和:
\[ 0.0016 \]
求平均:
\[ L_{MSE} = \frac{0.0009+0.0016}{2} \]
得到:
\[ \boxed{0.00125} \]
注意上一些教学例子如果直接把平方误差求和,会得到:
\[ 0.0025 \]
那属于 squared-error sum。
而严格意义上的 MSE 还会进行:
\[ Mean \]
所以这里把二者区分清楚。
五、PyTorch 中的 MSE
对应代码:
loss_fn = nn.MSELoss()loss = loss_fn( pred_action, expert_action)假设:
\[ pred\_action:[32,7] \]\[ expert\_action:[32,7] \]
默认情况下,PyTorch 会对这些元素的平方误差做 reduction,通常得到一个标量:
\[ loss:[] \]
数据链:
\[ [32,7] + [32,7] \rightarrow 224个平方误差 \rightarrow 求平均 \rightarrow 1个Loss \]
然后:
loss.backward()就从这个 scalar Loss 开始反向传播。
六、MSE 有一个非常重要的特性:大错误会受到更重惩罚
假设误差:
\[ e=1 \]
平方以后:
\[ e^2=1 \]
如果误差:
\[ e=2 \]
平方以后:
\[ e^2=4 \]
误差只是变成原来的两倍,但惩罚变成:
\[ 4 \]
倍。
如果:
\[ e=10 \]
那么:
\[ e^2=100 \]
所以 MSE 会特别重视:
预测得非常离谱的样本。
这既可能是优点,也可能带来问题。
七、为什么异常值会明显影响 MSE?
假设大部分 Action 误差:
\[ 0.1 \]
平方:
\[ 0.01 \]
但某一个异常样本误差:
\[ 5 \]
平方:
\[ 25 \]
那么这个单独的异常点可能远远超过其他样本的 Loss。
所以:
MSE 对大误差比较敏感。
如果 Dataset 中存在:
- 错误 Demonstration;
- 传感器异常;
- Action 突变;
- 时间同步错误;
这些异常数据可能对 MSE 产生较大影响。
这也是为什么 Robot Dataset 的质量非常重要。
八、另一种思路:L1 Loss
假设我们不平方。
而是直接取误差绝对值:
\[ L_{L1} = \frac{1}{N} \sum_{i=1}^{N} |\hat y_i-y_i| \]
这叫:
L1 Loss
也经常叫:
Mean Absolute Error,MAE
例如误差:
\[ e=2 \]
MSE 惩罚:
\[ 4 \]
L1:
\[ 2 \]
误差:
\[ e=10 \]
MSE:
\[ 100 \]
L1:
\[ 10 \]
所以 L1 对特别大的异常误差没有 MSE 那么敏感。
九、MSE 和 L1 到底有什么直觉区别?
可以直接比较:
| Error | L1 | Squared Error |
|---|---|---|
| 0.1 | 0.1 | 0.01 |
| 1 | 1 | 1 |
| 2 | 2 | 4 |
| 5 | 5 | 25 |
| 10 | 10 | 100 |
可以看到:
MSE 对大误差的惩罚增长得更快。
因此:
\[ \boxed{ MSE \rightarrow 更强烈惩罚大误差 } \]
而:
\[ \boxed{ L1 \rightarrow 对异常大误差相对更稳健 } \]
但是这不意味着:
L1 一定比 MSE 好。
具体 Loss 应该根据:
- Action 表示;
- 数据噪声;
- 模型设计;
- 原论文方法;
来确定。
以后分析真实 Repository 时,不能因为“连续 Action”就自动假设一定使用 MSE。
必须去查代码。
十、为什么连续 Action 常见回归 Loss?
假设 Action:
\[ a= [\Delta x,\Delta y,\Delta z] \]
每个维度都是连续数值。
例如:
\[ \Delta x=0.013 \]
模型可以预测:
\[ 0.012 \]
也可以预测:
\[ 0.018 \]
问题是:
预测值离目标值有多远?
这种问题叫:
Regression
回归。
因此常见:
\[ MSE \]
或者:
\[ L1 \]
等回归 Loss。
所以:
\[ Continuous\ Action \]
经常对应:
\[ Regression \]
但这里只能作为基本思想,不是所有 VLA 的统一规则。
十一、如果 Action 不是连续值呢?
假设模型的 Action 不再是:
\[ 0.013 \]
这种连续数值。
而是从几个动作类别中选一个:
0 → Left 1 → Right 2 → Forward 3 → Backward 4 → Gripper Close现在正确答案可能是:
\[ class=2 \]
这个问题就不是:
预测数值离 2 有多少距离?
因为类别编号:
\[ 0,1,2,3,4 \]
本身并不代表实际几何距离。
比如:
class 0 = Left class 4 = Gripper Close不能说:
因为 0 离 4 比较远,所以两个动作“差得特别大”。
类别编号只是标签。
因此这时候需要的是:
Classification Loss
最典型就是:
Cross Entropy
交叉熵。
十二、分类模型真正输出的通常不是一个类别编号
假设有 4 个 Action Class。
模型通常不是直接输出:
class = 2而是先输出四个数:
\[ z=[2.1,0.3,4.0,-1.2] \]
这些通常叫:
Logits
可以把它们理解成:
模型对各个类别的原始打分。
Shape:
\[ [B,C] \]
其中:
- \(B\):Batch Size;
- \(C\):类别数量。
例如:
\[ [32,4] \]
表示:
32 个样本,每一个样本对 4 个类别都产生一个分数。
十三、Softmax 做什么?
假设 Logits:
\[ z=[2.1,0.3,4.0,-1.2] \]
Softmax 会把它们转换成类似:
\[ p=[0.12,0.02,0.85,0.01] \]
满足:
\[ \sum_i p_i=1 \]
于是可以理解:
Class 0:12% Class 1:2% Class 2:85% Class 3:1%模型当前认为:
\[ Class\ 2 \]
最可能。
十四、Cross Entropy 在惩罚什么?
假设正确答案:
\[ y=2 \]
模型给正确类别的概率:
\[ p_2=0.85 \]
那么 Loss 会比较小。
如果模型只给正确类别:
\[ p_2=0.01 \]
Loss 就会非常大。
对于单个正确类别,一个核心形式可以理解为:
\[ L=-\log p_y \]
其中:
\[ p_y \]
就是:
模型分配给正确类别的概率。
十五、算两个数字就非常直观
如果正确类别概率:
\[ p_y=0.9 \]
那么:
\[ L=-\log(0.9) \]
约:
\[ 0.105 \]
Loss 很小。
如果正确类别概率:
\[ p_y=0.01 \]
那么:
\[ L=-\log(0.01) \]
约:
\[ 4.605 \]
Loss 很大。
所以 Cross Entropy 在告诉模型:
你应该给正确类别更高概率。
十六、PyTorch 里的 CrossEntropyLoss 有一个很容易踩坑的地方
假设:
logits.shape == [32, 10]表示:
- 32 个样本;
- 10 个类别。
Target:
target.shape == [32]里面可能是:
[3, 1, 7, 0, ...]每个数字表示正确类别编号。
代码:
loss_fn = nn.CrossEntropyLoss()loss = loss_fn(logits, target)这里通常:
直接把 logits 交给
CrossEntropyLoss。
不需要自己先写:
softmax(logits)因为 PyTorch 的CrossEntropyLoss已经以数值稳定的方式组合了相关计算。
这是以后写分类代码非常重要的点。
十七、现在连续 Action 和离散 Action 就能区分了
连续 Action
例如:
\[ a= [0.01,-0.02,0.03] \]
重点:
数值差多少?
可能使用:
\[ MSE \]\[ L1 \]
或者其他回归类 Loss。
离散 Action
例如:
Left Right Forward Close重点:
正确类别是哪一个?
可能使用:
\[ Cross\ Entropy \]
所以:
\[ \boxed{ Regression \rightarrow 数值距离 } \]\[ \boxed{ Classification \rightarrow 类别概率 } \]
十八、这和后面 VLA 的 Action Representation 有直接关系
现在你应该明白为什么我们最早就规定:
阅读任何 VLA,必须先确认 Action 到底是什么。
因为 Action Representation 会直接影响:
\[ Output\ Head \]
以及:
\[ Loss \]
例如:
Continuous Action Regression和:
Discrete Action Token模型的训练目标可能完全不同。
所以以后看到一篇 VLA 论文,不能只问:
Backbone 是什么?
还必须问:
Action怎么表示? ↓ 模型输出什么? ↓ Loss怎么定义?这是同一条链。
十九、机器人 Action 的尺度为什么会影响 Loss?
这是非常重要的一点。
假设 Action:
\[ a= [x,y,z,\theta,g] \]
其中位置单位是米。
例如:
\[ x\in[-0.05,0.05] \]
但某个角度使用度:
\[ \theta\in[-180,180] \]
而夹爪:
\[ g\in[0,1] \]
如果直接对所有维度使用 MSE:
\[ L= \frac1D \sum_i (\hat a_i-a_i)^2 \]
就可能出现:
角度这一维因为数值尺度巨大,主导整个 Loss。
例如位置误差:
\[ 0.01 \]
平方:
\[ 0.0001 \]
角度误差:
\[ 10 \]
平方:
\[ 100 \]
那么 Optimization 几乎都在关心角度。
位置维度的误差在数值上显得非常微小。
二十、这就是 Action Normalization 为什么重要
一种办法是先把不同 Action Dimension 归一化到相近尺度。
例如:
\[ a'_i= \frac{a_i-\mu_i}{\sigma_i} \]
那么原来不同量纲:
米 弧度 夹爪值进入模型后都处在相对可比的数值范围。
这样 Loss 才不容易被某一个巨大尺度的维度完全支配。
所以:
\[ \boxed{ Normalization \neq 只是让数字好看 } \]
它会真正影响:
\[ Loss \]
进而影响:
\[ Gradient \]
最终影响:
\[ Parameter\ Update \]
完整因果链:
\[ Action\ Scale \rightarrow Loss\ Scale \rightarrow Gradient \rightarrow Training \]
二十一、也可以给不同 Action Dimension 不同权重
假设:
\[ L= \lambda_{pos}L_{pos} + \lambda_{rot}L_{rot} + \lambda_{gripper}L_{gripper} \]
这里:
\[ \lambda \]
表示不同部分的 Loss 权重。
例如开发者可能认为:
Gripper 是否闭合非常关键。
于是让:
\[ \lambda_{gripper} \]
更大。
这意味着模型训练时:
Gripper 错误会受到更强惩罚。
所以 Loss Weight 实际上是在表达:
哪些错误对任务更重要。
以后看到论文里的:
\[ L=L_1+\lambda L_2 \]
不能只背公式。
必须问:
为什么第二个 Loss 要乘 \(\lambda\)?
二十二、一个模型可以同时有多个 Loss
后面 ACT、VLA、VLM 中非常常见。
例如模型可能同时学习:
Action Prediction + Latent Representation + Auxiliary Task于是:
\[ L_{total} = L_{action} + \lambda L_{aux} \]
Backward 最终针对的是:
\[ L_{total} \]
所以不同 Loss 都可能通过计算图影响 Parameter。
这也是为什么读论文时:
Loss Function 是核心方法的一部分。
二十三、把 Loss 放回完整 BC 训练
现在一个最基础连续 Action BC:
for batch in dataloader: obs = batch["observation"] expert_action = batch["action"] optimizer.zero_grad() pred_action = model(obs) loss = torch.nn.functional.mse_loss( pred_action, expert_action ) loss.backward() optimizer.step()现在每一行都已经有完整含义了。
假设:
\[ obs:[32,10] \]
进入 Policy:
\[ [32,10] \rightarrow [32,64] \rightarrow [32,7] \]
得到:
\[ pred\_action:[32,7] \]
专家:
\[ expert\_action:[32,7] \]
然后:
\[ MSE \]
比较这:
\[ 32\times7 \]
个预测数字。
得到:
\[ loss:[] \]
Backward:
\[ loss \rightarrow \frac{\partial L}{\partial\theta} \]
Optimizer:
\[ \theta \rightarrow \theta_{new} \]
整个 Behavior Cloning Training Loop 现在已经真正闭合。
二十四、Loss 低是不是代表 Robot Policy 一定好?
仍然不是。
这和第二课 Distribution Shift 联系起来了。
模型可能在 Dataset 上:
\[ MSE=0.001 \]
非常低。
但是实际 Rollout:
第一次动作略有偏差 ↓ 进入Dataset很少出现的Observation ↓ 继续预测错误 ↓ 误差累积 ↓ 任务失败所以:
\[ \boxed{ Training\ Loss \neq 最终机器人任务表现 } \]
Loss 是非常重要的训练信号。
但最终仍然要做:
Rollout Evaluation
例如真正评估:
\[ Success\ Rate \]
这也是为什么 Robot Learning 不能只看训练曲线。
二十五、本章主线回看
现在loss_fn已经不再是一个黑盒。
整个逻辑是:
\[ \boxed{ Model\ Output + Target \rightarrow Loss \rightarrow 定义“什么叫错误” } \]
如果是连续值预测:
\[ Continuous\ Action \rightarrow Regression \rightarrow MSE/L1/\text{其他方法} \]
如果是离散类别:
\[ Discrete\ Action \rightarrow Classification \rightarrow Cross\ Entropy/\text{其他方法} \]
同时:
\[ Action\ Scale \]
会影响:
\[ Loss \]
继而影响:
\[ Gradient \]
最后影响:
\[ Parameter\ Update \]
所以训练从来不是:
随便挑一个 Loss 然后 Backward。
而是:
Loss 必须与模型输出的语义和训练目标匹配。
第九课自测
为什么不能直接把正负误差简单相加作为回归 Loss?
MSE 中的三个词 Mean、Squared、Error 分别是什么意思?
专家:
\[ a=[1,3] \]
预测:
\[ \hat a=[2,5] \]
请计算 MSE。
为什么 MSE 比 L1 更容易受到异常大误差影响?
连续 Action 为什么通常属于 Regression 问题?
离散 Action Classification 为什么不能直接把“类别编号之间的数字距离”当成动作误差?
logits:[32,10]中的 32 和 10 分别是什么?Cross Entropy 的核心目标为什么可以理解成“提高正确类别的概率”?
为什么 PyTorch 的
nn.CrossEntropyLoss()通常直接接 logits,而不是自己提前做 Softmax?如果某个 Action 中位置范围大约:
\[ [-0.05,0.05] \]
而另一个维度范围:
\[ [-180,180] \]
为什么直接使用 MSE 可能产生严重问题?
为什么 Action Normalization 会直接影响模型训练,而不只是数据格式处理?
最后请解释这条链:
\[ Action\ Representation \rightarrow Model\ Output \rightarrow Loss \rightarrow Gradient \rightarrow Policy \]