上篇梳理结尾时,我把 On-Policy Distillation 这条线暂时定在了"用固定权重的蒸馏约束帮助 student 在稀疏奖励环境下稳定起步"上。当时自己很清楚,这只是把问题往后推了一步:固定权重意味着 teacher 的监督强度不会随着 student 的真实水平变化,早期够用,中期开始别扭,后期基本反噬。所以 6 月到 9 月这三个月,我把重心从 reward shaping 挪到了"自适应监督"方向上,中间又不得不反复回到 shaping 的老问题里补课。这篇就按时间线把这三个月的进展、卡壳、和最终跑通的东西梳理一遍。内容偏向实际复现和调参过程,适合在做 RL 稀疏奖励训练、行为蒸馏、或者在线模仿学习的同行参考。
1. 为什么6月还在折腾reward shaping:稀疏奖励下的老路与新问题
1.1 项目背景与旧路线的瓶颈
我这次推进的任务是一条六自由度机械臂的抓取-放置流水线,状态空间是关节角、末端执行器位姿和物体坐标,动作是连续的四维控制量。环境本身是典型的稀疏奖励设定:只有物体落到目标区域才给 +1,其他所有时刻奖励都是 0。第一次跑 PPO 时,agent 在几百步内几乎学不到任何梯度信号,探索完全靠随机初始化策略去撞运气——这种情况谁跑谁知道,基本等于让新手闭着眼找路。
5 月份之前我依赖的是 reward shaping,用贴合任务先验的势能函数把连续进度信号注入环境。起初效果立竿见影,agent 很快学会了"靠近物体并抓取"的前半段技能,但越往后越不对劲。6 月的工作就是在给这套 shaping 方案做手术:试图修补它在任务中后期暴露出的缺陷。
1.2 势能塑形的原理和它承诺的东西
Reward shaping 的理论根基是 Ng 等人在 1999 年提出的 potential-based shaping,核心公式是:
F(s, s') = γΦ(s') - Φ(s)
把 F 加到原始奖励上,得到新奖励 R' = R + F。这个形式最漂亮的性质是:只要 Φ 只依赖状态(不依赖动作),那么新奖励下的最优策略和原始奖励下的最优策略完全一致。也就是说,理论上我可以随便设计一个合理的 Φ 来引导 agent,而不必担心"引导过头导致最终策略偏掉"。
实际落地时我们会给 F 乘一个缩放系数 η,变成 R' = R + η(γΦ(s') - Φ(s)),因为环境本身的奖励和塑形信号量纲不同,直接相加会让某一边主导。我 6 月最初的版本就是拿"当前末端执行器与目标点的负欧氏距离"当 Φ,让 agent 每走一步都获得接近目标的密集正反馈。这个版本把训练收敛步数从漫无边际压到了可接受范围,但我很快遇到了更隐蔽的问题。
1.3 6月实践中暴露的三个深坑
第一个坑是塑形信号与真实任务的博弈。当远端抓取和最终放置属于两个子任务时,单一距离势能会在中间段形成"能量低谷"。agent 学会了靠近物体,却会在抓到物体之后停在原地磨蹭,因为继续前进带来的塑形增益还不如当前"接近目标"的稳态红利。直观说,距离势能把 agent 导向了局部极大值区域,而这个区域并不是真实稀疏奖励希望的路径。
第二个坑是 η 的尺度非常难调。我试过固定 η 和让 η 随训练步数退火,都只适用于单一环境。换一个物体初始位置分布、换一个目标区域大小,最优 η 就变了。这个调参过程消耗大量轮次,而且每换一次任务配置都要重新来,根本算不上一个通用方案。
第三个坑最致命:shaping 在某些状态下会主动毁掉探索。因为塑形奖励是稠密的,agent 一旦发现某个方向会产生稳定正反馈,就会固化在那一小片状态区域里。策略熵快速下降,真实稀疏奖励还没碰到过,agent 已经提前觉得"我找到了最优"。后来复盘时我看过采集的状态分布,中后期所有 episode 的初始位置高度集中,agent 根本没有尝试过把物体抓到其他角度再放置的可能性。
所以 6 月下旬我做了一个决定:不再单独依赖 handcrafted shaping,而是引入一个外部监督信号来源,让 agent 不只从稀疏奖励里自己摸索,而是有一个"老师"在示范该往哪里走。这条路就是 On-Policy Distillation。
2. 转向On-Policy Distillation:监督信号的本质变化
2.1 为什么蒸馏比shaping更适合做稀疏奖励的引导信号
Reward shaping 本质上是在工程层面修改 MDP 的奖励函数,它通过"数值反馈"告诉 agent 当前状态好不好。但数值反馈有一个天然短板:它不包含动作信息。agent 知道哪个状态好,依然不知道该往哪个动作方向调整。Distillation 完全不同,它的监督信号是 teacher 在当前状态下给出的动作概率分布,是直接的行为指引,信息密度比一个标量奖励高很多。
我最终选择走 On-Policy Distillation 而不先考虑常规行为克隆,原因是样本分布匹配问题。行为克隆要求离线收集固定示范数据集,但 teacher 和 student 的状态覆盖范围往往不同,student 在训练中访问的状态很容易落到示范集稀疏的区域。On-policy 蒸馏的思路是,student 一边与真实环境交互,一边从 teacher 的在线输出中学习,teacher 是在 student 当前造访过的状态下给出监督,分布一致性天然好一些。
2.2 第一版蒸馏方案的设计与实现细节
7 月中旬我搭出了第一版完整方案,核心结构如下:
- teacher:用密集奖励版本(带 shaping 但训练充分收敛)预训练的 PPO 策略,冻结不再更新;
- student:与 teacher 同架构但随机初始化的在线 PPO 策略,每步与环境交互;
- 训练损失:student 自有的 PPO loss 加一项蒸馏损失。
蒸馏项我采用了 KL 散度,方向选择的是 KL(π_student || π_teacher)。这个方向有一个明确的好处:它是 mode-seeking 的,student 只需要拟合 teacher 分布中的主要模式,而不必强行覆盖 teacher 分布的全部尾部。考虑到 student 网络容量有限,拟合主要模式可以让训练更稳定,不容易被 teacher 在低置信度状态下的随机动作带偏。
一次训练的损失大概是这样的:
# student_policy: 当前策略 # teacher_policy: 冻结的策略 # old_policy: 更新前采样时的策略 def distillation_loss(states, actions, old_log_probs, advantages): # PPO 策略项 new_log_probs, entropy = evaluate_actions(states, actions) ratio = torch.exp(new_log_probs - old_log_probs) policy_loss = -torch.min( ratio * advantages, torch.clamp(ratio, 1.0 - clip_eps, 1.0 + clip_eps) * advantages ) # 蒸馏项 student_dist = student_policy.get_dist(states) teacher_dist = teacher_policy.get_dist(states) kld_loss = torch.distributions.kl_divergence(student_dist, teacher_dist) # KL(student || teacher) total_loss = policy_loss + beta * kld_loss return total_loss第一版跑出来效果超出预期:student 收敛速度比纯 shaping 时快了不少,尤其是前期抓取动作,teacher 的示范让 student 少走了大量弯路。但跑到中后期问题又出现了:student 越来越像 teacher,最终性能被 teacher 封死在一个上界附近,别说超越,连持平都勉强。更麻烦的是 student 自发探索几乎停滞,因为蒸馏损失总是趋向于把 student 拉向 teacher 的确定性输出,这和有熵正则的探索机制是冲突的。
2.3 固定权重蒸馏的关键缺陷
我后来把问题归结为两个层面。
第一是时间层面的固定权重。训练早期 student 一张白纸,强蒸馏是对的;到中后期 student 已经具备一定能力,蒸馏信号和真实稀疏奖励开始出现分歧,如果权重还是那么大,student 会优先服从 teacher 而不是从真实奖励里学习。直观来说,一个已经认得回家的路的小孩,你还要全程牵着他走,他永远学不会独立判断。
第二是状态空间层面的固定权重。不同状态下的 teacher 可信度差异可能非常大。有些状态 teacher 知道得很清楚,有些状态 teacher 本身也很困惑,动作分布的熵很高。一个全局 β 完全没有能力区分这两种情况。所以 8 月我把工作重点转向了"自适应监督":把蒸馏监督的强度从固定超参变成随 student 学习状态动态变化的量。
提示:固定权重蒸馏在稀疏奖励设定下不是不能跑,而是很难跑出超越 teacher 的效果。如果目标只是复现 teacher 的水平,固定权重加一个合适的退火就够;但如果目标是让 student 最终超过 teacher,必须让监督信号学会"什么时候放手"。
3. 自适应监督设计:从退火权重到state-level动态调度
3.1 自适应监督想解决的核心矛盾
自适应监督的本质是处理一个动态权衡:student 早期需要 teacher 带,后期需要 teacher 放;teacher 在某些状态值得信,某些状态不值得信。这个权衡如果用固定超参表达,那就永远是"一刀切",无法匹配训练动态和状态差异。所以我把监督强度拆解为两个控制维度:一个全局层面的β控制整体蒸馏强度,一个状态层面的α控制每个样本点的蒸馏权重。
全局β解决的是训练阶段问题:student 能力越接近 teacher,β应该越小,最终趋近于 0,把全部学习交给真实环境奖励。状态层α解决的是空间差异问题:student 在该状态下与 teacher 的分布越不一致,说明 student 越需要 teacher 帮助,α越大;已经和 teacher 没有分歧的状态,α应该压下来,让 student 自行决定是否探索更好的行为。
3.2 两层调度机制的最终形态
我最终的方案可以写成这样:
全局β_t:
- β_t 由 eval 阶段 student 的平均回报与目标回报的差距决定;
- 差距大,β_t 大;回报接近或超过目标,β_t 指数下降;
- 用 sigmoid 或者简单的 clip 映射都可以,重点是让 β_t 随真实能力信号变化,而不是随训练步数线性退火。
beta = beta_max * sigmoid(-k * (eval_return - return_target))状态层α(s):
- 输入是当前状态下 teacher 分布与 student 分布的 KL 散度;
- 当 KL 大,说明 student 偏离 teacher 明显,需要更强烈的监督;
- 当 KL 小,说明 student 已经跟上 teacher,监督权重下调;
- 为了稳定性,α还要做一个平滑处理,不能直接用瞬时值。
kl = kl_divergence(teacher_dist(states), student_dist(states)) alpha = clamp(sigmoid(k_alpha * (kl - kl_target)), alpha_min, alpha_max) alpha = ema_smooth(alpha, alpha_ema) # 关键,避免噪声最终蒸馏损失:
distill_loss = mean(beta_t * alpha(s) * kl)注意这里 KL 方向我改回了 KL(teacher || student)。因为在自适应权重下,我们期望用 KL 的大小来标定"student 与 teacher 的距离",用对称或者 forward 方向更合理,mode-seeking 的 reverse KL 会让 KL 在 student 远离 teacher 时被低估(因为只覆盖主要模式),导致 α 无法及时拉高。
3.3 关键实现细节:梯度断开与平滑
自适应监督写起来不难,但两个细节直接决定实验能不能跑通。
第一个是 α 必须detach()。如果你让 α 是 student 分布的函数,并且直接乘进蒸馏损失里回传,student 会学会一条捷径:通过降低自己的低置信度来压低 KL 进而压低 α,蒸馏梯度因此被削弱。这不是我们想要的。我们想要 α 扮演"监督调度器"的角色,而不是 student 可操纵的逃逸开关。所以我在实现里把 α 强制从计算图中断开:
with torch.no_grad(): student_dist_for_kl = student_policy.get_dist(states) teacher_dist_for_kl = teacher_policy.get_dist(states) kl = kl_divergence(teacher_dist_for_kl, student_dist_for_kl) alpha = torch.clamp(torch.sigmoid(k * (kl - kl_target)), alpha_min, alpha_max) # 使用 detach 的 alpha 参与蒸馏损失 distill_loss = (alpha.detach() * beta * kl).mean()第二个是 α 的平滑。最初我直接拿每个 batch 内逐样本的 KL 算 α,结果训练曲线抖动得很厉害。原因是 teacher 在某些状态的分布熵本身就很高,KL 会突然飙升,导致 α 脉冲式波动。我给 α 加了一个简单的 EMA(指数滑动平均)之后,训练瞬间稳定下来。这个 EMA 的系数不能太大也不能太小,我试下来 0.98~0.995 之间的效果比较合理。如果你在复现时发现蒸馏权重震荡,优先检查这里的平滑。
3.4 为什么这套设计能同时解决 shaping 和固定蒸馏的问题
Reward shaping 的问题在于它给的是"状态价值的局部梯度",agent 只能从中推断方向,却不能获得动作级反馈。自适应蒸馏的监督信号来自 teacher 的动作分布,动作信息密度远高于 scalar shaping,同时它的强度又是动态的:student 越弱,teacher 越强势介入;student 越强,teacher 越退到幕后。这正好补上了固定权重蒸馏"把 teacher 的盲区和学生潜力一起封死"的短板。
全局 β 和状态层 α 的作用范围是互补的。β 负责宏观学程管理,决定某个训练阶段整体上 teacher 该多大声音说话;α 负责细粒度信用分配,在同一个 batch 内部区分哪些状态 teacher 该多说、哪些状态该少说。两者相乘,同一个监督信号就同时具备了"时间自适应"和"空间自适应"。
4. 实验复盘:哪些效果是真实的,哪些是假象
4.1 实验设置与对比基线
8 月底到 9 月初我做了一组横向对比实验,在三个环境配置下分别跑四种方法:
- RS-only:纯 reward shaping,η 做步长退火;
- Distill-fixed:On-Policy Distillation,β 固定为 0.5;
- Distill-anneal:全局 β 按步数线性退火,无状态层 α;
- Adaptive:本文的自适应监督方案(全局 β + 状态层 α)。
所有配置共用同一套 PPO 超参数,student 网络结构一致,seed 覆盖 5 个。评估指标取训练收敛后的平均 episode return、成功率、以及收敛所需的环境步数。实验数据基于我当时跑的配置,不同环境有波动,但趋势是稳定的。
4.2 关键对比结果
环境1:抓取-放置中距离任务(物体初始位置中等偏移)
| 方法 | 平均回报 | 成功率 | 收敛步数 |
|---|---|---|---|
| RS-only | 0.21 | 14% | 未完全收敛 |
| Distill-fixed | 0.62 | 71% | 1.2M |
| Distill-anneal | 0.68 | 74% | 1.1M |
| Adaptive | 0.79 | 89% | 0.9M |
环境2:近距离堆叠任务(teacher 本身表现较好的场景)
| 方法 | 平均回报 | 成功率 | 收敛步数 |
|---|---|---|---|
| RS-only | 0.18 | 9% | 未完全收敛 |
| Distill-fixed | 0.58 | 66% | 0.8M |
| Distill-anneal | 0.64 | 70% | 0.8M |
| Adaptive | 0.85 | 93% | 0.6M |
环境3:长距离规避任务(teacher 也有明显失败率)
| 方法 | 平均回报 | 成功率 | 收敛步数 |
|---|---|---|---|
| RS-only | 0.09 | 3% | 未完全收敛 |
| Distill-fixed | 0.23 | 18% | 0.7M |
| Distill-anneal | 0.27 | 22% | 0.7M |
| Adaptive | 0.41 | 39% | 0.7M |
三个环境趋势一致:自适应监督在回报、成功率和收敛速度上都显著优于固定权重和单纯退火。环境3尤其说明问题,teacher 自己也弱,固定蒸馏把 student 锁死在了一个低水平上;自适应监督在 teacher 明显困惑的状态下降低了监督权重,给了 student 自己绕开失败区域的余地。
4.3 给我印象最深的两个失败案例
第一个失败的尝试是直接用 KL 阈值做硬切换。我不想让 α 连续变化,而是设了一个阈值:KL 大于某个值就开启蒸馏,小于就关闭。结果 student 在边缘状态反复"失去老师",上一轮还有监督,下一轮突然没有,策略在两种模式之间疯狂切换,训练彻底崩了。这个案例让我意识到,监督强度的变化必须是软的、平滑的,硬门控会造成优化目标不连续,PPO 的梯度估计直接恶化。
第二个失败是忽略 teacher 熵导致的 α 失真。我在某个版本里发现 α 在所有状态下都偏高,student 根本没有"独立空间"。检查后发现问题出在 teacher 在远离任务状态的区域分布熵很大,KL 始终很大,α 一直压在最大值。后来我加了 teacher 本身的置信度调制:如果 teacher 在当前状态的熵超过某个上限,我们就认为 teacher 在这个状态并没有真正的掌握知识,监督权重也应当压低。这个修正让 α 的分布从"处处都大"变成了"在该大的地方大",效果提升明显。
4.4 关于评估的一点提醒
我也踩过一个假象陷阱。自适应监督方案在训练中段的回报曲线看起来不如固定蒸馏,因为前者的 β 在早期很大,student 更多时间在模仿,看起来"学得慢";而后者的 β 固定,student 很快在 teacher 的强约束下表现得"像 teacher",让人误以为它学得更快。但随着训练推进,固定方案的回报曲线很快撞上 teacher 上界,自适应方案则持续上升实现反超。如果你只看前 300k 步的曲线,很可能得出相反结论。评估这种方案时一定要跑完全程,中期曲线短期的"落后"恰恰是后期超越的必要代价。
5. 下一步打算与这三个月最想说的经验
5.1 下一步想做的两个扩展
第一个方向是把 teacher 本身变成混合模型。单 teacher 在部分状态不可靠的问题,与其靠 α 做被动压制,不如直接换成多个 teacher 的集成输出,用 teacher 间的分歧度作为不确定性的估计。如果集成 teacher 在各状态的动作分布高度一致,说明这个 state 的监督信号是可靠的;如果分歧大,说明这个 state 连专家都不确定,α 应该自动降低。这相当于把"对 teacher 信任度的估计"从手工设计变成了数据驱动。
第二个方向是把 reward shaping 的势能函数也纳入自适应框架。现在 shaping 和蒸馏是两套独立的信号,前者给数值反馈,后者给分布监督。但我越来越觉得它们可以统一:shaping 的势能 Φ 可以用 teacher 的 value function 替代,这样 agent 获得的塑形信号也是"状态价值势能差"的形式,而蒸馏则作为动作层面的辅助监督,两者强度由同一套自适应机制协同调度。
5.2 这三个月我个人最想分享的几句经验
做 Reward shaping 时,别太信任"势能函数保证最优策略不变"这句话。它保证的是理论上的最优策略,不是实际训练中的收敛策略。有限样本、有限表达能力的 agent 会被塑形信号带进各种局部模式,这些模式离理论最优非常远。
做 Distillation 时,把"学生何时该独立"当成一个一等公民的建模问题,而不是调参问题。固定权重和简单退火都是在训练之前就定死了监督节奏,完全没有考虑 agent 在训练中的真实状态。自适应监督的效果之所以好,本质上不是因为某个公式,而是因为它让监督信号具备了反馈闭环:agent 的状态决定监督强度,监督强度反过来又影响 agent 的状态。
最后,如果只能给一条具体的操作建议,我会说:先用 KL(student || teacher) 还是 KL(teacher || student) 这种细节不要纠结太久,但 α 的平滑和 detach 必须在第一版就做对。平滑决定稳定性,detach 决定学习方向是否正确,这两处错了,后面所有精调都是在错误的地基上反复折腾。