🤖 roboto_origin_03 Wiki
首页 / RSL-RL / RND 随机网络蒸馏探索

随机网络蒸馏(Random Network Distillation, RND)是一种基于预测误差的内在奖励探索机制。其核心思想极为简洁:维护一个固定不变的随机目标网络(Target Network)和一个可训练的预测网络(Predictor Network),当智能体遇到训练分布之外的状态时,预测网络无法复现目标网络的输出,二者嵌入向量之间的差异即构成内在奖励,从而激励智能体探索未知状态空间。本文档将从数学原理、模块架构、训练流程集成与配置实践四个维度,深入剖析 RSL-RL 中 RND 的完整实现。

RND 核心原理与数学基础

RND 的理论根基建立在监督学习泛化误差的直观解释之上。设目标网络为固定映射 $f: \mathbb{R}^d \to \mathbb{R}^k$,其参数在初始化后永久冻结;预测网络为 $g_\theta: \mathbb{R}^d \to \mathbb{R}^k$,通过梯度下降进行优化。对于给定状态 $s$,内在奖励定义为两个网络输出嵌入的 L2 距离:

$$r^{\text{int}}t = | f(s_t) - g\theta(s_t) |_2$$

在训练过程中,预测网络仅在已频繁访问的状态区域上表现良好,因为梯度下降使 $g_\theta$ 在这些区域过度拟合;而对于罕见状态,预测误差保持较高水平,从而自动产生探索信号。本框架中的总奖励为外在奖励与缩放后内在奖励的叠加:

$$r^{\text{total}}_t = r^{\text{ext}}_t + w \cdot r^{\text{int}}_t$$

其中 $w$ 为可通过调度策略动态调整的权重系数。值得强调的是,目标网络始终处于 eval() 模式且不参与梯度计算,确保其输出分布的固定性,这是 RND 能够产生稳定探索信号的数学前提。

Sources: rnd.py

模块架构与类设计

RandomNetworkDistillation 继承自 nn.Module,内部封装了预测网络、目标网络、状态归一化器、奖励归一化器以及权重调度器五大组件。以下 Mermaid 类图展示了该模块的内部结构及其与外部系统的交互关系:

classDiagram
    class RandomNetworkDistillation {
        +MLP predictor
        +MLP target
        +state_normalizer
        +reward_normalizer
        +update_counter
        +weight_scheduler
        +get_intrinsic_reward(obs) Tensor
        +update_normalization(obs) None
        +get_rnd_state(obs) Tensor
        +train(mode) Self
        +eval() Self
    }
    class MLP
    class EmpiricalNormalization
    class EmpiricalDiscountedVariationNormalization
    RandomNetworkDistillation --> MLP : predictor/target
    RandomNetworkDistillation --> EmpiricalNormalization : state_normalization
    RandomNetworkDistillation --> EmpiricalDiscountedVariationNormalization : reward_normalization

预测网络与目标网络均采用 MLP 实现,二者在隐藏层维度与激活函数上可独立配置。若配置中将隐藏维度设为 -1,框架会自动将其替换为输入状态维度,提供一种便捷的“宽度自适应”机制。目标网络在初始化后即调用 eval() 并始终保持推理模式,其参数不参与任何优化步骤。此外,该类显式禁用了 forward 方法,强制调用方使用语义更清晰的 get_intrinsic_reward,以避免误用。

Sources: rnd.py

训练流程集成:从 Rollout 到策略更新

RND 并非独立算法,而是作为辅助模块深度嵌入 PPO 的训练生命周期。下图展示了 RND 在 OnPolicyRunner 主循环中的介入点:

flowchart TD
    A[环境步: env.step] --> B[process_env_step]
    B --> C[更新状态归一化器]
    B --> D[计算内在奖励 r_int]
    B --> E[r_total = r_ext + w * r_int]
    E --> F[存入 RolloutStorage]
    F --> G[compute_returns]
    G --> H[update: PPO + RND]
    H --> H1[采样 mini-batch]
    H1 --> H2[提取 rnd_state]
    H2 --> H3[Predictor vs Target MSE]
    H3 --> H4[rnd_optimizer.step]
    H4 --> H5[PPO optimizer.step]

process_env_step 阶段,PPO 算法首先调用 rnd.update_normalization(obs) 更新状态统计量,随后通过 rnd.get_intrinsic_reward(obs) 计算内在奖励并将其直接叠加到外在奖励上。这一设计意味着 RND 的探索信号会立即影响优势估计与回报计算,无需修改存储结构或 GAE 逻辑。进入 update 阶段后,框架在标准 PPO 损失之外,额外计算预测网络与目标网络输出的均方误差(MSE)作为 RND 损失,并使用独立的 rnd_optimizer(默认 Adam)进行优化。两个优化器分别执行 backward()step(),梯度互不干扰,确保策略更新与探索表征学习解耦。

Sources: ppo.py, ppo.py

状态与奖励归一化机制

RND 对输入状态的分布极为敏感,未归一化的状态可能导致目标网络输出尺度漂移,进而使内在奖励失去可比性。为此,框架提供了两层可选归一化:

归一化类型 类名 作用域 核心机制
状态归一化 EmpiricalNormalization rnd_state 输入 在线维护全局均值与方差,执行 $(x - \mu) / (\sigma + \epsilon)$
奖励归一化 EmpiricalDiscountedVariationNormalization 内在奖励输出 维护折扣回报的移动平均,以其标准差作为分母缩放奖励

状态归一化器通过 update() 方法在每次环境步后增量更新统计量,采用 Welford 在线算法实现数值稳定性,并在 until 计数达到上限后自动停止学习。奖励归一化器则更为复杂:它使用 _DiscountedAverage 计算折扣回报 $\bar{R}t = \gamma \bar{R}{t-1} + r_t$,随后以该折扣回报的统计标准差对原始内在奖励进行缩放。这一设计直接借鉴了 Pathak 等人在大规模 PPO 研究中的实践,有效缓解非平稳奖励尺度对值函数学习的干扰。

Sources: normalization.py, normalization.py, rnd.py

权重调度策略

内在奖励的权重 $w$ 决定了探索信号与任务奖励之间的权衡。框架支持三种调度模式,通过 weight_schedule 字典配置:

模式 参数 行为描述
constant 始终维持初始 weight
step final_step, final_value final_step 前保持初始值,之后跳变为 final_value
linear initial_step, final_step, final_value initial_step 前保持初始值,在 initial_stepfinal_step 之间线性插值,之后固定为 final_value

调度器以 update_counter 为步数计数器,该计数器在每次调用 get_intrinsic_reward 时递增,其步进频率与每轮学习迭代中的环境步数一致。此外,resolve_rnd_config 函数在配置解析阶段会自动将用户配置的 weight 乘以 env.unwrapped.step_dt,确保权重在物理时间尺度上保持量级一致,避免不同仿真频率下的超参漂移。

Sources: rnd.py, rnd.py, rnd.py

配置解析与观测组映射

RND 模块需要明确知道从完整的 TensorDict 观测中抽取哪些字段作为 rnd_stateresolve_rnd_config 函数在 OnPolicyRunner 构造算法时自动完成这一解析:它遍历 obs_groups["rnd_state"] 列表,累加各观测组的特征维度,并将结果注入 rnd_cfg["num_states"]。这一设计保持了与 criticteacher 等观测组相同的配置范式,用户只需在训练配置的 obs_groups 中声明 rnd_state 列表即可,无需手动计算维度。同时,该函数会校验 rnd_state 中所有观测必须为 1D 向量(shape[-1] 存在且 len(shape) == 2),以防止错误地将图像或序列数据传入 MLP。

Sources: rnd.py, on_policy_runner.py

多 GPU 分布式支持

在多 GPU 训练场景下,RND 的预测网络参数与梯度需要参与分布式同步。PPO.broadcast_parameters 方法在训练开始前将主进程的预测网络状态广播到所有从进程,确保各卡上的 RND 初始化一致。在反向传播后,PPO.reduce_parameters 将策略网络与 RND 预测网络的梯度拼接为一个统一缓冲区,执行 all_reduce 求平均,再按参数形状分写回原始梯度张量。值得注意的是,目标网络始终不参与广播与梯度聚合,因为它在每个进程上的随机初始化是独立但功能等价的——只要预测网络同步,目标网络的绝对参数差异不会影响损失计算的相对关系。

Sources: ppo.py, ppo.py

模型持久化与生命周期管理

OnPolicyRunnersaveload 方法中对 RND 进行了专门处理:保存时,若 rnd_cfg 启用,则将 rnd.state_dict()rnd_optimizer.state_dict() 一并写入检查点;加载时按相同逻辑恢复。训练模式切换(train_mode / eval_mode)也会级联到 RND 模块,确保状态归一化器和奖励归一化器在推理阶段停止统计量更新。日志系统在每轮迭代结束时记录当前 rnd_weight,便于开发者追踪探索强度的动态变化。

Sources: on_policy_runner.py, on_policy_runner.py, on_policy_runner.py

配置参数速查表

以下表格汇总了在 algorithm 配置节中启用 RND 所需的关键参数:

参数路径 类型 默认值 说明
rnd_cfg dict | None None RND 配置根字典,设为 None 则禁用
rnd_cfg.num_states int 自动解析 输入状态维度,通常无需手动填写
rnd_cfg.num_outputs int 必填 目标/预测网络的输出嵌入维度
rnd_cfg.predictor_hidden_dims list[int] 必填 预测网络隐藏层尺寸,-1 表示使用输入维度
rnd_cfg.target_hidden_dims list[int] 必填 目标网络隐藏层尺寸
rnd_cfg.activation str "elu" 激活函数类型
rnd_cfg.weight float 0.0 内在奖励初始权重,将被自动乘以 step_dt
rnd_cfg.state_normalization bool false 是否对输入状态进行在线经验归一化
rnd_cfg.reward_normalization bool false 是否对内在奖励进行折扣方差归一化
rnd_cfg.weight_schedule dict | None None 权重调度配置,含 mode 与相应参数
rnd_cfg.learning_rate float 1e-3 RND 预测网络专属优化器学习率

obs_groups 配置中,还需额外声明 rnd_state 列表,指定从完整观测中提取哪些字段供 RND 使用。

Sources: rnd.py, ppo.py

阅读延伸

RND 作为高级训练特性,其内在奖励会直接影响 PPO 的优势估计与回报计算,因此深入理解 PPO 算法实现与训练流程 中的 GAE 与 Clip 机制有助于更精准地调参。若训练任务涉及对称性约束,可进一步查阅 对称性增强与镜像损失,了解 RND 与对称性损失在 update 循环中的并行计算方式。对于多 GPU 部署细节,多 GPU 分布式训练机制 提供了完整的梯度同步与参数广播说明。