
简介《强化学习的数学原理》英文专著PDF由西湖大学赵世钰教授撰写面向希望从数学层面系统理解强化学习的学习者与研究者也适合具备一定数学基础、需要夯实理论框架的读者。全书从基础概念与网格世界示例切入依次讲解状态值与贝尔曼方程、最优状态值、值迭代与策略迭代、蒙特卡洛方法、随机近似、时间差分、值函数近似、策略梯度及Actor-Critic方法附录还讨论序列收敛性。压缩包仅含1个PDF文件大小约18.9MB便于集中阅读与检索。已有848人学习内容以大量例子串联公式推导强调从零开始揭示本质原理可帮助读者掌握核心思想、理解算法背后的数学工具并用于分析强化学习方法的收敛性与设计思路。1. 强化学习不是调参玄学十万行训练代码背后其实只在解一个方程多数人第一次接触强化学习是从一条起伏不定的 reward 曲线开始的跑起来、调学习率、看它涨不涨涨了就说 work 了崩了就开始怀疑环境、怀疑随机种子。这种体验很容易让人把强化学习归到玄学那一栏。但只要把视角往底层推一层任何一次q(s,a) ← q(s,a) α[...]的更新本质上都是在逼近某个方程的解——要么是贝尔曼方程要么是贝尔曼最优方程。收敛与否在写下那个方程的时候就已经基本决定了和你堆多深的网络、采多少样本关系没那么大。《强化学习的数学原理》这份书稿的价值就在这里它没有从智能体怎么和环境交互这种工程视角铺开而是先把网格世界拆成状态、动作、转移、奖励、策略、回报紧接着把问题写成矩阵方程再一路推到不动点。搞懂这条线索那些看起来靠运气调出来的曲线就变成了有据可查的数值过程。2.1 网格世界把环境压缩成四件套整本书只用了一个环境——网格世界。这个选择乍看有点偷懒实则是最省认知成本的做法状态是格子的坐标动作是上下左右撞墙就停在原地到达目标格给正奖励踩到陷阱格给负奖励。一旦环境小到可以用纸笔算出每个状态的值你写的代码算错了就无处可藏。很多人在 CartPole 上跑了几百次最后也说不清问题出在算法还是实现而在一个 5×5 的格子上答案通常是唯一的。形式化之后一个马尔可夫决策过程由五样东西定义状态集合 S、动作集合 A、转移概率 p(s|s,a)、奖励 r(s,a)、折扣因子 γ。转移概率是环境的物理规律奖励是环境给的即时反馈γ 是一个介于 0 和 1 之间的数。策略 π(a|s) 是智能体自己的规则它和环境的区别必须分清楚——策略是你训练的、可以被改的转移概率是你改不了的。这条边界在后面讲无模型方法时会变得极其重要。马尔可夫性要单独说一句。它要求下一状态只依赖当前状态和动作与更早的历史无关。现实任务里这个假设经常不成立比如只看一帧画面判断不了球的运动方向工程上的补救办法是把最近几帧拼成一个状态让拼出来的状态重新满足马尔可夫性。这不是数学上的妥协而是把记忆塞进了状态的表示里。2.2 状态值为什么不能盯着即时奖励看回报 G_t 的定义是把未来所有奖励按 γ 折现加起来G_t R_{t1} γR_{t2} γ²R_{t3} ...折扣因子 γ 的存在有两个理由一个是数学上的一个是经济上的。数学上的理由是如果 γ 1 且任务没有终止这个级数可能不收敛值函数直接发散γ 1 时因为每个奖励都有界级数被一个几何级数压住必然收敛。经济上的理由是同样的奖励早拿到比晚拿到更有价值γ 就是这个时间偏好的量化。状态值 v_π(s) 定义为从状态 s 出发、始终遵循策略 π 时回报的期望v_π(s) E[G_t | S_t s]这里必须取期望因为转移是随机的、策略也可能是随机的同一条轨迹再走一遍回报就不一样。状态值刻画的是这个位置有多好注意它绑定在某个策略上——换一个策略同一个格子的值就变了。很多人第一次看到 v_π(s) 会下意识以为它描述环境其实它描述的是环境 策略的组合。2.3 贝尔曼方程一次递归就把无穷项折没了直接在定义里展开 G_t会得到一个无穷长的和没法算。贝尔曼方程做的唯一一件事就是把第一项拆出来剩下的部分重新识别成下一状态的状态值v_π(s) Σ_a π(a|s) Σ_{s} p(s|s,a) [ r(s,a) γ v_π(s) ]这一步之所以成立靠的是马尔可夫性和期望的线性性。拆完之后无穷项消失了取而代之的是一个关于 v_π 的线性方程组——等号两边都出现了 v_π。这就是它被称为方程而不是公式的原因它不是在算一个值而是在描述一组值之间必须满足的约束关系。把状态维度拉出来写成向量方程变成v_π r_π γ P_π v_π其中 P_π 是 |S|×|S| 的矩阵第 s 行第 s 列的元素是在状态 s 下遵循 π 后转移到 s 的总概率[P_π]_{s,s} Σ_a π(a|s) p(s|s,a)这个矩阵每行加起来等于 1本质上是一个马尔可夫链的转移矩阵。r_π 是把奖励对动作求期望后得到的向量。这一步转换的意义在于原来那个带 Σ 的递归式被压成了线性代数里最标准的形式接下来怎么解全是现成的工具。2.4 闭式解与迭代解有解析解为什么还要迭代既然 v_π r_π γP_π v_π 是线性方程组直接移项就能得到闭式解v_π (I - γP_π)^{-1} r_π这个式子成立的前提是 I - γP_π 可逆。因为 P_π 的谱半径不超过 1γ 1 时 γP_π 的谱半径严格小于 1所以 I - γP_π 一定可逆解唯一。数学上很漂亮但工程上有代价矩阵求逆是 O(|S|³)状态数上万时基本没法用。另一条路是把它当成不动点来迭代v_{k1} r_π γP_π v_k每次迭代只做一次矩阵-向量乘法复杂度 O(|S|²)。更重要的是这个迭代的误差按 γ^k 的速度几何衰减γ 越接近 1 收敛越慢——这解释了为什么折扣因子设成 0.99 时训练前期总是磨磨蹭蹭。下面这段代码把两种解法放在一起可以直接跑import numpy as np def policy_eval_closed(P_pi, r_pi, gamma): 闭式解v (I - gamma * P_pi)^(-1) r_pi P_pi: (S, S)策略诱导的转移矩阵 r_pi: (S,)策略下的期望即时奖励 状态数上千时慎用O(S^3) S P_pi.shape[0] return np.linalg.solve(np.eye(S) - gamma * P_pi, r_pi) def policy_eval_iter(P_pi, r_pi, gamma, tol1e-10, max_iter100000): 迭代解反复套 v - r gamma * P v 每轮 O(S^2)误差每轮至少缩 gamma 倍 v np.zeros(P_pi.shape[0]) for k in range(max_iter): v_next r_pi gamma * P_pi v if np.max(np.abs(v_next - v)) tol: return v_next, k 1 v v_next return v, max_iter两个函数算出来的是同一个向量差别只在代价结构。闭式解一步到位但内存和算力开销大迭代解慢但可以随时中断、随时看当前误差。这个可中断的性质在后面会变得关键当模型未知、只能靠采样估计的时候闭式解彻底失效剩下的只有迭代这一条路而迭代的形式恰好允许你把某一步的精确计算换成采样估计。顺带说一句动作值。状态值回答这个位置有多好动作值 q_π(s,a) 回答在这个位置做这个动作有多好。两者的关系是 q_π(s,a) Σ_{s} p(s|s,a)[r(s,a) γv_π(s)]也就是把贝尔曼方程里对动作的那层求和拆掉。为什么需要它因为选动作的时候要比较的是做 a 和做 b 哪个好而 v_π(s) 已经把动作平均掉了没法用来做决策。这个区分在后面讲无模型方法时会直接决定算法能不能落地。3. 贝尔曼最优方程max 算子凭什么保证迭代一定收敛3.1 从评估一个策略到找一个最好的策略上一章的贝尔曼方程解决的是评估问题给定 π算出 v_π。但强化学习真正想要的是一个好策略。定义最优状态值 v*(s) max_π v_π(s)即在所有可能的策略里挑出使该状态值最大的那个。这个定义有个隐患max 是对无穷多个策略取的怎么保证最大值真的取得到答案分两步。第一步在所有策略里一定存在一个确定性的、只依赖当前状态的策略不劣于任何其他策略所以搜索空间可以从所有策略缩到确定性策略而确定性策略的数量是有限的 |A|^|S|最大值必然存在。第二步这个最优策略可以用贪心方式从 v* 直接读出来π*(a|s) 1 当 a argmax_a q*(s,a)这条性质是整个领域的基石。它的意思是你不需要在策略空间里搜索只要拿到最优值函数策略是免费的。3.2 贝尔曼最优方程max 让线性变成了非线性把贝尔曼方程里的对动作求期望换成对动作取最大就得到贝尔曼最优方程BOEv(s) max_a Σ_{s} p(s|s,a) [ r(s,a) γ v(s) ]写成矩阵-向量形式是 v max_a (r_a γP_a v)这里的 max 是逐元素取的——对每个状态分别选出让它最大的那个动作。这个细节很容易被写错有人会试图把它写成 v max_π (r_π γP_π v)形式上像但那个 max 是对策略取的而策略是动作上的分布两个 max 的含义完全不同。差别在哪前一个 max 作用在向量上得到的是逐状态的凸函数后一个 max 作用在策略集合上本质是线性规划。前者可以直接迭代后者不行。书里强调这一点的用意是BOE 之所以需要压缩映射定理正是因为右端那个逐元素 max 破坏了线性性闭式解那一套用不了了。还有一个重要的观察如果已知 v*那么 q*(s,a) 可以直接算出来最优动作就是让 q* 最大的那个。而当多个动作并列最优时任何在这些动作上的分布都是最优策略——这解释了为什么实践中不同的随机种子会收敛到行为不同、但值相同的策略上。3.3 压缩映射定理γ 到底压住了什么定义一个算子 f它的输入是一个值向量输出是 BOE 的右端f(v)(s) max_a Σ_{s} p(s|s,a)[r(s,a) γv(s)]如果能证明 f 是一个压缩映射那么它就只有一个不动点且从任意初值出发反复迭代 f 都会收敛到这个不动点。压缩性用无穷范数写出来是||f(v1) - f(v2)||∞ ≤ γ ||v1 - v2||∞推导只用两步。第一步max 算子本身是 1-Lipschitz 的两个向量逐元素取 max差的最大值不会超过原向量差的最大值。第二步转移概率对 s 求和等于 1所以 Σ_{s} p(s|s,a) 乘上任何不超过 δ 的量结果也不超过 δ。两步合起来γ 原封不动地保留下来压缩系数就是 γ。γ 在这里的角色变得很清晰它不只是折扣这样一个含糊的调节旋钮它直接就是收敛速率。γ 0.9 意味着每迭代一次误差至少缩到 0.9 倍γ 0.999 则是每轮只缩千分之一。这就解释了那个常见的困惑为什么把 γ 调大之后训练变得又慢又抖因为你不只是改变了任务的目标更看重远期还改变了求解算法的收敛速度。由压缩映射可以直接得到两个结论不动点唯一v*、迭代误差满足 ||v_k - v*||∞ ≤ γ^k ||v_0 - v*||∞。后一个式子给了你一个可以写进代码的停止准则当相邻两次迭代的差小于 (1-γ)/γ 乘以容差时就能保证当前结果与真解的误差在容差之内。3.4 值迭代与策略迭代解同一个方程的两条路径值迭代就是直接把 f 反复作用v_{k1}(s) max_a Σ_{s} p(s|s,a)[r(s,a) γv_k(s)]它的本质是一边评估一边改进糅在一起做。每次迭代都同时完成了两步用当前的 v 算出每个动作的 q然后贪心地选最大值。因为 v_k 本身还不是真值这个贪心是短视的但压缩映射保证了短视的累积仍然指向正确方向。策略迭代走的是另一条路把评估和改进显式拆开先完整地算出当前策略的 v_π内层循环可能需要迭代很多轮再根据 v_π 贪心得到新策略外层循环然后重复。它的收敛性来自策略改进定理——贪心地基于 v_π 更新策略得到的新策略一定不劣于旧策略且如果不等至少有一个状态的值严格提升。因为确定性策略的数量有限这个过程必然在有限步内停在一个不再变化的位置那个位置就是最优。两条路的代价结构不一样值迭代每轮很便宜但轮数多策略迭代每轮很贵内层要收敛但外层轮数通常很少。书里给出的第三条路——截断策略迭代——把它们统一了内层不跑到底只跑固定 k 轮。k 1 时退化成值迭代k ∞ 时就是策略迭代。这个统一视角在工程上很有用它告诉你中间那些评估跑几轮就换的变体不是随意发明出来的而是同一个谱系上的不同位置。下面这段代码用同一个网格世界把三种算法并排实现方便对照import numpy as np def value_iteration(P, r, gamma, tol1e-8): P: (S, A, S) 转移概率r: (S, A) 即时奖励 S, A, _ P.shape v np.zeros(S) while True: q r gamma * np.einsum(sas,s-sa, P, v) # 每个动作的 q v_next q.max(axis1) # 逐状态取 max if np.max(np.abs(v_next - v)) tol: return v_next, q.argmax(axis1) v v_next def policy_evaluation(P_pi, r_pi, gamma, tol1e-8): v np.zeros(P_pi.shape[0]) while True: v_next r_pi gamma * P_pi v if np.max(np.abs(v_next - v)) tol: return v_next v v_next def policy_iteration(P, r, gamma, eval_itersNone): eval_itersNone 表示内层跑到收敛标准策略迭代 eval_itersk 表示内层只跑 k 轮截断策略迭代k1 即值迭代 S, A, _ P.shape pi np.zeros(S, dtypeint) while True: # 用当前策略展开成 P_pi 和 r_pi P_pi P[np.arange(S), pi, :] r_pi r[np.arange(S), pi] if eval_iters is None: v policy_evaluation(P_pi, r_pi, gamma) else: v np.zeros(S) for _ in range(eval_iters): v r_pi gamma * P_pi v # 策略改进对每个状态选 q 最大的动作 q r gamma * np.einsum(sas,s-sa, P, v) new_pi q.argmax(axis1) if np.array_equal(new_pi, pi): return v, pi pi new_pi第三行那个np.einsum是把对 s 求和写成张量缩并等价于对每个 (s,a) 做一次内积。如果你把它换成显式循环会发现值迭代的每一轮和 BOE 的右端是一一对应的——代码结构就是方程结构这不是巧合。eval_iters参数则把值迭代、策略迭代、截断策略迭代收进了同一个函数改一个数字就能在三种算法之间切换。4. 从有模型到无模型蒙特卡洛和随机近似怎么用采样替掉期望4.1 MC Basic把期望换成算术平均前面所有算法都默认你能拿到 p(s|s,a)。现实里这个概率几乎永远拿不到——你没法知道机器人在某个位置执行某个动作后精确地转移到哪个位姿的概率是多少。无模型方法要解决的就是这件事把对模型的依赖全部换掉。策略迭代里有两处用到模型。第一处是策略评估算 v_π 时要用 P_π第二处是策略改进算 q_π 时要用 p(s|s,a)。蒙特卡洛的做法很直接既然 v 和 q 都是期望那就采样用样本均值去估计期望。从状态 s 出发按策略 π 跑完整条轨迹把沿途的回报记下来重复 N 次取平均就得到 v_π(s) 的估计。动作值同理从 (s,a) 出发跑 N 条轨迹取平均。这一步替换带来的连锁反应值得细看。因为不再有模型你没法从 v 推出 q那个式子需要 p所以 MC 方法直接估计 q 而不是 v。这不是设计上的偏好是被逼出来的策略改进必须比较动作而比较动作只能用 q。MC Basic 是最朴素的版本它的缺点也很明显每条轨迹只用来更新一个 (s,a)样本利用率极低而且它要求每个 (s,a) 都能被访问到足够多次否则那些没被采到的组合估计值一直是零。这两个问题分别催生了后面的两个补丁。4.2 Exploring Starts 与 ε-greedy探索的两个补丁第一个补丁叫探索性起点。既然问题是有些 (s,a) 采不到那就强制每条轨迹都从不同的 (s,a) 出发保证覆盖。这个假设在数学上很干净在工程上基本不可行——你没法让真实机器人从任意位姿开始也没法让游戏从任意中间帧启动。所以它更多是一个理论上的过渡形态。第二个补丁是 ε-greedy 策略它把强制探索从环境侧挪到了策略侧。策略不再总是选当前最优动作而是以 1-ε 的概率选贪心动作以 ε 的概率在全部动作上均匀随机π(a|s) 1 - ε ε/|A| 如果 a 是贪心动作 π(a|s) ε/|A| 其他动作这个式子的关键性质是每个动作的概率都严格大于零。只要 ε 0任意 (s,a) 在无限长的轨迹里都会被访问到无穷多次覆盖性就有了保证不再需要探索性起点。同时ε-greedy 关于 q 是单调的——如果某个动作的 q 变大了它在 ε-greedy 下的概率也随之变大这个性质保证了策略改进的正确性不会因为引入随机而丢失。ε 怎么选是个老问题。ε 太大策略长期在随机游走学到的 q 一直带着探索噪声ε 太小某些动作几乎采不到估计偏差会固化下来。常见的做法是前期大后期小但衰减节奏要和任务长度匹配——如果衰减得比学习速度快太多等于没探索过就锁死了策略。4.3 Robbins-Monro步长为什么必须满足两个条件从平均 N 条完整轨迹过渡到每来一个样本就更新一次中间需要一套理论支撑这就是随机近似。Robbins-Monro 算法处理的是这样一类问题你要求方程 g(w) 0 的根但只能拿到 g 的带噪观测。它的迭代形式是w_{k1} w_k - α_k * ĝ(w_k)收敛性依赖步长序列的两个条件Σ α_k ∞ 且 Σ α_k² ∞第一个条件保证步长总和足够大不管起点离根多远都能走到第二个条件保证步长平方和有限后期的扰动不会累积成持续的震荡。1/k 是满足这两个条件的经典选择常数步长则不满足第二条——用常数步长的算法不会精确收敛到不动点而是在附近一个有界的邻域内来回跳。这个结论解释了很多实践中的现象。为什么学习率衰减能提升最终精度因为常数步长下误差有一个由 α 决定的下界衰减才能把这个下界压下去。为什么有些实现用 1/k 反而学得很慢因为早期步长掉得太快还没走到大致正确的区域就已经不怎么动了。折中方案是分段常数或者 1/k^0.6 这类介于两者之间的形式理论保证弱一点实践中更稳。把随机近似用在均值估计上就得到新估计 ← 旧估计 α(新样本 - 旧估计)这个形式。这个式子看起来平平无奇但它是后面所有 TD 方法的骨架把新样本换成回报就是蒙特卡洛把新样本换成一步奖励加下一状态估计就是时序差分。4.4 从 SGD 到 TDbootstrap 换来了什么又付出了什么随机梯度下降是随机近似在优化上的应用。给定目标函数 J(w) E[(v_π(S) - v̂(S,w))²]它的梯度需要知道 v_π 这个真值而你恰恰不知道。用采样替代之后就得到标准的 SGD 更新。TD 学习把这个思路往前推了一步。它不去估计从 s 出发的完整回报而是用一步奖励加上下一状态的当前估计来构造目标v(s_t) ← v(s_t) α [ r_{t1} γ v(s_{t1}) - v(s_t) ]方括号里那项叫 TD error是实际看到的减去原本以为的。这个更新的精妙之处在于目标 r γv(s_{t1}) 里的 v 是你自己的估计用它来更新它自己这个操作叫 bootstrap。bootstrap 带来的好处是方差大幅下降。蒙特卡洛的目标是一整条轨迹的回报轨迹越长随机性累积越多方差越大TD 的目标只有一步随机奖励方差小得多。代价是偏差因为 v(s_{t1}) 本身还不准目标就是有偏的。所以 MC 是无偏但高方差TD 是有偏但低方差这个取舍贯穿了整个领域。书里用一张统一视角把 MC 和 TD 串起来如果把更新目标写成若干步真实奖励加若干步后的估计值那么步数等于轨迹长度时是 MC等于 1 时是 TD(0)中间就是 n-step TD。它们不是互不相干的算法而是同一个谱系上取不同截断点。把 TD 从状态值推广到动作值有两个分支区别只在更新目标里的下一状态值怎么取def sarsa_update(q, s, a, r, s_next, a_next, alpha, gamma, done): # on-policy用实际执行的下一个动作 a_next 来构造目标 target r if done else r gamma * q[s_next, a_next] q[s, a] alpha * (target - q[s, a]) def qlearning_update(q, s, a, r, s_next, alpha, gamma, done): # off-policy不管实际执行什么都假设下一步走最优动作 target r if done else r gamma * q[s_next].max() q[s, a] alpha * (target - q[s, a])两者的差别只有一行但性质完全不同。Sarsa 用实际执行的动作 a_next所以它在评估的就是当前行为策略属于 on-policyQ-learning 用 max 替换 a_next它在评估的是最优策略与行为策略无关属于 off-policy。off-policy 的好处是你可以用一个探索性的策略采样、同时学一个贪心策略样本利用率更高坏处是行为策略和目标任务之间的分布差异会带来偏差这个偏差在表格情形下不影响收敛一旦加上函数逼近就可能炸。5. 排查手册五个让看起来在学其实没在学的坑强化学习的调试难在它不会报错。代码跑得通、损失在下降、曲线也在动但学到的策略就是不对。下面这五条是我在实际项目里反复撞到的按现象 → 原因 → 解决整理。5.1 回报不折扣值函数直接发散现象训练一开始值函数数值就飞快增长几轮之后出现 inf 或 nan或者曲线在某次更新后突然跳到离谱的量级。原因γ 被设成了 1或者任务本身没有终止状态。书里的贝尔曼方程收敛性依赖 γ 1 这个前提一旦取 1级数可能不收敛闭式解里的 (I - γP)^{-1} 也失去意义。更隐蔽的情况是代码里写了 γ 0.99但终止判断有 bug轨迹永远不结束回报累加到后来数值精度扛不住。解决先确认环境真的有吸收态并且终止信号传到了更新公式里——终止那一步的目标必须是 r 本身不能是 r γv(s)。如果任务确实没有自然终止比如持续运行的调度任务就定义平均奖励形式或者人为加一个上限并显式截断。我一般会在训练前先打印一条完整轨迹的回报和它的长度看一眼量级是否合理。5.2 终止状态的值没钉成 0整张表整体漂移现象策略看起来是合理的但所有状态的值都比预期高出一个固定的偏移量而且这个偏移随训练轮数缓慢增长。原因终止状态被当成普通状态参与了自举更新。终止状态之后没有奖励它的值必须永远是 0如果代码里把它也塞进了状态表并且允许被更新那么 v(terminal) 会被 TD error 一点点推高再通过自举把这个偏移传播回所有能到达终止的状态。解决在更新前显式判断 donedone 为真时目标只取 r。状态表初始化时给终止状态一个固定值并且跳过对它的写操作。这一条在表格情形下表现为整体偏移在函数逼近情形下表现为收敛到一个偏高的平台都不是很好定位所以最好在写第一个版本时就把它处理干净。5.3 ε 衰减和探索需求完全脱节现象训练前期学得很快中途突然卡住最终策略总是绕开某片区域或者在某几个状态下反复横跳。原因ε 衰减得太快或者衰减的步数和训练总步数不匹配。策略过早地变成了近似贪心那些还没被充分采样的 (s,a) 就一直停留在早期的错误估计上。ε-greedy 的收敛性依赖每个动作都被无穷多次采到这条前提被破坏估计偏差就固化成了策略错误。解决把 ε 的衰减挂在实际交互步数上而不是 episode 数上并确保每个 (s,a) 的访问次数没有严重的长尾。排查时可以统计一下访问频次分布如果存在大量访问次数为个位数的状态动作对说明探索还不够。另外要注意评估时的 ε 应该设为 0用纯贪心策略去测否则测出来的性能被随机动作拉低容易被误判成学得不好。5.4 离策略更新漏了重要性采样比现象用 Q-learning 或者带经验回放的实现时值函数在训练一段时间后开始缓慢发散或者收敛到一个明显次优的固定点。原因off-policy 的更新假设了目标策略和行为策略之间的分布关系在表格情形下它恰好能容忍这个差异但一旦进入函数逼近或者使用了重要性采样却没做截断方差会急剧放大。反过来如果算法本身要求重要性采样比如某些 off-policy 策略梯度方法而代码里直接省掉了那个比值算出来的梯度方向就是错的。解决先明确你的算法是 on-policy 还是 off-policy再决定要不要重要性采样。需要的话比值必须用截断clipping控制方差否则单条长轨迹的连乘会让比值指数爆炸。经验回放缓冲区的数据分布也要检查如果早期用随机策略采的数据在缓冲区里占了大部分实际训练用的分布和目标策略差得很远学习会变得很不稳定。5.5 函数逼近下把 TD 当成真梯度用现象表格实现完全正常换成线性函数或者神经网络之后权重越来越大最后发散。原因函数逼近下TD 更新用的目标是 r γv̂(s,w)这个目标本身也依赖 w。真正的梯度要把它一起求导但半梯度 TD 直接把它当常数所以更新的方向不是目标函数的真梯度。当状态被反复访问、特征之间高度相关、又叠加 off-policy 的分布偏移时这个近似误差会累积成发散——这就是所谓的死亡三角函数逼近 自举 离策略三个凑齐就有风险。解决优先从特征设计入手让状态表示之间的相关性降下来其次降低学习率、缩小网络再不行就引入目标网络把自举的目标固定几个步长或者用经验回放打散样本的时间相关性。诊断的时候可以同时监控权重范数和 TD error 的绝对值如果权重范数单调增长而 TD error 不降基本就是这个问题。6. 设计取舍为什么这本书把十章画成了一张二维地图书稿开篇有一张图把十章摊在两个维度上横轴是有模型 → 无模型纵轴是表格表示 → 函数表示。第 2 到 4 章在有模型、表格的象限里讲贝尔曼方程、贝尔曼最优方程、值迭代和策略迭代第 5 到 7 章进入无模型、表格讲蒙特卡洛、随机近似、时序差分第 8 章跨到函数逼近第 9、10 章在函数表示上做策略梯度和演员-评论家。这个排布顺序不是随手排的它背后有一个很务实的设计取向先让你在能算对的地方算对再去处理算不对的地方。如果一上来就讲无模型的 Q-learning你只知道更新公式长什么样但不知道它在逼近什么而先在有模型的情形下把贝尔曼方程解出来无模型方法的每一次采样估计都能和一个已知的真值对照偏差出现在哪一步一目了然。这就是为什么书里所有算法都在同一个网格世界上跑——它给了你一个可以手算的参考答案。另一个取舍是数学深度。书稿把概率论和线性代数的基础放进附录正文里那些偏测度论的部分用灰底框标出可以跳过。这个做法承认了一件事读这本书的人目标不一样有人想要完整的收敛性证明有人只想知道这个算法在解哪个方程。把两部分显式分开比模糊地折中要好。如果你要验证自己是不是真读懂了我建议的检验方式是合上书自己在网格世界上把值迭代、MC Basic、TD(0)、Q-learning 各实现一遍然后逐轮打印 v 或 q和手算结果对照。四个都对齐了说明你理解的是方程而不是公式。我自己的习惯是每换一个新环境先跑一遍值迭代拿到标准答案再拿它去校准后面所有采样算法的结果这样一旦曲线不对能立刻分清是算法理解错了还是实现写错了。从那以后我每次上手一个新的强化学习算法都会强制先在小规模、可穷举的环境上把它跑通确认它收敛到的手算解一致再迁移到大环境上。这个习惯救过我很多次——那些后来在大任务上炸掉的 bug基本都能在小格子上提前暴露。希望帮到你。本文还有配套的精品资源点击获取