解读 182 · 策略网络:61→14 在学什么
解读对象:
duck-control/src/obs.rs(406 行)+duck-control/src/policy.rs(811 行)+ 训练配置的网络段
需要的前置:解读 181(策略与奖励两个词)· 解读 100(obs.rs 逐函数版)
上一课说"策略是条件反射"。这篇打开看:反射的入口是 61 个数,出口是 14 个数,中间夹一个
普通的多层感知机。它不是大脑,更像脊髓——不管你多聪明,走路这件事在脊髓里就处理完了。
1. 入口:61 个数是鸭子的"全部感官"
obs.rs 的 build() 每 20 毫秒把传感器数据拼成定长的 [f32; 61],布局七段(课 04 口径):
| 索引 | 宽度 | 内容 |
|---|---|---|
| 0–2 | 3 | 躯干角速度(转多快) |
| 3–5 | 3 | 投影重力(歪没歪) |
| 6–19 | 14 | 相对 HOME 的关节角 |
| 20–33 | 14 | 关节速度 |
| 34–47 | 14 | 上一次的动作 |
| 48–50 | 3 | 走路指令 vx, vy, yaw |
| 51–54 | 4 | 头颈指令 |
| 55–60 | 6 | 身体位姿指令槽 |
前 48 个是"身体的自我感觉",后 13 个是"人给的命令"。两个值得说的实现细节:代码用
split_first_chunk_mut::<N>() 把缓冲区按表劈成定长块,块宽进了类型系统,总和 61 有测试
钉着——布局写错是编译不过的;34–47 的"上次动作"是给网络的肌肉记忆:知道自己上一拍
干了什么,才学得出平滑的下一拍,而不是抽搐。
2. 出口:14 个数不是角度,是偏移
输出的顺序固定:0–4 左腿、5–8 头颈、9–13 右腿——不对称,是全链路共用的约定。每个数是
相对 HOME 姿态的关节位置偏移,不是"转到几度";真机 15 只舵机,第 15 个驱动嘴巴,
策略不输出它,由运行时把 14 个数映射回 15 个槽位(scatter_action)。出口之后还有
安全层(解读 102)把关,才落到总线——网络说的不算数,过了安检才算。
3. 中间:没有规则,只有权重
网络本体是 MLP:隐藏层 (512, 256, 128)、elu 激活(配置 MicroduckRlCfg)。约 19 万个权重
参数里存的不是"如果左脚打滑就右迈"这种规则,而是 61 维空间到 14 维空间的一张查表式映射——
训练就是用千万次试错把这张表磨出来的。三个最常见的追问:
- 多大? 推理就是几次矩阵乘法,树莓派都跑得动——训练才需要 GPU,推理不需要。
- 怎么切换技能?
policy.rs的Net枚举(Walk/Stand/SitStand/GroundPick/Skill)
选哪个网络在场;走还是站由速度指令模长判:twist_magnitude <= 0.05就切站。 - 真机和训练是同一个网络吗? 权重同一个,但训练时它身后还跟着一个 76 维观测的
critic("教练视角",评估动作好坏用),导出 ONNX 上真机的只有 actor 那 61→14 部分。
为什么 61→14 一个都不能动?训练仓按这张表生成观测,真机按同一张表读取回填,两拨代码
靠它对齐——悄悄调换两列,ONNX 照样"能跑",鸭子当场摔倒。
你带走的收获
- 61 = 3+3+14+14+14+3+4+6:前 48 个自我感觉,后 13 个人类命令;"上次动作"负责平滑。
- 14 个输出是相对 HOME 的偏移,顺序左腿→头颈→右腿,嘴巴不进动作空间。
- 中间是 MLP (512,256,128),没有可读的规则,只有磨出来的权重;推理便宜,训练贵。
- 61/14 是训练与部署两拨代码之间的合同,动它等于给鸭子换神经。