## 1. 引言
在机器人强化学习中,策略通常需要同时面对多种状态:正常运动、不同地形、不同负载,以及传感器或执行器发生异常的情况。
传统 PPO 通常使用一个 Actor 网络和一个 Critic 网络。所有状态共享同一套参数,这种结构简单、稳定,但也存在一个问题:不同工作模式之间可能产生参数干扰。例如,正常行走策略和故障恢复策略需要关注的动作规律并不完全相同,使用同一个网络强行拟合这些模式,可能导致策略专门化能力不足。
Mixture-of-Experts(MoE,混合专家)提供了一种解决思路:不再让一个网络承担所有行为,而是构造多个专家网络,再使用门控网络根据当前状态决定各个专家的贡献。
MoE-PPO 就是将 MoE 的专家结构和 PPO 的策略优化方法结合起来,使策略能够学习多个相对专门化的行为子策略。
本文主要解释四个问题:
1. PPO 为什么能够稳定地更新策略;
2. MoE 的专家、门控和加权聚合分别是什么;
3. MoE 如何嵌入 PPO 的 Actor 和 Critic;
4. MoE-PPO 与故障检测、故障容错之间是什么关系。
本文只做原理和源码映射,不展开项目安装与运行步骤。
---
## 2. PPO 的基本问题:如何更新策略
### 2.1 强化学习中的策略
在强化学习中,智能体根据状态 \(s_t\) 选择动作 \(a_t\),环境返回奖励 \(r_t\) 和下一状态 \(s_{t+1}\)。策略可以表示为:
$$
\pi_\theta(a_t|s_t)
$$
其中 \(\theta\) 是 Actor 网络参数。
策略的目标是最大化期望累积回报:
$$
J(\theta)=\mathbb{E}_{\tau\sim\pi_\theta}
\left[\sum_{t=0}^{T}\gamma^t r_t\right]
$$
其中:
- \(\tau\) 表示一条轨迹;
- \(\gamma\) 是折扣因子;
- \(r_t\) 是第 \(t\) 步奖励。
直接最大化这个目标通常不稳定,因为策略参数变化后,采样数据对应的策略分布也会变化。
### 2.2 策略梯度
策略梯度定理给出了一个重要结论:
$$
\nabla_\theta J(\theta)
=\mathbb{E}_t\left[
\nabla_\theta\log\pi_\theta(a_t|s_t) A_t
\right]
$$
其中 \(A_t\) 是优势函数,用来衡量动作 \(a_t\) 相对于当前状态平均水平的好坏:
- \(A_t>0\):这个动作比平均水平好,应提高其概率;
- \(A_t<0\):这个动作表现较差,应降低其概率。
实际训练中,优势函数通常由 Critic 估计。
### 2.3 Critic 与优势函数
Critic 学习状态价值函数:
$$
V_\phi(s_t)\approx
\mathbb{E}\left[\sum_{l=0}^{\infty}\gamma^l r_{t+l}\right]
$$
最简单的优势估计是:
$$
A_t=R_t-V_\phi(s_t)
$$
其中 \(R_t\) 是从当前时刻开始计算的回报。
在 PPO 中更常使用 GAE(Generalized Advantage Estimation):
$$
\delta_t=r_t+\gamma V_\phi(s_{t+1})-V_\phi(s_t)
$$
$$
\hat A_t=\sum_{l=0}^{\infty}(\gamma\lambda)^l\delta_{t+l}
$$
这里的 \(\lambda\) 控制偏差和方差之间的平衡:
- \(\lambda\) 较小,估计偏差较大但方差较低;
- \(\lambda\) 较大,估计更接近完整回报但方差更高。
---
## 3. PPO 的核心:限制策略更新幅度
### 3.1 新旧策略的概率比
PPO 使用旧策略 \(\pi_{\theta_{old}}\) 采集数据,再用新策略 \(\pi_\theta\) 更新参数。为了衡量新策略相对于旧策略的变化,引入概率比:
$$
r_t(\theta)=
\frac{\pi_\theta(a_t|s_t)}
{\pi_{\theta_{old}}(a_t|s_t)}
$$
如果 \(r_t>1\),说明新策略提高了该动作的概率;如果 \(r_t<1\),说明新策略降低了该动作的概率。
未经限制的策略目标可以写成:
$$
L^{CPI}(\theta)=
\mathbb{E}_t[r_t(\theta)\hat A_t]
$$
但这个目标允许策略一次更新过大,从而造成训练崩溃。
### 3.2 Clipped Surrogate Objective
PPO 对概率比进行裁剪:
$$
L^{CLIP}(\theta)=
\mathbb{E}_t\left[
\min\left(
r_t(\theta)\hat A_t,
\operatorname{clip}(r_t(\theta),1-\epsilon,1+\epsilon)\hat A_t
\right)
\right]
$$
其中 \(\epsilon\) 通常取 0.1 或 0.2。
直观理解:
- 当新策略变化不大时,允许它按照优势函数继续优化;
- 当概率比超过区间 \([1-\epsilon,1+\epsilon]\) 时,限制继续扩大更新;
- 因此 PPO 在样本利用率和训练稳定性之间取得了平衡。
在代码中,这部分对应:
```python
ratio = torch.exp(actions_log_prob - old_actions_log_prob)
surrogate = -advantages * ratio
surrogate_clipped = -advantages * torch.clamp(
ratio,
1.0 - clip_param,
1.0 + clip_param,
)
surrogate_samples = torch.max(
surrogate,
surrogate_clipped,
)
```
代码使用最小化损失的形式,所以目标函数前面出现负号,并通过 `torch.max` 实现最大化目标的等价形式。
### 3.3 PPO 的完整损失
PPO 的总损失通常由三部分组成:
$$
L_{PPO}=L_{policy}+c_vL_{value}-c_eH(\pi)
$$
策略损失:
$$
L_{policy}=-L^{CLIP}
$$
价值函数损失:
$$
L_{value}=\left(V_\phi(s_t)-R_t\right)^2
$$
熵正则项:
$$
H(\pi)=-\mathbb{E}[\log \pi(a|s)]
$$
熵项用于鼓励探索,避免策略过早变得确定。代码中对应:
```python
loss = (
surrogate_loss
+ value_loss_coef * value_loss
- entropy_coef * entropy.mean()
)
```
---
## 4. MoE 的基本结构
### 4.1 专家网络
MoE 包含多个结构相似但参数不同的专家网络:
$$
e_i(s)=f_i(s;\theta_i),
\quad i=1,2,\ldots,N
$$
每个专家可以学习不同的行为模式。例如在机器人控制中,不同专家可以分别偏向于:
- 正常运动;
- 特定地形;
- 特定负载;
- 某一类执行器故障后的恢复动作。
专家并不一定天然对应某一种模式。对于 learned gate,专家分工通常是在训练过程中逐渐形成的。
### 4.2 门控网络
门控网络根据输入状态计算每个专家的权重:
$$
z(s)=g(s;\theta_g)
$$
其中 \(z(s)\) 是专家 logits。经过 Softmax 后得到归一化权重:
$$
w_i(s)=
\frac{\exp(z_i(s))}
{\sum_{j=1}^{N}\exp(z_j(s))}
$$
因此有:
$$
w_i(s)\geq 0,
\qquad \sum_{i=1}^{N}w_i(s)=1
$$
在代码中,门控部分对应:
```python
gate_logits = self.gate(gate_input)
weights = self.softmax(gate_logits)
```
### 4.3 加权聚合
标准软路由 MoE 将所有专家输出按权重相加:
$$
y(s)=\sum_{i=1}^{N}w_i(s)e_i(s)
$$
项目中的对应实现是:
```python
return (expert_out * weights).sum(dim=-1)
```
若专家输出张量为:
```text
expert_out: [batch, output_dim, num_experts]
```
门控权重为:
```text
weights: [batch, 1, num_experts]
```
那么广播相乘后沿专家维度求和,就得到最终输出:
```text
output: [batch, output_dim]
```
这正是:
$$
y=\sum_i w_i e_i
$$
---
## 5. Dense Routing 与 Sparse Top-k Routing
### 5.1 Dense Routing
Dense Routing 保留所有专家的 Softmax 权重:
$$
y=\sum_{i=1}^{N}w_i e_i
$$
即使某些专家的权重很小,它们仍然会参与输出和梯度计算。
优点是训练平滑、表达能力完整;缺点是每次前向都需要计算所有专家。
### 5.2 Top-k Sparse Routing
Sparse Routing 只保留权重最大的 \(k\) 个专家:
$$
\mathcal{S}(s)=\operatorname{TopK}(z(s),k)
$$
然后只在集合 \(\mathcal{S}(s)\) 内归一化:
$$
w_i(s)=0,\quad i\notin\mathcal{S}(s)
$$
$$
y=\sum_{i\in\mathcal{S}(s)}w_i(s)e_i(s)
$$
项目中的实现流程是:
```python
topk_vals, topk_idx = torch.topk(
gate_logits,
k=top_k,
dim=-1,
)
masked_logits = torch.full_like(gate_logits, -1e9)
masked_logits.scatter_(
dim=-1,
index=topk_idx,
src=topk_vals,
)
weights = self.softmax(masked_logits)
```
需要区分两个概念:
- 稀疏路由限制的是门控权重;
- 具体是否跳过未选中专家的前向计算,还取决于网络调度和部署实现。
设定
假设有4 个专家,某 token 的 gate logits 为:
text
gate_logits = [2.0, 1.0, 0.5, -1.0]
top_k = 2(稀疏路由只取前 2 个专家)。
_gate_dense的结果
直接对全部 4 个 logits 做 softmax:
text
exp: [7.39, 2.72, 1.65, 0.37] 总和 = 12.13 full_weights = [0.609, 0.224, 0.136, 0.030]
→4 个专家全部有非零权重,token 会把计算分给所有专家。
_gate_sparse的结果
第 1 步:先算全量 softmax(缓存用),结果同上:
text
全量 softmax = [0.609, 0.224, 0.136, 0.030]
第 2 步:取 top-2,即专家 0 和专家 1(logits 2.0 和 1.0),其余置-1e9:
text
masked_logits = [2.0, 1.0, -1e9, -1e9]
第 3 步:对这 4 个 masked logits 再做 softmax:
text
exp: [7.39, 2.72, ~0, ~0] 总和 ≈ 10.11 full_weights = [0.731, 0.269, 0.0, 0.0]
→ 只有专家 0、1 有权重,且在两者之间重新归一化(0.731 + 0.269 = 1)。专家 2、3 权重为 0,不会被 dispatch。
---
## 6. MoE 如何嵌入 PPO
MoE-PPO 并不是把 PPO 换成另一种算法,而是替换 PPO 内部的 Actor、Critic 表达结构。
### 6.1 MoE Actor
普通 PPO Actor 表示为:
$$
\pi_\theta(a|s)
$$
MoE Actor 则先由各个专家产生动作相关输出,再由门控网络融合:
$$
h_i(s)=e_i(s)
$$
$$
h(s)=\sum_iw_i(s)h_i(s)
$$
最后由动作分布得到策略:
$$
a\sim\pi_\theta(a|s)
$$
对于连续控制,常见做法是让网络输出高斯分布的均值和标准差:
$$
\pi(a|s)=\mathcal{N}(\mu(s),\sigma(s))
$$
在这个项目中,MoE 专家首先产生动作均值相关输出,然后根据门控权重得到整体动作均值。
### 6.2 MoE Critic
Critic 的目标是估计价值:
$$
V_\phi(s)
$$
MoE Critic 可以让不同专家分别估计状态价值,再进行加权:
$$
V(s)=\sum_iw_i(s)V_i(s)
$$
这样,当状态处于不同工作模式时,价值估计也可以具有一定专门化能力。
Actor 和 Critic 可以分别使用 MoE,也可以只有其中一侧使用 MoE。这是结构选择,不会改变 PPO 的基本目标。
### 6.3 共享特征与专家专属层
MoE 网络可以有三种典型参数结构:
1. 每个专家拥有完整独立的 MLP;
2. 专家共享前面的 backbone,只保留不同的专家 head;
3. 专家共享 backbone 和输出 head。
共享 backbone 可以降低参数量,但会带来一个新问题:不同专家的训练梯度可能相互冲突。
对于同一个共享特征,如果专家 1 希望把参数向一个方向更新,而专家 2 希望向另一个方向更新,那么共享参数的更新结果可能同时损害多个专家。
---
## 7. MoE-PPO 的额外训练机制
### 7.1 Gate Entropy
如果门控网络过早地把几乎全部权重分配给一个专家,其他专家可能得不到足够训练。因此可以引入门控熵:
$$
H_{gate}=-\sum_iw_i\log w_i
$$
将它加入最小化损失时,通常写成:
$$
L=L_{PPO}-\beta H_{gate}
$$
较大的门控熵意味着权重分布更加均匀,可以降低训练早期的专家塌缩风险。
仓库中的实现为:
```python
gate_entropy = -(
weights * torch.log(weights + 1e-8)
).sum(dim=-1).mean()
loss = loss - 1.0e-4 * gate_entropy
```
它不是替代 PPO 损失,而是 PPO 目标上的一个小型辅助项。
### 7.2 Load-Balance Loss
仅最大化门控熵并不能完全保证专家负载均衡。还可以统计一个 batch 中各专家被使用的频率,使平均利用率接近均匀分布:
$$
\bar{w}_i=\frac{1}{B}\sum_{b=1}^{B}w_i(s_b)
$$
理想情况下:
$$
\bar{w}_i\approx\frac{1}{N}
$$
Dense Routing 中,代码根据平均门控权重和均匀分布的差异构造损失;Sparse Routing 中,还会考虑 top-k 专家的实际选择频率。
这类损失的作用是避免某些专家长期没有样本,而不是直接提升某个具体动作的回报。
### 7.3 PCGrad
当多个专家共享 backbone 时,可以把每个专家对应样本产生的梯度看作不同任务:
$$
g_1,g_2,\ldots,g_N
$$
如果两个梯度方向冲突:
$$
g_i^Tg_j<0
$$
PCGrad 会将冲突分量投影掉,再合并梯度。这样做的目标不是改变 PPO 的策略目标,而是减少共享参数更新过程中的任务干扰。
在该项目中,PCGrad 只针对共享 backbone 的 PPO 梯度进行修正,专家 head 和辅助损失仍保留普通梯度。它要求显式路由和共享 backbone,因此更适合“每个 expert ID 对应明确任务”的场景。
---
## 8. MoE 动作分布的一个关键区别
MoE Actor 的输出有两种常见理解。
### 8.1 加权平均后的单一高斯
最直接的实现是先加权得到均值:
$$
\mu(s)=\sum_iw_i(s)\mu_i(s)
$$
再把它作为单个高斯分布的均值:
$$
\pi(a|s)=\mathcal{N}(\mu(s),\sigma(s))
$$
这种方式计算简单,也容易直接接入 PPO。
项目的普通模式会对各专家标准差按同样的门控权重进行融合:
```python
std = (weights.unsqueeze(-1) * expert_std).sum(dim=1)
```
但需要注意:加权均值和加权标准差得到的单一高斯,并不严格等价于多个高斯分布的概率混合。
### 8.2 高斯混合分布
更严格的 MoE 概率模型是:
$$
\pi(a|s)=\sum_iw_i(s)\mathcal{N}(a;\mu_i(s),\sigma_i(s))
$$
其 log-probability 为:
$$
\log\pi(a|s)=
\log\sum_i
w_i(s)\mathcal{N}(a;\mu_i(s),\sigma_i(s))
$$
代码中 `use_gaussian_mixture=True` 时使用 `DiagonalGaussianMixture`,通过 `logsumexp` 计算多个 Gaussian component 的边缘概率:
```python
return torch.logsumexp(
log_weights + component_log_probs,
dim=-1,
)
```
高斯混合能够表示多峰动作分布,适合多个专家给出差异较大的合理动作的情况;代价是 log-probability、熵和 KL 的计算更加复杂。
这也是 MoE-PPO 中非常重要的一点:MoE 不只是改变网络结构,也可能改变策略分布本身。
---
## 9. 显式路由、故障检测与故障容错
### 9.1 显式路由是什么
显式路由是指外部系统已经提供专家编号:
$$
k\in\{0,1,\ldots,N-1\}
$$
MoE 根据这个编号使用第 \(k\) 个专家:
$$
w_i=
\begin{cases}
1,&i=k\\
0,&i\ne k
\end{cases}
$$
这相当于硬路由:
$$
y=e_k(s)
$$
在仓库代码中,`ExplicitExpertMoENet` 将观测最后一维作为 expert ID,转换成 one-hot 权重。
### 9.2 Learned Gate 是什么
Learned Gate 不要求外部提供 expert ID,而是学习:
$$
k\approx g(s)
$$
或者更一般地,直接输出连续权重:
$$
w(s)=\operatorname{softmax}(g(s))
$$
它适合没有明确故障标签、希望模型自动发现状态模式的场景。
### 9.3 Fault Detection 不等于 Expert Routing
需要明确区分三个环节:
```text
故障检测 Fault Detection
↓
故障状态估计 / 故障类别识别
↓
专家路由 Expert Routing
↓
故障容错策略 Fault-Tolerant Policy
```
故障检测负责回答:
> 当前系统是否发生故障?发生了什么故障?
专家路由负责回答:
> 当前状态应该使用哪个专家,或者各专家分别占多大权重?
故障容错策略负责回答:
> 在当前故障状态下,应该采取什么动作?
因此,MoE 本身不是一个故障检测器。它可以利用故障检测结果,也可以通过 learned gate 从观测中学习状态到专家的映射,但不能在没有输入诊断信息的情况下自动保证故障被正确识别。
### 9.4 以四个专家为例
假设有四个专家:
- Expert 0:正常状态策略;
- Expert 1:执行器故障 A 的恢复策略;
- Expert 2:执行器故障 B 的恢复策略;
- Expert 3:严重故障下的保守策略。
如果故障诊断器输出 `expert_id=2`,显式 MoE 使用:
$$
y=e_2(s)
$$
如果使用 learned gate,则由门控网络输出:
$$
w(s)=[0.05,0.10,0.80,0.05]
$$
最终策略是多个专家动作的加权结果:
$$
y=0.05e_0(s)+0.10e_1(s)+0.80e_2(s)+0.05e_3(s)
$$
当故障类别边界不明确时,软路由还可以实现策略平滑过渡,减少专家之间突然切换带来的动作跳变。
---
## 10. `expert_output_dims` 与动作空间专门化
在某些故障场景下,不同专家能够控制的执行器数量可能不同。例如,一个专家对应完整机器人,另一个专家对应部分执行器不可用的机器人。
可以让不同专家输出不同数量的动作:
$$
d_1,d_2,\ldots,d_N
$$
再将输出 padding 到统一动作维度,并用 mask 屏蔽无效动作。
例如:
```text
expert_output_dims = [12, 9, 9, 6]
```
表示:
- 专家 0 输出 12 个动作;
- 专家 1 输出 9 个动作;
- 专家 2 输出 9 个动作;
- 专家 3 输出 6 个动作。
这可以表达不同故障模式下的动作子空间差异,但它依旧不是故障检测。动作 mask 只描述“当前专家允许控制哪些动作”,故障类别仍需要由状态、诊断结果或路由机制决定。
---
## 11. 测试代码验证了什么
`tests/test_rsl_rl_v5_compat.py` 主要用于验证 RSL-RL v5 接口兼容性和 MoE 组件行为,而不是实现完整的 Fault Detection。
例如测试环境中:
```python
observations[:, -1] = torch.arange(self.num_envs) % 3
```
这段代码只是人为设置不同的 expert ID,用来测试显式路由是否正确工作。
测试中的:
```python
model.mlp._last_gate_weights
```
可以用来读取最近一次前向传播产生的路由权重;但它表示的是 MoE 路由结果,不是故障检测置信度。
测试还验证了:
- 不同 actor/critic 侧是否正确构造 MoE;
- sparse gate 是否只保留 top-k 专家;
- explicit expert 的无效动作是否被 mask;
- PCGrad 是否更新共享 backbone;
- MoE 分布模式能否完成 PPO update;
- checkpoint、JIT 和 ONNX 导出是否兼容。
因此,这些测试证明的是“MoE-PPO 工程实现能够运行并保持接口一致”,并不等价于已经完成故障识别实验。
---
## 12. MoE-PPO 的整体优化目标
将 PPO 目标和 MoE 辅助项放在一起,可以写成:
$$
L_{total}=
L_{policy}
+c_vL_{value}
-c_eH_{action}
-\beta H_{gate}
+\alpha L_{balance}
$$
其中:
- \(L_{policy}\):PPO clipped policy loss;
- \(L_{value}\):Critic 价值损失;
- \(H_{action}\):动作分布熵;
- \(H_{gate}\):门控分布熵;
- \(L_{balance}\):专家负载均衡损失;
- \(c_v,c_e,\beta,\alpha\):各项权重。
在项目实现中,gate entropy 和 load-balance 项属于可选辅助损失,标准 PPO 的策略损失、价值损失和动作熵仍然是训练主体。
从优化角度看,MoE-PPO 同时解决两个层面的问题:
1. PPO 解决“如何安全地更新策略”;
2. MoE 解决“如何让多个策略子模块形成专门化分工”。
二者的关系不是替代,而是组合:PPO 提供优化框架,MoE 提供策略表达结构。
---
## 13. 总结
MoE-PPO 可以理解为一种具有条件计算和策略专门化能力的 PPO。
PPO 的核心是通过概率比和 clipping 限制策略更新幅度:
$$
L^{CLIP}=\min(r_t\hat A_t,
\operatorname{clip}(r_t,1-\epsilon,1+\epsilon)\hat A_t)
$$
MoE 的核心是使用多个专家和一个门控网络:
$$
y(s)=\sum_iw_i(s)e_i(s)
$$
将二者结合后:
- Actor 可以由多个专家共同产生动作策略;
- Critic 可以由多个专家共同估计状态价值;
- Top-k 路由可以形成稀疏专家选择;
- Gate entropy 和 load balance 可以缓解专家塌缩;
- PCGrad 可以减少共享 backbone 上的梯度冲突;
- 显式专家和动作 mask 可以表达不同故障模式下的动作约束;
- 高斯混合分布可以保留多专家策略的多峰性。
对于故障容错机器人,完整系统应当是:
```text
机器人观测
↓
故障检测 / 状态估计
↓
MoE 门控或显式 expert ID
↓
专门化恢复策略
↓
安全动作输出
```
最终需要强调:MoE 负责的是专家建模和策略路由,PPO 负责的是稳定策略优化,而 Fault Detection 负责的是识别系统当前处于什么状态。只有三者结合,才能构成完整的故障容错强化学习系统。
---
## 参考源码
```text
moe_rsl_rl/modules/ac_moe.py
moe_rsl_rl/modules/ac_moe_common.py
moe_rsl_rl/modules/ac_moe_gated.py
moe_rsl_rl/modules/ac_moe_explicit.py
moe_rsl_rl/algorithms/ppo.py
tests/test_rsl_rl_v5_compat.py
```
具体参考代码:GitHub - iit-DLSLab/moe-rsl-rl: Extension of RSL-RL for using Mixture-of-Experts in IsaacLab · GitHub