强化学习入门笔记
REINFORCEMENT LEARNING · 学习笔记
强化学习入门笔记

从一辆学习自动泊车的小车出发,一路走到马尔可夫决策过程、贝尔曼方程、Q-learning、DQN、策略梯度、PPO,再到大语言模型的 RLHF 与 DPO。

12章
58小节
69道思考题
123个公式块
12个交互演示
主线:MDP → 贝尔曼方程 → 动态规划 → MC / TD → DQN → 策略梯度 → PPO → RLHF / DPO

📖 阅读说明:本笔记以问答方式推进。

  • 🤔 思考:引导性问题,建议先自己想一想再往下读;
  • ✍️ 我的回答:学习时的原始作答(可能不完整或有误);
  • 解析:正式讲解与结论,遇到常见错误会标注 ⚠️ 常见误区。

符号表

书写约定

  • 随机变量与取值:大写字母(\(S_t, A_t, R_{t+1}, G_t\))表示随机变量,小写字母(\(s, a, r\))表示具体取值;\(s'\)、\(a'\) 表示下一步的状态与动作。
  • 集合:用花体字母表示,如状态集合 \(\mathcal{S}\)、动作集合 \(\mathcal{A}\)、经验回放池 \(\mathcal{D}\);\(|\mathcal{A}|\) 表示动作个数。
  • 条件概率:统一用 \(\mid\),如 \(\pi(a\mid s)\)、\(P(s'\mid s,a)\)。
  • 参数化函数:参数写在下标,如 \(Q_\theta(s,a)\)、\(Q_{\theta^-}(s,a)\)、\(V_\phi(s)\)、\(\pi_\theta(a\mid s)\)、\(r_\psi(x,y)\)。
  • 目标与损失:要最大化的目标记作 \(J\)(如 \(J(\theta)\)、\(J^{\text{CLIP}}\)),要最小化的损失记作 \(\mathcal{L}\)(如 \(\mathcal{L}_{\text{RM}}\)、\(\mathcal{L}_{\text{DPO}}\))。
  • 与原论文不同的写法:为避免撞名,本笔记有三处与常见论文写法不同,正文首次出现时都有标注:
    • PPO 的概率比写作 \(\rho_t(\theta)\)(原论文为 \(r_t(\theta)\),与奖励 \(r\) 撞名);
    • SAC 的温度系数写作 \(c_{\text{ent}}\)(原论文为 \(\alpha\),与学习率撞名);
    • 确定性策略写作 \(\pi_\theta(s)\)(原论文为 \(\mu_\theta(s)\),与高斯均值撞名)。
符号 含义 首次详细出现
\(S_t,\ s\) \(t\) 时刻的状态 / 某个具体状态 1.1、2.1
\(A_t,\ a\) \(t\) 时刻的动作 / 某个具体动作 1.1、2.1
\(R_{t+1},\ r\) 执行 \(A_t\) 后获得的即时奖励 1.1、3.1
\(\mathcal{S},\ \mathcal{A}\) 状态集合 / 动作集合 2.1
\(P(s'\mid s,a)\) 状态转移概率 2.1
\(R(s,a,s')\) 奖励函数 2.1
\(\gamma\) 折扣因子,\(\gamma\in[0,1]\)(\(\gamma=1\) 仅用于分幕任务) 1.4、2.1
\(G_t\) 从 \(t\) 时刻起的折扣累积回报 3.1
\(G_{t:t+n}\) \(n\)-步截断回报(尾部用价值估计修补) 5.5、7.1
\(G_t^\lambda\) \(\lambda\)-回报 7.2
\(\pi(a\mid s),\ \pi_\theta(a\mid s)\) 随机策略(目标策略)/ 参数为 \(\theta\) 的策略网络 1.3、9.2
\(\pi_\theta(s)\) 确定性策略(只带一个参数,直接输出动作) 9.6
\(b(a\mid s)\) 行为策略 6.3
\(\mu_\theta(s),\ \sigma_\theta(s)\) 高斯策略的均值 / 标准差(9.5 节简写为 \(\mu(s),\ \sigma(s)\)) 9.5
\(V^\pi(s),\ V^*(s)\) 状态价值 / 最优状态价值 2.4、3.2
\(Q^\pi(s,a),\ Q^*(s,a)\) 动作价值 / 最优动作价值 2.4、3.3
\(A^\pi(s,a)\) 优势函数 \(Q^\pi(s,a) - V^\pi(s)\) 9.3
\(\hat{A}_t\) 优势的估计值(TD 误差、GAE 等) 9.4、10.4
\(\delta_t\) TD 误差 \(R_{t+1} + \gamma V(S_{t+1}) - V(S_t)\) 6.1
\(\alpha\) 学习率 6.1
\(\epsilon\) \(\epsilon\)-greedy 的探索率 1.3、5.4
\(\epsilon_{\text{clip}}\) PPO 的裁剪范围 10.3
\(\lambda\) \(\lambda\)-回报 / 资格迹 / GAE 的衰减参数 7.2、10.4
\(E_t(s)\) 资格迹 7.3
\(\theta,\ \theta^-\) 网络参数(Q 网络或 Actor)/ DQN 目标网络参数 8.2
\(\phi,\ \bar{\phi}\) Critic 网络参数 / Critic 的目标网络参数 9.4、9.6
\(\psi\) 奖励模型参数 11.2
\(\mathcal{D}\) 经验回放池 / 数据集 8.2
\(J(\theta),\ \mathcal{L}\) 要最大化的目标 / 要最小化的损失 9.2
\(\Psi_t\) 策略梯度中的评价信号 9.2
\(\rho_t\) 重要性采样比率(PPO 中为新旧策略概率比 \(\rho_t(\theta)\)) 6.3、10.2
\(\mathcal{H}(\pi),\ c_{\text{ent}}\) 策略熵 / 熵正则系数(SAC 中即温度系数) 9.5、9.6
\(c_v\) PPO 中价值损失的权重 10.5
\(r_\psi(x,y)\) 奖励模型对提示词 \(x\)、回答 \(y\) 的打分 11.2
\(\beta\) RLHF / DPO 中 KL 约束的强度 11.3、11.5
\(\pi_{\text{ref}}\) RLHF / DPO 中的参考模型(SFT 模型) 11.1

第 1 章 全局概览:建立对强化学习的直觉

🧭 本章不追求严谨,主要借助“自动泊车小车”这个例子,把强化学习的主要概念从头到尾走一遍,先建立直觉。这里出现的每个概念,后续章节都会给出正式定义和推导(见 1.7 的学习路线)。

1.1 核心要素:智能体、环境、状态、动作、奖励

强化学习(Reinforcement Learning, 简称 RL)是机器学习的一个重要分支。如果说监督学习是“老师拿着标准答案批改作业”,那么强化学习就像是“训狗”或者“婴儿学步”:一个智能体(Agent)在不断与环境(Environment)互动的过程中,通过“试错”(Trial-and-Error)来摸索规律,目标是获得最大的累计奖励(Reward)。强化学习最核心的概念包含智能体、环境、状态、动作、奖励五大基石。

强化学习的核心建立在智能体与环境的持续交互循环上。我们可以用“玩家玩《超级马里奥》”来理解这套交互闭环中的核心要素:

  • 🤖 智能体(Agent):做决策的实体。比如手握手柄、正在控制角色的你(或算法)。

  • 🌍 环境(Environment):智能体所处的外部世界,接收动作并产生反馈。比如马里奥游戏本身的物理引擎和关卡设定。

  • 📍 状态(State, \(S\)):某一时刻环境的具体情况。比如当前马里奥所处的屏幕画面、所在坐标、剩余时间以及身边的蘑菇怪物位置。

  • 🕹️ 动作(Action, \(A\)):智能体在某一状态下能做出的选择。比如向左走、向右跑、起跳。

  • 🏆 奖励(Reward, \(R\)):环境根据动作给予的即时反馈信号(可正可负)。比如吃到金币得 \(+10\) 分,撞到怪物失去一条命得 \(-100\) 分。

强化学习整体循环如下:智能体观察当前状态 \(S_t\) \(\rightarrow\) 决定采取动作 \(A_t\) \(\rightarrow\) 环境反馈新的状态 \(S_{t+1}\) 并给出即时奖励 \(R_{t+1}\)。

1.2 奖励设计:稀疏奖励与奖励破解

马里奥帮我们认识了五个要素。从这里开始,本章换成一个更贴近工程实际的例子——自动泊车,并用它贯穿全章。

🤔 思考:如果让智能体学习自动驾驶汽车泊车,在这个任务中:

  1. 它的动作(Action)可以是什么?
  2. 如果只设置“成功停入车位奖励 \(+100\) 分”,中途没有任何其他正负反馈,智能体在训练初期可能会遇到什么困难?

✍️ 我的回答:1. 动作包含倒车、转向等;2. 智能体可能会停在各个方向上。

解析对于第一个问题,动作通常就是转向角度和油门/刹车/倒车控制(速度与方向)。对于第二个问题,如果只在“完全停好”时给 \(+100\) 分,智能体会在各种奇怪的角度停下,甚至根本不知道自己离目标还有多远。在强化学习中,这种现象被称为稀疏奖励(Sparse Reward):

  • 盲目试错:智能体在最开始是随机乱动的,它可能撞墙成千上万次,都碰巧进不了车位一次,因而永远拿不到那个 \(+100\) 分的信号,完全“学不会”。

为了解决这个问题,算法设计者通常会设计奖励函数(Reward Shaping)来引导它。

🤔 思考:如果由你来为自动驾驶小车设计中间的奖惩机制,除了“停好得 \(+100\) 分”之外,你会给它增加哪些即时的正面奖励或惩罚(负分),来引导它更快学会正确泊车呢?

✍️ 我的回答:车的方向与停车位方向越一致奖励越大,偏离越多惩罚越多。

解析引入车身朝向与车位朝向的夹角作为反馈(📐 角度对齐)非常关键,这能直接解决车身停歪的问题。不过在强化学习中,智能体常常会“钻空子”(这种现象称为 奖励破解 / Reward Hacking 🤖)。如果系统只奖励“方向一致”,小车在距离车位 10 米远的空地上就把车头摆正,然后停在原地不动,它也会持续获得很高的方向奖励,但实际上并没有完成泊车任务。

🤔 思考:为了避免智能体“偷懒”,并且确保泊车过程安全、高效,除了方向之外,我们通常还需要引入哪一到两个维度的反馈(奖励或惩罚)?

✍️ 我的回答:与车位的距离!

解析当车身中心离停车位中心越近,获得的奖励越高(或者距离越远,惩罚越多)。这样它就有动力从 10 米外主动驶向车位,而不是停在远处发呆。

结合前面提到的朝向与位置 ,智能体就同时拥有了“去哪儿”和“怎么摆正”的明确目标。

实际工程中,为了防止小车为了“抄近道”或“快速停进”而野蛮驾驶,我们通常还会加上一个最基本的惩罚项:

  • 💥 碰撞惩罚:一旦碰到马路牙子或旁边的车辆,直接扣大分(如 \(-500\) 分)并强制结束该轮训练。

现在,我们把这几个核心概念串起来看:

要素 在泊车任务中的体现
状态 (\(S\)) 📍 小车的坐标、车速、朝向角、与障碍物的距离
动作 (\(A\)) 🕹️ 转向角、油门/倒车/刹车力度
奖励 (\(R\)) 🏆 靠近车位 (+)、摆正车头 (+)、停好车 (+100)、碰撞 (-)

1.3 策略与探索-利用

当小车尝试过成千上万次后,它会形成一套固定的“决策套路”——在什么情况下打多少方向盘、给多大油门。在强化学习中,这种“根据当前状态选择动作的规则或函数”被称为策略(Policy, 通常用 \(\pi\) 表示)。

🤔 思考:这个策略(\(\pi\))更像是一个条件反射表(看到什么就固定做什么),还是一个输出各动作概率的计算模型?

解析在现代强化学习中,策略通常是一个计算动作概率分布的模型 🎲(称为随机性策略,记作 \(\pi(a \mid s)\))。

例如,在某个状态下,策略网络可能会输出:

  • 转向 5 度:\(70\%\)

  • 保持直行:\(25\%\)

  • 向反方向打轮:\(5\%\)

这种以“概率”做决策的方式,对智能体来说至关重要,因为它天然带来了一种能力——探索(Exploration)。

这就引出了强化学习中最经典的权衡难题:探索与利用(Exploration vs. Exploitation Trade-off):

🔍 探索(Exploration):尝试当前未知或概率较低的动作,去发现可能存在的更好解。

🏆 利用(Exploitation):坚持选择已知回报最高的动作,确保眼前收益最大化。

🤔 思考:如果一个小车在刚开始训练时,偶然发现“一直向前开”能拿到微小的距离奖励,之后它就完全停止探索,100% 只做这个动作(纯粹利用现有经验),它最终可能会陷入什么困境?

✍️ 我的回答:每次泊车只会径直往前开,而不会调整方向!

解析这就触碰到了强化学习中非常致命的问题:陷入局部最优(Local Optimum)。

因为智能体过早放弃了探索,它只认准眼前这套能拿到微小正反馈的“烂套路”,导致它永远学不会拐弯、倒车等虽然短期内可能没有立即得分、但长期来看必不可少的关键动作。因此在实际训练中,为了平衡这种矛盾,研究人员通常会使用一种经典的策略:\(\epsilon\)-贪心策略(Epsilon-Greedy)

  • 📈 大部分时候(概率 \(1 - \epsilon\)):它选择当前认为得分最高的动作(利用;1.4 节会看到,这个“得分”就是 \(Q\) 值)。

  • 🎲 少部分时候(概率 \(\epsilon\)):它随机尝试一个动作,比如故意打一下方向盘或踩一脚刹车(探索)。

  • ⏳ 随着训练进行:\(\epsilon\) 会慢慢变小。起步时多探索,熟练后多利用

现在,强化学习的核心循环我们已经完整走了一圈: 智能体 (Agent) 在 环境 (Environment) 中观察 状态 (State),基于 策略 (Policy) 做出 动作 (Action),并根据 奖励 (Reward) 不断调整决策。

1.4 价值、TD 误差与折扣因子(直觉版)

掌握了这些核心要素后,下一步来讨论算法如何根据奖励更新策略。

智能体要用拿到的奖励来改进策略,核心逻辑其实就像人类在脑海中做“复盘与记账”。

在算法中,最经典的桥梁就是价值函数(Value Function),尤其是 \(Q\) 值(Action-Value)。在给出定义之前,先看一个例子。

先看一个例子:只看眼前,会选错动作

假设小车现在的状态是:车身歪了约 30°,距离车位还有 5 米。它有三个动作可选:

动作 这一步的即时奖励 之后会发生什么 长期来看总共能得多少分
🅰️ 直接歪着倒车 \(+3\)(离车位更近了) 车身没摆正,大概率刮到旁边的车(\(-500\)) 约 \(-150\)(很差)
🅱️ 先往前开一段,把车摆正 \(-2\)(离车位反而远了) 摆正后能顺利倒进车位(\(+100\)) 约 \(+80\)(最好)
🅲 原地不动 \(0\) 永远停不进去,也拿不到 \(+100\) 约 \(0\)

如果只看即时奖励,小车会选 🅰️(\(+3\) 最高),结果却是撞车。真正该选的是 🅱️:它眼前还会扣 2 分,但长期来看收益最高。

表格最后一列“长期来看总共能得多少分”,就是 \(Q\) 值:

  • \(Q(\text{歪着离车位 5 米},\ \text{直接倒车}) \approx -150\)
  • \(Q(\text{歪着离车位 5 米},\ \text{先往前摆正}) \approx +80\)
  • \(Q(\text{歪着离车位 5 米},\ \text{原地不动}) \approx 0\)

有了这三个数,决策就很简单:选 \(Q\) 值最大的动作。

1. 什么是 \(Q\) 值?

\(Q(s, a)\) 可以理解为:在状态 \(s\) 下,如果做出动作 \(a\),未来预计能拿到多少长期回报。它不仅看当下的即时奖励 \(r\),还要把这个动作带来的后续收益(或损失)全部算上。

可以把 \(Q\) 值想象成一本记账本 📒:每一个“(状态,动作)”组合都有一个格子,格子里记着“在这个状态下做这个动作,长期能得多少分”。

⚠️ 不过,上面表格里的 \(-150\)、\(+80\) 是“上帝视角”给出的。刚开始训练时,小车并不知道这些数字,账本里全是 \(0\) 或乱猜的值。它只能在一次次尝试中,根据实际拿到的奖励不断修正这本账。那么,具体该怎么修正呢?

2. 算法如何“根据奖励”更新?

更新的核心思路是“现实与预期的差距”,也就是 TD 误差(Temporal Difference Error):

\[\text{差距(TD)} = \text{现实(即时奖励 + 下一步的预估回报)} - \text{旧估计 } Q(s,a)\]
  • 如果现实比预期好(差距 \(> 0\)):说明这个动作比想象中更有赚头,算法就会调高该状态下该动作的 \(Q\) 值。

  • 如果现实比预期差(差距 \(< 0\)):比如撞了墙扣了大分,算法就会调低该动作的 \(Q\) 值。

3. \(Q\) 值变了,策略怎么变?

有了这本动态更新的“账本”,策略更新就顺理成章了:

  • 每次面临选择时,策略会更偏向那些 \(Q\) 值最高的动作。就像上面的例子里,只要账本记对了,小车自然会选 🅱️“先往前摆正”。

🤔 思考:换一个状态来练习更新过程。假设小车当前处于“车头已对齐,但距离车位还有 2 米”的状态。

  1. 小车尝试了“倒车一步”,获得了即时奖励 \(+5\)。
  2. 此时它观察下一步的状态,预估从那里继续倒进去还能再拿 \(+90\) 的总回报(总计预计得分 \(5 + 90 = 95\))。
  3. 但它原本的记忆里,这个动作的 \(Q\) 值只有 \(40\)。

依据刚才的更新逻辑,算法接下来会调高还是调低小车对“倒车一步”的回报(\(Q\) 值)?在以后的类似场景中,它选择倒车的概率会发生什么变化?

✍️ 我的回答:调高 Q 值,以及在后面类似场景中,选中倒车的概率会变大!

解析这是因为实际获得的反馈(\(95\) 分)远高于旧记忆(\(40\) 分),算法就会大幅调高这个动作的 \(Q\) 值。随后在策略决策中,小车在这个状态下选择“倒车”的倾向性或概率就会显著提升。

但这又产生一个问题:在真实场景中,“眼前的奖励”和“未来的奖励”,分量真的完全一样吗?

在大多数情况下答案是不一样,因此在强化学习的计算中,通常会给未来的预估奖励乘上一个衰减系数 \(\gamma\)(折扣因子,通常在 \(0\) 到 \(1\) 之间,比如 \(0.99\))。前面“\(5 + 90 = 95\)”的例子其实默认了未来奖励不打折,相当于 \(\gamma = 1\)。加上折扣后,目标回报变成:

\[\text{当前目标回报} = \text{即时奖励 } R + \gamma \times \text{下一步的最大回报 } \max_{a'} Q(s', a')\]

🤔 思考:基于这个机制,

  • 如果把 \(\gamma\) 设得非常接近 0(比如 \(0.01\)),这个智能体在做决策时会表现出什么样的风格?
  • 反过来,如果 \(\gamma\) 接近 1(比如 \(0.99\)),它的风格又会如何变化?

解析我们可以把这种差异形象地总结为“目光短浅”与“深谋远虑”:

  • 当 \(\gamma\) 接近 0 时(短视型 🕶️):未来的奖励被大幅打折,甚至归零。智能体几乎只看重眼前的即时奖励。如果它面前有一块小糖果(即时得分 \(+1\)),但拿了之后会掉进坑里(未来扣分 \(-100\)),低 \(\gamma\) 的智能体会毫不犹豫地吃掉糖果。

  • 当 \(\gamma\) 接近 1 时(远见型 🔭):未来的预期奖励几乎完全被计入当前决策。智能体愿意为了长期的宏大目标而放弃甚至忍受当下的微小惩罚(例如:为了最终成功泊车 \(+100\),中途倒车花费燃料虽然扣 \(-1\),它也觉得非常值得)。

到现在为止,我们已经把强化学习中几个最底层的支柱都搭建起来了:

  • 交互闭环:状态 \(S\)、动作 \(A\)、奖励 \(R\)

  • 决策指南:策略 \(\pi\)、探索与利用平衡(\(\epsilon\)-贪心)

  • 价值度量:\(Q\) 值、TD 误差更新、折扣因子 \(\gamma\)

把这三根支柱拼在一起,其实就得到了一个完整的学习流程。小车每走一步,都重复下面四件事:

  1. 📒 查账本、选动作:看看当前状态下各个动作的 \(Q\) 值,用 \(\epsilon\)-贪心选一个动作(大多数时候选 \(Q\) 最大的,偶尔随机探索);
  2. 🕹️ 执行动作:拿到即时奖励 \(R\),来到新状态 \(s'\);
  3. ✏️ 修正账本:用“\(R + \gamma \times\) 新状态下最大的 \(Q\) 值”作为现实,与旧的 \(Q(s,a)\) 比较,按 TD 误差调高或调低;
  4. 🔁 进入新状态,回到第 1 步。

这个流程就是强化学习中最经典的算法之一——Q-learning。它的名字就来自它学习的对象:\(Q\) 值。第 6 章会给出它的正式公式和完整代码。

📌 \(Q\) 值、TD 误差和折扣回报的严格定义见第 3 章(贝尔曼方程)和第 6 章(时序差分学习)。

1.5 从表格到神经网络

Q-learning 的核心就是那本“记账本”。

🤔 思考:如果真的要把 Q-learning 写成代码,这本“记账本”在程序里该用什么数据结构来存储呢?

解析最直接的做法就是一张表格,称为 Q 表(Q-Table),在 Python 里可以用字典实现:

  • 键(Key):状态 \(s\)

  • 值(Value):该状态下各个动作的 \(Q\) 值。以 1.4 节的例子来说,状态“车身歪着、离车位 5 米”这一行记着 { "直接倒车": -150, "先往前摆正": 80, "原地不动": 0 }

(也可以直接用 (状态, 动作) 对作为键,这时每个键对应的就是单个 \(Q\) 值,两种写法等价。)

每当智能体走了一步,就查出旧的 \(Q(s, a)\),算出新的目标值,然后直接在字典里把这个数值原地更新覆盖。像这样用一张表记录所有价值的方法,统称为表格型方法。

但是,这种用“表格”记录的方法很快会撞上一堵厚墙 🧱。

想象一下两个场景:

  1. 井字棋(Tic-Tac-Toe):总状态数量只有几千种,用字典轻轻松松存下。

  2. 回到我们的自动驾驶倒车:小车的坐标是连续的实数(比如 \(x=2.314\text{m}\) 和 \(x=2.315\text{m}\)),还要考虑连续的角度、障碍物的图像像素(比如一张 \(1080\text{p}\) 的摄像头画面)。

在自动驾驶这种场景下,状态的总数量是近乎无穷无尽的。

字典要求键必须精确匹配。因此遇到未记录的状态,就会直接报错(KeyError),导致智能体陷入瘫痪,完全无法决策。

这种“状态数量过多导致字典/表格存不下、算不完”的困境,在强化学习中被称为维度灾难(Curse of Dimensionality) :

  • 💾 内存爆炸:无法为无限多的连续状态开辟空间。

  • 🧱 无法泛化(Generalization):智能体无法根据相似经验来举一反三。即使它学会了在 \(x=2.0\text{m}\) 倒车,换到 \(x=2.001\text{m}\) 时它依然像个“新生儿”一样毫无头绪。

为了打破字典的这种死板限制,我们需要一种工具,它不需要死记硬背每个具体数值,而是能通过拟合和泛化,对任意输入(无论见过与否)都给出一个合理的预测。

🤔 思考:如果了解过机器学习,应该很快就能猜到这个工具是什么。

解析正是 MLP(多层感知机 / 神经网络)!

当你把多层感知机(或深度神经网络)引入强化学习,用来替代原来那张死板的 Q 表时,你就踏入了当今 AI 领域最火爆的方向——深度强化学习(Deep Reinforcement Learning, DRL)。

于是诞生了当年 DeepMind 震惊业界的强化学习网络—— DQN(Deep Q-Network),其核心思想如下:

  • 输入:当前状态 \(s\)(比如输入自动驾驶小车的连续坐标、速度,甚至是车载摄像头画面的像素)。

  • 中间层:通过多层神经元进行特征提取和非线性拟合。

  • 输出:每个动作对应的预测 \(Q\) 值(比如直行、倒车、左转、右转的期望得分)。

从“改表格”到“训练网络”,本质变化只有一点:以前是把算出来的新数值直接覆盖在表格里;现在则是把“新目标值与网络当前预测的差距”当作损失,用梯度下降一点点调整网络权重,让预测向目标靠拢。

神经网络强大的拟合与泛化能力,让小车哪怕处在一个从未见过的全新坐标上,也能根据相近状态的规律“举一反三”地给出合理的判断。

📌 直接把神经网络套进 Q-learning 其实很容易训练崩溃,DQN 靠经验回放和目标网络两项技巧才稳定下来,详见第 8 章。

1.6 直接学习策略:策略梯度与 Actor-Critic

在前面的 DQN 中,我们是通过评估每个动作的 \(Q\) 值来间接做决策(基于价值的方法)。但面对“连续动作”(比如方向盘可以旋转任意连续角度),我们不可能给无限多个角度都算一个 \(Q\) 值。

这时,我们需要一种能直接输出动作的方法——策略梯度(Policy Gradient),以及在此基础上融合两者的经典框架——Actor-Critic(演员-评论家)。

策略梯度:跳过中介,直接学策略 🎯

  • 🧠 直接参数化:神经网络不再预测得分,而是直接代表策略 \(\pi_\theta(a \mid s)\)。

  • 📐 连续动作输出:对于连续动作(如转向角),网络通常输出一个概率分布的参数,比如高斯分布的均值 \(\mu\) 和标准差 \(\sigma\),小车再从这个分布中采样出一个具体的角度。

  • 📈 优化思想:如果某次尝试的结果整体很好(累计回报高),就通过梯度上升增大选该动作的概率;如果结果糟糕,就减小其概率。

但纯策略梯度有一个缺点:智能体必须跑完整个流程才能算总回报;而同一个动作,这一局可能碰巧拿高分、下一局又碰巧拿低分,学习信号忽高忽低(统计上称为方差大),学起来非常慢且不稳定。

为了兼顾策略梯度的灵活性与价值评估的稳定性,Actor-Critic 将任务拆分给两个协作的神经网络:

角色 负责内容 比喻
Actor(演员) 观察状态 \(s\),直接输出动作 \(a\)(如转向角) 正在开车的学员
Critic(评论家) 评估当前状态或动作的价值(预估得分 \(V\) 或 \(Q\)) 坐在副驾打分的教练

它的更新流程是:

  1. Actor 做出动作,环境给出即时反馈;

  2. Critic 评估这个动作是“超出预期”还是“低于预期”;

  3. Actor 根据 Critic 的即时评价,快速调整自己的动作倾向,而无需等整场泊车完全结束。

🤔 思考:检验一下这个协作机制。假设自动驾驶小车倒车时,Actor 打了 \(15^\circ\) 的方向盘。接下来,Critic 是根据什么信息来判断这个 \(15^\circ\) 的操作到底是“比预期好”还是“比预期差”呢?

✍️ 我的回答:车辆所处新位置与方向的改变,以及环境反馈的即时奖励!

解析在 Actor-Critic 框架中,Critic(评论家)正是通过对比两样东西来计算这个落差的:

  1. 🎯 事实带来的新回报:小车实际拿到的即时奖励 \(R\)(比如靠近了车位得分),加上在新位置预估还能获得的长期价值 \(\gamma V(S_{t+1})\)。

  2. 🔮 原本的预期:在打轮前,Critic 对旧位置价值的预估 \(V(S_t)\)。

两者的差值在数学上被称为优势(Advantage):

\[\text{优势 }\hat{A}=[R+\gamma V(S_{t+1})]-V(S_t)\]
  • 📈 优势为正:说明这一下 \(15^\circ\) 打轮让车子的处境比原本预期的更好(更靠近、方向更正)。

  • 📉 优势为负:说明不仅没改善,反而让处境变差了(比如偏离或更危险)。

Actor 就是根据这个正负信号来更新自己的网络的。

🤔 思考:顺着这个逻辑推导一下:如果算出来的优势值是一个很大的正数,Actor 神经网络会怎么调整其输出中关于“打 \(15^\circ\) 转向”的相关参数?

解析Actor 会利用梯度上升,调大对应参数,从而提高在此类状态下输出 \(15^\circ\) 转向的概率(或者将连续分布的均值往 \(15^\circ\) 靠拢,同时适当收紧方差使其更加确信)。反之,如果优势为负,参数就会朝相反方向调整,抑制这种操作。

📌 策略梯度的数学推导、Actor-Critic 的完整训练流程,以及 PPO 如何让更新更稳定,详见第 9、10 章。

1.7 小结与学习路线

走到这里,强化学习的整体图景已经完整了:智能体在环境中观察状态,按照策略选择动作,根据奖励评估好坏,再用价值或策略梯度改进自己。后面各章就是把这张图里的每一块严格化、算法化:

本章的直觉 严格化在哪里
状态、动作、奖励、“未来只看现在” 第 2 章 马尔可夫决策过程
\(Q\) 值、折扣回报、“现实 − 预期” 第 3 章 贝尔曼方程
模型已知时如何直接算出最优策略 第 4 章 动态规划
不知道环境规则时,靠试错来估计价值 第 5 章 蒙特卡洛、第 6 章 时序差分(Q-learning / SARSA、同策略与异策略)
在“走一步就学”与“走完再学”之间折中 第 7 章 \(n\)-步回报与资格迹
用神经网络代替 Q 表 第 8 章 DQN 及其改进
直接学策略、演员与评论家 第 9 章 策略梯度与 Actor-Critic(含 DDPG / TD3 / SAC)
让策略更新“小步快跑” 第 10 章 PPO
用人类偏好当奖励 第 11 章 RLHF 与大语言模型(含 DPO)

第 2 章 马尔可夫决策过程

马尔可夫决策过程(Markov Decision Process, 简称 MDP)是强化学习的数学基石 🏛️。几乎所有强化学习问题,本质上都是在尝试求解一个 MDP。

它为智能体(Agent)如何在不确定的环境(Environment)中做出一系列决策提供了严谨的形式化描述。其最核心的假设是马尔可夫性(Markov Property):未来只取决于“当前状态”,而与“过去的历史”无关(即“未来与过去独立,给定当下”)。

2.1 五元组 (𝒮, 𝒜, P, R, γ)

马尔可夫决策过程(MDP)通常用一个五元组 \((\mathcal{S}, \mathcal{A}, P, R, \gamma)\) 来严谨定义 📐:

  • 📍 状态集合 \(\mathcal{S}\)(State space):环境可能处于的所有有效状态集合。

  • 🕹️ 动作集合 \(\mathcal{A}\)(Action space):智能体在各个状态下能够执行的所有合法动作集合。

  • 🎲 状态转移概率 \(P\)(Transition probability):在状态 \(s\) 下执行动作 \(a\) 后,转移到下一个状态 \(s'\) 的概率,记作 \(P(s' \mid s, a)\)。

  • 🏆 奖励函数 \(R\)(Reward function):在状态 \(s\) 下采取动作 \(a\)(并到达 \(s'\))时,环境给予的即时反馈,通常记作 \(R(s, a)\) 或 \(R(s, a, s')\)。

  • ⏳ 折扣因子 \(\gamma\)(Discount factor):取值范围为 \(\gamma \in [0, 1]\),用来衡量未来长期奖励相比于眼前奖励的折算比例(\(\gamma = 1\) 只适用于必定终止的分幕任务,持续性任务需要 \(\gamma < 1\) 才能保证回报有限)。

    📌 分幕任务与持续性任务:像一局游戏、一次泊车这样有明确终点的任务称为分幕任务(Episodic Task),从开始到终点的一次完整交互称为一个回合(Episode);像服务器负载均衡、长期运行的机器人这样没有终点的任务称为持续性任务(Continuing Task)。

2.2 状态转移的不确定性

第 1 章的小车自动泊车任务其实就可以直接抽象为一个 MDP。

其中 \(\mathcal{S}, \mathcal{A}, R, \gamma\) 我们之前都讨论过,而这里的 \(P\)(状态转移概率) 尤为关键。

🤔 思考:现实中小车在当前状态 \(s\) 踩下油门(动作 \(a\)),下个状态 \(s'\) 一定是百分之百确定的吗?如果在雨天路面打滑,这个 \(P(s' \mid s, a)\) 会如何体现这种不确定性?

✍️ 我的回答:路面打滑,\(P\) 概率会快速降低!

解析⚠️ 常见误区:\(P\) 不是一个会整体“降低”的单一数值。状态转移概率 \(P(s' \mid s, a)\) 本质上是一个概率分布(所有可能转移到的新状态 \(s'\) 的概率之和必须为 1)。

  • ☀️ 抓地良好时(接近确定性环境):

    • \(P(\text{按原路线前进 } 1 \text{ 米} \mid s, \text{踩油门}) \approx 0.99\)

    • 发生侧滑或偏离的概率接近 \(0\)。

  • 🌧️ 雨天打滑时(随机性环境):

    • \(P(\text{按原路线前进 } 1 \text{ 米} \mid s, \text{踩油门})\) 可能骤降到 \(0.50\)。

    • 剩下 \(0.50\) 的概率会被分散给其他意外状态,比如 \(P(\text{向左侧滑} \mid s, \text{踩油门}) = 0.30\),\(P(\text{原地空转} \mid s, \text{踩油门}) = 0.20\)。

这就体现了强化学习所处环境的不确定性:做出相同的动作,并不保证一定会进入同一个结果。

2.3 马尔可夫性

搞清楚了五元组中的每一个元素后,我们再回头看这个模型名字里的马尔可夫(Markov)性质。

在数学上,马尔可夫性定义为:

\[P(S_{t+1} \mid S_t, A_t, S_{t-1}, A_{t-1}, ..., S_0, A_0) = P(S_{t+1} \mid S_t, A_t)\]

用通俗的话说就是:“未来只取决于现在时刻,而与过去无关”。

🤔 思考:如果我们在描述小车的当前状态 \(S_t\) 时,只记录了小车的绝对坐标 \((x, y)\),而没有记录当前车速 \(v\),那么这个系统还满足马尔可夫性质吗?为什么?

解析不满足。如果状态只包含位置 \((x, y)\):

  • 相同的位置 \((x, y)\),踩下同样力度的刹车(动作 \(a\));

  • 如果前一秒车速是 \(100\text{ km/h}\),小车在下一时刻 \(S_{t+1}\) 会滑出很远;

  • 如果前一秒车速是 \(0\text{ km/h}\),小车下一刻依然停在原地。

此时,为了预测下一刻的状态,智能体不得不去翻看过去的历史轨迹来推算车速。这就破坏了“未来只取决于现在”的假设,系统退化成了非马尔可夫决策过程。

2.4 状态价值 vs 动作价值

在进入 2.5 节的贝尔曼方程之前,先把第 1 章里出现过的两个价值函数区分清楚。它们是评估“前景好坏”的两种不同视角:

函数类型 符号 评估对象 含义
状态价值函数 📍 \(V(s)\) 状态本身 处在状态 \(s\),小车整体前景有多好?(不管具体选哪个动作)
动作价值函数 🕹️ \(Q(s, a)\) 状态 + 具体动作 处在状态 \(s\) 并且特定执行了动作 \(a\) 之后,长远得分有多高?

它们的关系非常紧密:

  • \(Q(s, a)\) 是具体的某一条分支路线的期望得分;

  • \(V(s)\) 是在当前状态下,对所有可能动作的 \(Q(s, a)\) 取期望(策略加权平均)或者取最大值(最优情况):

\[V^\pi(s)=\sum_a \pi(a\mid s)\,Q^\pi(s,a)\qquad\text{(按策略 }\pi\text{ 加权平均)}\]
\[V^*(s)=\max_a Q^*(s,a)\qquad\text{(最优策略下取最大值)}\]

回到 1.4 节的例子:在“车身歪了 30°、离车位 5 米”这个状态下,三个动作的 \(Q\) 值约为 \(-150\)(直接倒车)、\(+80\)(先摆正)、\(0\)(不动)。这个状态本身的价值 \(V(s)\) 有多高,取决于小车打算怎么选动作:

  • 如果小车已经学会了最优策略,每次都选“先摆正”:\(V^*(s) = \max(-150, 80, 0) = 80\);
  • 如果小车还是个新手,三个动作各以 \(1/3\) 的概率乱选:\(V^\pi(s) = \frac{1}{3}(-150) + \frac{1}{3}(80) + \frac{1}{3}(0) \approx -23.3\)。

同一个状态,对“老司机”来说前景很好,对“新手”来说前景却是负的。这说明 \(V\) 永远是相对于某个策略而言的,这也是符号里要带上角标 \(\pi\) 的原因。

在第 1 章预告过的两种核心架构中(详见第 8、9 章):

  • 🟦 在 DQN 中(动作价值函数 \(Q\)):

    • 网络结构:输入当前状态 \(s\)(如小车坐标、速度)。

    • 网络输出层:直接输出每一个动作对应的 \(Q\) 值(例如:[Q(直行), Q(左转), Q(倒车)])。

    • 体现在哪:DQN 网络本身就是一个 \(Q\) 函数逼近器(用神经网络去近似 \(Q\) 函数,而不是用表格逐个记录),输出的神经元数值直接代表动作价值 \(Q(s, a)\)。

  • 🟩 在 Actor-Critic 中(两者分工):

    • Critic 网络:通常负责逼近 \(V(s)\)(输入状态 \(s\),输出一个标量得分),用来给当前局面打基准分并计算优势(Advantage);

    • Actor 网络:负责输出策略 \(\pi(a \mid s)\)(直接输出概率或转向动作)。

2.5 期望与最大值:贝尔曼方程的直觉

既然环境在执行动作时存在这种概率性的转移 \(P(s' \mid s, a)\),智能体在评估“处于状态 \(s\) 到底有多大价值”时,就不能只看最好的一种可能,而是必须把所有可能的走向都考虑进来。

这便引出了 MDP 中最核心的数学工具——贝尔曼方程(Bellman Equation)。本节只讲直觉,完整推导见第 3 章。

回顾 2.4 节:状态价值函数 \(V(s)\) 衡量的是从状态 \(s\) 出发,按照某种策略一直走下去,能够获得的长期期望累计回报。

如果智能体在状态 \(s\) 采取了动作 \(a\):

  1. 它可能以不同概率转移到不同的下一个状态 \(s'\)(由 \(P(s' \mid s, a)\) 决定);

  2. 每种走向会带来即时奖励 \(R(s, a, s')\),以及新状态的长期价值 \(\gamma V(s')\)。

🤔 思考:考虑到这种转移的随机性,在数学上我们通常用什么方法把这些所有可能状态的回报汇总成一个单一的数值呢?

✍️ 我的回答:数学期望。

解析在包含转移概率 \(P(s' \mid s, a)\) 的环境中,将所有可能走向的回报合并为一个数值的数学工具是加权平均,也就是数学期望(Expected Value, \(\mathbb{E}\)):

\[V^\pi(s) = \sum_{a} \pi(a \mid s) \sum_{s'} P(s' \mid s, a) \left[ R(s, a, s') + \gamma V^\pi(s') \right]\]

每一个可能的新状态 \(s'\) 带来的收益,都要乘以转移到该状态的概率 \(P(s' \mid s, a)\),然后全部相加。

关于马尔可夫决策过程(MDP)的数学基础,还有最后一个关键环节:求解最优策略的贝尔曼最优方程(Bellman Optimality Equation)

🤔 思考:当小车学到了最完美的策略 \(\pi^*\) 时,它在每个状态 \(s\) 应该如何根据动作价值 \(Q^*(s, a)\) 来挑选动作?

✍️ 我的回答:选动作价值最大的动作 \(a\)!

解析站在当前状态 \(s\),小车有权自由挑选能够带来最高收益的那个动作,因此我们在所有可选的动作中寻找最大值(\(\max_a\))。

最优状态价值 \(V^*(s)\) 表示:从状态 \(s\) 开始,如果每一步都做出最完美决策,所能获得的长期期望回报。

因此数学表达式可以为:

\[V^*(s)=\max_{a\in\mathcal{A}}Q^*(s,a)\]

展开来看,就是将动作带来的即时奖励与后续折算价值结合:

\[V^*(s)=\max_{a\in\mathcal{A}}\sum_{s'}P(s'\mid s,a)\left[R(s,a,s')+\gamma V^*(s')\right]\]
  • 🏆 外层的 \(\max_a\):挑选出预期总分最高的那一个最优动作 \(a\)。

  • 🎲 内层的 \(\sum_{s'} P(\dots)\):对环境可能转移到的各个后继状态 \(s'\) 计算数学期望。

🤔 思考:如果小车在某个十字路口执行“左转”动作,有 \(70\%\) 的概率安全进入车道(价值为 \(100\)),但有 \(30\%\) 的概率打滑撞墙(价值为 \(-100\))。如果我们在状态转移时不管概率、直接给未来的下一个状态“取最大值”,算法会对这个“左转”动作做出怎样的误判?

解析如果我们对未来状态直接取最大值:

  • 两个可能的结果是:安全进入车道(价值 \(100\))和撞墙(价值 \(-100\))。

  • 取最大值就会只看到 \(\max(100, -100) = 100\)。

  • 这样算法就会完全忽略那 \(30\%\) 撞墙的巨大危险,误以为“左转”是万无一失的满分操作,从而变得过度鲁莽、盲目自信 🚗💥。

实际上,因为环境存在概率,真正的预期价值应该是加权平均:

\[0.7\times100+0.3\times(-100)=70-30=40\]

正因为如此,在状态转移时必须计算期望 \(\sum_{s'} P\),才能把风险也权衡进去。

现在,对比一下这两个操作:

  • 对动作:我们可以取最大值(\(\max_a\)),因为方向盘在智能体手里。

  • 对下一个状态:必须计算加权期望(\(\sum_{s'} P\)),因为轮子会不会打滑由环境决定。

把这两者结合起来,就构成了贝尔曼最优方程的核心逻辑。

🤔 思考:根据这个逻辑,当小车在某个路口面临以下两个动作时,它应该选择哪一个?

  • 🏁 动作 1(冒进):\(50\%\) 概率快速到达目的地(价值 \(100\)),\(50\%\) 概率发生严重剐蹭(价值 \(-50\))。
  • 🛡️ 动作 2(稳健):\(100\%\) 概率平稳通行(价值 \(35\))。

✍️ 我的回答:动作 2!

解析我们来算一下期望值:

  • 🏁 动作 1(冒进):\(0.5 \times 100 + 0.5 \times (-50) = 50 - 25 = 25\)

  • 🛡️ 动作 2(稳健):\(1.0 \times 35 = 35\)

因为 \(35 > 25\),根据贝尔曼最优方程 \(\max_a Q^*(s, a)\),智能体自然会选择期望收益更高的动作 2。即使动作 1 有机会摸到 \(100\) 分的最高奖励,但概率加权后的风险把它拉低了。

关于马尔可夫决策过程(MDP)的核心链条已经全部闭环:

  • 🧩 五元组 \((\mathcal{S}, \mathcal{A}, P, R, \gamma)\):描摹了环境状态、动作空间、转移机制、奖励与长远眼光。

  • 🎲 马尔可夫性:“未来只与当下有关”。如果信息不足(比如缺速度),必须扩充状态的描述来满足它。

  • 📐 价值评估与最优方程:

    • 对环境的不确定性算期望(\(\sum_{s'} P\))。

    • 对智能体的主动选择求最大(\(\max_a\))。

第 3 章 贝尔曼方程(形式推导)

第 2 章从直觉上说明了“对环境求期望、对动作取最大”。本章用统一的符号把这些结论严格推导出来。

3.1 回报的递归定义

在马尔可夫决策过程(MDP)中:

  • \(S_t\):\(t\) 时刻的状态

  • \(A_t\):\(t\) 时刻采取的动作

  • \(R_{t+1}\):执行动作后获得的即时奖励 🎁

  • \(\gamma \in [0, 1]\):折扣因子 ⏳(见 2.1 节的取值说明)

累积折扣回报 \(G_t\) 的定义为从当前时刻开始的所有未来奖励的折扣和:

\[G_t=R_{t+1}+\gamma R_{t+2}+\gamma^2R_{t+3}+\dots\]

提取公因子 \(\gamma\) 后,可以写成非常关键的递归形式:

\[G_t=R_{t+1}+\gamma G_{t+1}\]

举个例子:设 \(\gamma = 0.9\),一个回合从 \(t=0\) 开始,依次拿到奖励 \(R_1 = 1,\ R_2 = 2,\ R_3 = 3\) 后结束。

  • 直接按定义算:\(G_0 = 1 + 0.9 \times 2 + 0.9^2 \times 3 = 1 + 1.8 + 2.43 = 5.23\)
  • 用递归形式倒着算:
    • \(G_2 = R_3 = 3\)(最后一步之后没有奖励了)
    • \(G_1 = R_2 + \gamma G_2 = 2 + 0.9 \times 3 = 4.7\)
    • \(G_0 = R_1 + \gamma G_1 = 1 + 0.9 \times 4.7 = 5.23\) ✅

两种算法结果相同。递归形式的好处是:只要知道“下一步的回报”,就能算出“这一步的回报”,不用每次都从头累加。后面的贝尔曼方程(本章)、TD 学习(第 6 章),以及第 10 章的优势估计方法,都建立在这个递归结构之上。

3.2 状态价值的贝尔曼期望方程

状态价值函数 \(V^\pi(s)\) 表示在策略 \(\pi\) 下,从状态 \(s\) 出发能获得的期望回报:

\[V^\pi(s)=\mathbb{E}_\pi[G_t\mid S_t=s]\]

利用 \(G_t = R_{t+1} + \gamma G_{t+1}\) 代入:

\[V^\pi(s)=\mathbb{E}_\pi[R_{t+1}+\gamma G_{t+1}\mid S_t=s]\]

根据全期望公式(一个随机过程分几步发生时,可以先对第一步的各种可能求平均,再对后续求平均),智能体的决策链条为:当前状态 \(s \xrightarrow{\ \pi(a \mid s)\ }\) 动作 \(a \xrightarrow{\ P(s' \mid s,a)\ }\) 下一个状态 \(s'\)。第一步的随机性来自策略,第二步的随机性来自环境,所以要对两者依次求期望。

展开为求和形式:

\[V^\pi(s)=\sum_{a\in\mathcal{A}}\pi(a\mid s)\sum_{s'\in\mathcal{S}}P(s'\mid s,a)\left[R(s,a,s')+\gamma V^\pi(s')\right]\]

3.3 动作价值的贝尔曼期望方程

\(Q^\pi(s, a)\) 表示在状态 \(s\) 采取动作 \(a\) 后,后续继续遵循策略 \(\pi\) 的期望回报:

\[Q^\pi(s,a)=\mathbb{E}_\pi[R_{t+1}+\gamma G_{t+1}\mid S_t=s,A_t=a]\]

展开后为:

\[Q^\pi(s,a)=\sum_{s'\in\mathcal{S}}P(s'\mid s,a)\left[R(s,a,s')+\gamma V^\pi(s')\right]\]

注意到 \(V^\pi(s')\) 和 \(Q^\pi(s', a')\) 的互换关系:

\[V^\pi(s')=\sum_{a'\in\mathcal{A}}\pi(a'\mid s')Q^\pi(s',a')\]

代入即得仅含 \(Q\) 的方程:

\[Q^\pi(s,a)=\sum_{s'\in\mathcal{S}}P(s'\mid s,a)\left[R(s,a,s')+\gamma\sum_{a'\in\mathcal{A}}\pi(a'\mid s')Q^\pi(s',a')\right]\]

用一个数字例子把 3.2、3.3 串起来(为了方便计算,数字与 1.4 节不同,取 \(\gamma = 1\)):

小车处于“车身歪着”的状态 \(s\),当前策略 \(\pi\) 以 \(0.6\) 的概率选“先摆正”,以 \(0.4\) 的概率选“直接倒车”:

  • 先摆正:一定会进入“车身已摆正”的状态 \(s_{\text{正}}\),这一步奖励 \(-2\),已知 \(V^\pi(s_{\text{正}}) = 90\);
  • 直接倒车:环境有随机性,\(70\%\) 的概率刮车(奖励 \(-500\),回合结束),\(30\%\) 的概率侥幸倒进车位(奖励 \(+100\),回合结束)。回合结束后的状态价值为 \(0\)。

第一步:用 3.3 节的公式算每个动作的 \(Q^\pi\)(对环境的随机性求期望):

\[Q^\pi(s, \text{摆正}) = 1.0 \times (-2 + 90) = 88\]
\[Q^\pi(s, \text{倒车}) = 0.7 \times (-500 + 0) + 0.3 \times (100 + 0) = -320\]

第二步:用 3.2 节的公式算 \(V^\pi\)(对策略的随机性求期望):

\[V^\pi(s) = 0.6 \times 88 + 0.4 \times (-320) = 52.8 - 128 = -75.2\]

可以看到,贝尔曼期望方程就是两层加权平均:外层按策略 \(\pi\) 对动作加权,内层按转移概率 \(P\) 对结果加权。这个策略下状态价值是负的,原因是它有 \(40\%\) 的概率去冒险倒车。如果把策略改成永远“先摆正”,价值就变成 \(88\),这正是下一节最优方程要做的事:把外层的加权平均换成取最大值。

3.4 贝尔曼最优方程(Bellman Optimality Equation)

当策略达到最优 \(\pi^*\) 时,价值达到最大:

  • \(V^*(s) = \max_\pi V^\pi(s)\)

  • \(Q^*(s, a) = \max_\pi Q^\pi(s, a)\)

此时智能体不再按概率 \(\pi(a \mid s)\) 混合动作,而是直接选取能带来最大回报的动作:

\[V^*(s)=\max_{a\in\mathcal{A}}Q^*(s,a)\]

代入后得到两组核心最优方程:

\[V^*(s)=\max_{a\in\mathcal{A}}\sum_{s'}P(s'\mid s,a)\left[R(s,a,s')+\gamma V^*(s')\right]\]
\[Q^*(s,a)=\sum_{s'}P(s'\mid s,a)\left[R(s,a,s')+\gamma\max_{a'\in\mathcal{A}}Q^*(s',a')\right]\]

用 3.3 节的例子再算一遍(仍取 \(\gamma = 1\))

3.3 节只说了“已知 \(V^\pi(s_{\text{正}}) = 90\)”。要算最优价值,需要把 \(s_{\text{正}}\) 这个状态也说清楚:假设在“车身已摆正”的状态 \(s_{\text{正}}\) 下,小车有两个动作:

  • 倒车入库:一次到位,这一步奖励 \(+90\),回合结束;
  • 原地不动:什么也得不到,价值为 \(0\)。

原策略 \(\pi\) 在 \(s_{\text{正}}\) 本来就总是选“倒车入库”,所以 3.3 节的 \(V^\pi(s_{\text{正}}) = 90\) 也正是这里的最优值。下面从后往前算。

第一步:先算后继状态 \(s_{\text{正}}\) 的最优价值(用 \(Q^*\) 形式的最优方程,回合结束后价值为 \(0\)):

\[Q^*(s_{\text{正}}, \text{倒车入库}) = 90 + 0 = 90,\qquad Q^*(s_{\text{正}}, \text{原地不动}) = 0\]
\[V^*(s_{\text{正}}) = \max_{a'} Q^*(s_{\text{正}}, a') = \max(90,\ 0) = 90\]

第二步:再算当前状态 \(s\) 的两个动作(内层仍然对环境的随机性求期望,这一点和 3.3 节完全一样):

\[Q^*(s, \text{摆正}) = 1.0 \times \left(-2 + \max_{a'} Q^*(s_{\text{正}}, a')\right) = -2 + 90 = 88\]
\[Q^*(s, \text{倒车}) = 0.7 \times (-500 + 0) + 0.3 \times (100 + 0) = -320\]

第三步:外层取最大值(这是与 3.3 节唯一的区别):

\[V^*(s) = \max\left(Q^*(s, \text{摆正}),\ Q^*(s, \text{倒车})\right) = \max(88,\ -320) = 88,\qquad \pi^*(s) = \text{摆正}\]

把 3.3 节和本节的结果放在一起对比:

步骤 3.3 节:贝尔曼期望方程(策略 \(\pi\)) 3.4 节:贝尔曼最优方程
内层:对环境求期望 \(Q^\pi(s,\text{摆正}) = 88\),\(Q^\pi(s,\text{倒车}) = -320\) \(Q^*(s,\text{摆正}) = 88\),\(Q^*(s,\text{倒车}) = -320\)
外层:对动作怎么处理 按策略加权:\(0.6 \times 88 + 0.4 \times (-320)\) 取最大值:\(\max(88, -320)\)
状态价值 \(V^\pi(s) = -75.2\) \(V^*(s) = 88\)

两个 \(Q\) 值恰好相同,是因为这个例子里后继状态 \(s_{\text{正}}\) 的原策略已经是最优的,而“倒车”之后回合直接结束。差距完全来自外层:原策略有 \(40\%\) 的概率去冒险。

为什么最优策略可以是确定性的? 如果把“先摆正”的概率记为 \(p\),那么 \(V^\pi(s) = p \times 88 + (1-p) \times (-320) = 408p - 320\):

\(p\)(选“摆正”的概率) \(0\)(永远倒车) \(0.6\)(3.3 节的策略) \(1\)(永远摆正)
\(V^\pi(s)\) \(-320\) \(-75.2\) \(88\)

这是一条关于 \(p\) 的直线,最大值一定出现在端点 \(p = 1\) 上。也就是说,任何“按概率混合动作”的策略都不会比“总是选 \(Q^*\) 最大的动作”更好,这正是 \(V^*(s) = \max_a Q^*(s,a)\) 的直观含义。

📌 这个例子之所以能从后往前一步算完,是因为状态之间没有循环:每个状态的价值只依赖“更靠后”的状态。一般情况下状态会互相依赖,这时就需要第 4 章的迭代算法。

第 4 章 动态规划(已知模型)

3.4 节的例子里,我们直接从后往前把 \(V^*\) 和 \(\pi^*\) 算了出来。但那个例子有一个特殊之处:状态之间没有“绕回来”的路。“倒车”之后回合结束,“摆正”之后到达的 \(s_{\text{正}}\) 也不会再回到 \(s\),所以可以先算出后继状态,再回头算当前状态。

一般的 MDP 并不这么“友好”。比如 4.4 节的 3 状态世界中,\(s_1\) 向左会撞墙留在原地,\(s_2\) 向左又会回到 \(s_1\)。写出贝尔曼最优方程后会发现:

  • \(V^*(s_1)\) 的方程右边含有 \(V^*(s_1)\) 和 \(V^*(s_2)\);
  • \(V^*(s_2)\) 的方程右边又含有 \(V^*(s_1)\)。

所有状态的方程互相咬在一起,每个方程里还带着非线性的 \(\max\),没有“最后一个状态”可以作为起点往回代,也无法像普通线性方程组那样直接求解。

贝尔曼最优方程告诉了我们最优解必须满足的条件,却没有直接给出求解的步骤。本章的策略迭代(Policy Iteration)与值迭代(Value Iteration)就是两类经典的动态规划算法:它们从一个随便猜的初始值出发,反复用贝尔曼方程更新,一轮轮逼近满足方程的那个解。它们都在已知环境模型(即完全知晓转移概率 \(P\) 和奖励函数 \(R\))的前提下,用来求解最优策略 \(\pi^*\)。

两种算法都会反复用到同一个计算:在当前价值函数 \(V\) 下,状态 \(s\) 执行动作 \(a\) 的动作价值(这正是第 3 章贝尔曼方程中的内层期望):

\[Q(s,a)=\sum_{s'}P(s'\mid s,a)\left[R(s,a,s')+\gamma V(s')\right]\]

后面的伪代码统一用辅助函数 compute_q 来表示它:

# 辅助函数:在当前价值 V 下,计算状态 s 执行动作 a 的动作价值
def compute_q(s, a, V):
  return sum(P[s][a][s_next] * (R[s][a][s_next] + gamma * V[s_next])
             for s_next in S)

4.1 策略迭代

🔄 策略迭代(Policy Iteration):采用“交替进行”的双步循环。

  1. 策略评估 🧐:固定当前策略 \(\pi\),反复迭代贝尔曼期望方程,直到状态价值 \(V^\pi(s)\) 彻底收敛。

  2. 策略改进 🚀:基于算出的价值函数,贪心地选取能带来更大动作价值 \(Q(s, a)\) 的动作,生成全新策略 \(\pi'\)。

  3. 重复以上两步,直至策略不再改变。

对应的伪代码如下:

# 初始化:随机策略与全 0 价值
pi = {s: random_action() for s in S}
V = {s: 0.0 for s in S}

while True:
  # --- 步骤 1: 策略评估 (反复迭代直到 V 匹配当前策略 pi) ---
  while True:
    delta = 0
    for s in S:
      v_old = V[s]
      # 核心:只计算当前策略指定的动作 pi[s] 的价值
      V[s] = compute_q(s, pi[s], V)
      delta = max(delta, abs(v_old - V[s]))
    if delta < theta:
      break

  # --- 步骤 2: 策略改进 (基于算出的 V 贪心选择新策略) ---
  policy_stable = True
  for s in S:
    old_action = pi[s]
    pi[s] = argmax(a, compute_q(s, a, V))
    if old_action != pi[s]:
      policy_stable = False

  if policy_stable:  # 策略不再变化,说明已达最优
    break

🤔 思考:策略迭代在算完当前策略的价值 \(V^\pi\) 后,接下来的“策略改进”阶段又是如何利用这些计算结果来得到一个更好的新策略的?

✍️ 我的回答:取一个价值最大的策略作为一个新策略!

解析思路正确,这就是经典的贪心改进(Greedy Improvement)。但更精确地说,并不是在“策略”之间挑最大,而是逐个状态挑出价值最大的动作,再拼成新策略:

  • 📍 遍历每个状态 \(s\):利用刚刚算出的状态价值 \(V^\pi\),计算出每个动作对应的动作价值 \(Q(s, a)\)。

  • 🕹️ 贪心选择动作:在这个状态下,挑出能让 \(Q(s, a)\) 最大的那个动作,作为该状态下的新动作:

\[\pi'(s)=\arg\max_{a\in\mathcal{A}}\sum_{s'}P(s'\mid s,a)\left[R(s,a,s')+\gamma V^\pi(s')\right]\]

所有状态的动作更新完毕后,就拼成了一个全新的策略 \(\pi'\)。根据策略改进定理,这个新策略 \(\pi'\) 的表现必定大于或等于原策略 \(\pi\)。直观理解:在每个状态都换成“按当前估值来看至少不差”的动作,整体表现只会变好或持平,不会变差。

现在,整个策略迭代的双步循环就闭环了:

  1. 策略评估 🧐:计算当前策略的价值 \(V^\pi\)。

  2. 策略改进 🚀:基于 \(V^\pi\) 贪心更新出新策略 \(\pi'\)。

4.2 值迭代

⚡ 值迭代(Value Iteration):采用“一步到位”的直接更新。

  • 它不再等待某个策略的价值完全收敛,而是直接将贝尔曼最优方程转化为更新规则:
\[V_{k+1}(s)\leftarrow\max_{a\in\mathcal{A}}\sum_{s'}P(s'\mid s,a)\left[R(s,a,s')+\gamma V_k(s')\right]\]
  • 每轮迭代中,对每个状态价值直接取最大值 \(\max_a\) 来刷新价值,直到价值数值的变化小于设定阈值,最后一步提取出最优策略。

  • 这种“把贝尔曼最优方程的右边当作一次更新操作,作用在当前 \(V\) 上”的做法,也称为应用一次贝尔曼最优算子。

值迭代的核心是直接更新价值,直到数值收敛,最后提取策略:

# 初始化:全 0 价值
V = {s: 0.0 for s in S}

# --- 步骤 1: 反复用贝尔曼最优算子更新 V,直到数值收敛 ---
while True:
  delta = 0
  for s in S:
    v_old = V[s]
    # 核心:直接对所有动作取最大值
    V[s] = max(compute_q(s, a, V) for a in A)
    delta = max(delta, abs(v_old - V[s]))
  if delta < theta:  # 价值变化小于阈值,视为收敛
    break

# --- 步骤 2: 基于收敛后的 V 一次性提取最优策略 ---
pi = {s: argmax(a, compute_q(s, a, V)) for s in S}

4.3 两者对比与终止条件

我们来对比一下这两者的计算特点:

在状态数量非常庞大的复杂环境中,策略迭代在“策略评估”这一步往往需要迭代很多次才能让价值完全收敛。

🤔 思考:从计算开销的角度看,值迭代为什么能够在单次迭代中省去大量重复的评估计算?它是怎么简化这个过程的?

解析值迭代之所以省时,正是因为每一步都直接采用贝尔曼最优算子,内嵌了 \(\max_a\):

  • 🔄 策略迭代:先选定一个具体的策略 \(\pi\)(哪怕它现在很烂),傻傻地去算“在这个策略下,每个状态到底能得多少分”,反复算很多轮直到数值完全收敛,然后再去改进策略。

  • ⚡ 值迭代:完全不等当前策略的真实价值算清楚。它在每一次更新时,只看眼前哪一步能取到最大值(\(\max_a\)),直接拿这个最大估计去更新 \(V(s)\)。

换句话说,值迭代本质上是把策略迭代中的“策略评估”截断到了仅仅迭代 1 次,评估完一次立刻隐式地进行“策略改进”。

既然两者都能最终收敛到最优策略 \(\pi^*\),我们可以对比一下它们的优缺点:

算法 单轮耗时 收敛所需的总轮数(Iterations) 适用场景特点
策略迭代 (Policy Iteration) 较长(每次评估要迭代多次) 通常非常少(几次或十几次即可收敛) 动作空间小、状态转移简单的场景
值迭代 (Value Iteration) 极短(每轮只扫一次加一次 \(\max\)) 通常较多(数值逐步逼近) 状态规模大、追求快速单步更新的场景

再对比 4.1 与 4.2 两段代码中更新 V[s] 的那一行:

  • 值迭代使用的是:V[s] = max(compute_q(s, a, V) for a in A)

  • 策略迭代(评估阶段)使用的是:V[s] = compute_q(s, pi[s], V)

对比一下这两者的更新目标:

  • ⚡ 值迭代:直接瞄准最优价值函数 \(V^*\) 进行更新。

  • 🔄 策略迭代(评估阶段):瞄准当前策略的价值函数 \(V^\pi\) 进行更新。

最后看终止条件。策略迭代的循环里有一句关键判断:if policy_stable: break。

🤔 思考:算法怎么判断策略已经达到最优、可以停下来了?具体是以什么作为终止条件的呢?

解析回顾 4.1 代码中的这一段逻辑:

policy_stable = True
for s in S:
  old_action = pi[s]
  pi[s] = argmax(a, compute_q(s, a, V))
  if old_action != pi[s]:
    policy_stable = False

if policy_stable:
  break

注意看这里的 if old_action != pi[s]:。

  • 🔍 在值迭代中:我们比较的是连续两次迭代的价值数值差异(\(\Delta V < \theta\)),属于“数值收敛”。

  • 🧭 在策略迭代中:我们直接比较的是动作本身!只要在所有状态 \(s\) 下,新策略挑选出来的动作与原策略选出的动作完全一致(即没有任何一个状态发生动作切换),policy_stable 就保持为 True,算法立刻终止。

因为策略空间是有限的(有限个状态,有限个动作),所以策略迭代通常在非常少的几轮循环内就会彻底稳定下来。

现在,我们已经把基于动态规划的两大基石彻底摸透了:

  1. 策略迭代 🔄:策略评估(算当前策略得分)与策略改进(换更好的动作)交替进行,直到选择的动作不再变化。

  2. 值迭代 ⚡:每次直接贪心取 \(\max_a\) 逼近最优价值,直到价值数值变化小于阈值。

到这里,关于已知 MDP 模型的经典解法已经形成了一个完整的闭环。

4.4 数值演练:3 状态线性世界

下面在已知 \(P\) 和 \(R\) 的前提下,通过一个极简例子(3 状态线性世界),先用值迭代、再用策略迭代各走一遍完整的计算过程,最后对比两者。

场景设定:3 状态线性世界 🗺️

假设智能体处于一条直线上,共有 3 个离散状态:

\[\mathcal{S} = \{s_1, s_2, s_3\}\]
  • 📍 状态分布:

    • \(s_1\):起点/普通格子

    • \(s_2\):中间格子

    • \(s_3\):目标终点(吸收态/终止状态),进入后游戏结束,不再产生后续回报,即 \(V(s_3) = 0\)。

  • 🕹️ 动作集合:

    • 在每个非终止状态下,智能体有两个动作:\(\mathcal{A} = \{\text{向左 (L)}, \text{向右 (R)}\}\)。
  • 🎲 转移概率 \(P\)(确定性转移,无打滑):

    • 在 \(s_1\):执行 \(\text{L}\) 撞墙停在 \(s_1\);执行 \(\text{R}\) 到达 \(s_2\)。

    • 在 \(s_2\):执行 \(\text{L}\) 到达 \(s_1\);执行 \(\text{R}\) 到达 \(s_3\)。

  • 🏆 奖励函数 \(R\):

    • 只有一步进入终点 \(s_3\)(在 \(s_2\) 执行 \(\text{R}\))时,环境给予奖励 \(R = +10\)。

    • 其他任何移动的即时奖励均为 \(R = 0\)。

  • ⏳ 折扣因子:设 \(\gamma = 0.5\)。

值迭代更新规则回顾 📐

\[V_{k+1}(s)=\max_{a\in\mathcal{A}}\sum_{s'}P(s'\mid s,a)\left[R(s,a,s')+\gamma V_k(s')\right]\]

初始状态(\(k = 0\))

所有状态价值初始化为零:

  • \(V_0(s_1) = 0\)

  • \(V_0(s_2) = 0\)

  • \(V_0(s_3) = 0\)

第一轮迭代(\(k = 1\))

我们来计算第 1 轮迭代中每个状态的价值:

计算 \(s_1\):

  1. 选择向左 \(\text{L}\):撞墙,停留在s1,即时奖励 \(0\),总动作价值为 \(0 + 0.5 \times V_0(s_1) = 0 + 0 = 0\)。

  2. 选择向右 \(\text{R}\):到达 \(s_2\),即时奖励 \(0\),总动作价值为 \(0 + 0.5 \times V_0(s_2) = 0 + 0 = 0\)。

  3. 取最大值:\(V_1(s_1) = \max(0, 0) = 0\)。

计算 \(s_2\):

  1. 选择向左 \(\text{L}\):到达 \(s_1\),即时奖励 \(0\),总动作价值为 \(0 + 0.5 \times V_0(s_1) = 0 + 0 = 0\)。

  2. 选择向右 \(\text{R}\):到达 \(s_3\),即时奖励 \(+10\),总动作价值为 \(10 + 0.5 \times V_0(s_3) = 10 + 0 = 10\)。

  3. 取最大值:\(V_1(s_2) = \max(0, 10) = 10\)。

目前状态价值为:\(V_1 = [V(s_1)=0, \, V(s_2)=10, \, V(s_3)=0]\)。

接下来进入第二轮迭代(\(k=2\))。

计算 \(s_1\):

  • ⬅️ 向左(L):\(0 + 0.5 \times V_1(s_1) = 0 + 0.5 \times 0 = 0\)

  • ➡️ 向右(R):\(0 + 0.5 \times V_1(s_2) = 0 + 0.5 \times 10 = 5\)

  • 🏆 取最大值:\(V_2(s_1) = \max(0, 5) = 5\)

终点的奖励通过折扣因子 \(\gamma = 0.5\),成功“倒流”并传播到了相距两步的起点 \(s_1\)。

现在,我们再看一下同一个阶段(\(k=2\))下的 \(s_2\):

  • ⬅️ 向左(L):转移到 \(s_1\),价值为 \(0 + 0.5 \times V_1(s_1) = 0\)

  • ➡️ 向右(R):转移到终点 \(s_3\),价值依然是 \(10 + 0.5 \times V_1(s_3) = 10\)

  • 📊 结果:\(V_2(s_2) = \max(0, 10) = 10\)

目前状态价值为:\(V_2 = [V(s_1)=5, \, V(s_2)=10, \, V(s_3)=0]\)。

接下来进入第三轮迭代(\(k=3\))。

  • 📍 \(V_3(s_1)\):\(\max(\underbrace{0 + 0.5 \times V_2(s_1)}_{\text{L}},\ \underbrace{0 + 0.5 \times V_2(s_2)}_{\text{R}}) = \max(2.5,\ 5) = 5\)

  • 📍 \(V_3(s_2)\):\(\max(\underbrace{0 + 0.5 \times V_2(s_1)}_{\text{L}},\ \underbrace{10 + 0.5 \times V_2(s_3)}_{\text{R}}) = \max(2.5,\ 10) = 10\)

可以发现状态价值已经没有变化,因为各状态的价值变动量 \(\Delta V = 0 < \theta\),算法正式收敛(Convergence) 。此时得到的价值就是理论上的最优价值函数 \(V^*\):

\[V^*=[V^*(s_1)=5,\,V^*(s_2)=10,\,V^*(s_3)=0]\]

最后一步:提取最优策略 \(\pi^*\)

根据值迭代的最后一步,我们需要利用刚刚算出的最优价值 \(V^*\),为每个状态选出贪心动作:

\[\pi^*(s)=\arg\max_{a\in\mathcal{A}}\sum_{s'}P(s'\mid s,a)\left[R(s,a,s')+\gamma V^*(s')\right]\]

根据我们算出的最优价值函数:

  • 📍 在 \(s_1\):

    • 向左(L):撞墙留在原地,期望收益为 \(0 + 0.5 \times V^*(s_1) = 2.5\)

    • 向右(R):期望收益为 \(0 + 0.5 \times V^*(s_2) = 5\)

    • 显然选择 向右(R) ➡️。

  • 📍 在 \(s_2\):

    • 向左(L):期望收益为 \(0 + 0.5 \times V^*(s_1) = 2.5\)

    • 向右(R):期望收益为 \(10 + 0.5 \times V^*(s_3) = 10\)

    • 显然也选择 向右(R) ➡️。

因此最终的最优策略为:

\[\pi^*(s_1)=\text{R},\quad\pi^*(s_2)=\text{R}\]

智能体学会了“不论从哪里出发,都坚定地一路向右直奔终点”。

同一个例子,用策略迭代再做一遍 🔄

环境设定完全不变(\(\gamma = 0.5\),只有在 \(s_2\) 向右进入终点时得 \(+10\))。为了看清策略是怎样一步步变好的,我们故意从最差的策略开始:

\[\pi_0(s_1) = \text{L},\quad \pi_0(s_2) = \text{L}\qquad\text{(全部向左)}\]

💡 策略评估其实是在解线性方程组:策略固定以后,每个状态只有一个确定的动作,贝尔曼期望方程里就没有 \(\max\) 了,只剩线性方程。这个小例子可以直接解出来;状态很多时,则用 4.1 节伪代码中的方式反复迭代到收敛,结果是一样的。

第 1 轮

  • 策略评估(求 \(V^{\pi_0}\)):

    • \(s_1\) 向左撞墙留在原地:\(V(s_1) = 0 + 0.5 \times V(s_1)\),解得 \(V(s_1) = 0\)
    • \(s_2\) 向左回到 \(s_1\):\(V(s_2) = 0 + 0.5 \times V(s_1) = 0\)
    • 结果:\(V^{\pi_0} = [V(s_1)=0,\ V(s_2)=0,\ V(s_3)=0]\)。一直往左走,永远到不了终点,当然一分都拿不到。
  • 策略改进(在每个状态比较两个动作的 \(Q\) 值):

    • \(s_1\):向左 \(0 + 0.5 \times V(s_1) = 0\),向右 \(0 + 0.5 \times V(s_2) = 0\),两者相等
    • \(s_2\):向左 \(0 + 0.5 \times V(s_1) = 0\),向右 \(10 + 0.5 \times V(s_3) = 10\),选向右
    • 新策略:\(\pi_1(s_1) = \text{L},\ \pi_1(s_2) = \text{R}\)

    ⚠️ 平局怎么办? \(s_1\) 的两个动作打平时,约定保留原来的动作(仍然向左)。如果平局时随便挑一个,策略可能在两个同样好的动作之间来回切换,4.3 节的终止条件“策略不再变化”就永远满足不了。

第 2 轮

  • 策略评估(求 \(V^{\pi_1}\)):

    • \(s_2\) 向右直达终点:\(V(s_2) = 10 + 0.5 \times 0 = 10\)
    • \(s_1\) 仍然向左撞墙:\(V(s_1) = 0 + 0.5 \times V(s_1)\),解得 \(V(s_1) = 0\)
    • 结果:\(V^{\pi_1} = [0,\ 10,\ 0]\)。\(s_2\) 已经学会了,但 \(s_1\) 还在原地打转。
  • 策略改进:

    • \(s_1\):向左 \(0 + 0.5 \times 0 = 0\),向右 \(0 + 0.5 \times 10 = 5\),选向右(\(s_2\) 的高价值终于被 \(s_1\) “看到”了)
    • \(s_2\):向左 \(0 + 0.5 \times 0 = 0\),向右 \(10\),仍选向右
    • 新策略:\(\pi_2(s_1) = \text{R},\ \pi_2(s_2) = \text{R}\)

第 3 轮

  • 策略评估(求 \(V^{\pi_2}\)):

    • \(V(s_2) = 10\)
    • \(V(s_1) = 0 + 0.5 \times V(s_2) = 5\)
    • 结果:\(V^{\pi_2} = [5,\ 10,\ 0]\)
  • 策略改进:

    • \(s_1\):向左 \(0 + 0.5 \times 5 = 2.5\),向右 \(0 + 0.5 \times 10 = 5\),选向右
    • \(s_2\):向左 \(0 + 0.5 \times 5 = 2.5\),向右 \(10\),选向右
    • 新策略与 \(\pi_2\) 完全相同,policy_stable = True,算法终止 ✅

最终得到 \(\pi^* = \pi_2\)(两个状态都向右),\(V^* = [5,\ 10,\ 0]\),与值迭代的结果完全一致。

两种算法的过程对比

轮次 值迭代:价值 \([V(s_1), V(s_2)]\) 策略迭代:当前策略 → 评估出的价值
初始 \([0,\ 0]\) \(\pi_0\) = (L, L)
第 1 轮 \([0,\ 10]\) (L, L) → \([0,\ 0]\),改进为 (L, R)
第 2 轮 \([5,\ 10]\) (L, R) → \([0,\ 10]\),改进为 (R, R)
第 3 轮 \([5,\ 10]\),变化为 0,收敛 (R, R) → \([5,\ 10]\),策略不变,收敛
最后 从 \(V^*\) 中提取策略 (R, R) 直接得到 (R, R)

两者都用了 3 轮,但每一轮做的事不同:

  • 值迭代每轮只对每个状态做一次 \(\max\) 更新,价值“一格一格”地从终点往回传;
  • 策略迭代每轮都要把当前策略的价值完整算准(这里解了一个小方程组;状态多时要内层迭代很多次),换来的是每轮策略都有实质性的改进。

在这个小例子里差别不大;状态很多时,这就是 4.3 节表格中“单轮耗时”和“收敛轮数”的权衡。

通过这次具体的数值演练,我们完整见证了:

  1. 价值如何从包含即时奖励的终点一步步向后“反向传播” ⏳;

  2. 状态价值在迭代中逐渐稳定收敛的过程 📊;

  3. 如何利用收敛后的 \(V^*\) 直接提取最优动作策略 \(\pi^*\) 🧭;

  4. 策略迭代如何通过“评估 → 改进”的交替,让策略从“全部向左”一步步变成“全部向右”,以及平局时为什么要保留原动作 🔄。

到这里,已知模型下的动态规划求解闭环已经很清晰了。

4.5 从已知模型到无模型

动态规划的前提是模型已知。在强化学习中,当状态转移概率 \(P(s' \mid s, a)\) 和奖励函数 \(R(s, a)\) 完全已知时,我们称其处于基于模型(Model-based)的环境中。这种“上帝视角”通常出现在规则由人类完全定义或物理机制非常明确的场景中,主要有以下几类典型情况:

  • ♟️ 规则完备的棋盘与博弈游戏:例如井字棋、国际象棋、围棋或二十一点。每一步落子后棋盘变成什么样是百分之百确定的,胜负规则和得分也是人为写死的。

  • 🏭 受控的人造离散系统:例如库存管理、排队网络或通信路由。系统的到达率、服务时间和惩罚成本通常服从已知的统计分布(如泊松分布)。

  • 📐 精确的物理或工程数学模型:例如倒立摆、小车爬坡等简单控制问题,其背后的牛顿力学微分方程是已知的,状态转移完全由解析式给出。

  • 🖥️ 代码硬编码的网格世界(GridWorld):教学或基准测试中自定义迷宫环境,程序员直接定义了撞墙概率、滑行概率以及吃到宝藏的得分。

而在真实世界中,\(P\) 和 \(R\) 往往是不可知的,例如自动驾驶、机器人抓取或大语言模型,这就需要后面介绍的无模型(Model-free)方法。

第 5 章 蒙特卡洛方法

5.1 核心思想:用采样平均逼近期望

第 4 章的动态规划需要预先知道转移概率 \(P\) 和奖励函数 \(R\)。而蒙特卡洛方法(Monte Carlo Methods, MC) 🎲 则带领我们正式迈入无模型强化学习(Model-Free RL)的世界。

蒙特卡洛的核心思想非常质朴:用经验采样的平均值来逼近真实期望。智能体不需要知道环境背后的转移矩阵,而是像玩游戏一样完整打完一个个回合(Episode),记录下每个状态后获得的真实累积回报 \(G_t\)。根据大数定律 ,只要采样的完整轨迹足够多,回报的算术平均值就会自然收敛于当前策略下真实的状态价值 \(V^\pi(s)\) 或动作价值 \(Q^\pi(s, a)\)。

5.2 首次访问 vs 每次访问

在蒙特卡洛方法中,智能体通过完整的轨迹(Episode)来估计状态价值 \(V(s)\)。当同一个状态在同一个回合中被多次经过时,就引出了这两种处理方式的分歧:

  • 🥇 首次访问法(First-Visit MC):在一个回合中,只记录该状态第一次被访问时所获得的累积回报 \(G_t\)。后续如果在同一回合中再次经过该状态,则直接忽略。

  • 🔁 每次访问法(Every-Visit MC):在一个回合中,只要访问了该状态,每一次都记录从该时刻起到回合结束的累积回报 \(G_t\),全部纳入平均值的计算。

我们通过一个具体的例子来感受它们的操作差异:

假设折扣因子 \(\gamma = 1\),智能体在一个回合中经历了如下轨迹:

\[s_1\xrightarrow{R_1=2}s_2\xrightarrow{R_2=1}s_1\xrightarrow{R_3=3}\text{终点}\]

在这一回合中,\(s_1\) 被访问了两次:

  1. 第一次出现在时刻 \(t = 0\);

  2. 第二次出现在时刻 \(t = 2\)。

在蒙特卡洛方法中,累积回报 \(G_t\) 有一个非常核心的定义:无论从哪一步出发,回报都要一直累加到整个回合的终点 🏁。也就是说,对于折扣因子 \(\gamma = 1\):

\[G_t=R_{t+1}+R_{t+2}+\dots+R_T\]

因此,这一回合中两次经过 \(s_1\) 时分别对应的总回报为:

  • 第 1 次访问 \(s_1\)(时刻 \(t = 0\)):从这里一路走到终点,经历的所有奖励是 \(R_1 + R_2 + R_3\)。

  • 第 2 次访问 \(s_1\)(时刻 \(t = 2\)):从第二次经过 \(s_1\) 一路走到终点,经历的奖励只有 \(R_3\)。

🤔 思考:把具体数值代进去:

  1. 从第 1 次访问算到终点,回报 \(G_0\) 是多少?
  2. 从第 2 次访问算到终点,回报 \(G_2\) 是多少?
  3. 这两个数值分别会被首次访问法和每次访问法如何采用呢?

✍️ 我的回答:首次访问:\(G_0=6\),\(G_2=6\);每次访问:\(G_0=6\),\(G_2=3\)。

解析先看回报的数值计算:

  • 从 \(t = 0\) 出发:\(G_0 = 2 + 1 + 3 = 6\)

  • 从 \(t = 2\) 出发:\(G_2 = 3\)

每次访问法(Every-Visit)会把该状态经历的每一次回报都记录下来,因此 \(s_1\) 会得到两个样本:\(6\) 和 \(3\),这部分回答正确。

⚠️ 常见误区:首次访问法(First-Visit)并不是把第一次的回报“复制”给后续访问。

  • 首次访问法的规则是:只记录第一次碰到的时刻,后续再碰到就直接忽略 🚫。

  • 因此在这一回合里,首次访问法只产生一个样本 \(G_0 = 6\),对于第二次访问(\(t = 2\)),它完全不记录样本,而不是让 \(G_2 = 6\)。

现在我们来看看这两种方法如何利用这些样本:

方法 收集到的 \(s_1\) 回报样本
首次访问法(First-Visit) \([6]\)
每次访问法(Every-Visit) \([6, 3]\)

🤔 思考:如果只看这一个回合的数据,按照这两种方法计算,状态 \(s_1\) 的平均估计价值 \(V(s_1)\) 分别会是多少呢?

解析

  • 首次访问法(First-Visit MC):样本只有一个 \([6]\),平均值就是 \(V(s_1) = 6\)。

  • 每次访问法(Every-Visit MC):样本为 \([6, 3]\),平均值为 \(\frac{6 + 3}{2} = 4.5\)。

在比较两种方法之前,先明确两个统计学概念,它们会贯穿后面好几章:

🎯 偏差与方差:可以用“投飞镖”来理解,把“估计值”想象成飞镖,“真实价值”想象成靶心。

  • 偏差(Bias):多次投掷的平均落点离靶心有多远,衡量“准不准”。平均落点正好在靶心,就称为无偏估计。
  • 方差(Variance):每次落点之间分散得有多开,衡量“稳不稳”。

理想的估计既无偏、方差又小,但两者常常不可兼得,这就是后面反复出现的偏差-方差权衡。

从这个极简例子中,我们可以看出两种方法的重要理论特性:

  • 首次访问法:每个回合对状态 \(s_1\) 至多只取一个回报样本。因此,不同回合采集到的样本是严格独立同分布的,这让它成为真实价值 \(V^\pi(s)\) 的一个严格无偏估计。

  • 每次访问法:在同一个回合内多次访问产生的样本(如这里的 \(6\) 和 \(3\))往往具有相关性。虽然在小样本时它存在微小的初始偏差,但随着访问次数趋于无穷,它同样会渐进无偏,并且在实际工程中均方误差(MSE)通常更低。

🤔 思考:在同一个回合中,\(t=2\) 时刻的回报 \(G_2=3\) 和 \(t=0\) 时刻的回报 \(G_0=6\) 为什么会存在相关性?它们之间共享了哪些经历呢?

解析它们完全共享了从第二次到达 \(s_1\) 直到终点的这段轨迹(也就是 \(R_3 = 3\) 这一段)。

  • 🔗 数学表达:

    • \(G_0 = R_1 + R_2 + G_2\)

    • 可以看出,\(G_2\) 直接构成了 \(G_0\) 的后半部分。

因为它们包含了完全相同的随机变量(即同一段奖励序列),这两个样本自然存在正相关关系(\(\text{Cov}(G_0, G_2) > 0\))。

这就引出了统计学上的经典权衡 ⚖️:

方法 样本独立性 方差与偏差 核心特点
首次访问法 (First-visit) 样本严格独立同分布 严格无偏 (Unbiased),方差相对较大 理论性质纯粹,易于进行严格数学证明
每次访问法 (Every-visit) 样本间存在相关性 渐进无偏,方差通常更小 (Lower Variance) 充分利用了计算数据,数据利用率更高

5.3 为什么无模型控制要估计 Q 而不是 V

现在我们清楚了如何用蒙特卡洛评估一个策略的状态价值 \(V(s)\)。

🤔 思考:如果在无模型 (Model-free) 场景下,我们想要进一步改进策略,找到最优策略,只知道每个状态的价值 \(V(s)\) 足够吗?还是说需要估计另一个函数?为什么呢?

解析如果我们只知道各个状态本身的价值 \(V(s)\),要在某个状态 \(s\) 做出最优决策,我们就必须预测“执行动作 \(a\) 后会跳到哪个状态 \(s'\),以及这一步能拿到多少即时奖励/代价 \(R\)”,观察基于模型的贪心策略改进公式:

\[\arg\max_a\sum_{s'}P(s'\mid s,a)\left[R(s,a,s')+\gamma V(s')\right]\]

但在无模型(Model-free)设定下:

  • ❌ 转移概率 \(P\) 是未知的。

  • ❌ 即时奖励/代价函数 \(R\) 也是未知的。

既然智能体在做决定时手头没有 \(P\) 和 \(R\),光有 \(V(s)\) 确实无法评估每一步走出去的具体代价和收益。

为了解决这个问题,在无模型控制中,我们通常不再单独估计状态价值 \(V(s)\),而是转向估计另一个把动作也绑定进去的价值函数——动作价值函数。

这就是 2.4 节介绍过的动作价值函数(Action-Value Function) \(Q(s, a)\),简称 \(Q\) 函数。

它衡量的正是:在当前状态 \(s\) 下,先执行动作 \(a\),之后都遵循特定策略 \(\pi\) 时,所能获得的期望累积回报:

\[Q^\pi(s,a)=\mathbb{E}_\pi\left[G_t\mid S_t=s,A_t=a\right]\]

为什么无模型方法偏爱 \(Q(s, a)\)?

回顾一下在已知模型下更新策略的方式:

\[\pi'(s)=\arg\max_a\sum_{s'}P(s'\mid s,a)\left[R(s,a,s')+\gamma V(s')\right]\]

这里必须借助转移概率 \(P\) 和奖励函数 \(R\) 才能把 \(V(s')\) 换算成每个动作的优劣。

但是,如果我们已经通过采样直接估算出了 \(Q(s, a)\),策略改进就变成了:

\[\pi'(s)=\arg\max_a Q(s,a)\]

我们完全不需要知道 \(P\) 和 \(R\),直接看哪一个动作的 \(Q\) 值最大,就选哪一个动作 !

然而,这又产生了一个问题:在蒙特卡洛中如何估计 \(Q(s, a)\)?

估计 \(Q(s, a)\) 的方式与估计 \(V(s)\) 几乎一样:

  • 依然让智能体运行完整的回合(Episode)。

  • 记录下每次遇到“状态-动作对” \((s, a)\) 时,后续一路累积的回报 \(G_t\)。

  • 同样可以用首次访问法或每次访问法计算算术平均值。

5.4 MC 控制与 ε-greedy

但这里会产生一个非常棘手的新挑战。

🤔 思考:假设智能体在一个状态 \(s\) 下有 4 个动作可选:上、下、左、右。如果智能体当前始终按照一个确定性策略(比如每次都选“向上”)去玩游戏,那么对于“向左”、“向右”、“向下”这些动作,我们还能通过采样收集到数据并估计它们的 \(Q\) 值吗?

✍️ 我的回答:无法采集!

解析一旦某个动作从未被执行过,我们就完全没有它的回报样本,也就根本无法评估它的 \(Q\) 值 📉。这就可能导致智能体守着一个平庸的动作,却永远错过了真正最优的选择。

这正是 1.3 节提到的探索与利用(Exploration vs. Exploitation)矛盾,在蒙特卡洛控制里它变得非常具体。

为了让每一个状态-动作对 \((s, a)\) 都有机会被采样到,经典蒙特卡洛方法通常有两种解决思路(第二种就是 1.3 节初识的 \(\epsilon\)-greedy):

  • 探索性开局(Exploring Starts):理论上假设每个回合开始时,系统都以非零概率随机挑选任意一个状态和动作作为起点。这样随着回合数增加,所有 \((s, a)\) 都会被访问到。

  • 软策略(如 \(\epsilon\)-greedy 策略):在真实环境下,我们往往无法随意指定起点。因此智能体在每一步决策时,都不完全按“最优”来走,而是留出一定概率去“随机乱逛”。

🤔 思考:假设我们采用后一种思路:如果智能体手头有 4 个动作,大部分时间(比如 \(90\%\) 的概率)它会选择当前已知的最好动作(利用)。那么剩下的 \(10\%\) 概率,我们应该怎么设计动作的选择机制,才能确保所有未尝试的动作都能持续得到探索呢?

解析在标准的 \(\epsilon\)-greedy(\(\epsilon\)-贪心)策略中,划分规则如下:

  • 那 \(90\%\)(即 \(1 - \epsilon\) 的利用部分):只留给目前评估出的唯一最优动作(即当前 \(\arg\max_a Q(s, a)\))。

  • 剩下的 \(10\%\)(即 \(\epsilon\) 的探索部分):给环境留出“开小差”的机会。

对于这探索的 \(10\%\),最通用的设计是让智能体在所有可选动作中进行均匀随机选择。

🤔 思考:在 4 个可选动作的环境下,如果探索时我们采用“在全部 4 个动作里均等随机扔骰子”,那么每一个具体的探索动作被抽中的概率会是多少呢?

解析在总共有 \(|\mathcal{A}| = 4\) 个动作的情况下,那 \(\epsilon = 10\%\)(即 \(0.1\))的探索预算被 4 个动作平分,每个动作分到的探索概率就是:

\[\frac{\epsilon}{|\mathcal{A}|}=\frac{0.1}{4}=0.025\quad(2.5\%)\]

我们把利用(\(1-\epsilon\))和探索(\(\epsilon\))合并起来,看最终每个动作被选中的总概率:

  • 🥇 当前最优动作 \(a^*\):

    • 它能独享 \(1 - \epsilon = 0.9\) 的利用概率;

    • 同时在均匀探索的抽签中,它也有机会被抽中(\(\frac{\epsilon}{4} = 0.025\));

    • 最终总被选概率为:\(1 - \epsilon + \frac{\epsilon}{4} = 0.9 + 0.025 = 0.925\)(\(92.5\%\))。

  • 🚶 其他 3 个非最优动作:

    • 无法获得利用份额;

    • 仅通过探索获得机会,每个动作被选概率为:\(\frac{\epsilon}{4} = 0.025\)(\(2.5\%\))。

这样设计的妙处在于:每个动作被选中的概率都严格大于 0,即使某个动作一开始表现糟糕,未来也有机会被再次尝试,从而彻底避免了“部分动作从未被采样”的死局。

现在,蒙特卡洛控制(MC Control)已经构建好了:

  1. 🕹️ 交互与收集:使用 \(\epsilon\)-greedy 策略生成完整回合轨迹;

  2. 📊 策略评估:用首次访问法或每次访问法更新 \(Q(s, a)\);

  3. 🚀 策略改进:基于最新的 \(Q(s, a)\) 更新 \(\epsilon\)-greedy 动作分布。

5.5 MC 的局限:截断、自举与偏差-方差权衡

蒙特卡洛方法虽然直观,但它有一个根本限制——必须等整个回合打完(Terminal State)才能拿到 \(G_t\) 进行计算。

🤔 思考:如果一个任务没有明确的终点(持续性任务,比如自动驾驶、服务器负载均衡),或者一个回合的步骤极其漫长,蒙特卡洛方法会遇到什么挑战?我们该如何突破这个限制?

✍️ 我的回答:设定一个假定的阶段性终点(人为截断)。

解析

  • 思路一:人为截断。这正是工程实践中最常用的方法之一(例如限制每个 Episode 最多跑 1000 步)。但如果单纯做截断,截断点之后的长期回报就会被完全丢弃,可能导致智能体变得“短视”。
  • 思路二:截断 + 偏差修正。在人为设定的截断时间步,补上一个基于当前状态价值的估计值,以弥补丢失的未来收益。这正是强化学习演化的一个重要方向,下面详细展开。

折扣无限期任务(Discounted Continuing Tasks)中的截断偏差修正

首先,偏差从哪里来?

对于一个持续性任务来说,理论上的累积回报是一个无限项的折现和:

\[G_t=\sum_{k=0}^{\infty}\gamma^kR_{t+k+1}=R_{t+1}+\gamma R_{t+2}+\dots\]

在工程实现中,我们不可能让采样一直运行到无穷大。假设我们在经过 \(H\) 步(Horizon / 时间步上限)后人为截断(Truncation),得到的截断回报为:

\[G_{t:t+H}=\sum_{k=0}^{H-1}\gamma^kR_{t+k+1}=R_{t+1}+\gamma R_{t+2}+\dots+\gamma^{H-1}R_{t+H}\]

如果我们直接把这个截断的 \(G_{t:t+H}\) 当作对 \(V(S_t)\) 的无偏样本,这里就产生了偏差,因为 \(H\) 步之后的收益被直接丢弃了。

🤔 思考:\(H\) 步之后的那一部分“尾巴”(截断误差)该如何表达?

解析为了看清截断丢掉的部分,我们直接用完整回报减去截断回报:

\[G_t-G_{t:t+H}=\sum_{k=0}^{\infty}\gamma^kR_{t+k+1}-\sum_{k=0}^{H-1}\gamma^kR_{t+k+1}\]

展开来看:

  • 完整项:\((R_{t+1} + \dots + \gamma^{H-1} R_{t+H}) + (\gamma^H R_{t+H+1} + \gamma^{H+1} R_{t+H+2} + \dots)\)

  • 截断项:\(R_{t+1} + \dots + \gamma^{H-1} R_{t+H}\)

两式相减后,剩下的“尾巴”项从第 \(H\) 项开始:

\[\text{尾巴}=\gamma^HR_{t+H+1}+\gamma^{H+1}R_{t+H+2}+\dots\]

如果我们把公因子 \(\gamma^H\) 提出来,式子就会变成:

\[\text{尾巴}=\gamma^H\cdot\left(R_{t+H+1}+\gamma R_{t+H+2}+\dots\right)\]

🤔 思考:括号里的这一串奖励求和,正好是从哪一个时间步(或哪个状态)开始算起的未来总回报?如果我们要用状态价值函数来近似它,它应该对应谁的价值呢?

解析括号里的内容正是从时间步 \(t+H\) 开始一直累加到无穷的折现回报:

\[R_{t+H+1}+\gamma R_{t+H+2}+\dots=G_{t+H}\]

它的数学期望正对应着状态 \(S_{t+H}\) 的真实价值:

\[\mathbb{E}[G_{t+H}\mid S_{t+H}]=V(S_{t+H})\]

加上前面提出来的公因子 \(\gamma^H\),被我们丢掉的“尾巴”在期望意义下就是:

\[\text{丢弃的尾巴期望}=\gamma^HV(S_{t+H})\]

这意味着:

  1. 纯截断的偏差:如果我们直接把截断回报当成目标,实际上相当于强行假设了 \(V(S_{t+H}) = 0\)。如果未来其实有很高的正奖励,智能体的估计值就会严重偏低。

  2. 修补方法(Bootstrapping / 自举):为了消除或缓解这个偏差,我们可以把当前对该状态价值的估计值 \(\hat{V}(S_{t+H})\) 补回去:

\[G_{t:t+H}^{\text{修补}}=\underbrace{\sum_{k=0}^{H-1}\gamma^kR_{t+k+1}}_{\text{真实采样的 }H\text{ 步奖励}}+\underbrace{\gamma^H\hat{V}(S_{t+H})}_{\text{用估计价值修补的尾巴}}\]

🤔 思考:观察式子中的前缀项 \(\gamma^H\):

  • 当截断步数 \(H\) 设得非常大时,系数 \(\gamma^H\) 会发生什么变化?
  • 这种变化对我们“估计不准确的 \(\hat{V}(S_{t+H})\) 所引入的误差”有什么影响呢?

✍️ 我的回答:当 \(H\) 变得很大时,由于 \(\gamma\) 在 0-1 之间,\(\gamma^H\) 会变得非常小,修补项此时可以忽略不计。

解析这意味着:

  • 🛡️ 误差衰减:即使价值估计 \(\hat{V}(S_{t+H})\) 算得很不准,乘上极小的 \(\gamma^H\) 之后,对整体回报的负面影响也会被大幅压缩。

  • 🎯 逼近真实:此时回报主要由前面 \(H\) 步真实的采样奖励决定,估计值的偏差(Bias)变得非常低。

这引出了强化学习中极其核心的一个权衡 ⚖️。

如果单纯为了消除偏差,我们似乎只要把 \(H\) 设得尽可能大即可。

🤔 思考:随着步数 \(H\) 变得非常大,真实奖励序列中累加的随机因素(环境状态转移的随机性、策略动作选择的随机性)会越来越多。这会对我们采样的回报数值带来什么负面影响?也就是在统计学上常说的方差(Variance)会发生什么变化?

✍️ 我的回答:回报数值会变得不稳定,因此方差会变大!

解析随着步数 \(H\) 的增加,采样的累积回报会受到整条轨迹上所有随机状态转移和动作选择的累积影响。这就好比掷骰子:掷 \(1\) 次的波动有限,但连续掷 \(1000\) 次并求和,每一次的随机性都会叠加进总和里,这就导致回报的方差(Variance)急剧增大。

为了直观对比这种权衡,我们可以看截断步数 \(H\) 在两个极端下的表现:

维度 \(H=1\)(一步即截断并修补) ⚡ \(H\to\infty\)(极长的截断,趋近经典 MC) 🎲
偏差(Bias) 高:严重依赖估计值 \(\hat{V}\),若估计不准则偏差大 极低:主要由真实奖励构成,\(\gamma^H \hat{V} \approx 0\)
方差(Variance) 低:只包含单步随机性,其余部分被平滑的函数值替代 高:多步环境转移与策略选择的随机性持续累加
等待时间 每走 \(1\) 步即可更新 必须采样 \(H\) 步才能进行一次更新

这个权衡直接引出了强化学习算法演进的两个经典分支:

  1. ⚡ 让 \(H=1\) 的极值:时序差分学习(Temporal Difference, TD(0))(第 6 章)

    放弃等待多步,每走一步就用即时奖励加上下一步的预估值(自举)来更新当前状态。

  2. 🎛️ 折中与融合:\(n\)-步回报(\(n\)-step Return)与 TD(\(\lambda\))(第 7 章)

    不走极端,通过参数来在单步 TD 和完全 MC 之间平滑滑动,寻找偏差与方差的最佳平衡点。

第 6 章 时序差分学习

6.1 TD(0):走一步、学一步

5.5 节的偏差-方差权衡中,\(H=1\) 是一个极端。本节先探讨 \(H=1\) 时的单步时序差分(One-step Temporal Difference, TD(0)),我们将看到强化学习如何摆脱对“完整回合”的依赖,实现走一步、学一步。

1. 核心目标:构建 TD 目标与 TD 误差

第 3 章的贝尔曼期望方程告诉我们,状态的真实价值满足:

\[V^\pi(s)=\mathbb{E}_\pi\left[R_{t+1}+\gamma V^\pi(S_{t+1})\mid S_t=s\right]\]

TD 学习的思路就是:既然不知道 \(P\),没法直接算这个期望,那就用实际走的一步来当作一次采样。下面的 TD 目标和 TD 误差,正是 1.4 节“现实 − 预期”的正式版本。

  • 🎯 TD 目标(TD Target):

    当智能体在时刻 \(t\) 执行动作,观测到即时奖励 \(R_{t+1}\) 并转移到下一状态 \(S_{t+1}\) 时,我们用一步真实奖励与下一步的预估价值拼出一个当期目标:

\[\text{Target}=R_{t+1}+\gamma V(S_{t+1})\]
  • 📉 TD 误差(TD Error, \(\delta_t\)):

    目标与智能体当前估计 \(V(S_t)\) 之间的差值就是预测偏差:

\[\delta_t=R_{t+1}+\gamma V(S_{t+1})-V(S_t)\]

2. 更新规则 🔄

利用这个误差,状态价值以学习率 \(\alpha \in (0, 1]\) 进行增量更新(相当于让 \(V(S_t)\) 朝 TD 目标移动一小步,也可看作对平方误差 \((\text{Target}-V(S_t))^2\) 做半梯度下降):

\[V(S_t)\leftarrow V(S_t)+\alpha\delta_t\]

展开即为:

\[V(S_t)\leftarrow V(S_t)+\alpha\left[R_{t+1}+\gamma V(S_{t+1})-V(S_t)\right]\]

这种利用后续估计值 \(V(S_{t+1})\) 来更新当前估计值 \(V(S_t)\) 的机制,在强化学习中被称为自举(Bootstrapping)。

假设当前参数如下:

  • 学习率 \(\alpha = 0.5\)

  • 折扣因子 \(\gamma = 0.9\)

  • 智能体现有估计:\(V(s_1) = 2.0\),\(V(s_2) = 4.0\)

智能体在状态 \(s_1\) 行动,获得奖励 \(R = 1.0\),并转移到了状态 \(s_2\)。

🤔 思考:

  1. 此时的 TD 目标 是多少?
  2. 更新后,\(V(s_1)\) 的新价值变成了多少?

解析

  • 🎯 TD 目标:\(R + \gamma V(s_2) = 1.0 + 0.9 \times 4.0 = 4.6\)

  • 📉 TD 误差:\(\delta = 4.6 - 2.0 = 2.6\)

  • 🔄 更新后的价值:\(V(s_1) \leftarrow 2.0 + 0.5 \times 2.6 = 3.3\)

TD(0) 的核心特质 ⚡

在这个单步计算中,可以观察到几个关键特性:

  1. 即时更新:智能体刚跨出第一步,还没看到整个任务的终点,就已经根据 \(s_2\) 的预估情况完成了对 \(s_1\) 的纠偏。

  2. 误差来源:如果 \(s_2\) 本身的估计值(\(4.0\))存在偏差,那么这一次算出的 TD 目标(\(4.6\))也是有偏差的。但数学证明表明,只要状态空间有限且学习率满足一定衰减条件,这种自举更新仍然能够保证收敛到真实的 \(V^\pi\)。

6.2 SARSA 与 Q-learning

从评估走向控制:动作价值函数的 TD 更新 🧭

正如 5.3 节讨论过的:在无模型(Model-Free)环境下,为了做决策控制,我们通常需要估计动作价值函数 \(Q(s, a)\),而不仅仅是 \(V(s)\)。

如果把上面的状态更新式迁移到状态-动作对 \((S_t, A_t)\) 上,一般的更新框架形如:

\[Q(S_t,A_t)\leftarrow Q(S_t,A_t)+\alpha\left[\text{Target}-Q(S_t,A_t)\right]\]

智能体在当前步执行了动作 \(A_t\),获得了奖励 \(R_{t+1}\),并来到了新状态 \(S_{t+1}\)。

此时要拼出下一步的价值估计,智能体必须面对一个关键抉择:在状态 \(S_{t+1}\) 下,该用哪个动作 \(A_{t+1}\) 的 \(Q(S_{t+1}, \cdot)\) 来作为下一步的评估?

这里主要有两种典型的选取思路:

  1. 按照当前实际用来与环境交互的策略(如 \(\epsilon\)-greedy)掷骰子,挑出下一个动作去更新;

  2. 不管接下来实际走哪一步,直接挑选在 \(S_{t+1}\) 下预估价值最大的那个动作去更新。

🤔 思考:这两种思路实际对应强化学习中两个最经典的无模型控制算法,可以猜一猜是哪两个。

解析这两大经典算法完美对应了这两种思路:

📌 下面会出现“同策略(On-Policy)”和“异策略(Off-Policy)”两个名字,这里先记住它们分别对应哪个算法,严格定义留到 6.3 节。

  • 🚶 SARSA(On-Policy / 同策略):

    • 对应第一种思路。

    • 智能体在 \(S_{t+1}\) 按照当前的探索策略(如 \(\epsilon\)-greedy)实际挑选下一个动作 \(A_{t+1}\),并用该动作的价值更新:

\[\text{Target}=R_{t+1}+\gamma Q(S_{t+1},A_{t+1})\]
  • 名字正是来自于这一步的完整转移五元组:\((S_t, A_t, R_{t+1}, S_{t+1}, A_{t+1})\)。

  • 🦅 Q-learning(Off-Policy / 异策略):

    • 对应第二种思路。

    • 不管智能体下一步实际掷骰子会不会去随机探索,在算目标时直接假设采取最优动作:

\[\text{Target} = R_{t+1} + \gamma \max_{a} Q(S_{t+1}, a)\]

悬崖行走(Cliff Walking)中的行为差异 🧗

假设在一个网格世界里,智能体要从起点走到终点,路下方是一排悬崖(掉下去扣 100 分并回到起点,走普通路扣 1 分)。智能体在探索时采用 \(\epsilon = 0.1\) 的 \(\epsilon\)-greedy 策略(有 \(10\%\) 的概率随机走)。

🤔 思考:Q-learning 假设未来总是走理论上最优的动作(贪心),而 SARSA 会把未来可能由 \(\epsilon\) 探索导致的失误(随机走)也算进期望里。面对“沿着悬崖边走(步数最短但容易失误掉落)”和“绕远路走(步数多但远离悬崖很安全)”这两条路线,Q-learning 和 SARSA 各会倾向于选择哪一条路线呢?为什么?

解析

  • 🦅 Q-learning 评估的是假定未来“步步最优”的价值。在它的眼中,只要不主动跳下悬崖,悬崖边就是步数最短的最优解,因此它学到的是理论最优路径(沿悬崖边缘走)。

  • 🚶 SARSA 评估的是智能体“在当前带探索机制的策略下”的实际期望。由于它实打实地考虑到了有 \(\epsilon\) 概率会“手滑”失误摔下去,悬崖边缘的动作价值被 \(-100\) 的惩罚大幅拉低,因此它学到的是安全稳妥路径(绕远路走)。

为了直观对比两者在面对探索时的机制,我们可以总结如下:

特性 SARSA (On-Policy) 🚶 Q-learning (Off-Policy) 🦅
目标构建 基于实际采取的下一动作 \(A_{t+1}\) 基于理论最大的动作 \(\max_a Q(S_{t+1}, a)\)
考虑失误吗? 是,把探索失误的代价计入当前价值 否,假定后续行动完美无瑕
最终策略 适应当前探索策略的安全解 渐进逼近最优价值 \(Q^*\) 及对应的最优策略

🤔 思考:如果训练结束,我们把探索率彻底关掉(设置 \(\epsilon = 0\),智能体完全贪心决策,不再随机开小差)。此时,如果让按 Q-learning 学到的策略和按 SARSA 学到的策略各自去跑一次测试,哪一个在实际耗时或累积得分上表现会更优呢?为什么?

解析在关闭探索(\(\epsilon = 0\))后,Q-learning 学到的策略表现更优:

  • 🦅 Q-learning 始终沿着距离最短的悬崖边缘行走,用最少的时间步到达终点,获得最高的累积回报。

  • 🚶 SARSA 则会继续执行它学到的那条保守路线,绕远路避开悬崖,步数明显更多。

这个现象揭示了 同策略(On-Policy) 与 异策略(Off-Policy) 的核心差别:

算法类型 评估的对象 训练时的表现 (\(\epsilon>0\)) 测试时的表现 (\(\epsilon=0\))
SARSA (On-Policy) 🚶 评估当前探索策略本身的价值 更高分(很少掉下悬崖) 较保守(绕远路)
Q-learning (Off-Policy) 🦅 评估理想贪心策略的最优价值 较低分(探索时频繁失误摔下悬崖) 最优(沿最短路径直达)

可以说,Q-learning 是“勇敢的理论家”,而 SARSA 是“现实的谨慎派”。(同策略与异策略的正式定义见 6.3 节。)

6.3 同策略与异策略的正式定义

上面用“评估的对象不同”直观区分了 SARSA 和 Q-learning。要把这个区别说严谨,需要先区分智能体身上同时存在的两个策略:

  • 🎮 行为策略(Behavior Policy)\(b(a\mid s)\):实际与环境交互、产生数据的策略。为了探索,它通常带有随机性(如 \(\epsilon\)-greedy)。
  • 🎯 目标策略(Target Policy)\(\pi(a\mid s)\):我们正在评估和改进、最终想要得到的策略。

据此给出定义:

类型 定义 含义
同策略(On-policy) 🚶 \(b = \pi\) 用谁采的数据,就评估和改进谁;“边做边学自己”
异策略(Off-policy) 🦅 \(b \ne \pi\) 用一个策略采数据,去评估和改进另一个策略;“看别人做、学最优”

🤔 思考:按照这个定义,分别判断 SARSA 和 Q-learning 的行为策略与目标策略是什么。

解析

  • SARSA:数据由 \(\epsilon\)-greedy 产生;TD 目标 \(R_{t+1} + \gamma Q(S_{t+1}, A_{t+1})\) 中的 \(A_{t+1}\) 也由同一个 \(\epsilon\)-greedy 选出。行为策略 = 目标策略 = \(\epsilon\)-greedy,因此是同策略。
  • Q-learning:数据同样由 \(\epsilon\)-greedy 产生,但 TD 目标里用的是 \(\max_a Q(S_{t+1}, a)\),相当于假设下一步执行贪心策略。行为策略是 \(\epsilon\)-greedy,目标策略是 greedy,两者不同,因此是异策略。

异策略的收益与代价

  • ✅ 数据可复用:既然不要求数据来自当前策略,就可以反复使用历史数据(第 8 章 DQN 的经验回放)、学习人类或其他智能体的示范数据,甚至完全离线训练(Offline RL)。
  • ⚠️ 分布不匹配:数据的分布来自 \(b\),而我们要估计的是 \(\pi\) 下的期望。像 Q-learning 这样基于 \(\max\) 的单步方法恰好绕开了这个问题;但对于多步回报或策略梯度,就需要用重要性采样(Importance Sampling)比率来修正:
\[\rho_t = \frac{\pi(A_t \mid S_t)}{b(A_t \mid S_t)}\]

这个比率在第 10 章 PPO 里还会再次出现(10.2 节),PPO 正是借助它来有限度地复用“旧策略”采集的数据,因此被称为近似同策略。

  • ⚠️ 稳定性风险:异策略与函数逼近、自举结合时,训练可能发散,也就是 8.1 节讲的“死亡三要素”。

6.4 代码实现:Q-learning

我们用一个极简的 一维宝藏直线世界(Line World) 来实现 Q-learning

先设定一个最轻量的环境规则:

  • 🗺️ 状态空间:6 个位置,索引为 0, 1, 2, 3, 4, 5。起始点是 0,宝藏终点是 5。

  • 🕹️ 动作空间:2 个动作,0 表示向左,1 表示向右。

  • 🎁 奖励机制:到达终点 5 获得奖励 +1,其余步为 0。到达终点则回合结束。

我们可以先用 NumPy 初始化核心的数据结构:

import numpy as np

# 1. 初始化超参数与 Q 表
num_states = 6
num_actions = 2  # 0: 左, 1: 右

alpha = 0.1  # 学习率
gamma = 0.9  # 折扣因子
epsilon = 0.1  # 探索率

# 📊 Q 表:形状为 (6, 2),初值全部设为 0
Q = np.zeros((num_states, num_actions))

有了 Q 表之后,在每一步循环中,智能体处于状态 s,采取动作 a,获得即时奖励 r,并转移到下一个状态 s_next。

根据 6.2 节的 Q-learning 更新公式:

\[Q(S_t,A_t)\leftarrow Q(S_t,A_t)+\alpha\left[\text{Target}-Q(S_t,A_t)\right]\]

计算 TD 目标 的代码可以直接这样写:

# 🎯 计算 TD 目标
td_target = r + gamma * np.max(Q[s_next, :])

# 🔄 依据 TD 误差更新 Q 表
Q[s, a] += alpha * (td_target - Q[s, a])

这一行代码直接对应了公式中的 \(R_{t+1} + \gamma \max_{a'} Q(S_{t+1}, a')\)。np.max(Q[s_next, :]) 取出了下一个状态 s_next 下所有动作的最大预估价值。

在终点状态时,因为没有未来的折现回报,\(\text{td\_target}\) 就完全退化为即时奖励本身:

\[\text{td\_target}=r=1\]

在代码实现中,为了避免写繁琐的 if-else 分支,我们通常会引入一个布尔变量 done(表示当前转移是否进入了终止状态)。这样就可以用一行优雅的代码统一非终止和终止状态:

# 🎯 统一计算 TD 目标:如果 done 为 True,后半项自动变为 0
td_target = r + gamma * np.max(Q[s_next, :]) * (1 - int(done))

# 🔄 依据 TD 误差更新 Q 表
Q[s, a] += alpha * (td_target - Q[s, a])

动作选择:\(\epsilon\)-greedy 的实现 🎲

更新逻辑搞定后,循环里只差选择动作这一步了。

对于当前状态 s:

  • 以 \(\epsilon\) 的概率进行随机探索;

  • 以 \(1 - \epsilon\) 的概率进行贪心利用。

在 NumPy 中,这两个操作非常直接:

  • 🎲 随机探索:使用 np.random.choice(num_actions),它会从可行动作中等概率随机抽取一个。

  • 🏆 贪心利用:使用 np.argmax(Q[s, :]),它返回该行中最大数值对应的动作索引。

我们把它们整合成一个标准的动作选择函数 🕹️:

def choose_action(s, Q, epsilon, num_actions):
    if np.random.rand() < epsilon:
        return np.random.choice(num_actions)  # 探索 🎲
    else:
        return np.argmax(Q[s, :])             # 利用 🏆

现在,强化学习的核心组件已经齐备:

  1. 初始化:\(Q\) 表全零;

  2. 决策:\(\epsilon\)-greedy 策略;

  3. 学习:单步 Q-learning 更新。

接下来就是将它们放入训练循环(Episode 循环)。每个回合开始时,智能体都从起点 \(s = 0\) 出发:

num_episodes = 50

for episode in range(num_episodes):
    s = 0  # 回合起点
    done = False

    while not done:
        a = choose_action(s, Q, epsilon, num_actions)

        # 模拟环境反馈:执行动作 a 后的转移和奖励
        # 动作 0 向左,动作 1 向右
        if a == 0:
            s_next = max(0, s - 1)
        else:
            s_next = min(num_states - 1, s + 1)

        done = (s_next == num_states - 1)
        r = 1.0 if done else 0.0

        # 🎯 Q-learning 单步更新
        td_target = r + gamma * np.max(Q[s_next, :]) * (1 - int(done))
        Q[s, a] += alpha * (td_target - Q[s, a])

        # 进入下一个状态
        s = s_next

🤔 思考:在最初的几个回合中,奖励信号(\(+1\))只在位置 \(4 \to 5\) 被首次触发。那么,这个奖励信号是如何一步步“倒流”传递回起点状态 \(0\) 的呢?

解析这个奖励信号的“倒流”过程,正是自举(Bootstrapping)的精髓 💡。整个传播链条是这样逐步展开的:

  1. 第一阶段(发现宝藏) 🏆:

    一开始所有 \(Q\) 值都是 0。直到某个回合,智能体偶然走到位置 4 并向右移动到达终点 5。此时触发了即时奖励 \(r = 1\):

    \(Q(4,\text{右}) \leftarrow Q(4,\text{右}) + \alpha\left[1 + 0 - Q(4,\text{右})\right]\)

    这一步让 \(Q(4, \text{右})\) 首次变成了正数,而其它状态的 \(Q\) 值依然是 0。

  2. 第二阶段(向后渗透) 🌊:

    在后续的回合中,当智能体来到位置 3 并向右走到 4 时,虽然即时奖励 \(r = 0\),但是此时:

    \(\max_a Q(4,a) = Q(4,\text{右}) > 0\)

    因此计算出的 TD 目标:

    \(\text{td\_target} = 0 + \gamma \cdot Q(4,\text{右}) > 0\)

    这使得 \(Q(3, \text{右})\) 也被更新为一个正数!

  3. 以此类推(链式反应) ⛓️:

    随着训练不断进行,奖励信号就像波浪一样:

    \(5 \longrightarrow 4 \longrightarrow 3 \longrightarrow 2 \longrightarrow 1 \longrightarrow 0\)

    一步一步沿着状态链“倒流”回起点 0。最终,整条路线上所有“向右”的动作价值都被点亮。

🤔 思考:随着价值一步步倒流,每退后一步都会乘上折扣因子 \(\gamma\)(假设 \(\gamma = 0.9\))。最终收敛时,\(Q(0, \text{右})\) 的数值会比 \(Q(4, \text{右})\) 更大还是更小?这在物理意义上代表了什么?

✍️ 我的回答:更小,说明 \(Q(0,\text{右})\) 比 \(Q(4,\text{右})\) 更加远离目标!

解析因为每远离终点一步,未来的奖励就要多乘一次折扣因子 \(\gamma\)(假设 \(\gamma = 0.9\)):

  • 📍 位置 4 向右:距离终点只有 \(1\) 步,其理论最优价值为:
\[Q^*(4,\text{右})=1.0\]
  • 📍 位置 0 向右:距离终点足足有 \(5\) 步,经过连续 \(4\) 次折现后,其理论最优价值为
\[Q^*(0,\text{右})=\gamma^4\times1.0=0.9^4\approx0.656\]

数值更小不仅反映了空间距离上的遥远,在物理意义上也直接体现了时间与机会成本:智能体更偏好“尽快拿到奖励”,而不是“很久以后才拿到相同的奖励”。

6.5 代码实现:SARSA

下面把 6.4 节的 Q-learning 代码改造为 SARSA,并通过对比观察它们的行为差异。为了让循环更简洁,这里把 6.4 节中环境转移的那几行代码(计算 s_next、done、r)封装成了函数 env_step(s, a)。

回顾一下,在 Q-learning 中,我们计算 TD 目标的代码是:

# 🦅 Q-learning: 假设下一步采取理论最优动作
td_target = r + gamma * np.max(Q[s_next, :]) * (1 - int(done))

而 SARSA 是同策略(On-Policy)算法,它的更新依赖于五元组 \((S_t, A_t, R_{t+1}, S_{t+1}, A_{t+1})\)。

SARSA 每次更新都要用到“当前动作”和“下一个动作”,它的主循环结构和 Q-learning 略有不同——需要在进入循环前先预选好第一个动作:

for episode in range(num_episodes):
    s = 0
    # 1. 回合开始前,先选出初始动作 a
    a = choose_action(s, Q, epsilon, num_actions)
    done = False

    while not done:
        # 2. 执行 a,环境给出反馈
        s_next, r, done = env_step(s, a)

        # 3. 面对 s_next,用同样的 epsilon-greedy 策略选出 a_next
        a_next = choose_action(s_next, Q, epsilon, num_actions)

        # 4. 🚶 SARSA 单步更新
        td_target = r + gamma * Q[s_next, a_next] * (1 - int(done))
        Q[s, a] += alpha * (td_target - Q[s, a])

        # 5. 状态和动作无缝滑入下一步!
        s = s_next
        a = a_next

注意看第 5 步:在 Q-learning 中每次循环只需更新 s = s_next;而在 SARSA 中,刚才选出来的 a_next 直接变成了下一轮循环的当前动作 a,避免了重复采样。

6.6 悬崖行走对比实验

在原本简单的直线宝藏任务中,因为没有负惩罚,两者最终学到的动作方向几乎一样(都是往右走)。

但如果我们把环境换成著名的悬崖行走(Cliff Walking):

  • 路上方是安全的大路;

  • 路下方紧挨着悬崖:走错一步掉下去直接扣 \(100\) 分并回起点。

在训练时,因为有 \(\epsilon = 0.1\) 的概率会随机“手滑”乱走:

  • Q-learning 算目标时使用的是 np.max(Q[s_next, :]),它完全忽视了这个“手滑”概率,坚定地认为自己未来每一步都绝对完美;

  • SARSA 算目标时用的是实际选出的 Q[s_next, a_next],因此在更新时,有 \(10\%\) 的几率会把那次失误掉下悬崖的巨额扣分计入价值中。

🤔 思考:在训练过程中,当智能体站在紧贴着悬崖边缘的状态时,哪一个算法学到的该状态价值(Q 值)会被大幅拉低?为什么?

✍️ 我的回答:SARSA 算法的状态价值会被大幅拉低,因为可能会将巨额扣分也算进去。

解析由于 SARSA 评估的是带有实际探索行为的策略,智能体在悬崖边缘时,有 \(\epsilon\) 的几率会因为随机探索而跌落悬崖 🧗。这一步直接带来的 \(-100\) 巨额惩罚会通过时序差分更新传递回去,导致悬崖边缘状态的动作价值被显著拉低。

相对而言,Q-learning 的更新项是 \(\max_a Q(s', a)\) 🦅。它在更新时假定下一步绝对会执行最佳动作(永远不会主动跳崖),因此悬崖边缘的价值依然保持很高。

我们可以把两种算法学到的路径直观对比一下:

算法 行为特征 悬崖边缘的评估 最终选择的路线 🗺️
Q-learning 🦅 乐观 / 理论最优 忽略随机探索的失误风险 贴着悬崖走(最短路线)
SARSA 🚶 保守 / 现实稳健 计入可能失误带来的 \(-100\) 惩罚 绕开悬崖走(安全路线)

代码对比验证 💻

要直观观察这种差异,我们只需要在之前的网格代码中做两处关键改动:

  1. 环境加入惩罚区 ⚠️:比如指定某些状态为悬崖,落入扣除 100 分并重置回起点。

  2. 记录训练过程中的回报曲线 📈:观察两者在训练中的累积奖励(Episode Reward)。

🤔 思考:在训练过程中(\(\epsilon > 0\) 保持探索),Q-learning 和 SARSA 哪一个在每个回合获得的平均回报(总得分)会更低?为什么会这样呢?

✍️ 我的回答:Q-learning 获得的平均回报更低,因为掉入悬崖产生的巨额扣分会直接拉低回报!

解析

  • 🦅 Q-learning 在训练时的表现:因为 Q-learning 学到的是紧贴悬崖走的最优路径,在训练阶段(有 \(\epsilon\) 概率随机乱走),只要在悬崖边稍有“手滑”,就会频繁掉下悬崖,吃到大量的 \(-100\) 扣分。因此在整个训练过程中,它的平均回报得分反而更低且波动极大。

  • 🚶 SARSA 在训练时的表现:SARSA 在学习阶段就充分考虑了手滑的风险,早早地学会了绕开悬崖走安全路线。即使在有探索(\(\epsilon > 0\))的情况下,因为远离危险区,它极少掉下悬崖,所以在训练阶段它的平均回报得分更高、更稳定。

我们现在把两者的核心代码放到一起跑一个对比实验。

为了模拟著名的悬崖行走(Cliff Walking)环境,我们设定一个 \(4 \times 12\) 的二维网格:

  • 🚩 起点:左下角 \((3, 0)\)

  • 🏁 终点:右下角 \((3, 11)\)

  • ⚠️ 悬崖:底部中间 \((3, 1)\) 到 \((3, 10)\)。掉入惩罚 \(-100\) 并送回起点。

  • 👟 普通移动:每步奖励 \(-1\)。

下面是两种算法在同一个环境下的核心逻辑对比:

import numpy as np

# 共通的超参数
alpha = 0.5    # 学习率
gamma = 1.0    # 悬崖问题通常设为 1.0 或 0.99
epsilon = 0.1  # 训练探索率

# -----------------------------------------------
# 🦅 1. Q-learning 的单步更新逻辑
# -----------------------------------------------
# 在状态 s 执行动作 a,环境返回 s_next, r, done
td_target_q = r + gamma * np.max(Q_learning[s_next, :]) * (1 - int(done))
Q_learning[s, a] += alpha * (td_target_q - Q_learning[s, a])

# -----------------------------------------------
# 🚶 2. SARSA 的单步更新逻辑
# -----------------------------------------------
# 必须先用 epsilon-greedy 在 s_next 选出下一步实际执行的 a_next
a_next = choose_action(s_next, Q_sarsa, epsilon, num_actions)
td_target_sarsa = r + gamma * Q_sarsa[s_next, a_next] * (1 - int(done))
Q_sarsa[s, a] += alpha * (td_target_sarsa - Q_sarsa[s, a])

# 关键传递:下一轮迭代时,当前动作直接继承 a_next
a = a_next

实验现象观察 📊

如果记录训练 500 个回合的累积回报曲线:

  1. 训练曲线:SARSA 的得分曲线会快速上升并稳定在较高区间(约 \(-20\) 到 \(-30\) 左右),而 Q-learning 的曲线会反复出现大坑(时不时掉崖导致单回合扣到 \(-100\) 甚至更低)。

  2. 路线输出:打印最终贪心策略路线时:

    • Q-learning 走:起点 \(\to\) 紧贴悬崖直冲终点(理论步数最短:13 步)。

    • SARSA 走:起点 \(\to\) 向上绕一个大圈,沿着最上方远离悬崖的安全路线走向终点(步数稍长:17 步)。

6.7 工程选择:在线安全与 Sim-to-Real

🤔 思考:如果我们要训练一台真实的昂贵扫地机器人(撞到家具会损坏硬件),或者训练一个自动驾驶系统:在真实世界上线训练时,会倾向于使用 SARSA 的思路还是 Q-learning 的思路?为什么呢?

✍️ 我的回答:物理世界中会选择 SARSA。

解析因为“掉入悬崖”并不像模拟器里那样可以免费点一次 Reset:

  • 🤖 硬件损坏与安全成本:机器人撞墙可能电机烧毁、外壳碎裂,物理实体直接报废,自然就谈不上“后续继续训练”了;

  • 🛡️ 在线安全性(Online Safety):SARSA 评估的是真实策略(带探索噪声)的表现,能够主动避开探索时可能引发致命事故的边缘区域,因此在需要在线边探索、边学习(Online Learning)的物理系统或高风险场景中更加稳妥。

相对地,在现实工程中如果要用 Q-learning 这类异策略(Off-Policy)方法,业界通常采用仿真器预训练(Sim-to-Real)或离线强化学习(Offline RL):先在没有损耗风险的虚拟环境或历史日志数据里尽情探索,学成之后再部署到实体机上。

第 7 章 n-步回报与资格迹

5.5 节留下了一个问题:单步 TD 和蒙特卡洛之间,能不能有折中?本章就来探讨连接两者的桥梁:\(n\)-步回报(\(n\)-step Return)与资格迹(Eligibility Traces)

回顾一下我们之前探讨过的两个极端:

  • ⚡ 单步 TD (\(n=1\)):走 \(1\) 步就利用估计值自举(Bootstrapping)。偏差高、方差低、更新最快。

  • 🎲 蒙特卡洛 (\(n=\infty\)):一直走到终点,用完整的真实回报更新。无偏差、方差高、必须等待回合结束。

自然的思考是:我们能不能走 \(n\) 步真实路径,然后再用第 \(n\) 步的状态价值做自举?

7.1 n-步回报

在时刻 \(t\):

  • \(1\)-步回报(TD(0)):
\[G_{t:t+1}=R_{t+1}+\gamma V(S_{t+1})\]
  • \(2\)-步回报:
\[G_{t:t+2}=R_{t+1}+\gamma R_{t+2}+\gamma^2 V(S_{t+2})\]
  • 推广到通用的 \(n\)-步回报:
\[G_{t:t+n}=R_{t+1}+\gamma R_{t+2}+\cdots+\gamma^{n-1}R_{t+n}+\gamma^n V(S_{t+n})\]

这正是 5.5 节推导过的截断修补公式!通过调节 \(n\) 的大小,我们可以在偏差(Bias)与方差(Variance)之间自由滑动 。

7.2 λ-回报(前向视角)

既然选 \(n=1\) 太短视,选 \(n=10\) 或 \(n=\infty\) 又可能方差大,那到底选几步才是最好的呢?

Sutton 和 Barto 提出了一个精妙的构想:为什么非得单选某一个 \(n\),而不是把所有步数的回报做一次加权平均?

为了让权重合理,我们引入一个衰减参数 \(\lambda \in [0, 1]\),让近期的回报权重大,远期的回报权重随着 \((1-\lambda)\lambda^{n-1}\) 几何级数衰减。这就是著名的 \(\lambda\)-回报(\(\lambda\)-return):

\[G_t^\lambda = (1 - \lambda) \sum_{n=1}^{\infty} \lambda^{n-1} G_{t:t+n}\]

看看权重具体长什么样:

\(\lambda\) \(G_{t:t+1}\) 的权重 \(G_{t:t+2}\) \(G_{t:t+3}\) \(G_{t:t+4}\) \(G_{t:t+5}\) ……
\(0.5\) \(0.5\) \(0.25\) \(0.125\) \(0.0625\) \(0.031\) 迅速衰减
\(0.9\) \(0.1\) \(0.09\) \(0.081\) \(0.073\) \(0.066\) 衰减很慢

每一行的权重加起来都等于 \(1\),所以 \(G_t^\lambda\) 确实是一个加权平均。\(\lambda = 0.5\) 时,一半的权重都压在 1 步回报上,几乎就是 TD;\(\lambda = 0.9\) 时,权重均匀地铺向很远的未来,更接近蒙特卡洛。\(\lambda\) 越大,越“相信”长距离的真实奖励;\(\lambda\) 越小,越“相信”近处的价值估计。

🤔 思考:代入两个极端值验证一下:

  1. 当 \(\lambda = 0\) 时,这个式子退化成了几步回报?
  2. 当 \(\lambda = 1\) 时,这个式子又对应了哪种经典方法呢?

解析

① 当 \(\lambda = 0\) 时 ⚡

公式中的前缀是 \((1 - \lambda)\),求和项里是 \(\lambda^{n-1}\):

  • 当 \(n=1\) 时,\(\lambda^{1-1} = \lambda^0 = 1\);

  • 对于所有 \(n \ge 2\),由于 \(\lambda = 0\),项里都有 \(0^{n-1} = 0\)。

所以当 \(\lambda = 0\) 时:

\[G_t^{\lambda=0} = (1 - 0) \cdot [1 \cdot G_{t:t+1} + 0 + 0 + \dots] = G_{t:t+1}\]

它并没有停留在任意的 \(n\) 步,而是直接退化成了最纯粹的 1-步回报(即单步 TD(0))!

② 当 \(\lambda = 1\) 时 🎲

它退化成了蒙特卡洛(MC)方法!

注意:如果直接把 \(\lambda = 1\) 代入上面的无穷级数,前缀 \((1-\lambda)=0\) 会让整个式子变成 0,看不出结论。对于在时刻 \(T\) 结束的分幕任务,\(n \ge T-t\) 之后的 \(n\)-步回报都等于完整回报 \(G_t\),把这些项合并后,\(\lambda\)-回报应写为:

\[G_t^\lambda = (1-\lambda)\sum_{n=1}^{T-t-1}\lambda^{n-1}G_{t:t+n} + \lambda^{T-t-1}G_t\]

当 \(\lambda = 1\) 时,前面的求和项因 \((1-\lambda)=0\) 全部消失,只剩下最后一项 \(G_t\)(整条轨迹全部走完的真实回报),即蒙特卡洛目标。

对比总结 📊

\(\lambda\) 就像一个极其优雅的滑块,连接了整个强化学习谱系:

\[\text{TD(0)} \xleftarrow{\quad \lambda = 0 \quad} \text{TD}(\lambda) \xrightarrow{\quad \lambda = 1 \quad} \text{MC}\]
\(\lambda\) 取值 等价方法 核心特性
\(\lambda = 0\) TD(0)(单步 TD)⚡ 走 1 步立即自举,偏差最高,方差最低,无需等待
\(0 < \lambda < 1\) TD(\(\lambda\)) 🎛️ 几何衰减融合多步预测,平衡偏差与方差
\(\lambda = 1\) 蒙特卡洛(MC) 🎲 算完完整回合,无偏差,方差最高,必须等到结束

一个严峻的工程难题 🛑

虽然 \(\lambda\)-回报在理论上非常漂亮,但请看这个定义:

\[G_t^\lambda = (1 - \lambda) \sum_{n=1}^{\infty} \lambda^{n-1} G_{t:t+n}\]

要计算出时刻 \(t\) 的 \(\lambda\)-回报,我们必须先拿到 \(G_{t:t+1}, G_{t:t+2}, \dots, G_{t:t+\infty}\)。

这意味着:我们依然必须等到整个 Episode 彻底结束,拿到未来所有步的数据后,才能回头去算第 \(t\) 步的更新!

这被称为前向视角(Forward View / 理论家视角)——站在当前,望向未来。

为了在工程中实现“走一步、更新一步”,我们不希望等到未来结束才算账。Sutton 提出了一个革命性的转换:后向视角(Backward View / 工程师视角),并引入了一个神奇的工具——资格迹(Eligibility Traces)。

7.3 资格迹(后向视角)

🤔 思考:试想生活中的这个场景:晚上吃了面包、喝了牛奶、吃了一颗花生,几个小时后胃部剧烈腹痛 🤢。

  • 如果按纯粹的单步 TD,好像只能怪最后那颗花生;
  • 如果按蒙特卡洛,所有吃进去的东西好像责任都均摊了。

直觉上,大脑在追究“到底是哪个食物导致腹痛”时,通常会考量哪两个关键维度?

解析大脑在进行这种“责任归咎”(Credit Assignment)时,通常会本能地权衡两个核心维度 🧠:

  1. ⏱️ 时间临近性(Recency):事情发生得越靠近现在,嫌疑通常越大(例如最后吃的东西可能嫌疑较重)。

  2. 🔁 发生频率(Frequency):出现次数越多的事物,嫌疑越大(例如如果整晚反复吃过很多次某种食物)。

在强化学习中,Sutton 正是用资格迹 \(E_t(s)\) 将这两个直觉结合了起来。

对每个状态 \(s\),我们维护一个临时的“责任分数”(初始为 \(0\))。在每一步 \(t\):

\[E_t(s) = \gamma \lambda E_{t-1}(s) + \mathbf{1}(S_t = s)\]
  • 📉 衰减(Recency):每过一个时间步,所有状态的历史权重都会衰减乘上 \(\gamma \lambda\)。很久没被访问的状态,痕迹逐渐淡化归零。

  • ➕ 累加(Frequency):如果当前时刻智能体恰好踩中了状态 \(s\)(即 \(\mathbf{1}(S_t = s) = 1\)),它的资格迹就立即加 \(1\)。多次访问该状态,权重就会持续累积。

现在智能体在当前步 \(t\) 感受到了一个即时的单步时序差分误差 \(\delta_t = R_{t+1} + \gamma V(S_{t+1}) - V(S_t)\)。

在传统的单步 TD(0) 中,我们只拿着 \(\delta_t\) 更新刚刚经历的状态 \(S_t\)。

🤔 思考:在后向视角 TD(\(\lambda\)) 中,既然所有状态的资格迹 \(E_t(s)\) 记录了它们当前各自的“责任大小”,我们应该如何利用当前的 \(\delta_t\) 和各状态的 \(E_t(s)\),来同步更新系统中所有状态的价值估计 \(V(s)\) 呢?

解析在 TD(\(\lambda\)) 的后向视角中,更新方式非常优美而统一:拿当前的单步 TD 误差 \(\delta_t\),乘以所有状态各自的资格迹 \(E_t(s)\),一次性广播更新! 📡

对系统中的每一个状态 \(s\):

\[V(s) \leftarrow V(s) + \alpha \, \delta_t \, E_t(s)\]

这里的物理意义非常直观:

  • ⚡ \(\delta_t\)(单步误差) 扮演了“意外/惊喜信号”:它代表当前这一步与预期的差距;

  • 🧠 \(E_t(s)\)(资格迹) 扮演了“责任分配比例”:谁在过去越常出现、离当前越近,谁的 \(E(s)\) 就越高,分配到的责任(更新幅度)就越大。

举个例子:设 \(\gamma = 1\)、\(\lambda = 0.5\)(每过一步,痕迹衰减一半),学习率 \(\alpha = 0.1\)。智能体依次经过 \(s_A \to s_B \to s_C\),在 \(s_C\) 这一步掉下悬崖,产生 TD 误差 \(\delta = -100\)。

时刻 \(E(s_A)\) \(E(s_B)\) \(E(s_C)\)
\(t=0\)(在 \(s_A\)) \(1\) \(0\) \(0\)
\(t=1\)(在 \(s_B\)) \(0.5\) \(1\) \(0\)
\(t=2\)(在 \(s_C\),掉崖) \(0.25\) \(0.5\) \(1\)

用 \(\Delta V(s) = \alpha\,\delta\,E(s)\) 一次性更新三个状态:

  • \(s_C\):\(0.1 \times (-100) \times 1 = -10\)(直接肇事者,罚得最重)
  • \(s_B\):\(0.1 \times (-100) \times 0.5 = -5\)
  • \(s_A\):\(0.1 \times (-100) \times 0.25 = -2.5\)(离得最远,罚得最轻)

这就是时间临近性。再看频率:如果路径是 \(s_A \to s_B \to s_A \to s_C\),那么 \(s_A\) 在 \(t=2\) 时的痕迹是 \(0.25 + 1 = 1.25\),到 \(t=3\) 衰减为 \(0.625\),比只来过一次时的 \(0.25\) 大得多,分到的责任也更多。

极简代码直观感受 💻

在代码实现中,后向视角完全不需要等待回合结束,依然是每走一步就地更新:

# 1. 计算当前的单步 TD 误差
delta = r + gamma * V[s_next] - V[s]

# 2. 累加当前状态的资格迹(累积迹 Accumulating Trace)
E[s] += 1

# 3. 广播更新所有状态的价值,并衰减资格迹
for state in range(num_states):
    V[state] += alpha * delta * E[state]  # 谁的痕迹重,谁就改得多
    E[state] *= gamma * lambda_param      # 随时间衰减

💡 关键结论:数学上可以严格证明,在离线(Offline)更新的设定下,这种每步就地用资格迹更新的后向视角(Backward View),与必须等到回合结束才能计算的前向视角 \(\lambda\)-回报(Forward View)是严格等价的!

悬崖行走中的“闪电式学习” ⚡

还记得在单步 TD(0) 中,悬崖行走的负奖励是如何回传的吗?

  • 单步 TD(0):第 1 次掉下悬崖,只有悬崖边那 \(1\) 个格子知道痛;必须经历多次反复掉崖,奖励才能一步一步倒流回起点;

  • 引入资格迹后(TD(\(\lambda\))):智能体从起点走了一条长路掉入悬崖,产生巨大的负误差 \(\delta\)。此时整条路径上的所有状态都有残留的痕迹 \(E(s) > 0\)!

  • 结果:仅仅掉了一次悬崖,之前走过的所有状态瞬间全部学会了扣分!学习效率呈几何级数提升。

第 8 章 深度强化学习 I:基于价值的方法

8.1 为什么需要函数逼近

第 4–7 章的所有方法都用表格存储价值,比如 6.4 节用二维数组 Q[s, a] 存储每个动作价值。1.5 节已经预告过,面对现实任务时,表格会遇到两个根本问题:

  • 维度灾难:若输入是一张 \(84 \times 84\) 的灰度图像(如 Atari 游戏),可能的状态数高达 \(256^{84 \times 84}\),无论内存还是算力都无法遍历。

  • 缺乏泛化能力:表格中每个状态的值相互独立。智能体在状态 \(s\) 撞了墙,如果转移到极度相似的状态 \(s + 0.001\),表格依然对新状态一无所知。

为了打破表格限制,我们用一个参数化网络(神经网络)来拟合动作价值函数:

\[Q_\theta(s, a) \approx Q^*(s, a)\]

其中 \(\theta\) 是神经网络的权重。此时网络接受状态 \(s\) 作为输入,输出各个可能动作的预估 \(Q\) 值。

在传统的监督学习(Supervised Learning)中,训练样本通常是独立同分布(i.i.d.)且标签固定的。但当我们直接把神经网络插进前面写过的单步 Q-learning 中时,训练极易发散崩塌。理论上,函数逼近 + 自举 + 异策略三者同时出现时,训练就可能发散,这被称为强化学习的“死亡三要素(Deadly Triad)”,而 DQN 恰好三者全占。

除了这一理论层面的风险,在工程上还有更直接的问题。

🤔 思考:结合强化学习智能体与环境交互的特点,与标准的监督学习相比,强化学习在收集数据和计算损失函数(Loss)时,存在哪两个显著打破稳定训练假设的难题?

解析将神经网络直接套入 Q-learning 时,会遇到打破监督学习基石的两大核心不稳定根源:

① 数据的强相关性(Correlated Data)🔗

  • 监督学习假设:样本之间独立同分布(i.i.d.)。

  • 强化学习现实:智能体采集的数据是一条连续轨迹 \((S_t, A_t, R_{t+1}, S_{t+1})\)。\(S_{t+1}\) 强烈依赖于 \(S_t\),相邻样本之间高度自相关。如果拿着连续样本直接做梯度下降,网络极易陷入局部过拟合或灾难性遗忘(学了新数据就把旧知识冲掉,8.2 节有具体例子)。

② 移动的目标(Moving Target)🎯🏃

回顾单步 Q-learning 的均方误差损失函数(Loss):

\[\mathcal{L}(\theta) = \mathbb{E}\left[ \left( \underbrace{R + \gamma \max_{a'} Q_\theta(S', a')}_{\text{目标 Target } y} - Q_\theta(S, A) \right)^2 \right]\]
  • 监督学习:标签 \(y\) 是固定的真值。

  • 强化学习:目标 \(y\) 本身也是通过当前网络参数 \(\theta\) 计算出来的!每做一次参数更新,被追逐的目标也会随之变动,相当于“拿着一把长度随时变化的尺子去量桌子”,极易导致数值振荡甚至发散。

8.2 DQN:经验回放与目标网络

2015 年,DeepMind 在 Nature 发表的经典 DQN 论文正是提出了两项机制来分别击破这两个难题:

  1. 📦 经验回放(Experience Replay):

    • 设立一个固定容量的回放池(Buffer),把智能体经历的转移元组 \((s, a, r, s', done)\) 存入其中。

    • 训练时,从池中均匀随机抽取一个 Batch 的历史样本进行更新。

    • 这一机制打破了样本在时间序列上的强相关性,还原了近乎独立同分布的训练条件。

    • 🌰 举个例子:就像背单词,如果按字母顺序背,连续背了 200 个 a 开头的单词,脑子里只剩下 a 开头的词,前面背过的 z 开头的词很快就忘了;把单词本打乱再背,每一轮都能照顾到各种单词。泊车小车也一样:假设它连续 100 步都在直道上行驶,如果直接拿这 100 条相邻数据训练,网络会被“直道经验”反复冲刷,把之前学会的倒车、转弯忘掉(灾难性遗忘)。有了回放池,池子里存着过去几万步的经历,每次随机抽 32 条,里面既有直道、也有倒车和转弯,网络每次更新都能“温故而知新”。

  2. 🧊 独立目标网络(Target Network):

    • 维护两套结构相同但参数不同的网络:

      • 当前网络 \(Q_\theta(s, a)\):用于实时选择动作并持续进行梯度下降更新。

      • 目标网络 \(Q_{\theta^-}(s, a)\):专门用来计算 TD 目标。

    • 其参数 \(\theta^-\) 会被“冻结”一段时间,每隔固定的 \(C\) 步才从当前网络硬复制一次(\(\theta^- \leftarrow \theta\))。

观察引入目标网络后的 TD 目标公式:

\[y = R + \gamma \max_{a'} Q_{\theta^-}(S', a')\]

🤔 思考:将目标网络参数 \(\theta^-\) 冻结固定一段时间,在数值优化层面上解决了什么问题?它为什么能显著降低训练发散的风险?

解析在数值优化层面上,将目标网络参数 \(\theta^-\) 冻结一段时间,核心是为了解耦当前预测与更新目标 ⛓️❌。

具体来说,它带来了两个关键收益:

  1. 🎯 化“动态追踪”为准静态回归:

    • 如果没有独立目标网络,每次梯度下降改变 \(\theta\),目标 \(y\) 就会立刻跟着变动,导致优化问题变成一个非平稳(Non-stationary)目标。

    • 冻结 \(\theta^-\) 后,在 \(C\) 个步长之内,目标网络产生的 \(y\) 变成了一批固定的伪标签(Pseudo-labels)。优化问题暂时退化为一个近似标准的监督回归任务,使得损失函数有明确、稳定的收敛方向。

  2. 📉 阻断误差正反馈放大:

    • 若使用同一个网络,当网络碰巧高估了某个动作的价值时,这个偏高的估计立刻会作为下一步的目标值,导致后续更新进一步拔高该估计,形成恶性正反馈循环,引发数值爆炸。

    • 目标网络的延迟更新切断了这种即时的正反馈链条,大大降低了数值爆炸和发散的风险。但要注意,它解决的是“误差被即时放大”的问题;\(\max\) 操作本身带来的系统性过估计依然存在,这正是 8.3 节 Double DQN 要处理的。

DQN 的完整算法骨架 🧩

结合经验回放与目标网络,经典的 DQN 算法流程如下:

初始化经验回放池 D
初始化当前网络 Q_θ(s, a) 与目标网络 Q_{θ^-}(s, a),令 θ^- = θ

对每个回合循环:
    初始化状态 S
    对每个时间步 t 循环:
        以 ε-greedy 策略根据 Q_θ(S, ·) 选择动作 A
        执行动作 A,观察奖励 R、下一状态 S' 及结束标志 done
        将转移样本 (S, A, R, S', done) 存入 D

        从 D 中随机采样一个 Batch 的样本 (s, a, r, s', done)
        计算目标值:
            y = r + γ * max_a' Q_{θ^-}(s', a') * (1 - int(done))
        以 (y - Q_θ(s, a))^2 为损失更新当前网络参数 θ

        每隔 C 步,令 θ^- ← θ
        S ← S'

8.3 Double DQN:抑制过估计

DQN 虽然成功让神经网络玩转了强化学习,但在理论与实践中依然存在一个著名的缺陷:\(\max\) 操作带来的系统性过估(Overestimation Bias)。

🤔 思考:在计算目标 \(y = r + \gamma \max_{a'} Q_{\theta^-}(s', a')\) 时,如果目标网络对各个动作的评估存在随机噪声(有的偏高、有的偏低),直接套用 \(\max_{a'}\) 会导致计算出的目标值偏向保守还是偏向乐观?为什么?

解析直接套用 \(\max_{a'}\) 会导致计算出的目标值偏向乐观(高估) 📈。

背后的数学直觉源于一个统计学性质:最大值操作不满足期望线性性。

假设某状态下所有动作的真实价值其实都一样,比如都是 \(0\)。但由于神经网络估计存在误差或噪声 \(\epsilon_a\)(假设均值为 0,有的动作被随机高估了 \(+0.2\),有的被低估了 \(-0.2\)):

  • 单独看每个动作的估计,期望还是 \(0\)。

  • 但当我们执行 \(\max_{a'} (0 + \epsilon_{a'})\) 时,我们选出的总是正噪声最大的那一个!

因此,数学上有:

\[\mathbb{E}\left[\max_{a'} \hat{Q}(s', a')\right] \ge \max_{a'} \mathbb{E}\left[\hat{Q}(s', a')\right]\]

这意味着只要估计存在随机扰动,\(\max\) 操作就会系统性地挑出最大的误差,导致 TD 目标持续偏大,进而像滚雪球一样造成整个网络的 Q 值严重高估 🏔️。

为了解决这个顽疾,DeepMind 后来提出了著名的 Double DQN (DDQN) 🥊。

它的核心思想是解耦:不再让同一个网络既当裁判又当运动员。

在标准的 DQN 中,选动作和评估动作都是目标网络一肩挑:

\[y = R + \gamma Q_{\theta^-}\big(S', \arg\max_{a'} Q_{\theta^-}(S', a')\big)\]

🤔 思考:如果要把“选择动作”和“评估该动作的价值”彻底分开,分别交给我们的两个网络(当前网络 \(\theta\) 和目标网络 \(\theta^-\)),应该让哪一个网络来挑选最优动作,再让哪一个网络来计算该动作对应的具体 Q 值呢?

解析在 Double DQN (DDQN) 中,这两个分工是这样安排的:

  • 🎯 当前网络 \(\theta\) 负责“挑选最优动作”:利用最新的策略选出当前看起来最好的动作:
\[a^* = \arg\max_{a'} Q_\theta(S', a')\]
  • 🧊 目标网络 \(\theta^-\) 负责“评估该动作的价值”:给出这个动作的客观数值预估:
\[y^{\text{DoubleQ}} = R + \gamma Q_{\theta^-}(S', a^*)\]

整合成一个完整的更新目标式:

\[y^{\text{DoubleQ}} = R + \gamma Q_{\theta^-}\left(S', \arg\max_{a'} Q_\theta(S', a')\right)\]

这样一来,即使当前网络 \(\theta\) 因为估计误差挑出了一个被高估的动作,由于评估是由权重不同且相对冻结的目标网络 \(\theta^-\) 完成的,该动作的最终得分依然会受到客观约束,从而切断了误差的放大链条 🛡️。

8.4 Dueling DQN:拆分状态价值与优势

除了解决高估问题的 Double DQN,DeepMind 还对网络结构本身做了一个经典改造——Dueling DQN。

🤔 思考:在赛车游戏中,当赛车正行驶在一处完全空旷笔直的高速公路上时,方向盘稍微往左微调还是往右微调,其实对大局影响不大,此时决定价值高低的核心在于“赛车处于安全状态”本身;只有在马上要撞车的紧要关头,选择哪个具体动作才起决定性作用 🚗。

顺着这个直觉,如果把动作价值 \(Q(s, a)\) 进一步拆解为两个独立的物理量分别建模,可以拆成哪两部分呢?

解析在 Dueling DQN 中,我们正是把 \(Q(s, a)\) 拆解为两个分支 🌿:

  1. 🌐 状态价值 \(V(s)\)(State Value):只评估当前状态 \(s\) 本身有多好,与采取什么具体动作无关。

  2. ⚖️ 优势函数 \(A(s, a)\)(Advantage Function):衡量在当前状态下,选择特定动作 \(a\) 相比于其他动作的相对优劣程度。

直观上,二者的关系可以写作:

\[Q(s, a) = V(s) + A(s, a)\]

网络的前半部分共享卷积特征提取层,在全连接层处分叉为两条独立的支路:一条输出标量 \(V(s)\),另一条输出向量 \(A(s, a)\),最后合并为各个动作的 \(Q(s, a)\)

唯一性难题与中心化技巧 🧩

如果直接使用 \(Q(s, a) = V(s) + A(s, a)\),会遇到一个不可辨识(Identifiability)问题:

  • 给定一组 \(Q\) 值,比如 \(Q=10\),它可以是 \(V=10, A=0\),也可以是 \(V=0, A=10\),解不唯一,导致两个分支无法明确分工。

为了让解唯一,实践中通常强制优势函数在所有动作上的均值为 0,也就是使用减去均值的形式:

\[Q(s, a) = V(s) + \left( A(s, a) - \frac{1}{|\mathcal{A}|} \sum_{a'} A(s, a') \right)\]

回顾我们在深度强化学习中看到的三个核心组件:

  • 🧊 DQN:通过回放池和目标网络稳定训练

  • 🥊 Double DQN:解耦选择与评估,抑制过高估计

  • 🌿 Dueling DQN:分离状态价值与动作相对优势

到这里,经典的基于价值(Value-based)的深度强化学习脉络已经清晰展现。

第 9 章 深度强化学习 II:策略梯度与 Actor-Critic

9.1 连续动作下 maxa Q 为什么算不动

当面临机械臂控制、自动驾驶转角这类连续动作空间(动作不是离散的几个按键,而是连续的实数值)时,\(Q\)-learning 中求解 \(\max_a Q(s, a)\) 会遇到巨大的计算困难。

🤔 思考:在连续动作空间下,为什么直接求 \(\max_a Q(s,a)\) 会极其困难?我们要如何跳出“评估每个动作的 Q 值”这种思路?

解析在连续动作空间中,求解 \(\max_a Q(s, a)\) 会遇到一个核心计算瓶颈 🛑:

  • 🔢 离散动作:只有几个候选动作(例如“左”、“右”),只需把每个动作代入网络算一遍,取最大值 np.max() 即可。

  • 🌊 连续动作:动作是一个实数向量(例如方向盘转角 \([-30^\circ, 30^\circ]\),油门大小 \([0, 1]\))。在实数区间内存在无限多个动作。如果每次更新和决策都要找最大值,本质上要在每一步都运行一次复杂的非线性数值优化算法(如梯度上升),计算开销极其庞大,甚至无法保证收敛到全局最大。

为了彻底打破“枚举动作找最大”的限制,强化学习走向了另一条核心路径——策略梯度(Policy Gradient) 与 Actor-Critic(行动者-评论家)架构 🎭。

这种思路不再让网络直接输出每个动作的价值,而是进行分工:

  1. 🎭 Actor(行动者):直接根据状态 \(s\) 决定采取什么动作(例如输出方向盘转角分布的均值和标准差,或者直接输出一个具体数值)。

  2. 👨‍⚖️ Critic(评论家):评估当前这个动作或状态有多好,指导 Actor 进行调整。

🤔 思考:既然有了专门负责输出具体动作的 Actor,我们还需要在每一步通过遍历所有可能的动作来找 \(\max_a Q(s, a)\) 吗?Actor 输出动作的方式,是如何避开这个计算难题的?

解析我们完全不再需要遍历所有动作去求 \(\max_a Q(s, a)\) 了。

Actor 避开这个计算难题的方式非常巧妙:

  • 🎯 直接参数化策略:

    Actor 网络不是输出每个动作的分数,而是直接把状态 \(s\) 映射为一个具体的动作输出:

    • 确定性动作 🏎️:网络直接输出具体的数值控制量,例如 \(a = \pi_\theta(s)\)(直接算出方向盘转角为 \(+12.5^\circ\))。

    • 随机性策略 🎲:网络输出概率分布的参数,例如高斯分布的均值 \(\mu(s)\) 和标准差 \(\sigma(s)\),然后直接从该分布中采样生成动作。

  • ⚡ 计算复杂度从“全局搜索”降为“单次前向传播”:

    在每个时间步,无论动作维度多高、动作取值是否连续,我们只需要进行一次神经网络的前向推断(Forward Pass),就能立刻拿到执行动作,彻底绕开了复杂的极值优化求解。

在这种分工下:

  • 🎭 Actor 负责生成动作 \(a\);

  • 👨‍⚖️ Critic 负责评估这个动作的质量,计算出一个“优势信号”或者评价分。

现在动作已经生成并执行了,接下来就到了核心的学习更新环节。

9.2 策略梯度定理

🤔 思考:如果 Critic 评估后发现,Actor 刚才做出的这个动作带来的回报远超预期(表现为正向惊喜),Actor 应该如何调整自身的网络参数,来确保以后在类似状态下更倾向于做出这个动作呢?

解析当 Critic 给出正向反馈(例如正的优势值 \(\hat{A}_t > 0\))时,Actor 会通过梯度上升(Gradient Ascent)来调整参数 📈:

  • 🎯 对于随机性策略:增大该动作在状态 \(s\) 下的选择概率 \(\pi_\theta(a \mid s)\),也就是提高它在动作概率分布中的峰值。

  • 🏎️ 对于确定性策略:直接调整网络权重,让输出的连续动作向使得 Critic 评价更高(\(\nabla_a Q(s, a)\))的方向微调。

直觉很清楚:好的动作要“多做”,差的动作要“少做”。但“多做”具体要怎么转化成对参数 \(\theta\) 的梯度?下面用约 10 行推导回答这个问题。

① 优化目标

设一条完整轨迹为 \(\tau = (s_0, a_0, r_1, s_1, a_1, \dots)\),它的总回报为 \(R(\tau)\)。策略网络的目标是最大化期望回报:

\[J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta}\left[R(\tau)\right] = \sum_{\tau} p_\theta(\tau)\, R(\tau)\]

其中轨迹出现的概率由初始状态分布、策略和环境转移共同决定:

\[p_\theta(\tau) = \rho(s_0)\prod_{t} \pi_\theta(a_t \mid s_t)\, P(s_{t+1} \mid s_t, a_t)\]

② 对数导数技巧(Log-Derivative Trick)

对 \(\theta\) 求梯度时,\(R(\tau)\) 与 \(\theta\) 无关,只需对 \(p_\theta(\tau)\) 求导。利用恒等式 \(\nabla p = p \cdot \nabla \log p\):

\[\nabla_\theta J(\theta) = \sum_\tau \nabla_\theta p_\theta(\tau)\, R(\tau) = \sum_\tau p_\theta(\tau)\,\nabla_\theta \log p_\theta(\tau)\, R(\tau) = \mathbb{E}_{\tau\sim\pi_\theta}\left[\nabla_\theta \log p_\theta(\tau)\, R(\tau)\right]\]

这一步的意义在于:梯度重新变回了一个期望,因此可以直接用采样的轨迹来估计。

③ 环境模型自动消失

把 \(p_\theta(\tau)\) 取对数,乘积变成求和:

\[\log p_\theta(\tau) = \log \rho(s_0) + \sum_t \log \pi_\theta(a_t \mid s_t) + \sum_t \log P(s_{t+1} \mid s_t, a_t)\]

第一项和第三项来自环境,与 \(\theta\) 无关,求梯度后直接为 0。于是:

\[\nabla_\theta \log p_\theta(\tau) = \sum_t \nabla_\theta \log \pi_\theta(a_t \mid s_t)\]

💡 这就是策略梯度可以无模型使用的原因:整个梯度里完全不需要知道转移概率 \(P\)。

④ 因果性:只看“之后”的回报

\(t\) 时刻的动作不可能影响 \(t\) 时刻之前已经拿到的奖励,因此可以把整条轨迹的回报 \(R(\tau)\) 换成从 \(t\) 时刻开始的回报 \(G_t\)(这样做不改变期望,还能降低方差)。最终得到策略梯度定理的常用形式:

\[\nabla_\theta J(\theta) = \mathbb{E}_{\pi_\theta}\left[\sum_t \nabla_\theta \log \pi_\theta(A_t \mid S_t)\, G_t\right]\]

(严格推导在折扣设定下还会带一个 \(\gamma^t\) 系数,实践中通常省略。)

举个例子:\(\nabla_\theta \log \pi_\theta\) 到底把参数往哪推?

设小车在某个路口有三个动作,策略网络最后一层输出三个分数(logits)\(z\),经过 Softmax 得到概率:

动作 左转 直行 右转
\(\pi_\theta(a\mid s)\) \(0.5\) \(0.3\) \(0.2\)

对于 Softmax,有一个很简洁的结论:\(\dfrac{\partial \log \pi(a)}{\partial z_k} = \mathbf{1}(k = a) - \pi(k)\)。

假设这次采样到了“直行”,并且这一回合的回报 \(G_t = +10\):

  1. 梯度方向:\(\nabla_z \log \pi(\text{直行}) = [0 - 0.5,\ 1 - 0.3,\ 0 - 0.2] = [-0.5,\ +0.7,\ -0.2]\)
  2. 乘上回报:\(G_t \times [-0.5,\ 0.7,\ -0.2] = [-5,\ +7,\ -2]\)
  3. 梯度上升(学习率 \(0.01\)):“直行”的分数 \(+0.07\),“左转”\(-0.05\),“右转”\(-0.02\)
  4. 更新后的概率:左转 \(0.479\)、直行 \(\mathbf{0.324}\)、右转 \(0.197\),“直行”的概率从 \(0.30\) 升到了 \(0.324\) ✅

如果这次的回报是 \(G_t = -10\),方向就完全反过来,“直行”的概率会降到 \(0.277\)。

从这个例子能看出两点:

  • \(\nabla \log \pi\) 只负责指出“怎样让刚才这个动作更可能发生”,而 \(G_t\)(后面推广为 \(\Psi_t\))决定“该往这个方向推多少,还是反着推”;
  • 梯度分量是 \(1 - \pi(a)\):越是不常被选的动作,一旦被采样到且结果好,概率提升得越多。这正好补偿了它被采样的机会少。

把 \(G_t\) 换成更一般的评价信号 \(\Psi_t\),就得到 Actor 参数 \(\theta\) 更新方向的通用形式:

\[\nabla_\theta J(\theta) \propto \mathbb{E} \left[ \nabla_\theta \log \pi_\theta(A_t \mid S_t) \cdot \Psi_t \right]\]

这里有两部分核心配合:

  1. 🧭 \(\nabla_\theta \log \pi_\theta(A_t \mid S_t)\):指出“往哪个方向调整参数,可以增加动作 \(A_t\) 的发生概率”。

  2. ⚖️ \(\Psi_t\)(评判权重):由 Critic 提供的反馈信号。如果表现好(\(\Psi_t > 0\)),就顺着梯度推进;如果表现糟糕(\(\Psi_t < 0\)),就反方向压制该动作。

9.3 REINFORCE 与基线

直接取 \(\Psi_t = G_t\),就是最朴素的策略梯度算法 REINFORCE(Williams, 1992):

for episode in range(num_episodes):
    trajectory = run_episode(policy)          # 用当前策略完整跑一个回合
    for t, (s, a, G) in enumerate(returns_to_go(trajectory)):
        loss = -log_prob(policy, s, a) * G    # 取负号:梯度上升 → 最小化 loss
        loss.backward()
    optimizer.step(); optimizer.zero_grad()

它的问题在第 5 章就见过:\(G_t\) 是整条轨迹的蒙特卡洛采样,方差极大,而且必须等回合结束。

🤔 思考:假设在泊车任务里,所有动作拿到的回报都在 \(+90\) 到 \(+100\) 之间。按照 REINFORCE,每个动作的 \(G_t\) 都是正的,都会被“增大概率”。这样的信号有什么问题?

解析信号只告诉了网络“都不错”,却没有说“谁比平均水平更好”。就像考试考了 80 分,好不好要看平均分:如果平均分是 90,那 80 分其实该反思。因此我们希望减去一个基线(Baseline) \(b(s)\),只保留“比平均好多少”:

\[\nabla_\theta J(\theta) = \mathbb{E}\left[\nabla_\theta \log \pi_\theta(A_t \mid S_t)\,\big(G_t - b(S_t)\big)\right]\]

为什么减去基线不会引入偏差? 只要 \(b(s)\) 不依赖于动作 \(a\),额外减去的那一项期望恒为 0:

\[\mathbb{E}_{a\sim\pi_\theta}\left[\nabla_\theta \log \pi_\theta(a\mid s)\, b(s)\right] = b(s)\sum_a \pi_\theta(a\mid s)\,\frac{\nabla_\theta \pi_\theta(a\mid s)}{\pi_\theta(a\mid s)} = b(s)\,\nabla_\theta \underbrace{\sum_a \pi_\theta(a\mid s)}_{=1} = 0\]

也就是说,基线只改变方差、不改变期望。最自然的基线选择就是状态价值 \(V(s)\)(“这个状态下的平均水平”),此时评价信号 \(G_t - V(S_t)\) 正好是优势函数 \(A^\pi(S_t, A_t) = Q^\pi(S_t, A_t) - V^\pi(S_t)\) 的一个无偏估计。而谁来提供 \(V(s)\)?正是 Critic。

评价信号 \(\Psi_t\) 的演进

Critic 提供给 Actor 的这个评价信号 \(\Psi_t\),直接决定了学习的方差和稳定性:

机制 \(\Psi_t\) 的取值 特点
REINFORCE 🎲 完整回报 \(G_t\) 蒙特卡洛采样,方差极大,必须等待回合结束
Q-Actor-Critic 📊 动作价值 \(Q(s, a)\) 用 Critic 网络拟合单步价值,降低了方差
Advantage Actor-Critic (A2C) ⚖️ 优势函数 \(A^\pi(s, a) = Q^\pi(s, a) - V^\pi(s)\) 减去基线(Baseline),进一步大幅削减方差

9.4 Actor-Critic 与 A2C

上表最后一行的 A2C 是实践中最常用的形式。在 A2C 中,我们通常用 TD 误差 \(\delta_t = R_{t+1} + \gamma V(S_{t+1}) - V(S_t)\) 来近似替代优势函数 \(A^\pi(S_t, A_t)\)(因为 \(\mathbb{E}[R_{t+1} + \gamma V(S_{t+1}) \mid S_t, A_t] = Q^\pi(S_t, A_t)\),所以 \(\delta_t\) 的期望正是优势)。

🤔 思考:如果使用 TD 误差作为评价信号,Critic 网络只需要学习预估哪一个物理量(\(V(s)\) 还是 \(Q(s, a)\))?这样为什么能进一步简化网络结构?

解析Critic 网络只需要学习预估状态价值 \(V(s)\),这样设计带来了两个显著优势:

  • 📉 输出维度简化:\(V(s)\) 仅以状态 \(s\) 为输入,输出一个标量值。网络完全不需要把高维的连续动作 \(a\) 拼接到输入层中,也不需要为每个动作分支建模。

  • ⚙️ 降低拟合难度:在连续动作空间中,如果评估 \(Q(s, a)\),函数逼近器必须在无限连续的动作维度上拟合曲面;而评估 \(V(s)\) 只需要拟合当前策略下的平均状态价值,学习目标更平滑、方差更低。

⚠️ 这与 5.3 节并不矛盾:5.3 节说无模型控制要估计 \(Q\) 而不是 \(V\),前提是“靠价值函数来选动作”——只有 \(V\) 的话,不知道 \(P\) 就没法比较各个动作。而在 Actor-Critic 中,选动作的工作交给了 Actor,Critic 不需要替 Actor 比较动作,只需要回答“这个状态平均有多好”,给优势提供一个基准。所以这里只学 \(V\) 就够了。

现在,整个 Actor-Critic 架构的运转逻辑已经清晰:

  1. 🎭 Actor 根据状态 \(S_t\) 输出动作 \(A_t\)。

  2. 🌍 环境执行 \(A_t\),反馈即时奖励 \(R_{t+1}\) 并转移到 \(S_{t+1}\)。

  3. 👨‍⚖️ Critic 计算单步 TD 误差:\(\delta_t = R_{t+1} + \gamma V_\phi(S_{t+1}) - V_\phi(S_t)\)

  4. 🔄 双向更新:

    • Critic 靠均方误差损失更新参数 \(\phi\),让 \(V(S_t)\) 的预测越来越准。

    • Actor 顺着梯度 \(\nabla_\theta \log \pi_\theta(A_t \mid S_t) \cdot \delta_t\) 调整参数 \(\theta\)。

9.5 连续动作:高斯策略、tanh 修正与熵正则

让我们将焦点移到 Actor 的动作生成 上 🔍。

🤔 思考:如果我们要控制一个机械臂的关节(动作取值为连续实数,比如输出力矩 \([-1.0, 1.0]\)),Actor 神经网络通常会输出哪两个参数来定义一个概率分布?智能体又是如何从这个分布中获取具体动作的呢?

解析处理连续动作空间时,Actor 神经网络通常假设动作服从高斯分布(正态分布) \(\mathcal{N}(\mu, \sigma^2)\) 。为了完整定义这个连续分布,网络通常会输出两个核心参数:

  • 🎯 均值 \(\mu(s)\):代表当前状态下网络认为最合理的动作中心值(例如预计关节转动 \(0.3\) 弧度)。

  • 📏 标准差 \(\sigma(s)\)(实践中多输出对数标准差 \(\log \sigma\)):代表动作的离散程度或探索的不确定性大小。

智能体获取动作的具体步骤如下:

  1. 🎲 采样:从构造好的正态分布中随机抽取一个连续数值:\(a \sim \mathcal{N}(\mu(s), \sigma^2(s))\)。

  2. 🗜️ 截断/映射:为了确保动作不超出机械臂的物理限位(如 \([-1.0, 1.0]\)),通常会将采样值通过激活函数(如 \(\tanh\))进行压缩映射。

⚠️ tanh 压缩后的 log-prob 修正:策略梯度要用到 \(\log \pi_\theta(a\mid s)\)。如果先采样 \(u \sim \mathcal{N}(\mu, \sigma^2)\),再令 \(a = \tanh(u)\),那么 \(a\) 的分布已经不再是高斯分布,不能直接用高斯密度计算概率。根据随机变量变换公式,需要减去 \(\tanh\) 的雅可比项(即 \(\tanh\) 的导数,衡量这次“压缩”把概率密度拉伸了多少):

\[\log \pi_\theta(a\mid s) = \log \mathcal{N}(u;\,\mu(s), \sigma^2(s)) - \sum_{i} \log\left(1 - \tanh^2(u_i)\right)\]

(\(i\) 遍历动作的每个维度。)忘记这一项是实现 PPO、SAC 等算法时的常见 bug,会让策略梯度方向出现系统性偏差。

随着训练的推进,智能体对环境越来越熟悉,我们通常希望机械臂的动作越来越稳定、精准,而不是像刚开始那样胡乱抖动。

🤔 思考:在理想的学习过程中,输出的 \(\sigma\)(标准差) 应该发生怎样的变化?如果在训练早期 \(\sigma\) 过快衰减到了接近 0,可能会导致什么问题呢?

解析在训练过程中,\(\sigma\)(标准差) 的理想变化通常是从大到小逐渐衰减 :

  • 🚀 训练初期(重在探索):\(\sigma\) 保持较大值,让智能体在更宽泛的动作空间内随机尝试,发现潜在的高回报区域。

  • 🎯 训练后期(重在利用):随着策略逐步成熟,\(\sigma\) 逐渐收敛到一个较小值,使输出动作紧紧围绕最优均值 \(\mu(s)\),从而保证控制的高精度与稳定性。

过快衰减的陷阱 ⚠️

如果 \(\sigma\) 在训练早期过快收缩到接近 \(0\),会导致以下严重问题:

  1. 🔒 过早收敛(Premature Convergence):动作几乎变成确定性选择,丧失了探索新动作的能力,策略极易被锁死在糟糕的局部最优解。

  2. 💥 梯度方差剧增、数值不稳定:高斯策略对均值的梯度为 \(\nabla_\mu \log\pi = \frac{a-\mu}{\sigma^2}\),\(\sigma \to 0\) 时分母趋近 0,梯度极易爆炸;同时分布的熵(Entropy)过低(熵衡量一个分布有多“随机”,下文会用它来做正则化),一旦陷入次优区域,策略难以自我纠错。

常见的解决策略 🛠️

为了防止策略过早丧失探索能力,通常会在损失函数中引入熵正则化项(Entropy Regularization):

\[\mathcal{L}_{\text{Actor}}(\theta) = -\mathbb{E} \left[ \log \pi_\theta(A \mid S) \cdot \delta \right] - c_{\text{ent}} \, \mathcal{H}(\pi_\theta(\cdot \mid S))\]
  • 🌪️ 策略熵 \(\mathcal{H}(\pi_\theta)\):衡量概率分布的随机程度(高斯分布的方差越大,熵越高)。

  • ⚖️ 熵系数 \(c_{\text{ent}}\):鼓励策略保持一定的不确定性,避免方差骤降(常取 \(0.01\) 左右)。

💡 实现时 \(\delta\) 要当作常数(在 PyTorch 中用 .detach()),梯度只流向 Actor 的 \(\log\pi_\theta\);Critic 的参数由它自己的均方误差损失单独更新。

从离散 Q-learning 到连续动作的 Actor-Critic,整个深度强化学习的核心脉络已基本成型。

9.6 异策略 Actor-Critic:DDPG、TD3 与 SAC

9.2–9.5 节的 Actor-Critic(以及第 10 章的 PPO)都是同策略方法:数据用完就扔,样本效率较低。在机器人控制这类交互成本很高的连续动作任务里,人们更希望像 DQN 一样用经验回放反复利用历史数据。这就催生了一类异策略 Actor-Critic(同策略/异策略的定义见 6.3 节)。

① DDPG:把 DQN 搬到连续动作上

DDPG(Deep Deterministic Policy Gradient,Lillicrap et al., 2016)的思路是:

  • Critic 像 DQN 一样学习 \(Q_\phi(s, a)\),同样使用经验回放和目标网络;
  • Actor 是一个确定性策略 \(a = \pi_\theta(s)\)(本笔记中 \(\pi_\theta(s)\) 只带一个参数时表示确定性策略,原论文记作 \(\mu_\theta(s)\)),直接输出动作,从而避开 \(\max_a Q(s,a)\) 的求解难题(9.1 节)。

🤔 思考:Critic 已经能告诉我们 \(Q(s, a)\) 有多高。如果 Actor 直接输出一个确定的动作 \(a = \pi_\theta(s)\),该怎样调整 \(\theta\) 才能让 \(Q\) 值变大?

解析沿着 \(Q\) 对动作的梯度 \(\nabla_a Q\) 推动动作,再通过链式法则传回 Actor 的参数,这就是确定性策略梯度(DPG):

\[\nabla_\theta J(\theta) = \mathbb{E}_{s\sim\mathcal{D}}\left[\nabla_a Q_\phi(s, a)\big|_{a=\pi_\theta(s)}\;\nabla_\theta \pi_\theta(s)\right]\]

直观上,Critic 告诉 Actor “方向盘再往左一点分数会更高”,Actor 就朝那个方向微调。因为确定性策略自身不会探索,训练时还要在动作上额外加噪声(如 \(a = \pi_\theta(s) + \mathcal{N}(0, \sigma^2)\))。

② TD3:修补 DDPG 的过估计

DDPG 继承了 DQN 的老毛病:\(Q\) 值过估计(8.3 节),而且 Actor 会主动去“钻” Critic 高估的地方,训练很不稳定。TD3(Twin Delayed DDPG,Fujimoto et al., 2018)用三个技巧修补:

技巧 做法 解决的问题
双 Critic 取最小(Clipped Double Q) 训练两个 Critic \(Q_{\phi_1}, Q_{\phi_2}\),目标值取两者较小的一个 抑制过估计(思路同 Double DQN)
延迟策略更新(Delayed) Critic 更新多次(如 2 次)后,Actor 才更新 1 次 先让 Critic 估准,再指导 Actor
目标策略平滑(Smoothing) 计算目标值时,给目标动作加一点截断噪声 防止 Actor 利用 \(Q\) 函数中尖锐的错误峰值

③ SAC:最大熵强化学习

SAC(Soft Actor-Critic,Haarnoja et al., 2018)回到了随机策略,并把 9.5 节的“熵正则”从一个辅助技巧升级为优化目标本身:

\[J(\pi) = \mathbb{E}_{\pi}\left[\sum_t \gamma^t \Big(R_{t+1} + c_{\text{ent}}\,\mathcal{H}\big(\pi(\cdot\mid S_t)\big)\Big)\right]\]

其中 \(c_{\text{ent}}\) 就是 9.5 节的熵系数,在 SAC 里常被称为温度系数(原论文记作 \(\alpha\),本笔记为避免与学习率混淆统一写作 \(c_{\text{ent}}\)),控制“拿高分”与“保持随机”之间的权衡。

🤔 思考:在奖励之外还“奖励随机性”,对智能体有什么好处?

解析

  • 🔍 探索内生化:不需要手工设计 \(\epsilon\) 或动作噪声,策略自己就会在“不确定哪个更好”的地方保持分散;
  • 🧩 保留多种解法:如果有两条同样好的路线,最大熵策略会两条都保留,而不是过早押注其中一条,因此更鲁棒;
  • 📈 训练更稳定:熵项防止了 9.5 节提到的 \(\sigma\) 过早坍缩。

SAC 的核心组件:

  • Soft Q 目标:沿用 TD3 的双 Critic 取最小,并在目标里加上熵项(\(a'\) 从当前策略采样):
\[y = r + \gamma\,(1 - \text{done})\left(\min_{j=1,2} Q_{\bar{\phi}_j}(s', a') - c_{\text{ent}} \log \pi_\theta(a'\mid s')\right),\quad a' \sim \pi_\theta(\cdot\mid s')\]
  • 策略更新:最小化 \(\mathbb{E}_{s\sim\mathcal{D}}\left[c_{\text{ent}} \log\pi_\theta(a\mid s) - \min_j Q_{\phi_j}(s, a)\right]\)。其中动作用重参数化技巧采样:\(a = \tanh\big(\mu_\theta(s) + \sigma_\theta(s)\odot\xi\big)\),\(\xi \sim \mathcal{N}(0, I)\),这样梯度可以穿过采样过程传回 \(\theta\);而 \(\log\pi_\theta(a\mid s)\) 必须使用 9.5 节的 tanh 修正。
  • 自动调温:给定目标熵(常取 \(-\dim(\mathcal{A})\)),自动学习 \(c_{\text{ent}}\),免去手动调参。

三种算法与 PPO 的对比

算法 策略类型 同/异策略 探索方式 特点
PPO 随机 同策略(近似) 策略自身的随机性 + 熵正则 稳定、易调,样本效率一般;离散/连续动作都适用
DDPG 确定性 异策略 动作上加外部噪声 样本效率高,但对超参数敏感、易过估计
TD3 确定性 异策略 动作上加外部噪声 DDPG 的稳定版
SAC 随机 异策略 最大熵目标,自动调温 连续控制任务的常用首选,稳定且样本效率高

第 10 章 PPO 近端策略优化

10.1 动机:步长控制与样本复用

9.6 节为了提高样本效率走向了异策略。另一条路线是留在同策略框架内,想办法让每批数据多用几次、同时让更新更稳,这就是 PPO。

先看问题出在哪:在传统的策略梯度(Policy Gradient)中,Actor 每次采样一批数据更新策略网络后,通常只能更新一步,而且迈的“步子”特别难控制:

  • 步子太小 🐢:训练极慢,样本利用率低。

  • 步子太大 💥:一旦策略更新走偏,采集到的后续数据直接变差,导致模型性能雪崩且很难恢复。

也就是说,传统 Actor-Critic 同时面临两个痛点:样本利用效率低、训练容易不稳定。PPO(Proximal Policy Optimization,Schulman et al., 2017)在继承 Actor-Critic 结构的基础上,用一句话概括它的核心思想就是:“小步快跑,绝不冒进”。它给策略的更新幅度加上了“安全绳”,既允许复用当前批次的数据多次更新网络,又强制限制新旧策略之间的差距。

我们来对比一下它们的核心差异:

维度 传统 Actor-Critic 🎭 PPO 优化版 🚀
策略更新性质 严格的 On-policy(同策略) 近似 On-policy(借用重要性采样)
数据重复使用 ❌ 采集一批数据,只能更新网络 1 次,用完即扔 ✅ 同一批数据可以重复更新网络 多次(Multiple Epochs)
步长控制机制 依赖学习率(Learning Rate),步子容易过大导致崩盘 引入 Clipped 约束,强制限制新旧策略的差距
样本效率 较低(需要与环境进行海量交互) 明显提升(充分榨干每批数据的价值)

进化的关键:为什么传统 A2C 不敢多练几次?

在标准的 Policy Gradient 和 Actor-Critic 中,目标函数直接依赖当前策略 \(\pi_\theta\) 采集到的动作分布。一旦网络参数 \(\theta\) 更新了一次,变成了 \(\theta_{\text{new}}\),之前采集的那批数据就属于“旧策略 \(\pi_{\theta_{\text{old}}}\)”了。如果强行用旧数据继续更新,梯度估计就会失真,导致策略崩溃。

10.2 重要性采样比率

为了让同一批数据能多学几轮,PPO 引入了统计学中的 重要性采样(Importance Sampling) 比率:

\[\rho_t(\theta) = \frac{\pi_\theta(A_t \mid S_t)}{\pi_{\theta_{\text{old}}}(A_t \mid S_t)}\]

注意这里的比率记作 \(\rho_t\)(与 6.3 节的重要性采样比率同一个符号),而不是原论文的 \(r_t\),以免和奖励 \(r\) 混淆。这个比率衡量了新策略相比旧策略,选这个动作的概率变大还是变小了。

它之所以能让旧数据“复活”,是因为下面这个恒等式:用旧策略采样,再乘上比率,期望就等于在新策略下采样:

\[\mathbb{E}_{a\sim\pi_{\theta_{\text{old}}}}\left[\rho_t(\theta)\,\hat{A}_t\right] = \sum_a \pi_{\theta_{\text{old}}}(a\mid s)\,\frac{\pi_\theta(a\mid s)}{\pi_{\theta_{\text{old}}}(a\mid s)}\,\hat{A}_t = \mathbb{E}_{a\sim\pi_\theta}\left[\hat{A}_t\right]\]

因此,只要新旧策略相差不太远,就可以用同一批旧数据对新策略做多轮优化。

举个例子:旧策略采集数据时,在某个状态下选了“倒车”,当时这个动作的概率是 \(\pi_{\theta_{\text{old}}}(\text{倒车}\mid s) = 0.2\),Critic 算出的优势为 \(\hat{A}_t = +4\)。经过一轮更新后:

情况 新策略概率 比率 \(\rho_t\) 这条样本的贡献 \(\rho_t \hat{A}_t\) 含义
新策略更爱倒车 \(0.3\) \(0.3 / 0.2 = 1.5\) \(1.5 \times 4 = 6\) 新策略更常做这个动作,这条经验的分量要放大
新策略没变 \(0.2\) \(1.0\) \(4\) 与普通策略梯度完全一样
新策略不爱倒车了 \(0.1\) \(0.1 / 0.2 = 0.5\) \(0.5 \times 4 = 2\) 新策略很少做这个动作,这条经验的分量要缩小

直观地说:这条数据是旧策略“以 20% 的概率”采到的;如果新策略做这个动作的概率是 30%,那么在新策略下它本该出现得更频繁,所以要按 \(1.5\) 倍来计算。比率 \(\rho_t\) 就是在给旧数据“重新称重”,让它看起来像是新策略采集的一样。(注意表中第一行的 \(1.5\) 已经偏离 \(1\) 较多,10.3 节会看到,PPO 正是要对这种“新旧策略差太远”的情况加以限制。)

🤔 思考:当系统更新了几次后,如果新策略对某个动作的概率变得极大(例如 \(\rho_t(\theta)\) 变成了 10 甚至 100),或者变得极小(接近 0),会对梯度更新产生什么潜在风险?

解析当比率 \(\rho_t(\theta)\) 变得非常大(比如 10 或 100)时,梯度会被急剧放大 💥。这会导致网络参数发生剧烈突变,很可能直接摧毁模型之前学到的优秀策略,也就是常说的策略崩塌(Policy Collapse)。反之,如果比率极端波动,梯度的方差也会变得极大,训练根本无法收敛。

10.3 Clip 目标函数

为了解决这个“步子迈太大”的问题,PPO 给这个比率加上了一把安全锁 —— 裁剪(Clipping) ✂️。

它将比率限制在一个很窄的合理区间内:

\[\rho_t(\theta) \in [1 - \epsilon_{\text{clip}}, 1 + \epsilon_{\text{clip}}]\]

通常取 \(\epsilon_{\text{clip}} = 0.2\),也就是强制让新旧策略的比率保持在 \([0.8, 1.2]\) 之间。

完整的 PPO 目标函数(要最大化,原论文记作 \(L^{CLIP}\))设计如下:

\[J^{\text{CLIP}}(\theta) = \hat{\mathbb{E}}_t \left[ \min\left( \rho_t(\theta) \hat{A}_t, \; \text{clip}(\rho_t(\theta), 1-\epsilon_{\text{clip}}, 1+\epsilon_{\text{clip}}) \hat{A}_t \right) \right]\]

这里的 \(\hat{A}_t\) 就是 Critic 计算出来的优势函数(Advantage) ⚖️,用来衡量动作比平均水平好还是差。

现在我们来看看这个设计的作用。

🤔 思考:假设 Critic 评定某个动作表现很好,即 优势函数 \(\hat{A}_t > 0\)。此时新策略把这个动作的概率提得特别高,使得比率 \(\rho_t(\theta) = 1.5\)(超出了 \(1.2\))。在这个情况下,公式里的 \(\text{clip}\) 和 \(\min\) 会把这一项的收益固定在多少?这样做如何阻止策略贪心过度?

解析收益项会被固定在 \(1.2 \cdot \hat{A}_t\) 🎯。

因为 \(\text{clip}(1.5, 0.8, 1.2) = 1.2\),而目标函数取的是两者的最小值:

\[\min(1.5 \cdot \hat{A}_t, \; 1.2 \cdot \hat{A}_t) = 1.2 \cdot \hat{A}_t\]

它是如何阻止贪心的?

当 \(\rho_t(\theta) > 1.2\) 时,这个目标项的值变成了一个常数(上限是 \(1.2 \cdot \hat{A}_t\))。对参数求导时,超出 \(1.2\) 的部分梯度为 0 。

这意味着:无论策略网络怎么继续增加该动作的概率,都不会获得额外的奖励梯度。网络自然失去了“继续扩大步长”的动力,从而有效避免了步子迈得太大。

现在,我们来看相反的极端情况。

🤔 思考:如果 Critic 发现某个动作是个糟糕的尝试,即 优势函数 \(\hat{A}_t < 0\)。此时策略更新想要大幅降低该动作的概率,导致比率跌得很低,比如 \(\rho_t(\theta) = 0.5\)(低于下限 \(0.8\))。结合公式中的 \(\text{clip}\) 和 \(\min\),此时目标函数会取哪一项?它又是如何起到保护作用的?

解析当优势函数为负数(\(\hat{A}_t < 0\))时,比较大小时要注意负数的方向会反过来。

我们把具体数值代入目标函数的两项来看看:

  1. 未截断项:\(\rho_t(\theta) \hat{A}_t = 0.5 \times \hat{A}_t\)

  2. 截断项:\(\text{clip}(0.5, 0.8, 1.2) \hat{A}_t = 0.8 \times \hat{A}_t\)

因为 \(\hat{A}_t < 0\) 是一个负数,所以乘以较小的正数(0.5)反而会得到一个更大的数(例如:\(0.5 \times (-1) = -0.5\),而 \(0.8 \times (-1) = -0.8\))。

按照公式取最小值:

\[\min(0.5 \hat{A}_t, \; 0.8 \hat{A}_t) = 0.8 \hat{A}_t\]

它最终锁定的值是 \(0.8 \hat{A}_t\) 。

这起到了怎样的保护作用?

当一个动作表现不好(\(\hat{A}_t < 0\))时,算法希望惩罚这个策略,降低该动作出现的概率。

  • 如果不加截断,策略可能会用力过猛,一瞬间把这个动作的概率压降到接近 0,导致策略更新剧烈震荡。

  • 加上截断后,一旦比率跌破 \(0.8\),收益项就固定为常数 \(0.8 \hat{A}_t\),梯度同样归零 。

  • 这样就防止了算法因为单次不好的表现,直接把某个探索方向“一棍子打死”。

总结一下 PPO 的保护机制:

  • 表现好(\(\hat{A}_t > 0\))时:防止奖励过于贪心,把比率上限卡在 \(1 + \epsilon_{\text{clip}}\)。

  • 表现差(\(\hat{A}_t < 0\))时:防止惩罚用力过猛,把比率下限卡在 \(1 - \epsilon_{\text{clip}}\)。

💡 注意裁剪是单侧生效的:当 \(\hat{A}_t > 0\) 但 \(r_t < 1-\epsilon_{\text{clip}}\)(好动作的概率反而被降低了),\(\min\) 会选中未截断项,梯度照常存在,把错误“拉回来”;\(\hat{A}_t < 0\) 且 \(r_t > 1+\epsilon_{\text{clip}}\) 时同理。也就是说,PPO 只限制“朝有利方向走得太远”,从不阻止纠错。

到这里,我们已经搞清楚了 PPO 为什么能“用同一批数据更新多次,却不会像传统 A2C 那样走火入魔”。

10.4 GAE:广义优势估计

PPO 的目标函数里有一个关键输入:优势估计 \(\hat{A}_t\)。它该怎么算?这其实又回到了第 5、7 章的偏差-方差权衡。

🤔 思考:回忆 9.3、9.4 节,优势可以用单步 TD 误差 \(\delta_t\) 估计,也可以用蒙特卡洛回报 \(G_t - V(S_t)\) 估计。这两种估计各有什么缺点?

解析

  • 单步 TD 误差 \(\hat{A}_t = \delta_t = R_{t+1} + \gamma V(S_{t+1}) - V(S_t)\):方差低,但完全依赖 Critic 的 \(V\),Critic 不准时偏差大;
  • 蒙特卡洛 \(\hat{A}_t = G_t - V(S_t)\):无偏,但累加了整条轨迹的随机性,方差大。

这与第 7 章 \(n\)-步回报的困境一模一样,解法也一样:把多步的估计按 \(\lambda\) 几何加权。

① \(n\)-步优势可以写成 TD 误差之和

把相邻的 TD 误差加起来,中间的 \(V\) 会两两抵消(望远镜求和):

\[\delta_t + \gamma\delta_{t+1} = R_{t+1} + \gamma R_{t+2} + \gamma^2 V(S_{t+2}) - V(S_t)\]

一般地,\(n\)-步优势估计为:

\[\hat{A}_t^{(n)} = \sum_{l=0}^{n-1}\gamma^l\delta_{t+l} = \underbrace{R_{t+1} + \dots + \gamma^{n-1}R_{t+n} + \gamma^n V(S_{t+n})}_{n\text{-步回报 } G_{t:t+n}} - V(S_t)\]

② GAE:对所有 \(n\) 做 \(\lambda\) 加权平均

仿照 \(\lambda\)-回报,对 \(\hat{A}_t^{(1)}, \hat{A}_t^{(2)}, \dots\) 按 \((1-\lambda)\lambda^{n-1}\) 加权,化简后得到非常简洁的形式(Schulman et al., 2016):

\[\hat{A}_t^{\text{GAE}(\gamma,\lambda)} = \sum_{l=0}^{\infty}(\gamma\lambda)^l\,\delta_{t+l}\]

两个极端正好对应前面学过的两种估计:

\(\lambda\) 取值 GAE 退化为 特点
\(\lambda = 0\) \(\delta_t\)(单步 TD 误差) 偏差高、方差低
\(\lambda = 1\) \(\sum_l \gamma^l\delta_{t+l} = G_t - V(S_t)\)(MC 优势) 无偏、方差高
\(0<\lambda<1\)(常用 \(0.95\)) 两者折中 实践中效果最好

③ 递推计算

实现时不需要真的求无穷和,而是从轨迹末尾倒着递推,一次遍历即可:

\[\hat{A}_t = \delta_t + \gamma\lambda\,(1 - \text{done}_t)\,\hat{A}_{t+1}\]
def compute_gae(rewards, values, dones, next_value, gamma=0.99, lam=0.95):
    advantages = np.zeros(len(rewards))
    gae = 0.0
    for t in reversed(range(len(rewards))):
        v_next = next_value if t == len(rewards) - 1 else values[t + 1]
        delta = rewards[t] + gamma * v_next * (1 - dones[t]) - values[t]
        gae = delta + gamma * lam * (1 - dones[t]) * gae
        advantages[t] = gae
    returns = advantages + values   # Critic 的回归目标
    return advantages, returns

注意最后一行:\(\hat{A}_t + V(S_t)\) 就是 \(\lambda\)-回报,正好拿来当 Critic 的训练目标。

④ 数值例子

设 \(\gamma = 1\)、\(\lambda = 0.5\)。一个 3 步的回合:前两步奖励为 \(0\),最后一步拿到 \(+10\) 后结束。Critic 当前的估计为 \(V(s_0) = 4,\ V(s_1) = 5,\ V(s_2) = 8\),终止状态价值为 \(0\)。

先算每一步的 TD 误差:

  • \(\delta_0 = 0 + V(s_1) - V(s_0) = 5 - 4 = 1\)
  • \(\delta_1 = 0 + V(s_2) - V(s_1) = 8 - 5 = 3\)
  • \(\delta_2 = 10 + 0 - V(s_2) = 10 - 8 = 2\)

再从后往前递推 GAE:

  • \(\hat{A}_2 = \delta_2 = 2\)
  • \(\hat{A}_1 = \delta_1 + \gamma\lambda\,\hat{A}_2 = 3 + 0.5 \times 2 = 4\)
  • \(\hat{A}_0 = \delta_0 + \gamma\lambda\,\hat{A}_1 = 1 + 0.5 \times 4 = 3\)

对比 \(s_0\) 处三种优势估计:

方法 \(\hat{A}_0\) 说明
单步 TD(\(\lambda=0\)) \(\delta_0 = 1\) 只看一步,完全依赖 Critic
GAE(\(\lambda=0.5\)) \(3\) 折中
蒙特卡洛(\(\lambda=1\)) \(1 + 3 + 2 = 6 = G_0 - V(s_0) = 10 - 4\) 看完整回合,完全依赖真实奖励

GAE 的结果正好落在两个极端之间。Critic 对应的回归目标为 \(\hat{A}_t + V(s_t)\),即 \(s_0: 7,\ s_1: 9,\ s_2: 10\)。

10.5 完整损失与训练流程

实际训练时,PPO 的 Actor 和 Critic 通常共享一个总损失(或共享部分网络),由三项组成:

\[\mathcal{L}(\theta, \phi) = \underbrace{-J^{\text{CLIP}}(\theta)}_{\text{策略项}} + c_v \underbrace{\hat{\mathbb{E}}_t\left[\left(V_\phi(S_t) - \hat{R}_t\right)^2\right]}_{\text{价值项}} - c_{\text{ent}} \underbrace{\hat{\mathbb{E}}_t\left[\mathcal{H}\left(\pi_\theta(\cdot\mid S_t)\right)\right]}_{\text{熵项}}\]
  • 策略项:10.3 节的裁剪目标 \(J^{\text{CLIP}}\),取负号是因为优化器做的是最小化;
  • 价值项:让 Critic 拟合 GAE 算出的回报目标 \(\hat{R}_t = \hat{A}_t + V(S_t)\);
  • 熵项:9.5 节的熵正则,防止策略过早失去探索能力。

训练流程伪代码

for iteration in range(num_iterations):
    # 1. 采样:用当前策略(此刻即 π_old)与环境交互 T 步
    batch = collect_rollout(policy, env, T)   # 存下 s, a, r, done, log_prob_old, value

    # 2. 计算优势与回报目标(10.4 节 GAE)
    adv, ret = compute_gae(batch.rewards, batch.values, batch.dones, next_value)
    adv = (adv - adv.mean()) / (adv.std() + 1e-8)   # 优势标准化,稳定训练

    # 3. 同一批数据重复训练 K 轮
    for epoch in range(K):
        for mb in minibatches(batch, adv, ret):
            dist = policy(mb.states)
            log_prob = dist.log_prob(mb.actions)
            ratio = torch.exp(log_prob - mb.log_prob_old)          # r_t(θ)

            surr1 = ratio * mb.adv
            surr2 = torch.clamp(ratio, 1 - eps_clip, 1 + eps_clip) * mb.adv
            policy_loss = -torch.min(surr1, surr2).mean()

            value_loss = ((critic(mb.states) - mb.ret) ** 2).mean()
            entropy = dist.entropy().mean()

            loss = policy_loss + c_v * value_loss - c_ent * entropy
            optimizer.zero_grad(); loss.backward(); optimizer.step()

    # 4. 这批数据用完即弃(PPO 仍是近似 on-policy),回到第 1 步重新采样

常用超参数参考

超参数 常见取值 作用
\(\epsilon_{\text{clip}}\) \(0.1 \sim 0.2\) 新旧策略比率的裁剪范围
\(\gamma\) \(0.99\) 折扣因子
\(\lambda\)(GAE) \(0.95\) 优势估计的偏差-方差折中
\(K\)(每批训练轮数) \(3 \sim 10\) 数据复用次数,过大会让新旧策略差距过大
\(c_v\) \(0.5\) 价值损失权重
\(c_{\text{ent}}\) \(0.0 \sim 0.01\) 熵正则权重
学习率 \(3\times10^{-4}\) Adam 优化器常用值

第 11 章 RLHF 与大语言模型

11.1 强化学习要素的映射

PPO 最广为人知的应用,是训练 ChatGPT 这类大语言模型。在大语言模型(LLM)的世界里,RLHF(Reinforcement Learning from Human Feedback,基于人类反馈的强化学习) 🤝 是让模型从一个“只会续写文字的统计机器”,蜕变成“乐于助人、遵循指令的智能助手”的关键技术。

我们可以将之前学到的强化学习要素,直接映射到大模型的生成场景中:

  • 🤖 智能体(Agent / Policy \(\pi\)):就是正在被优化的大语言模型本身。

  • 📍 状态(State, \(S\)):用户输入的提示词(Prompt),以及模型在当前步骤之前已经生成的所有上下文。

  • 🕹️ 动作(Action, \(A\)):模型在词表(Vocabulary)中选择输出的下一个词元(Token),或者生成整段回复。

  • 🏆 奖励(Reward, \(R\)):由奖励模型对整段回复打分,再减去 KL 惩罚(见 11.2、11.3 节)。

用第 10 章的 PPO 来训练时,一共会同时用到四个模型:

模型 是否训练 作用
🎭 Actor(策略模型) ✅ 训练 正在被微调的语言模型,负责根据 Prompt 生成回答
⚖️ Critic(价值模型) ✅ 训练 预估当前生成前缀的未来期望收益 \(V(s)\),用于计算优势 \(\hat{A}_t\)(GAE)
🏆 Reward Model(奖励模型) ❄️ 冻结 对完整回复打分,充当“裁判”
🧭 Reference Model(参考模型) ❄️ 冻结 RL 开始前的 SFT 模型快照,用来计算 KL 惩罚(SFT 见下方说明)

📌 SFT(Supervised Fine-Tuning,监督微调):在强化学习之前,先用人工写好的“指令-回答”数据对预训练模型做一轮普通的监督训练,让它学会基本的对话和遵循指令。RLHF 的强化学习阶段就是从这个 SFT 模型出发的(完整流程见 11.4 节)。

11.2 奖励模型:从成对偏好中学习

在传统的游戏或自动驾驶中,环境会直接给出得分(比如得分 \(+10\) 或碰撞扣分)。但在大模型对话中,用户提出的问题千奇百怪(比如“写一首诗”或“解释量子力学”)。

这就带来了一个核心难题:奖励信号(Reward, \(R\))该从何而来?

🤔 思考:在训练时,每次模型生成一句话,如果让真实人类坐在电脑前实时给每一步打分,在工程落地时会遇到什么最大的瓶颈?

解析让真人实时打分,面对数以亿计的生成样本,会遇到这几个致命问题:

  • 💸 成本与吞吐量限制:人工打分速度极慢且成本高昂,根本无法跟上 GPU 每秒成千上万次的并行训练需求。

  • ⚖️ 主观一致性差:不同的人对同一个回答的标准不同,甚至同一个人在疲惫时打分也会飘忽不定,导致奖励信号充满噪声。

为了解决这个“无法让人类实时打分”的问题,强化学习的研究者们想出了一个巧妙的方案:找一个“代理人”来替人类打分。

这个在 RLHF 中专门负责模仿人类偏好的组件,通常被称为奖励模型(Reward Model, RM) 🏆。

🤔 思考:既然我们无法让人类去评价海量的生成结果,该如何先用较少的人工标注数据,去训练出这样一个能够自动打分的奖励模型(Reward Model)呢?它通常学习的是什么样的标注数据?

解析为了训练这个奖励模型(Reward Model, 简称 RM) 🏆,研究人员发现直接让人类打绝对分数(比如“这篇回答打 8.5 分,那篇打 7.2 分”)非常困难且标准不一。

因此,业界采用了一种更符合人类直觉的方式:成对对比排序(Pairwise Ranking) ⚖️。

具体流程如下:

  • 📝 准备提示词:给模型一个提示词(Prompt),让它生成两个或多个不同的候选回答(比如回答 A 和回答 B)。

  • 👥 人类做选择题:标注人员不需要打绝对分,只需要判断“回答 A 比回答 B 更好”(\(A \succ B\))。人类很擅长做这种两两对比。

  • 🎓 训练判分网络:奖励模型通常也是一个预训练语言模型,输入是“提示词 + 回答”,输出是一个实数分值。通过损失函数(如 Bradley-Terry 模型),优化 RM 的参数,使得胜出回答的得分显著高于落败回答。

Bradley-Terry 损失:设奖励模型为 \(r_\psi(x, y)\),对同一提示词 \(x\),人类偏好回答 \(y_w\)(win)胜过 \(y_l\)(lose)。Bradley-Terry 模型假设“\(y_w\) 胜出”的概率由两者的分差决定:

\[P(y_w \succ y_l \mid x) = \sigma\big(r_\psi(x, y_w) - r_\psi(x, y_l)\big)\]

其中 \(\sigma\) 是 Sigmoid 函数。训练时最大化人类标注的似然,也就是最小化:

\[\mathcal{L}_{\text{RM}}(\psi) = -\mathbb{E}_{(x, y_w, y_l)}\left[\log \sigma\big(r_\psi(x, y_w) - r_\psi(x, y_l)\big)\right]\]

直观理解:分差越大,\(\sigma\) 越接近 1,损失越接近 0;如果模型把落败回答打得更高,损失就会很大。注意 RM 只学习相对分差,所以打出的绝对分值本身没有意义,只有比较才有意义。

举个例子:对同一个问题,人类标注“回答 A 比回答 B 好”。

RM 当前的打分 分差 \(P(A \succ B)\) 损失 \(-\log P\) 说明
\(r(A) = 2.0,\ r(B) = 0.5\) \(+1.5\) \(\sigma(1.5) = 0.818\) \(0.201\) 判断正确,损失小
\(r(A) = 0.5,\ r(B) = 2.0\) \(-1.5\) \(\sigma(-1.5) = 0.182\) \(1.701\) 判断反了,损失大
\(r(A) = 102.0,\ r(B) = 100.5\) \(+1.5\) \(0.818\) \(0.201\) 与第一行完全等价

第三行说明了“只有分差有意义”:把两个分数同时加上 \(100\),损失完全不变。

11.3 奖励破解与 KL 约束

现在,我们有了一个不知疲倦的自动打分机(RM)。接下来就可以让大模型(Actor)不断生成回答,RM 实时给它打分,再用 PPO 算法来更新大模型。

但这里隐藏着一个经典的强化学习难题——奖励破解(Reward Hacking) 。

🤔 思考:如果完全不加限制,只让大模型全力讨好这个奖励模型,大模型可能会学会哪些“投机取巧”的作弊行为,反而导致回答质量变差?

解析奖励模型只是人类偏好的一个不完美的代理。当大模型只一味追求 RM 的高分时,往往会钻 RM 的漏洞,出现以下几种著名的 奖励破解(Reward Hacking) 现象:

  • 📜 冗长啰嗦(Verbosity Bias):RM 往往有“长文本看起来更详尽、更有条理”的偏好。模型会发现只要堆砌大量废话、罗列长列表,就能轻松拿高分,导致回答变得极其冗长且空洞。

  • 🍯 阿谀奉承(Sycophancy):模型会无条件迎合用户的观点(哪怕用户的观点在事实上是错误的),一味说好话,失去客观性和纠错能力。

  • 🎭 格式套路化:模型会过度模仿 RM 偏好的排版(比如大量使用特定的标记、夸张的免责声明),内容千篇一律。

  • 📉 退化与胡言乱语:在最极端的情况下,模型甚至会生成一些人类读起来毫无逻辑、语法错乱,但正好击中 RM 评分高频特征词的“乱码”。

为了防止大模型在强化学习训练中完全“丢掉底线”、彻底被 RM 带偏,研究人员通常会给训练目标加上一个“紧箍咒”。它会约束当前正在训练的模型,使其输出概率分布不能偏离强化学习开始前的参考模型(Reference Model,即 SFT 模型)太远。

🤔 思考:用什么数学工具来衡量两个概率分布之间“偏离了多远”?

解析正是 KL 散度(Kullback-Leibler Divergence,又称相对熵)!在 RLHF 中,这个机制被称为 KL 惩罚项(KL Penalty) 🛡️。

① 最终的优化目标

从整条回复 \(y\) 来看,优化目标是两部分的结合:

\[\text{最终奖励 } \tilde{r} = r_\psi(x, y) - \beta \cdot D_{\text{KL}}(\pi_\theta(y \mid x) \parallel \pi_{\text{ref}}(y \mid x))\]

在 PPO 的实际实现中,这个奖励会被拆到每个 token 上:每生成一个 token \(y_t\),都扣除一次该位置的 KL 惩罚,而 RM 的分数只在回复的最后一个 token 上给出:

\[r_t = -\beta \log\frac{\pi_\theta(y_t \mid x, y_{<t})}{\pi_{\text{ref}}(y_t \mid x, y_{<t})} + \mathbf{1}(t = T)\cdot r_\psi(x, y)\]
  • 🏆 \(r_\psi\):奖励模型打出的分数(鼓励讨好人类偏好)。

  • ⛓️ \(\beta \cdot D_{\text{KL}}\):当前策略网络 \(\pi_\theta\) 与参考模型(SFT 模型)\(\pi_{\text{ref}}\) 之间的 KL 散度惩罚。

  • 🎛️ \(\beta\):控制约束强弱的超参数。

② 这个“紧箍咒”如何起效?

  • 如果模型老老实实说话:当前生成的概率分布与参考模型相似,\(D_{\text{KL}} \approx 0\),扣分极少,主要拿 RM 的正向奖励。

  • 如果模型想“走捷径”作弊:比如为了迎合 RM 而输出极端概率的套话或乱码,它与参考模型的分布差距就会急剧拉大,\(D_{\text{KL}}\) 飙升,导致综合得分被狠狠倒扣。

🤔 思考:如果把这个超参数 \(\beta\) 设得极大(比如趋近于无穷大),模型在训练时会表现出什么状态?它还能学会人类偏好的新风格吗?

解析当 \(\beta \to \infty\) 时,偏离参考模型的惩罚变得无穷大:

  • 模型原地僵死:为了不被扣除巨额惩罚分,模型只能完全保持与参考模型一模一样的概率输出。

  • 奖励信号失效:无论人类或奖励模型(RM)给出多高的分数,都无法抵消偏离参考模型带来的巨大扣分,导致 RL 训练形同虚设。

因此,\(\beta\) 的选取是一个典型的平衡艺术 ⚖️:

  • \(\beta\) 太小:模型会肆无忌惮地利用漏洞(Reward Hacking 🕳️),甚至退化成人类读不懂的高分乱码。

  • \(\beta\) 太大:模型不敢做任何调整,完全学不会对齐人类偏好。

  • 适中的 \(\beta\):既保留参考模型原有的语言连贯性与知识储备,又促使其逐步朝人类偏好的表达风格演进。

11.4 完整流程

现在,整个 RLHF 的拼图已经完整了:

  1. 预训练语言模型(基座模型)📚

  2. 监督微调(SFT):用高质量的指令-回答数据微调基座模型,得到会遵循指令的初始策略,它同时也作为后续的参考模型 \(\pi_{\text{ref}}\) 📝

  3. 收集偏好数据并训练奖励模型(RM)(11.2 节)⚖️

  4. 以 SFT 模型为 Actor 的初始化,通过 PPO 算法(第 10 章)最大化奖励,同时引入 KL 散度 作为紧箍咒(11.3 节),防止策略崩坏 🚀🛡️

11.5 DPO:跳过奖励模型与 PPO

基于 PPO 的 RLHF 效果很好,但工程上很重:训练时要同时加载四个模型(11.1 节),PPO 的超参数也多,训练不够稳定。DPO(Direct Preference Optimization,Rafailov et al., 2023)提出了一个问题:既然奖励模型只是从偏好数据里学出来的中间产物,能不能直接用偏好数据训练策略?

① KL 约束下的最优策略有解析解

回顾 11.3 节,RLHF 的优化目标是“拿高分,同时别离参考模型太远”:

\[\max_{\pi}\ \mathbb{E}_{x,\ y\sim\pi(\cdot\mid x)}\left[r(x, y)\right] - \beta\, D_{\text{KL}}\big(\pi(\cdot\mid x)\,\parallel \pi_{\text{ref}}(\cdot\mid x)\big)\]

这个问题的最优解可以直接写出来:

\[\pi^*(y\mid x) = \frac{1}{Z(x)}\,\pi_{\text{ref}}(y\mid x)\,\exp\!\left(\frac{r(x, y)}{\beta}\right)\]

其中 \(Z(x) = \sum_y \pi_{\text{ref}}(y\mid x)\exp(r(x,y)/\beta)\) 是归一化常数。直观上,最优策略就是在参考模型的基础上,按奖励的指数给每个回答“加权”:奖励越高,概率被放大得越多;\(\beta\) 越大,放大得越温和。

② 把奖励“反解”出来

对上式取对数并移项,奖励可以用策略本身来表示:

\[r(x, y) = \beta \log\frac{\pi^*(y\mid x)}{\pi_{\text{ref}}(y\mid x)} + \beta \log Z(x)\]

问题在于 \(Z(x)\) 要对所有可能的回答求和,根本算不出来。

🤔 思考:回忆 11.2 节的 Bradley-Terry 模型,偏好概率只依赖于两个回答的奖励之差 \(r(x, y_w) - r(x, y_l)\)。把上面的表达式代进去,那个算不出来的 \(\beta\log Z(x)\) 会怎样?

解析\(y_w\) 和 \(y_l\) 对应的是同一个提示词 \(x\),所以两边的 \(\beta \log Z(x)\) 完全相同,相减时正好抵消!于是偏好概率只剩下策略与参考模型之比:

\[P(y_w \succ y_l\mid x) = \sigma\!\left(\beta\log\frac{\pi^*(y_w\mid x)}{\pi_{\text{ref}}(y_w\mid x)} - \beta\log\frac{\pi^*(y_l\mid x)}{\pi_{\text{ref}}(y_l\mid x)}\right)\]

③ DPO 损失

把 \(\pi^*\) 换成待训练的 \(\pi_\theta\),对人类偏好数据做最大似然,就得到 DPO 的损失函数:

\[\mathcal{L}_{\text{DPO}}(\theta) = -\mathbb{E}_{(x, y_w, y_l)}\left[\log\sigma\!\left(\beta\log\frac{\pi_\theta(y_w\mid x)}{\pi_{\text{ref}}(y_w\mid x)} - \beta\log\frac{\pi_\theta(y_l\mid x)}{\pi_{\text{ref}}(y_l\mid x)}\right)\right]\]

它和 11.2 节的奖励模型损失形式完全一样,只是“奖励”换成了隐式奖励 \(\hat{r}_\theta(x, y) = \beta\log\frac{\pi_\theta(y\mid x)}{\pi_{\text{ref}}(y\mid x)}\)。因此常说:语言模型本身就是一个奖励模型。

训练的直观效果:提高好回答 \(y_w\) 相对参考模型的概率,压低坏回答 \(y_l\) 相对参考模型的概率;由于衡量的是相对 \(\pi_{\text{ref}}\) 的变化,KL 约束已经隐含在目标里了。

举个例子:设 \(\beta = 0.1\)。对问题 \(x\),人类认为回答 \(y_w\) 比 \(y_l\) 好。下表中 \(\Delta\log\pi\) 表示“策略模型的序列对数概率 − 参考模型的序列对数概率”:

训练阶段 \(y_w\) 的 \(\Delta\log\pi\) \(y_l\) 的 \(\Delta\log\pi\) 隐式奖励 \(\hat{r}(y_w),\ \hat{r}(y_l)\) \(\sigma(\text{差})\) DPO 损失
训练开始(\(\pi_\theta = \pi_{\text{ref}}\)) \(0\) \(0\) \(0,\ 0\) \(\sigma(0) = 0.5\) \(0.693\)
训练一段时间后 \(+2.0\) \(-3.0\) \(+0.2,\ -0.3\) \(\sigma(0.5) = 0.622\) \(0.474\)
两者概率都下降了 \(-1.0\) \(-6.0\) \(-0.1,\ -0.6\) \(\sigma(0.5) = 0.622\) \(0.474\)
  • 第一行:刚开始策略和参考模型一样,隐式奖励都是 \(0\),模型分不出好坏,偏好概率是 \(0.5\)。
  • 第二行:模型提高了好回答的概率、压低了坏回答的概率,隐式奖励拉开差距,损失下降。
  • 第三行:两个回答的概率都比参考模型低,但只要“坏回答降得更多”,损失和第二行完全一样。这说明 DPO 只关心好坏回答之间的相对差距。实践中这也是一个已知的副作用:训练后好回答本身的概率可能也在下降。
def dpo_loss(policy, ref, x, y_w, y_l, beta=0.1):
    # 序列 log 概率 = 各 token 的 log 概率之和
    pi_w, pi_l = policy.seq_logprob(x, y_w), policy.seq_logprob(x, y_l)
    with torch.no_grad():
        ref_w, ref_l = ref.seq_logprob(x, y_w), ref.seq_logprob(x, y_l)
    logits = beta * ((pi_w - ref_w) - (pi_l - ref_l))
    return -F.logsigmoid(logits).mean()

④ DPO 与 PPO-RLHF 的对比

维度 PPO-RLHF DPO
训练阶段 SFT → 训练 RM → PPO SFT → 直接在偏好数据上训练
需要的模型 Actor、Critic、RM、Reference 四个 Policy、Reference 两个
训练时是否采样生成 是,需要在线生成回答(on-policy) 否,只用离线偏好数据,像监督学习一样训练
稳定性与成本 超参数多、显存占用大、训练较难调 简单稳定、成本低
局限 —— 受限于固定的偏好数据集,缺少在线探索;可能过拟合偏好数据

💡 从强化学习的视角看,DPO 已经不再有“与环境交互、试错”的过程,它更像一种带 KL 约束的偏好监督学习。它的出现说明:理解了 RL 目标的数学结构之后,有时可以绕开 RL 的训练循环,直接求解。

第 12 章 总结

12.1 算法谱系

强化学习算法可以沿三条主线来分类,本笔记涉及的算法对应如下(同策略/异策略的定义见 6.3 节):

维度 类别 A 类别 B
是否需要环境模型 基于模型:动态规划(策略迭代、值迭代) 无模型:MC、TD、SARSA、Q-learning、DQN、策略梯度、PPO、DDPG/TD3/SAC
学什么 基于价值:Q-learning、SARSA、DQN 系列 基于策略:REINFORCE;两者结合:A2C、PPO、DDPG、TD3、SAC
行为策略与目标策略 同策略(On-policy):SARSA、REINFORCE、A2C、PPO(近似) 异策略(Off-policy):Q-learning、DQN 系列、DDPG、TD3、SAC

从更新方式看,还有一条贯穿全文的偏差-方差主线:

\[\underbrace{\text{TD(0)}}_{\text{偏差高 · 方差低 · 每步更新}} \;\longleftrightarrow\; \underbrace{n\text{-步 / TD}(\lambda) / \text{GAE}}_{\text{用 } n \text{ 或 } \lambda \text{ 折中}} \;\longleftrightarrow\; \underbrace{\text{蒙特卡洛}}_{\text{无偏 · 方差高 · 回合结束才更新}}\]

各章节之间的演进关系可以概括为:

  1. MDP → 贝尔曼方程:把“长期回报”写成递归形式(第 2、3 章);
  2. 动态规划:模型已知时直接迭代求解(第 4 章);
  3. MC / TD:模型未知时用采样代替期望(第 5、6、7 章);
  4. DQN:状态太多时用神经网络代替 Q 表(第 8 章);
  5. 策略梯度 / Actor-Critic:动作连续时直接学策略;想复用数据则走向异策略的 DDPG、TD3、SAC(第 9 章);
  6. PPO:让策略更新既能复用数据又不失稳(第 10 章);
  7. RLHF / DPO:奖励来自人类偏好时,用奖励模型 + PPO + KL 约束;或利用 KL 约束问题的解析解,用 DPO 直接在偏好数据上训练(第 11 章)。

12.2 核心公式速查

名称 公式 出处
折扣回报 \(G_t = R_{t+1} + \gamma G_{t+1}\) 3.1
贝尔曼期望方程 \(V^\pi(s) = \sum_a \pi(a\mid s)\sum_{s'}P(s'\mid s,a)\left[R + \gamma V^\pi(s')\right]\) 3.2
贝尔曼最优方程 \(Q^*(s,a) = \sum_{s'}P(s'\mid s,a)\left[R + \gamma \max_{a'}Q^*(s',a')\right]\) 3.4
值迭代 \(V_{k+1}(s) \leftarrow \max_a \sum_{s'}P(s'\mid s,a)\left[R + \gamma V_k(s')\right]\) 4.2
TD(0) \(V(S_t) \leftarrow V(S_t) + \alpha\left[R_{t+1} + \gamma V(S_{t+1}) - V(S_t)\right]\) 6.1
SARSA \(Q(S_t,A_t) \leftarrow Q + \alpha\left[R_{t+1} + \gamma Q(S_{t+1},A_{t+1}) - Q\right]\) 6.2
Q-learning \(Q(S_t,A_t) \leftarrow Q + \alpha\left[R_{t+1} + \gamma \max_a Q(S_{t+1},a) - Q\right]\) 6.2
\(n\)-步回报 \(G_{t:t+n} = \sum_{k=0}^{n-1}\gamma^k R_{t+k+1} + \gamma^n V(S_{t+n})\) 7.1
资格迹 \(E_t(s) = \gamma\lambda E_{t-1}(s) + \mathbf{1}(S_t = s)\) 7.3
DQN 目标 \(y = r + \gamma \max_{a'} Q_{\theta^-}(s', a')\) 8.2
Double DQN 目标 \(y = r + \gamma Q_{\theta^-}\left(s', \arg\max_{a'}Q_\theta(s',a')\right)\) 8.3
策略梯度 \(\nabla_\theta J = \mathbb{E}\left[\nabla_\theta \log\pi_\theta(A_t\mid S_t)\,\Psi_t\right]\) 9.2
确定性策略梯度(DDPG) \(\nabla_\theta J = \mathbb{E}\left[\nabla_a Q_\phi(s,a)\big\vert_{a=\pi_\theta(s)}\nabla_\theta\pi_\theta(s)\right]\) 9.6
SAC 目标值 \(y = r + \gamma\left(\min_j Q_{\bar\phi_j}(s',a') - c_{\text{ent}}\log\pi_\theta(a'\mid s')\right)\) 9.6
GAE \(\hat{A}_t = \sum_{l}(\gamma\lambda)^l\delta_{t+l}\) 10.4
PPO-Clip \(\min\left(\rho_t\hat{A}_t,\ \text{clip}(\rho_t, 1\pm\epsilon_{\text{clip}})\hat{A}_t\right)\) 10.3
RM 损失 \(-\log\sigma\left(r_\psi(x,y_w) - r_\psi(x,y_l)\right)\) 11.2
RLHF 奖励 \(r_\psi(x,y) - \beta\, D_{\text{KL}}(\pi_\theta \parallel \pi_{\text{ref}})\) 11.3
DPO 损失 \(-\log\sigma\left(\beta\log\frac{\pi_\theta(y_w\mid x)}{\pi_{\text{ref}}(y_w\mid x)} - \beta\log\frac{\pi_\theta(y_l\mid x)}{\pi_{\text{ref}}(y_l\mid x)}\right)\) 11.5