随机网络蒸馏(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(),梯度互不干扰,确保策略更新与探索表征学习解耦。
状态与奖励归一化机制
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_step 与 final_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_state。resolve_rnd_config 函数在 OnPolicyRunner 构造算法时自动完成这一解析:它遍历 obs_groups["rnd_state"] 列表,累加各观测组的特征维度,并将结果注入 rnd_cfg["num_states"]。这一设计保持了与 critic、teacher 等观测组相同的配置范式,用户只需在训练配置的 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 求平均,再按参数形状分写回原始梯度张量。值得注意的是,目标网络始终不参与广播与梯度聚合,因为它在每个进程上的随机初始化是独立但功能等价的——只要预测网络同步,目标网络的绝对参数差异不会影响损失计算的相对关系。
模型持久化与生命周期管理
OnPolicyRunner 在 save 与 load 方法中对 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 使用。
阅读延伸
RND 作为高级训练特性,其内在奖励会直接影响 PPO 的优势估计与回报计算,因此深入理解 PPO 算法实现与训练流程 中的 GAE 与 Clip 机制有助于更精准地调参。若训练任务涉及对称性约束,可进一步查阅 对称性增强与镜像损失,了解 RND 与对称性损失在 update 循环中的并行计算方式。对于多 GPU 部署细节,多 GPU 分布式训练机制 提供了完整的梯度同步与参数广播说明。