你看的那个视频里到底发生了什么
一个小人抽搐、翻滚、脸着地,然后忽然站起来了。弹幕在刷「太神了」。这一章要做的事很朴素:把「它自己学会了」这句话拆开,看看这七个字里,究竟是谁做了什么。
先说那个游戏
QWOP 是 2008 年的一个网页游戏。你控制一个跑步运动员,但没有「前进」键——你有四个键:Q、W、O、P,分别控制大腿和小腿的四块肌肉。目标是跑一百米。
绝大多数人的第一次尝试是这样的:按 Q,运动员的一条腿往前一甩,整个人向后倒,脸着地,游戏结束,成绩 −1.3 米。
这个游戏之所以出名,是因为它把一件你从两岁起就再没想过的事重新变难了:走路是一个控制问题。你的小脑每秒钟在解它几十次,解得太熟了,熟到你以为它不是个问题。
然后有人训练 AI 来玩它,跑出了人类玩家望尘莫及的成绩。视频标题写着「AI 自己学会了走路」。
「自己学会了」这句话,可以是三件不同的事
这是第一个要拆开的地方。网上那些「AI 玩游戏」的视频,底下其实躺着三种完全不同的技术,而它们从外面看长得一模一样:都是一开始笨得可笑,慢慢变强,最后超过人类。
B 站和 YouTube 上最有名的那批「AI 学会玩马里奥」的视频——尤其是 SethBling 那个 MarI/O——用的不是强化学习,是 NEAT(增强拓扑的神经进化)。
它的做法是:随机生成一百个神经网络,全部拿去玩一遍,留下走得最远的几个,让它们「交配变异」产生下一代。没有梯度,没有价值函数,没有「这一步好不好」这种概念——一整局下来只产出一个数字:最终得分。
这个区别不是学究气。进化算法一整局只拿到一个反馈,强化学习每一步都拿到一个反馈——后者的信息量高好几个数量级。这正是为什么强化学习能一路扩展到 ChatGPT 的训练,而 NEAT 停在了玩小游戏。
不过公平地说:进化算法也没死。第 23 章那台两足小人用的 CEM(交叉熵方法)就是它的近亲,而且在参数少的时候意外地好用。它和强化学习共享的,恰恰是这本书的主题——那个打分函数。
拆开来看,只有三样东西
回到那个 QWOP 的视频。把「AI 自己学会了」拆开,实际发生的是这样:
循环,重复几百万次: 看一眼屏幕 ← 状态(关节角度、速度、躯干倾角……) 按下 Q / W / O / P 里的某几个 ← 动作 游戏往前走一帧 拿到一个数字 ← 奖励(往前挪了多少米) 如果摔倒了:这一局结束,重开
就这些。没有人告诉它「先抬左腿」,没有人写过一行描述步态的代码。它拿到的全部指导,就是那个数字。
而那个数字是人写的。这是整件事的关键,也是这本书的全部主题:
你写的从来不是策略,是打分器。
写普通程序的时候,你写的是行为:这个条件下做这件事。写强化学习的时候,你写的是评价:什么样的结果算好。行为是 agent 自己在几十万次试错里长出来的。
所以你的工作发生了一次转移——你不再调试行为,你调试的是「什么算好」这个定义。
那么,「往前跑得越远越好」有什么问题?
听起来毫无问题。你要的就是它跑得远,那就按距离给分,天经地义。
这本书里有一台真的两足小人:一具平面物理躯体,两条腿四个关节,脚落地时按弹簧-阻尼算支持力和摩擦力。它用 CEM 训练,在你的浏览器里当场跑。
我给它写的第一版奖励,就是最天经地义的那一版:
def reward(episode):
return episode.x # 往前走了多少米,就给多少分
训练三十代之后,它跑出了 6.62 米。听起来不错。
然后我去看了录像:它是一头栽出去的。身体前倾到几乎水平,两条腿在后面拖着,靠惯性滑行,500 帧的回合撑到第 224 帧就摔了,然后这局结束。
它没有做错任何事。我写的是「往前的距离」,它给我的就是最大的「往前的距离」。「站着」这个要求我从来没写过——那是我脑子里有、纸上没有的东西。
它会精确地最大化你写下的东西——而不是你想要的东西。
这两者的差距,就是这门学科所有的乐趣和所有的事故。
而这个差距有一个恶劣的性质:它从来不表现为「训练失败」。分数在涨,曲线在收敛,一切看起来都对——只有你去看它到底在干什么的时候,才会发现不对。
第 19、20 章整整两章都在讲这件事。这里先埋一个更极端的例子:我后来给奖励加了一项「要保持直立」,但姿态分只看最后一帧。它学会的最优解是——当场趴下,落地时把身体摆正。倾角 0,姿态分拿满,一步路都不用走。
这本书会怎么走
路线是这样的,六卷,一层一层往上:
| 卷 | 要回答的问题 | 拿到的东西 |
|---|---|---|
| I 试错 | 这个循环里最小的那个矛盾是什么 | 探索 vs 利用;一台真老虎机 |
| II 记账 | 奖励是当场给的,怎么为「以后」负责 | MDP、γ、价值函数、贝尔曼方程 |
| III 摸黑 | 拿不到游戏规则,还能不能学 | 蒙特卡洛、TD、Q-learning、SARSA |
| IV 换脑 | 状态多到宇宙都装不下怎么办 | DQN、致命三角、策略梯度、PPO |
| V 打分 | 那一行 reward 到底该怎么写 | 奖励塑形、奖励黑客、稀疏奖励 |
| VI 上场 | 怎么落到你自己的机器上 | RLHF、真实入门路线、排障表 |
注意卷 V 的位置。「奖励函数怎么写」在绝大多数教材里是一个附录、一个 remark,甚至根本不出现。这本书把它放在正中央,因为——那是你真正会写的那部分代码。算法你多半会直接调库,环境多半是别人写好的,只有那个 reward 是你自己敲的。
这本书的引擎是 JavaScript,因为它得在网页里跑。但你真做实验的时候,用的是 Python:
pip install gymnasium "stable-baselines3[extra]"
import gymnasium as gym
from stable_baselines3 import PPO
env = gym.make("CartPole-v1")
model = PPO("MlpPolicy", env, verbose=1)
model.learn(total_timesteps=50_000) # 笔记本上几分钟
跑完之后它能把杆子稳稳立住。但这三行什么也没教会你——它一收敛,你反而更不知道里面发生了什么。这本书要做的就是把这三行拆开。
顺带记住两个名字:Gymnasium 是环境接口的事实标准(OpenAI Gym 的官方继任者,老的 gym 包已经停更,别装错);Stable-Baselines3 是最常用的算法实现库。
下次再看到「AI 学会玩 XX」的视频,你可以问三个问题,多半能当场分辨出它是哪一路:
- 它一局里学几次?只在局末学 → 进化算法或蒙特卡洛;每一步都在学 → 时序差分那一系(DQN、PPO 都是)。
- 奖励是谁定的?直接用游戏分数 → 最省事,也最容易被钻空子(第 20 章有真实案例);人另外写的 → 那才是作者的真正工作量所在。
- 它见过多少局?这个数字通常大得惊人。DeepMind 那篇 Atari 论文里,每个游戏训练了 5000 万帧——按 60 FPS 算,是一个人不吃不睡玩九天。视频里的一分钟,背后是这个。
这一章的一句话
「AI 自己学会了」这句话里,AI 做的是搜索,你做的是打分。而它会精确地最大化你写下的那个分数——不是你心里想的那件事。
下一章:把这个循环里的五个词钉死。状态、动作、奖励、策略、回报——你会亲自走一局,边走边认,顺便撞上第一个反直觉的东西:你按了「上」,它却往旁边走了。