G1-23dof 强化学习网络架构深度解析
G1-23dof 强化学习网络架构深度解析
文档版本:V1.0
机器人平台:Unitree G1-23dof
仿真框架:IsaacLab 2.3.0 + IsaacSim 5.1.0
编写日期:2026-04-12
相关文档:Fusion 多传感器融合任务配置规范
1. 整体架构总览
Fusion 策略网络采用非对称 Actor-Critic(Asymmetric Actor-Critic)架构:
- Actor:仅依赖本体感知 + 视觉传感器(无 privileged 信息),用于真实部署
- Critic:额外依赖 privileged 信息(线速度真值、高度图),用于训练引导
┌─────────────────────────────────────────────────────────────┐
│ FUSION ACTOR-CRITIC │
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Policy Obs │ │ Critic Obs │ │
│ │ (6030D) │ │ (241D) │ │
│ └──────┬──────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ ENCODERS │ │ Critic MLP │ │
│ │ Depth→64D │ │ [241→512→ │ │
│ │ LiDAR→64D │ │ 256→128→3] │ │
│ └──────┬──────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────┐ │ │
│ │CrossAttention│ │ │
│ │ Fusion │ │ │
│ │ (64D) │ │ │
│ └──────┬──────┘ │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────────────┐ │ │
│ │ Concat(proprio, │ │ │
│ │ fused) │ │ │
│ │ = 142D │ │ │
│ └──────┬──────────────┘ │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────────────┐ │ │
│ │ Actor MLP │ │ │
│ │ [142→512→256→128→ │ │ │
│ │ 23] │ │ │
│ └─────────────────────┘ │ │
│ │ │ │
│ ▼ │ │
│ actions(23D) 3 values │
└─────────────────────────────────────────────────────────────┘
2. 观测空间维度映射
2.1 Policy 观测(6030D → 142D)
# 原始传感器维度
proprio: 78D # 本体感知
depth: 3072D # 深度图 (1, 48, 64)
lidar: 2880D # LiDAR 扫描 (1, 8, 360)
─────────────────────────────────────
Total: 6030D
# 编码后维度
depth_embed: 64D # DepthEncoder
lidar_embed: 64D # LiDAREncoder
proprio: 78D # 直接传递
─────────────────────────────────────
Total: 206D
# CrossAttention 融合后
fused: 64D # 跨注意力融合视觉特征
proprio: 78D # concat 前保留
─────────────────────────────────────
Actor Input: 142D # 最终输入 Actor MLP
2.2 Critic 观测(241D)
# Critic 专用 privileged 信息
base_lin_vel: 3D # 真值线速度(privileged)
height_scan: 160D # 高度图(privileged)
─────────────────────────────────────
额外: 163D
# 本体感知(同 Policy)
base_ang_vel: 3D
projected_gravity: 3D
velocity_commands: 3D
joint_pos_rel: 23D
joint_vel_rel: 23D
last_action: 23D
─────────────────────────────────────
本体: 78D
Critic Input: 241D # 直接输入独立 MLP(无融合模块)
3. 核心模块详解
3.1 Depth Encoder
class Conv2dHeadModel(nn.Module):
"""2D 卷积编码器,将 (1, H, W) 深度图编码为 (B, output_size) 向量"""
def __init__(self, image_shape=(1, 48, 64), output_size=64, ...):
# 输入: (B, 1, 48, 64) 深度图
# Conv1: 1→32, kernel=5, stride=2, padding=2 → (B, 32, 24, 32)
# Conv2: 32→64, kernel=3, stride=2, padding=1 → (B, 64, 12, 16)
# AdaptiveAvgPool → (B, 64, 1, 1)
# Flatten → (B, 64)
# Linear(64, 256) → (B, 256)
# Linear(256, 64) → (B, 64)
设计意图: - 2 层卷积捕获局部空间特征(边缘、障碍物轮廓) - 相比全连接网络,参数量更少且具有空间平移不变性 - 输出 64D 紧凑特征,供 CrossAttention 融合
3.2 CrossAttentionFusion(跨注意力融合)
这是整个架构最关键的设计,用本体感觉动态调节视觉融合权重。
class CrossAttentionFusion(nn.Module):
def __init__(self, proprio_dim=78, visual_dim=64, d_model=64, n_heads=4):
self.proprio_proj = nn.Linear(78, 64) # 本体感觉投影
self.mha = nn.MultiheadAttention(64, 4, batch_first=True)
self.layer_norm = nn.LayerNorm(64)
def forward(self, proprio, depth_embed, lidar_embed):
# Step 1: 将本体感觉投影为 Query
Q = self.proprio_proj(proprio).unsqueeze(1) # (B, 1, 64)
# Step 2: 堆叠 depth/lidar 作为 Key-Value
visual_stack = torch.stack([depth_embed, lidar_embed], dim=1)
# → (B, 2, 64),2 个 token
# Step 3: 跨注意力
attn_out, attn_weights = self.mha(Q, visual_stack, visual_stack)
# attn_weights: (B, 1, 2) — 每个样本对 [depth, lidar] 的注意力权重
# Step 4: 门控退化
# 当只有 2 个 KV token 时,注意力退化为:
# alpha_depth = sigma(s),alpha_lidar = 1 - sigma(s)
# 其中 s = (Q·K_depth - Q·K_lidar) / sqrt(d_k)
fused = self.layer_norm(attn_out.squeeze(1)) # (B, 64)
return fused, attn_weights
物理意义: - 机器人行走时,本体感觉(如当前关节角度)决定应该相信哪个传感器 - 低速平地上:更多依赖 LiDAR(稳定可靠) - 楼梯前:更多依赖 Depth Camera(精细深度信息) - 软质地面:更多依赖本体感觉(视觉不可靠)
ONNX 兼容性:要求 opset >= 14(支持 scaled_dot_product_attention)
3.3 Fusion Actor MLP
# 完整前向传播
def forward_actor(self, policy_obs):
# policy_obs: (B, 6030D) — 原始传感器数据
B = policy_obs.shape[0]
# Step 1: 解析各部分
proprio = policy_obs[:, :78] # 78D
depth = policy_obs[:, 78:3150] # 3072D → reshape (B,1,48,64)
lidar = policy_obs[:, 3150:] # 2880D → reshape (B,1,8,360)
# Step 2: 编码视觉传感器
depth_embed = self.depth_encoder(depth) # (B, 64)
lidar_embed = self.lidar_encoder(lidar) # (B, 64)
# Step 3: 跨注意力融合
fused, attn_weights = self.cross_attention(proprio, depth_embed, lidar_embed)
# fused: (B, 64), attn_weights: (B, 1, 2)
# Step 4: 拼接 + Actor MLP
actor_in = torch.cat([proprio, fused], dim=-1) # (B, 142)
actions = self.actor_mlp(actor_in) # (B, 23)
return actions, attn_weights
MLP 结构:
Input(142)
↓
Linear(142, 512) + ELU
↓
Linear(512, 256) + ELU
↓
Linear(256, 128) + ELU
↓
Linear(128, 23) ← 输出 23 关节位置残差
3.4 Critic MLP(3 独立副本)
# 每个 Critic 独立权重,接收 241D privileged 观测
class CriticHead(nn.Module):
def __init__(self, input_dim=241):
self.net = nn.Sequential(
Linear(input_dim, 512), nn.ELU(),
Linear(512, 256), nn.ELU(),
Linear(256, 128), nn.ELU(),
Linear(128, 1), # 输出单个标量 value
)
def forward(self, x):
return self.net(x)
# 3 个独立 Critic
self.critic_task = CriticHead(241) # 评估任务奖励
self.critic_reg = CriticHead(241) # 评估正则化
self.critic_safety = CriticHead(241) # 评估安全性
4. MultiCriticPPO 算法
4.1 优势函数混合
# 3 个 Critic 分别预测不同维度的 value
values = [critic_task(r_obs), critic_reg(r_obs), critic_safety(r_obs)]
# 优势加权混合
advantages = []
for i in range(num_critic_groups):
advantage = returns - values[i] # GAE 优势
advantages.append(advantage)
# 加权平均优势
mixed_advantage = sum(w * adv for w, adv in zip(advantage_weights, advantages))
# advantage_weights = [0.7, 0.1, 0.2] (Task/Reg/Safety)
4.2 辅助损失(Auxiliary Losses)
# 训练时额外监督信号,帮助编码器学习有物理意义的表征
# Terrain Head: 从 depth_embed 预测 heightmap
terrain_pred = self.terrain_head(depth_embed) # (B, 160D)
terrain_loss = MSELoss(terrain_pred, true_heightmap)
# Distance Head: 从 lidar_embed 预测每个 ray 的距离
distance_pred = self.distance_head(lidar_embed) # (B, 360D)
distance_loss = MSELoss(distance_pred, true_lidar_dist)
# Velocity Head: 从 concat(fused, proprio) 预测基座线速度
vel_pred = self.velocity_head(concat(fused, proprio)) # (B, 3D)
velocity_loss = MSELoss(vel_pred, true_base_lin_vel)
aux_loss = (lambda_terrain * terrain_loss
+ lambda_distance * distance_loss
+ lambda_velocity * velocity_loss)
lambda_terrain 衰减策略:
# 初期(0~30000 iter):强监督,帮助编码器快速学习
lambda_terrain = 0.5
# 中期(30000~60000 iter):逐步减少
lambda_terrain = 0.25
# 后期(>60000 iter):主要依赖策略自身学习
lambda_terrain = 0.1
4.3 分段学习率
# 编码器用更低学习率,防止遗忘预训练特征
optimizer = optim.Adam([
{'params': encoder_params, 'lr': 1e-4}, # Depth/LiDAR Encoder
{'params': other_params, 'lr': 3e-4}, # MLP, CrossAttention, Critic
])
5. FusionVecEnvWrapper 数据处理
5.1 展平与拼接
IsaacLab 原生 concatenate_terms=False 保持多维 tensor 结构,但 RSL-RL 的 OnPolicyRunner 需要 1D 向量输入:
class FusionVecEnvWrapper(VecEnv):
"""IsaacLab → RSL-RL 数据格式转换"""
def reset(self):
obs, info = self.env.reset()
return self._flatten_obs(obs) # (num_envs, 6030D)
def step(self, actions):
obs, rewards, terminated, truncated, info = self.env.step(actions)
# MultiRewardManager 返回 dict[str, Tensor]
# 展平为单一标量(加权求和)
reward = sum(w * r for w, r in zip(self.reward_weights, rewards.values()))
return obs, reward, terminated, truncated, info
def _flatten_obs(self, obs):
# obs: dict[str, dict] 或 dict[str, Tensor]
# 对每个观测项:resize + flatten → concat
flat_parts = []
for key in ['proprio', 'depth', 'lidar']:
t = obs[key]
flat_parts.append(t.reshape(t.shape[0], -1)) # (N, -1)
return torch.cat(flat_parts, dim=-1) # (N, 6030)
5.2 多奖励处理
# MultiRewardManager.compute() → dict[str, Tensor]
reward_dict = env.reward_manager.compute()
# {
# 'task.track_lin_vel_xy': tensor([...]), # (N,)
# 'task.track_ang_vel_z': tensor([...]), # (N,)
# 'task.alive': tensor([...]), # (N,)
# 'reg.joint_vel': tensor([...]), # (N,)
# 'reg.action_rate': tensor([...]), # (N,)
# 'reg.energy': tensor([...]), # (N,)
# 'safety.base_height': tensor([...]), # (N,)
# 'safety.flat_orientation': tensor([...]), # (N,)
# 'safety.undesired_contacts':tensor([...]), # (N,)
# }
# 展平为单一奖励(供 RSL-RL 使用)
total_reward = (
task_weights['track_lin_vel_xy'] * reward_dict['task.track_lin_vel_xy']
+ task_weights['track_ang_vel_z'] * reward_dict['task.track_ang_vel_z']
+ ...
)
6. 关键设计决策分析
6.1 为什么用 CrossAttention 而不是简单拼接?
| 方案 | 优点 | 缺点 |
|---|---|---|
| 简单拼接 (proprio + depth + lidar) | 简单 | 7000+ 维输入,MLP 负担重;固定权重融合 |
| CrossAttention(本文方案) | 动态权重、自条件、64D 紧凑融合 | 注意力计算开销 |
| 只用单一视觉传感器 | 最简单 | 丢失互补信息 |
CrossAttention 的优势:67 个自由度的关节状态动态调节视觉权重,物理意义清晰。
6.2 为什么 3 个 Critic 而不是 1 个?
单一 Critic 的问题:
- 不同维度的奖励差异巨大(alive ~0.15 vs base_height~-10),混合后难以平衡
- 一个 Critic 无法同时优化 Task(高分稀疏)和 Safety(低分密集)目标
3 个 Critic 分组: - Task Critic:关注速度跟踪(高奖励、正则少) - Reg Critic:关注步态平滑(低奖励、高密度) - Safety Critic:关注安全约束(大量惩罚项)
6.3 为什么需要 Auxiliary Losses?
视觉编码器从零开始学习是困难的(3072D → 64D 的极端压缩)。Aux Losses 提供: - 物理意义监督:heightmap 和 velocity 有明确的物理真值 - 表征正则化:防止编码器过拟合到策略 loss - 训练稳定性:多任务学习提供更丰富的梯度
版本记录
| 版本 | 日期 | 修改内容 | 作者 |
|---|---|---|---|
| V1.0 | 2026-04-12 | 初始版本,网络架构深度解析 | AI Assistant |
本文档由 AI 辅助整理自 unitree_lab_locomotion 仓库源码