IsaacLab 训练范式探索(二):从“上帝视角”到实机落地的蒸馏学习

在上一篇笔记中,我们探讨了如何利用 RNN(循环神经网络)让机器人拥有"隐式记忆",从而在部分可观测的环境中更好地生存。然而,强化学习的上限往往受限于机器人自身能获取的信息。如果机器人的传感器天生就少,或者获取的信息噪声极大,单靠 RNN 自己去"悟",往往收敛极慢,甚至容易陷入局部最优。

为了打破这种物理上的信息壁垒,学术界和工业界引入了蒸馏学习(Distillation) 。在强化学习结合机器人控制的领域,它通常与 不对称 Actor-Critic (Asymmetric Actor-Critic)特权信息学习 (Learning from Privileged Information) 概念绑定。

今天,我们将深入剖析 IsaacLab 中的 distillation_cfg.py 范式,从理论机制到代码配置,再到环境构造的诀窍,以及目前底层库的一个大坑。


一、 需求背景:为什么我们需要蒸馏?

想象一下,你在仿真环境(Isaac Sim)中训练机器人。在仿真器里,你是"上帝"。你可以轻易获取机器人脚底与地面的精确摩擦力系数、可以获取未来地形的高程图(Heightmap)、可以知道机器人质心的绝对线速度、甚至每个关节施加的精确外部干扰力。

如果把这些"上帝视角"的数据(特权信息,Privileged Information)全部喂给神经网络,机器人会学得飞快,步态异常稳健。但现实是残酷的:当这台机器人真正落地到物理世界时,它只有有限的传感器(比如 IMU 和关节编码器),它根本"看"不到摩擦力,也"看"不到高程图。

这就引出了一个矛盾:拥有特权信息的模型性能极高,但无法实机部署;只能依赖本体感知(Proprioception)的模型可以实机部署,但极难训练且性能孱弱。

蒸馏范式(Teacher-Student Paradigm) 就是为了解决这个矛盾而生的。它的核心思想是:先让一个拥有"上帝视角"的教师模型(Teacher)学成归来,然后再由这位全知全能的老师,手把手地教导一个只能看到本体传感器的学生模型(Student),最后让学生模型自己去实战微调。


二、 蒸馏范式的理论机制与底层拆解

在这个范式下,我们一般分为三步走,每一步的算法范式和优化目标都有着本质的区别。

第一步:教师模型训练 (Teacher Training)

  • 范式:标准强化学习 (PPO)。
  • 输入 :本体感知数据 + 大量特权信息(高程图、摩擦力、质量分布等)
  • 环境设置:极端复杂的环境、极大的域随机化(Domain Randomization, DR)。
  • 理论:由于教师拥有上帝视角,面对极端复杂的环境也能轻易找到最优解。这一步的目标是榨干仿真器的潜力,得到一个在任何恶劣物理条件下都能稳健行走的"完美策略"。

第二步:蒸馏克隆 (Distillation)

  • 范式:监督学习 (Supervised Learning, 行为克隆 Behavior Cloning)。
  • 输入
    • Teacher 网络:依然输入特权信息。
    • Student 网络:只输入本体感知数据
  • 理论 :这是最核心的一步。此时强化学习的环境奖励(Reward)被完全抛弃,取而代之的是均方误差(MSE Loss)。仿真环境依然在运行,但只是为了生成轨迹数据
    在每一个时间步,环境同时提供全量观测(给 Teacher)和受限观测(给 Student)。Teacher 根据全量观测输出一个完美的动作 AteacherA_{teacher}Ateacher;Student 根据受限观测输出一个猜测动作 AstudentA_{student}Astudent。
    算法通过计算 MSE(Ateacher,Astudent)MSE(A_{teacher}, A_{student})MSE(Ateacher,Astudent) 并反向传播,强迫学生去模仿老师的行为。在这个过程中,学生模型实际上是在学习如何"通过本体感知去隐式推断那些它看不见的特权信息"。

第三步:学生模型微调 (Student Finetuning)

  • 范式:标准强化学习 (PPO)。
  • 输入:仅本体感知数据。
  • 理论 :虽然学生在第二步中极力模仿老师,但监督学习(行为克隆)存在固有的"协变量偏移(Covariate Shift)"问题------一旦学生在实机中产生了一点点误差,就可能进入一个老师从未遇到过的状态,从而导致彻底崩溃。
    因此,第三步我们需要让学生"断奶"。加载第二步蒸馏得到的网络权重作为初始基线,重新在环境中开启 PPO 强化学习。让学生在没有老师指导的情况下,通过与环境的真实交互和 Reward 试错,修正自己的动作,从而获得真正的鲁棒性。

三、 算法配置详解:手把手教你写 Cfg

为了直观展示这三个步骤的配置差异,这里我不使用继承,而是为你提供完全展开、独立完整 的三个 Runner 配置类。请注意仔细观察它们 policyalgorithm 的不同。

1. 教师模型配置 (Teacher Runner Cfg)

这是最基础的 PPO 训练,重点在于给足网络容量和训练时间。

python 复制代码
from isaaclab.utils import configclass
from isaaclab_rl.rsl_rl import RslRlOnPolicyRunnerCfg, RslRlPpoActorCriticCfg, RslRlPpoAlgorithmCfg

@configclass
class Teacher_PPORunnerCfg(RslRlOnPolicyRunnerCfg):
    num_steps_per_env = 24
    max_iterations = 50000     # 教师模型需要见多识广,迭代次数要够多
    save_interval = 500
    experiment_name = "g1_rough_distillation" # 记住这个名字,后面要用
    empirical_normalization = True

    policy = RslRlPpoActorCriticCfg(
        init_noise_std=1.0,
        actor_obs_normalization=True,
        critic_obs_normalization=True,
        actor_hidden_dims=[512, 256, 128], # 教师网络的容量
        critic_hidden_dims=[512, 256, 128],
        activation="elu",
    )
    
    algorithm = RslRlPpoAlgorithmCfg(
        value_loss_coef=1.0,
        use_clipped_value_loss=True,
        clip_param=0.2,
        entropy_coef=0.01,
        num_learning_epochs=5,
        num_mini_batches=4,
        learning_rate=5.0e-4,
        schedule="adaptive",
        gamma=0.99,
        lam=0.95,
        desired_kl=0.01,
        max_grad_norm=1.0,
    )

2. 蒸馏模型配置 (Distillation Runner Cfg)

此时 Runner 和 Algorithm 变为了 Distillation 专用类,并且采用了上一章节介绍的RNN。这里的核心机制是监督学习。

python 复制代码
from isaaclab_rl.rsl_rl import RslRlDistillationRunnerCfg, RslRlDistillationAlgorithmCfg, RslRlDistillationStudentTeacherRecurrentCfg

@configclass
class Distillation_RunnerCfg(RslRlDistillationRunnerCfg):
    num_steps_per_env = 24
    max_iterations = 20000
    save_interval = 200
    run_name = "distillation"
    
    # 【核心注意】:这里的 experiment_name 必须指向教师模型所在的文件路径层级!
    # RSL-RL 底层会根据这个路径去加载老师的权重 checkpoint。
    experiment_name = "g1_rough_distillation" 
    
    empirical_normalization = True
    
    # 使用蒸馏专用的 Policy 配置
    policy = RslRlDistillationStudentTeacherRecurrentCfg(
        # 学生网络的结构(我们这里让学生带有 RNN 记忆)
        student_hidden_dims=[512, 256, 128],
        rnn_type="lstm",
        rnn_hidden_dim=256,
        rnn_num_layers=2,
        
        # 老师网络的结构(必须和上一步训练的一模一样)
        teacher_hidden_dims=[512, 256, 128],
        teacher_recurrent=False, # 强烈建议老师用纯 MLP,坑在文末解释
        
        activation="elu",
        init_noise_std=0.1, # 蒸馏时噪声通常设小一点
        student_obs_normalization=True,
        teacher_obs_normalization=True,
    )
    
    # 算法变成了监督学习算法
    algorithm = RslRlDistillationAlgorithmCfg(
        num_learning_epochs=5,
        gradient_length=5, # BPTT的序列长度
        learning_rate=1e-3,
        loss_type="mse",   # 均方误差,让学生的动作无限逼近老师
    )

3. 学生微调配置 (Student Finetune Runner Cfg)

蒸馏结束后,我们得到了一个有模有样的学生网络权重。接下来恢复到 PPO 训练,让它自己"磨炼"。

python 复制代码
@configclass
class Student_Finetune_PPORunnerCfg(RslRlOnPolicyRunnerCfg):
    num_steps_per_env = 24
    max_iterations = 20000
    save_interval = 200
    run_name = "student_finetune"
    
    # 同样,为了加载上一步蒸馏好的权重,需要指定到正确的路径
    experiment_name = "g1_rough_distillation" 
    
    empirical_normalization = True
    
    # 策略配置退化为普通的带有 RNN 的 PPO 策略,因为老师已经离开了
    policy = RslRlPpoActorCriticRecurrentCfg(
        init_noise_std=0.1, # 初始噪声不要太大,因为已经有一个好的基线了
        actor_hidden_dims=[512, 256, 128],
        critic_hidden_dims=[512, 256, 128],
        actor_obs_normalization=True,
        critic_obs_normalization=True,
        activation="elu",
        rnn_type="lstm",
        rnn_hidden_dim=256,
        rnn_num_layers=2,
    )
    
    algorithm = RslRlPpoAlgorithmCfg(
        value_loss_coef=1.0,
        use_clipped_value_loss=True,
        clip_param=0.2,
        entropy_coef=0.01,
        num_learning_epochs=5,
        num_mini_batches=4,
        # 【微调关键】:学习率一定要调低!比如从 5e-4 降到 1e-4。
        # 否则一开始极容易把蒸馏好的权重"洗掉"导致步态崩溃。
        learning_rate=1.0e-4, 
        schedule="adaptive",
        gamma=0.99,
        lam=0.95,
        desired_kl=0.01,
        max_grad_norm=1.0,
    )

四、 环境构造的诀窍:特权与妥协

仅仅有算法配置是不够的,IsaacLab 中对于这三个步骤的环境 (EnvCfg) 设置有着极深的门道。

1. 教师环境 (TeacherEnvCfg)

  • 观测 (Observations) :火力全开。在 TeacherObservationsCfg 中,我们不仅放入基本的本体感受,还要把雷达扫描 (height_scan)、甚至精确的脚底接触力 (feet_contact_forces) 全都塞进去。
  • 环境随机化 (Domain Randomization)无限放大。让摩擦力从冰面到柏油路随机,给机器人施加巨大的推力,极大地扰动质量分布。因为老师能"看见"这些干扰,所以再大的随机化它都能学会应对策略。

2. 蒸馏环境 (DistillationEnvCfg)

  • 观测 (Observations) :在这个环境里,我们必须同时提供两套观测字典:
    • policy: 也就是学生能看到的观测。
    • teacher: 之前老师看到的特权观测(必须和第一步一模一样!否则加载权重会报错)。
  • 环境随机化:保持和教师一致的高强度。我们要在这个极端环境中采集数据。

3. 学生微调环境 (StudentEnvCfg)

  • 观测 :恢复到只有 policy(本体观测)和 critic(价值评估观测)。把 teacher 组彻底删掉。
  • 环境随机化适当减弱 。学生毕竟没有全知视角,如果微调时的域随机化还像老师那么变态,它很可能会被逼疯导致策略退化。适当降低随机范围,让它平稳落地。

五、 避坑警告:底层库的 RNN 教师 Bug

在上面的蒸馏配置中,你可能会好奇:为什么学生用了 RNN,而老师却设置了 teacher_recurrent=False?难道老师不能也是 RNN 吗?

理论上是可以的,但在 RSL-RL 目前的底层源码中(尤其是处理 student_teacher_recurrent.py 时),存在一个极其致命的 Bug。

如果你设置 teacher_recurrent=True,底层的初始化逻辑会出错。具体来说,代码会首先使用教师观测维度(比如 316 维)实例化一个 RNN Memory,然后它会把 num_teacher_obs 这个变量强行覆盖为 RNN 的输出维度(比如 256 维)

然而,紧接着在初始化教师的观测归一化层 EmpiricalNormalization 时,它直接拿了被覆盖后的 256 维去初始化。

这导致的结果是:当你加载教师模型 Checkpoint 时,由于 Checkpoint 里的归一化层是正确的 316 维,而当前构建的网络归一化层是错误的 256 维,控制台会直接抛出 RuntimeError: size mismatch for _mean 等崩溃信息。

解决方案

在官方修复此 Bug 之前,最稳妥的方案是:教师模型一律使用标准的纯 MLP 结构(加入多帧历史观测来弥补时序),而学生模型使用 RNN 结构。

如果你非要老师也用 RNN,就必须手动深入 conda 环境下的 RSL-RL 库,修改 student_teacher_recurrent.py,用一个临时变量保存原始维度去初始化 Normalizer。


六、 部署指南:洗尽铅华呈素姿

当你辛辛苦苦跑完了长达数天的"三步走"训练,终于拿到了绿色的 policy.onnx。当你把它交给下游的 C++ 部署工程师时,他们需要特别编写什么复杂的逻辑吗?

完全不需要!

从推理的视角来看,经过微调后的最终学生模型,就是一个普普通通的策略网络。它的输入依然只有纯净的 obs,如果加入了RNN,则依旧以加入维持循环状态所需的 h_inc_in。它的输出依然是干脆利落的 actions(外加 h_out, c_out)。

蒸馏过程中的那些"特权信息"、高程图扫描、教师网络的权重残骸,在导出 ONNX 的那一刻已经被彻底剥离、灰飞烟灭了。C++ 工程师只需要按照我们在上一篇《RNN 策略篇》中提供的伪代码正常推理即可。这就是蒸馏范式的优雅之处:过程极其繁复,结果却无比清爽。


七、 测试数据与效果总结

图注:红色曲线为教师模型(Teacher)训练过程;蓝色曲线为蒸馏模型(Distillation)MSE Loss 过程;绿色曲线为学生模型(Student Finetune)的微调奖励回升过程。

现阶段效果总结:

实事求是地说,就我们目前的测试样本和有限的算力而言,这套冗长繁杂的三步走蒸馏范式,并没有展现出比"精调直接训练的 RNN"具有决定性的、压倒性的优势。

蒸馏链条过长带来了成倍的时间成本,超参数调节(尤其是蒸馏和微调的无缝衔接)也更加玄学。目前来看,除非面对的是极为受限的感知环境或极具挑战性的高动态任务(如纯盲眼过梅花桩),否则对于常规步态任务,我们暂时对其投入产出比持保留意见。

未来,随着训练样本的增加和对特权信息边界的更深入探索,我们或许能找到让蒸馏真正大放异彩的奇点。

相关推荐
吃个糖糖2 小时前
Open3D学习点云读取与显示
学习
DANGAOGAO2 小时前
Transformer学习
深度学习·学习·transformer
电子云与长程纠缠2 小时前
Godot学习04 - UI界面
学习·ui·godot
少许极端2 小时前
算法奇妙屋(三十五)-贪心算法学习之路 2
学习·算法·贪心算法
`Jay2 小时前
Python Redis连接池&账号管理池
redis·分布式·爬虫·python·学习
red_redemption2 小时前
自由学习记录(146)
学习
pq113_63 小时前
开源软件学习笔记 - nanoModbus
笔记·学习·nanomodbus
似水明俊德3 小时前
12-C#.Net-加密解密-学习笔记
笔记·学习·oracle·c#·.net
chinalihuanyu3 小时前
Linux-应用编程学习笔记(五、系统信息和系统资源)
笔记·学习