Loading

PPO、奖励函数设计、参数指标、网路结构

mujoco + stable + baseline3

images/PPO、奖励函数设计、参数指标、网路结构-20260906170200866.webp

一、流程、数据流向、结构

1.1 实际流水线(MuJoCo + SB3)

a1.xml(MuJoCo MJCF:12 关节 + 躯干的几何/惯性/关节限位)
   ↓  定义"机器人是谁"
A1SpeedFollowTrotEnv(gymnasium 接口:obs 35 / act 12 / step / reward)
   ↓  定义"任务是什么"
DummyVecEnv(向量化包装)
   ↓
VecNormalize(obs_rms 在线归一化 → 产出 _norm.pkl)
   ↓
VecMonitor(episode 统计 → 产出 monitor.csv)
   ↓
PPO(SB3)训练  →  model.save()
   ↓
phase1_final_xxx.zip + phase1_final_xxx_norm.pkl

1.2 一个 step 的数据流向(顺时针闭环)

步 数据 变换
1 原始状态 → obs(35)​ 关节角/角速度 + 躯干角速度 + 投影重力 + 上帧动作 + 速度命令
2 obs → 归一化 obs​ (obs − obs_rms.mean)/√var,再 clip
3 归一化 obs → Actor​ MLP 前向,输出 12 维(Gaussian 均值)
4 → action(12) ∈ [−1,1]​ tanh squash 到动作空间
5 action → q_des(12)​ q_des = stand_pos + scale × action
6 q_des → τ(12)​ PD:τ = Kp(q_des−q) + Kd(0−q̇)
7 τ → mj_step​ MuJoCo 物理积分,产生新状态
8 → reward + next_obs​ reward 由速度跟踪/能耗/姿态等加权
9 → RolloutBuffer​ 攒满 n_steps=2048 步
10 → GAE 优势 Â​ λ=0.95,Critic 提供 V(s)
11 → PPO 更新​ clip 裁剪,梯度更新 Actor + Critic 权重
12 next_obs 回流​ 成为下一时刻 obs,闭环

1.3 控制频率分层

层 频率 谁负责
Actor 推理 ≈50 Hz(每次 env.step 一次) ONNX/网络
env 内部物理 200 Hz(若 decimation=4) MuJoCo mj_step
PD 跟随 env.step 你的 env 代码
FOC(仅真机) ≈10 kHz 电机驱动器内部,仿真不涉及

1.4 MuJoCo+SB3 vs Isaac Lab(两条路线)

维度 路线(MuJoCo + SB3)​ Isaac Lab 路线
仿真后端 MuJoCo(CPU) PhysX / Isaac Gym(GPU)
并行环境数 1 ~ 几十 4096(2¹²)
RL 框架 Stable-Baselines3 PPO rsl_rl PPO
环境接口 gymnasium DirectRLEnv / ManagerBasedRLEnv
归一化 VecNormalize(obs_rms,自动存 pkl) 手写 obs scale 或 EmpiricalNormalization
模型保存 .zip + _norm.pkl .pt → 转 ONNX
适用阶段 快速原型 / 算法验证 / 教学​ 大规模训练、Sim2Real 主力

两者算法层完全一样(都是 PPO + Actor-Critic + GAE),差别只在"采样速度"和"工程封装"。你用 MuJoCo 把流程跑通,换到 Isaac Lab 只是换 env 和框架,思想不变。


二、MLP 网络

2.1 结构(model.policy 输出)

输入:obs (35,)
        │
        ├─── Actor 分支(policy_net)
        │      35 → 256 (Tanh)
        │     256 → 128 (Tanh)
        │     128 →  64 (Tanh)
        │      64 →  12 (action_net)  → 动作均值
        │      + 可学习 log_std(12)   → Gaussian 策略
        │
        └─── Critic 分支(value_net,更深)
               35 → 256 (Tanh) → 128 (Tanh) → 64 (Tanh)
               64 → 256 (Tanh) → 128 (Tanh) → 64 (Tanh)
               64 →  1  (value_net)  → V(s)

2.2 三个设计要点

  1. Tanh 而非 ReLU:输出有界、梯度平滑,机器人控制里比 ReLU 更稳(ReLU 死亡神经元会让动作突变)。
  2. Critic 比 Actor 深:价值函数要评估"长期回报",比"输出动作"更难学,所以多给一组 64→256→128→64 的容量。这是 SB3 的 MlpExtractor 配置导致的,不是 bug。
  3. SB3 连续动作是 Gaussian + tanh squash:action_net 输出的是均值,采样后过 tanh 压到 Box(−1,1);所以你看到的动作空间是 Box(-1.0, 1.0, (12,))。

2.3 参数量自校验

这是从文件大小反推网络结构的实战技巧,完美印证:

文件 大小 反推 结论
policy.pth 645,790 B ÷4 B/参数 ≈ 16.1 万​ Actor(35→256→128→64→12)≈5.1万 + Critic(更深)≈10.8万 ≈ 15.9 万​ ✓ 吻合(差 1.3% 来自少量非浮点计数器)
policy.optimizer.pth 1,293,780 B ÷4÷2 ≈ 16.2 万​ Adam 每个参数存 exp_avg + exp_avg_sq​ 两个动量 → 2×16.1万×4B ≈ 1.29 MB ✓ 完美印证​

所以:645KB 的权重 ≈ 16 万个 float32 参数​ —— 这就是你那副 35→[256,128,64]→12 网络的真实体量。以后看到 .pth 大小,除以 4 就能立刻估算参数量级。


三、训练过程参数解读与调参

3.1 关键参数

参数 你的值 含义 解读
observation_space Box(-inf, inf, (35,)) 原始观测(未归一化) 实际进网络前被 VecNormalize 归一化
action_space Box(-1, 1, (12,)) 12 维连续动作 对应 12 个关节
n_steps 2048​ 每次 update 前收集的步数 单 env 时即 2048 步/批
gamma 0.99​ 折扣因子 有效视野 ≈ 1/(1−0.99) = 100 步 ≈ 2 秒(50Hz)
learning_rate cosine_schedule​ 余弦衰减 从初值平滑降到 ~0,适合长训练(1M 步)
num_timesteps phase1 1,001,472​ / phase2 210,944​ 总交互步数 update 次数 = 1001472 ÷ 2048 ≈ 489 次​

3.2 其余常见 SB3-PPO 参数(调参时用)

参数 典型值 作用 调参方向
gae_lambda 0.95 优势估计的偏差-方差权衡 高了方差大、低了偏差大;0.95 是四足常用
clip_range 0.2 PPO 裁剪 ε(限制策略更新幅度) 训练不稳→调小(0.1);学太慢→调大(0.3)
ent_coef 0.01→0.001 熵正则(鼓励探索) 早期大一点促探索,后期减小促收敛
vf_coef 0.5 价值损失权重 一般不动
max_grad_norm 0.5 梯度裁剪 防梯度爆炸,训崩可降到 0.3
batch_size 64(n_steps 的约数) 小批量 显存够可加大
n_epochs 3~10 每批数据重复训练轮数 多了易过拟合

3.3 症状 → 调参对照表

症状 优先检查
reward 完全不涨 观测归一化是否生效?reward 量纲是否过大/过小?lr 是否太大?
训到一半崩了 clip_range ↓、max_grad_norm ↓、gae_lambda
动作抖动 / 不平滑 把"上帧动作"放进观测(你 35 维里已有)、加 action-rate 惩罚 reward、lr ↓
只会原地不动 速度跟踪 reward 权重、探索(ent_coef ↑)
Sim2Sim 与训练表现不一致 观测拼接顺序​ + 归一化 pkl 是否加载​ + PD 参数 + 控制频率
phase2 接着 phase1 训不动 确认 lr schedule 是否重置、obs 语义是否一致

3.4 Phase1 ↔ Phase2 兼容性结论

两者 obs=35 / act=12 / 网络结构 / n_steps / gamma / lr 策略 完全一致​ → 权重可直接互相加载。唯一区别是 num_timesteps(1.0M vs 211K)。

⚠️ 前提是两个阶段的观测向量语义顺序没变过——维度对得上不等于语义对得上。


四、结果文件结构与分析

4.1 zip 里的 6 个文件

phase1_final_260830150443.zip
├── _stable_baselines3_version   ← SB3 版本号
├── data                         ← ⚠️ 元数据(不是 data.pkl!)
├── policy.pth                   ← 645,790 B  网络权重(≈16 万参数)
├── policy.optimizer.pth         ← 1,293,780 B  Adam 动量状态
├── pytorch_variables.pth        ← 额外变量(如 schedule 状态)
└── system_info.txt              ← 环境信息

phase1_final_260830150443_norm.pkl   ← VecNormalize 对象(单独文件)
monitor.csv                           ← VecMonitor 产出(episode r/l/t)

4.2 两个踩过的坑

坑 错误写法 正确写法
① zip 内叫 data,不是 data.pkl​ zf.open("data.pkl") → KeyError 用 load_from_zip_file() 解析成 dict;或干脆 PPO.load() 后读属性
② _norm.pkl 是对象不是 dict​ data["observation_space"] → TypeError vn.observation_space(属性访问)
## ✅ 正确解析 zip 元数据为 dict
from stable_baselines3.common.save_util import load_from_zip_file
data, params, pytorch_vars = load_from_zip_file(zip_path, device="cpu")
print(data["observation_space"].shape[0])   ## 35
print(data["num_timesteps"])                ## 1001472

## ✅ 正确读 norm.pkl
import pickle
with open(norm_pkl_path, "rb") as f:
    vn = pickle.load(f)
print(vn.observation_space.shape[0])        ## 35
print(vn.obs_rms.mean, vn.obs_rms.var)      ## 归一化统计量

## ✅ 最省事:直接 load 模型
model = PPO.load(zip_path, device="cpu")
print(model.observation_space.shape[0], model.action_space.shape[0])

4.3 训练曲线(monitor.csv)

import pandas as pd
df = pd.read_csv("monitor.csv", skiprows=1)   ## 第一行是注释,跳过
print(df[["r", "l"]].rolling(50).mean())      ## r=episode奖励, l=长度
  • episode reward 滚动均值上升 → 在学
  • episode length​ 变长 → 狗能站稳更久(对站立/行走任务是好信号)
  • 配合 TensorBoard:tensorboard --logdir ./ppo_verify/ppo_model

4.4 部署时必须带上的东西

文件 是否必需 原因
policy.pth(在 zip 内) ✅ 网络权重
_norm.pkl ✅ (若训练时用了 VecNormalize)​ 不加载则观测尺度错位,策略直接失效
monitor.csv ❌ 仅分析用
optimizer.pth ❌ 只在"继续训练"时才需要;推理可弃

Sim2Sim / 真机部署的黄金法则:zip 提供"大脑",_norm.pkl 提供"感官校准",两者必须配对加载,缺一个就白训。


五、总结

MuJoCo+SB3 的本质是一条"35 维观测进、12 维动作出"的闭环:环境链把 XML 变成 gym 接口,PPO 每 2048 步用 GAE 优势更新一次 Actor-Critic,Actor 那副 35→256→128→64→12 的 Tanh 网络(≈16 万参数,被 645KB 的 policy.pth 和 1.29MB 的 optimizer.pth 双重印证)每 20ms 吐一次关节目标角,经 PD 变成力矩喂给 MuJoCo;产物里 zip 存权重、norm.pkl 存归一化、monitor.csv 存曲线——三者配对加载,才是可复现、可部署的完整模型。

奖励函数

其实最重要的就是奖励函数设计,分阶段+续训模式,最终奖励值越高越好,值越高越认可当前输出状态

  • 权重全都设置为正数,保证不混淆
  • 奖励项为正数,奖励值越大越倾向于当前状态行为
  • 惩罚项为负数,与设定的误差越大,惩罚越大,越不会倾向于当前状态行为
posted @ 2026-10-11 19:10  Invo1  阅读(7)  评论(0)    收藏  举报