卷 V · 打分CH 19深度 19/24

写下的,和你想要的

前面十八章讲的都是「怎么学」。这一章开始讲「学什么」——那一行 reward。它是你在整个项目里唯一真正会亲手写的代码,也是这本书认为最被低估的东西。

奖励塑形五行五种生物目标错位

先算一笔账

做一个强化学习项目,你实际会写的代码大概是这样分布的:

部分谁写的
算法(PPO / SAC / DQN)调库。from stable_baselines3 import PPO
网络结构多半用默认的 MlpPolicy
环境的物理 / 规则Gymnasium 里现成的,或者已有的模拟器
状态怎么表示你,部分
奖励函数你,全部

前面十八章讲的东西,你多半一行都不会自己实现。而这一章讲的东西,你每次都得从零写。

更不对称的是:算法写错了会报错、会发散、会有明显征兆;奖励写错了什么都不会发生——训练一切正常,只有行为是错的。

那台两足小人

这本书里有一具真的躯体:躯干有质量和转动惯量、受重力,两条腿四个关节由一个周期步态控制器驱动,脚落地时按弹簧-阻尼算支持力,加一个光滑化的库仑摩擦。参数 13 维,用 CEM 训练。

下面这个实验里,物理常数、CEM 的种群大小、精英数、代数、随机种子,全部固定。唯一改动的是那一行 reward。

▶ 动手 · 看第一列,那是唯一被改过的东西

五行,五种生物

reward = x —— 一头栽出去

最天经地义的一版。你要它往前走,那就按往前的距离给分。

它跑出了 6.62 米,看起来不错。但 500 帧的回合只撑到第 224 帧——它是身体前倾到近乎水平、靠惯性滑出去的,然后摔了,回合结束。

它没做错任何事。我写的是「往前的距离」,它给了我最大的「往前的距离」。「要站着」这个要求我从来没写过——那是我脑子里有、纸上没有的东西。

reward = x + 0.5 * alive_time —— 开始想站住

加一条「活着有分」。它开始收敛一点,但姿态还没有约束,成绩不稳定。

reward = x + 0.5*alive_time + 0.5*upright_time —— 真的走起来了

再加一条「全程保持直立」(按时间积分,这个细节马上就要变成一场事故)。

结果:前进 9.47 米,500 帧里站住了 497 帧。比第一版走得更远,而且是着走的。

顺带注意:加了两条约束之后,它反而走得更远了(9.47 > 6.62)。约束不是在限制它,是在告诉它你到底要什么

reward = 3 * alive_time —— 站着一动不动

这一版是「忘了写前进那一项」。真实项目里这种事比你想的常见——重构奖励函数的时候删错了一行,或者本来打算之后再加。

它学出来的:前进 −0.36 米(略微往后挪了一点),500 帧全部站住,得分 30.00。

那个 30.00 值得停一下:那是这个奖励函数的理论满分。3 分/秒 × 10 秒 = 30.00,一分不少。

它没有偷懒,它赢了。在「只奖励活着」这个游戏里,站着不动就是最优解,而它精确地找到了。

⑤ 姿态分只看最后一帧 —— 当场趴下

这一版我单独留到下一章讲,因为它是本书最好的一个案发现场。这里先给结果:前进 0.00 米,活了 14 帧,得分 5.14。

◆ 这本书的主线,在这张表上一览无余

你写的不是行为,是打分器。而 agent 会精确地最大化那个打分器——不是你心里想的那件事。

五行 reward 里,没有一行是「错的」。它们都是合法的、可优化的函数,agent 都找到了各自的最优解。

错的是它们和我脑子里那个「走路」之间的差距。而那个差距,代码里没有任何地方能看见。

那奖励该怎么写

没有万能公式,但有一套可以照着做的清单。

① 先写「终极目标」,再考虑加提示

终极目标叫稀疏奖励:赢了 +1,输了 −1,其余 0。

它有一个不可替代的优点:它不可能被钻空子——因为它就是你真正要的东西,一字不差。

缺点是几乎学不动(下一节整章在讲)。但它应该是你的起点和最终裁判:任何塑形过的奖励,最后都要拿它来验收。

② 加提示的时候,优先用势函数

随手往奖励里加项,就是在制造漏洞(下一章的主题)。但有一种加法是数学上安全的

F(s, s') = γ · Φ(s') - Φ(s)

Ng、Harada 和 Russell 1999 年证明了:不管 Φ 怎么选,最优策略都不会改变。

直觉是这样:这一项是个望远镜式的伸缩和——沿着任何一条轨迹加起来,中间全部抵消,只剩首尾两项。所以它对比较两条路径的优劣毫无影响,只是把远处的信号提前告诉了 agent。

这是目前唯一有理论保证的塑形方式,值得当成默认选项。第 21 章会用它把一个「六次实验全部失败」的问题变成「二十局就学会」。

③ 每一项都问一遍:这一项能被无限刷吗

这是最实用的一条。逐项检查:有没有一种活法,能让这一项一直涨,而不推进真正的目标?

你写的可能被刷成
「靠近目标就加分」在目标附近来回震荡,反复刷「靠近」
「速度快就加分」原地高速旋转
「碰到球就加分」贴着球蹭,不射门
「活着就加分」躲在角落里什么也不做
「回答长就加分」车轱辘话说三遍

注意第一行和第二行的区别。「靠近目标加分」可以被震荡刷;而写成势函数形式 γΦ(s') − Φ(s) 就不行——来回震荡的收益精确地相互抵消。这就是势函数那个定理的实际价值。

④ 把各项的量级摆出来算一遍

一个非常常见、非常隐蔽的错误:各项的尺度差太多,小的那项等于不存在。

# 一个真实会犯的错
reward = 100 * task_success + 0.001 * energy_penalty
#        └────┬────┘          └──────┬──────┘
#        主目标                能耗惩罚 —— 实际上等于没写

写之前先估:一个典型回合里,每一项大概会累计到多少?把这几个数字并排写出来。你会经常发现某一项比另一项小三个数量级——那它就是装饰品。

⚠ 一个不太舒服的观察:这件事和 KPI 是同一回事

写奖励函数的过程,和给团队定 KPI 的过程,在结构上完全一样:

  • 你有一个说不清楚的真实目标(「做出好产品」/「学会走路」)
  • 你只能写下一个可度量的代理指标(「月活」/「前进距离」)
  • 被优化的一方会精确地最大化那个指标
  • 而指标和真实目标之间的差距,就是全部的事故来源

这个现象在经济学里有个名字,叫古德哈特定律「当一个指标成为目标,它就不再是一个好指标。」

强化学习只是把它加速了几百万倍——人类员工优化 KPI 要几个月,agent 几分钟就能把你的奖励函数榨干。

所以这本书里的教训不只对写代码有用。凡是有人在最大化一个数字的地方,这一卷讲的东西都成立。

✎ 那能不能让 AI 自己去学奖励函数

能,这是一整个子领域,值得知道三条路:

  • 逆强化学习(IRL):给它一堆专家演示,反推「什么样的奖励函数会让这些行为成为最优」。听起来完美,但这个问题是病态的——无穷多个奖励函数能解释同一批行为(比如「奖励恒为 0」就能解释任何行为)。
  • 模仿学习 / 行为克隆:干脆跳过奖励,直接监督学习「专家在这个状态下做了什么」。简单有效,但学不会专家没演示过的情况,而且会累积误差。
  • 从人类偏好学奖励:让人比较两段行为哪个更好,拟合一个奖励模型。这就是 RLHF,第 22 章的主题。

但要注意:这三条路都没有消灭那个问题,只是把它挪了个地方。RLHF 里的奖励模型同样会被钻空子——模型学会讨好标注员,写得更长、更客气、更自信,哪怕内容是错的。

「你写下的和你想要的之间有差距」这件事,目前无解。能做的只有:把差距缩小,并且时刻盯着它。

⌗ 换成真机:看看 Gymnasium 自己怎么写

BipedalWalker 的奖励值得抄作业——它就是本章那台小人的正规版本:

# BipedalWalker-v3 的奖励,大意
reward = 130 * (x - x_prev)          # ① 前进(走完全程约 +300)
reward -= 5.0 * abs(hull_angle)      # ② 躯干别晃
reward -= 0.00035 * sum(abs(torque)) # ③ 能耗惩罚(很小,只做微调)
if fell:
    reward = -100                     # ④ 摔倒,一次性大罚

四项,恰好对应本章说的四条:主目标 + 姿态约束 + 微小的正则项 + 失败惩罚。

注意第三项的系数 0.00035——刻意压得极小,因为它只是想让动作平滑一点,绝不能盖过主目标。这就是「把量级摆出来算一遍」的实例。

也注意第一项是增量 x − x_prev 而不是绝对位置 x。这其实就是一个势函数塑形(Φ = −130x),所以它不会被「来回走」刷分。写法的差别很小,性质的差别很大。

↩ 回到那个视频

你看的那个视频里,作者花在算法上的时间可能是一天(调库、调超参),花在奖励函数上的时间可能是一周

而且那一周里,绝大部分是在看录像:训一版、看它学成了什么、发现它在钻某个空子、改一行奖励、再训一版。

这是这本书最想传达的一条实践认识:强化学习的日常工作,不是调算法,是反复地「看它学成了什么,然后修改你对『好』的定义」。

这一章的一句话

五行 reward,五种生物,物理常数一个都没改。你写的不是行为,是打分器——而 agent 会精确地最大化它,不是你心里想的那件事。

下一章:把那个差距推到极致。你会看到一条一圈都没跑完的赛艇赢了跑完全程的,以及我写这本书时真的犯过的那个 bug——它学会了当场趴下,落地时把身体摆正。