记录训练实验的诊断、根因、修复过程。每次遇到值得记录的问题都更新这里。
| 阶段 | 平均奖励 | 旋转度数 | 起跳率 | 落地率 | 成功率 | 结论 |
|---|---|---|---|---|---|---|
| jump | 13.4 | 9.5° | 0% | 0% | 0% | agent 几乎不动 |
| rotate | 554.0 | 51.2° | 100% | 100% | 0% | 跳不翻 |
| land | 1364.3 | 54.9° | 100% | 100% | 0% | 跳不翻 |
| full | 1877.2 | 59.9° | 100% | 100% | 0% | 跳不翻,6.5M 步无突破 |
Agent 发现的捷径:跳起来不旋转直接落地。
这条路径同时拿到三项奖励:
- 起跳奖励(W_JUMP=3.0 × vz):向上速度大就给
- 落地奖励(W_LANDING=10.0):只要脚着地就给,没有旋转门槛
- 姿态引导奖励(W_POSE_GUIDE=1.5 × 每步):帧2(腾空收腿)匹配即得
"不旋转纯跳" 的总收益远超 "费力翻转"。W_ROTATION=5.0 的旋转奖励根本无法竞争。
关键设计缺陷:
W_LANDING无旋转门槛 → agent 无需翻转就能拿到最大落地奖励W_ROTATION=5.0太弱 → 即使腾空旋转,收益也比 "跳+落" 低- 腾空时不旋转没有惩罚 → agent 在空中 "呆站" 等重力下拉完全合理
jump 阶段模型为什么 0% 起跳率?
jump 阶段(training_phase="jump")只有起跳奖励和姿态引导,没有落地奖励。
v1 实现中,姿态引导奖励在静止站立时已经不小(框架0 蹲伏姿态匹配)。
Agent 发现"原地不动"也能拿到稳定的 W_POSE_GUIDE 奖励,跳起来反而触发侧倒惩罚。
→ 最优策略是:静止趴着,稳稳拿姿态分。
rotate 阶段为什么突然 100% 起跳?
rotate 阶段引入了 W_LANDING:有了落地奖励目标,agent 被激励起跳(否则拿不到落地分)。 但它学会的是 "最小化成本的起跳":跳得刚好够高、然后立刻落地, 旋转对它来说是没有收益的额外代价。
| 修改项 | v1 | v2 | 原因 |
|---|---|---|---|
| W_ROTATION | 5.0 | 15.0 | 旋转奖励必须压过 "jump+land 不旋转" 的总收益 |
| 落地奖励旋转门槛 | 无 | ROTATION_GATE = π/2(90°) | 断掉 gaming 捷径;至少翻 90° 才能拿落地分 |
| 腾空不旋转惩罚 | 无 | W_NO_SPIN = 0.5/步 | 惩罚"空中僵站",制造旋转压力 |
修改文件: jprobot/training/env_backflip.py
# 权重变更
W_ROTATION = 15.0 # 原 5.0
# 新增常量
ROTATION_GATE = math.pi / 2 # 落地奖励的最低旋转要求(90°)
W_NO_SPIN = 0.5 # 腾空不旋转惩罚系数
# 落地奖励加门槛(env_backflip.py step() 中)
if (self._launched and not self._airborne and n_feet >= 2
and abs(self._pitch_accumulated) >= ROTATION_GATE): # ← 新增
reward += W_LANDING * (n_feet / 4)
# 腾空不旋转惩罚(env_backflip.py step() 中)
if self._airborne and abs(self._pitch_accumulated) < 0.1:
reward -= W_NO_SPIN旧训练数据备份: trained/backflip_v1/(保留以供对比)
v2 训练启动: 2026-02-23,从 jump 阶段重新开始完整 6.5M 步课程。
这是一个经典的 Reward Gaming 案例。
强化学习中,agent 会 精确地 优化你定义的奖励函数,而不是你 意图 的行为。 一旦奖励函数有漏洞,agent 一定会找到。
检查奖励函数的问题清单:
- 每项奖励有没有可被单独绕过的路径?
- 组合路径的总收益有没有超过"正确行为"路径?
- "不做 X" 有没有惩罚?(只奖励做了 X 是不够的)
- 中间阶段奖励会不会成为终局的局部最优?
后空翻训练特别注意:
- 落地奖励必须有旋转前置条件,否则 agent 会学纯跳不翻
- 旋转奖励权重需要明显大于(起跳 + 落地)的总和才有意义
- 姿态引导奖励(pose guide)应该辅助而不能主导 —— 权重太高会让 agent 原地模仿而不运动
详见 CLAUDE.md 中的"血泪教训"章节,不在此重复。
关键条目:
- 永远不要硬编码 PyBullet 关节索引(血泪 #1)
- 物理量必须检查单位和缩算(血泪 #2)
- opencat-gym 的 arm_contact 是逐 link 计数(血泪 #3)
| 算法 | 全称 | 类型 | JPRobot 是否使用 |
|---|---|---|---|
| PPO | Proximal Policy Optimization | On-policy,策略梯度 | ✅ 用过(四足行走、后空翻) |
| SVG | Stochastic Value Gradient | Off-policy,值梯度(学术向) | ❌ 未使用 |
| SAC | Soft Actor-Critic | Off-policy,Actor-Critic | ✅ 当前人形机器人主力算法 |
- PPO 是 on-policy(每批数据用完即丢),CPU 上样本量不够,人形行走 8M 步也只到 ep_len=146
- SAC 是 off-policy,有 1M 容量的回放缓冲区,历史数据可以反复学习,样本效率高 5-10 倍
- 实测:SAC 在 0.48M 步就突破 ep_len=500,PPO 8M 步做不到
SAC 同时训练两类网络:
Policy(Actor,策略网络)
- 输入:机器人当前状态(376 维观测)
- 输出:每个关节的力矩(17 维动作)
- 决定机器人"怎么动"
Value(Critic,价值网络)
- 输入:状态 + 动作
- 输出:这个状态 + 动作有多好(Q 值)
- SAC 用两个 Critic(防止高估),取小值
- 决定"当前动作好不好"
更新顺序:
改奖励函数权重
→ Critic 重新学"哪个姿态更值钱"
→ Actor 跟着 Critic 调整动作输出
→ 机器人行为改变
| 调的东西 | 本质 | 影响谁 |
|---|---|---|
healthy_z_min |
环境硬约束(生死门槛) | Critic 和 Actor 都要重新学 |
w_height / w_upright |
软奖励权重 | 先改变 Critic 的学习目标,再带动 Actor |
net_arch=[400,300] |
网络结构 | Actor 和 Critic 的表达能力 |
buffer_size=1M |
回放缓冲区大小 | 可用历史经验的数量 |
use_sde=True |
状态相关探索 | Actor 的探索方式(人形必须开) |
| 任务 | 算法 | 原因 |
|---|---|---|
| 四足 BittleX 行走 | PPO | 已跑通,任务相对简单 |
| 四足后空翻 | PPO | 离散阶段式任务,当时尚未切 SAC |
| 人形机器人行走 | SAC | 高维连续控制,PPO 根本跑不动 |
| 人形直立行走课程 | SAC | 同上,热启 + 课程学习组合 |