G1 编程与工程 IsaacLab

G1-23dof 强化学习网络架构深度解析

2026-04-12 · 5 min read

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 仓库源码

← 上一篇
G1-23dof Blind-Teacher 盲教师任务配置规范 (V0~V2)
← 返回博客列表