
很多朋友学强化学习上来就怼PPO、SAC、DQN结果卡在收敛性调试上怀疑人生。我倒建议先把书翻到“动态规划”那一章静下心啃一遍。原因很简单强化学习里的两大核心机制——价值迭代和策略搜索——本质上都是从动态规划的框架延伸出来的。搞不懂动态规划后面你看什么算法都会觉得是在“调参炼丹”而不是在“求解问题”。本文想做的就是把“强化学习 动态规划”这个组合彻底拆开动态规划在RL里到底扮演什么角色、贝尔曼方程怎么一步步推导出来、怎么用不到100行Python实现策略迭代和价值迭代跑通一个网格世界、以及在真实项目中哪些地方会踩坑。适合两类人一类是刚入门强化学习、被各种算法名词砸晕的新手另一类是已经在调模型、但总觉得“差一口气”的工程师——你缺的可能不是算力而是把问题结构化、把迭代过程理清楚的基本功。1. 动态规划在强化学习里到底处于什么位置1.1 有模型与无模型动态规划为什么属于“基于模型的强化学习”强化学习经典分类里动态规划属于“基于模型”的那一类。所谓模型指的就是状态转移概率 P(s|s,a) 和奖励函数 R(s,a)——在DP设定的问题中这两个分布完全已知不需要靠采样去估计。这里有一个我特别喜欢的类比动态规划像是“带着地图下棋”棋局规则和当前局面完全透明你可以把每一步都推演清楚而蒙特卡洛、Q-Learning这类无模型方法像是“蒙着眼睛下棋”只能靠走一步看一步拿反馈再反向修正自己的策略。DP在教科书里总是出现在强化学习第一课恰恰因为它是整个学科的思想基石。很多人学完策略迭代和价值迭代后会觉得“这不是数值计算课的内容吗和强化学习有什么关系”其实后面你看到的Q-Learning本质就是价值迭代在未知环境下的采样近似Actor-Critic算法本质就是策略迭代里策略评估和策略改进的交替执行。只不过它们用神经网络代替了查表用采样代替了全期望展开。弄明白DP回头看这些“高级算法”时你会发现它们的骨架都长得差不多。1.2 动态规划能做什么、不能做什么动态规划在强化学习里能做的事情有三件一是策略评估——给定一个策略 π算出每个状态的价值函数 Vπ(s)二是策略改进——根据当前价值函数做贪心选择得到比原来更好的策略三是把两者交替迭代最终收敛到最优策略。它解决问题的方式有一个很强的前提环境具备马尔可夫性质且状态空间有限可数。也就是说每个状态只依赖当前状态和动作不依赖历史状态集合要能够穷举并存储。不能做的事边界也很清晰如果环境模型未知、状态空间连续或者维度很高那DP的直接形式没法落地。深度强化学习的出现本质就是用神经网络做函数近似器把DP的思想搬到高维连续状态空间里。但不管网络多复杂核心的更新逻辑依然还是贝尔曼那套——先估计价值再根据价值改进策略唯一变的只是“价值函数怎么表示、期望怎么近似”。所以DP不是被淘汰了而是被藏到了神经网络后面。这里我想多说一句实际项目里当状态空间在 10^5 量级以内、转移概率可以建模时我通常会直接上DP或者表格型方法又快又稳完全没必要用深度强化学习。比如小型棋盘游戏、简易调度问题、小规模路径规划策略迭代跑出来的结果干净利落还不用调学习率。很多工程师一看到“强化学习”就默认要上PPO、要上GPU集群这其实是把简单问题复杂化了。2. 核心原理贝尔曼方程是怎么支撑起动态规划的2.1 最优性原理把大问题拆成子问题的关键动态规划最底层的逻辑是最优性原理如果全局策略是最优的那么从任意状态出发剩余的策略相对于该状态也是最优的。这个性质看起来稀松平常但它给了一种递归拆分问题的底气——求全局最优可以先求局部最优然后一层层反向堆叠起来。我常用一个生活类比来解释如果你的目标是“下班后最快回到家”那么在每个十字路口做决策时你只需要知道“从当前路口回家还有多远”完全不必关心之前是怎么绕到这里的。因为路径的未来只取决于当前所在位置路径的历史不影响接下来怎么走。这就是马尔可夫性。而“每个路口都知道最短剩余距离”这件事就是子问题的最优解。把无数个子问题的最优解拼接起来就得到了全局最优路径。正是基于这个原理强化学习里才敢把一段长序贯决策拆解成“单步奖励 后续价值”的递归形式。贝尔曼方程就是把这个递归关系用数学写出来的结果。你不需要一次性规划出整条轨迹只需要一个公式反复迭代价值信息就会在状态之间传播最终收敛到全局一致的值。2.2 从状态价值函数到贝尔曼最优方程状态价值函数 Vπ(s) 的定义是从状态 s 出发遵循策略 π所能获得的期望累积奖励。把它展开成递归形式就得到了贝尔曼方程Vπ(s) Σ_a π(a|s) Σ_{s} P(s|s,a) [ R(s,a) γ Vπ(s) ]这个公式看起来有点吓人但拆解开来其实就三层意思第一当前状态的价值等于所有动作的加权平均第二每个动作的价值等于即时奖励加上下一状态价值的折扣后估计第三因为下一状态可能不唯一所以要按转移概率对所有可能的 s 求期望。本质上就是一个“当前收益 未来收益”的两阶段估值。如果我们要找最优策略那不需要对所有动作做加权平均而是直接选择让Q值最大的动作。于是贝尔曼方程从“平均”变成“最大”V*(s) max_a Σ_{s} P(s|s,a) [ R(s,a) γ V*(s) ]这就是贝尔曼最优方程也是价值迭代每一步“贝尔曼备份”的依据。所谓备份backup就是把估计的价值函数“覆盖”回当前状态像把远处的信息一步一步搬回起点。策略迭代则是拆成两步先用贝尔曼方程做策略评估再基于评估结果贪心改进策略。不管哪条路线最终目标都是逼出最优价值函数 V* 和最优策略 π*。2.3 策略迭代与价值迭代两条不同的收敛路径策略迭代的思路可以概括为“稳步前进”。先固定当前策略 π反复更新 V 直到收敛这叫策略评估然后根据 V 贪心替换策略这叫策略改进。改进后的策略一定比原来好所以策略质量单调上升通常迭代十几轮到几十轮就能收敛。优点是轮数少缺点是一轮策略评估内部又要跑很多次状态扫描整体计算量不一定小。价值迭代的思路则更“跳跃”它不显式维护策略而是直接把贝尔曼最优方程“烧”进更新式里每个状态每轮都向当前最优值方向迈一步。由于每轮都做了 max价值函数变化通常比策略评估剧烈收敛路径也更曲但代码非常简洁。它收敛后就得到最优价值函数再从最优价值函数反推贪心策略就得到最优策略。实际使用中我的经验是状态空间较小时几千到几万策略迭代往往更高效因为它利用策略变化加速收敛状态空间较大时价值迭代实现更简单每一步也更好并行化。两者最终殊途同归但理解它们的差异对工程选型很有帮助。3. 代码实操4x4网格上手策略迭代与价值迭代3.1 环境定义与参数设置理论说再多不如亲手写一遍。我选了一个最简单的 GridWorld4x4 网格左上角状态0和右下角状态15是终止状态其他状态都是普通状态。智能体每一步可以选择上、下、左、右四个动作但撞墙时位置不变。除了终止状态之外每次转移的奖励都是 -1折扣因子 γ 设为 1因为这是一个有限步的剧集任务总回报是有限的。为什么选这种环境因为状态只有16个你可以手动验算每一步的数值结果非常直观。同时它又具备序贯决策的基本要素多个状态、动作改变状态、延迟奖励、终止状态。代码环境定义很简单import numpy as np # 状态编号0 ~ 15 # 布局 0 1 2 3 # 4 5 6 7 # 8 9 10 11 # 12 13 14 15 # 终止状态0 和 15 # 动作0上, 1下, 2左, 3右 TERMINAL_STATES {0, 15} GAMMA 1.0 def step(state, action): 根据当前状态和动作返回下一个状态 row, col divmod(state, 4) if action 0: # 上 row max(0, row - 1) elif action 1: # 下 row min(3, row 1) elif action 2: # 左 col max(0, col - 1) elif action 3: # 右 col min(3, col 1) return row * 4 col这个 step 函数完全基于网格拓扑。奖励函数直接在迭代代码里写成 -1 即可不需要单独封装。参数上最重要的是 GAMMA这里用 1.0 是为了方便手工验证真实场景里大多数还是设成 0.9 或 0.99 更合理。3.2 策略评估函数怎么写策略评估的目标是给定一个策略 π用迭代法求出每个状态的价值函数。实现思路是把贝尔曼方程当成“更新规则”一遍一遍扫过所有非终止状态直到价值函数的变化量小于阈值 θ。我在这里特意用了同步更新——先算完整轮所有状态的新价值再一次性替换旧价值而不是在新价值算出来后立刻覆盖原数组。新手很容易在这个细节上踩坑如果直接 V[s] new_V[s]那后面计算 V[s1] 时用的就已经是当前轮的新值了相当于混进了“未来信息”迭代行为会变得难以分析。同步更新虽然慢一点但语义清晰调试时也更容易复现。def policy_evaluation(policy, V, gammaGAMMA, theta1e-4): 策略评估迭代计算给定策略下的状态价值 while True: delta 0 new_V V.copy() for s in range(16): if s in TERMINAL_STATES: continue a policy[s] s_next step(s, a) new_V[s] -1 gamma * V[s_next] delta max(delta, abs(new_V[s] - V[s])) V new_V if delta theta: break return V代码里有两个地方值得说明。第一终止状态被跳过了因为终止状态没有后续动作也没有价值。如果环境有特殊终止奖励一般会在进入终止状态时即时结算而不是把终止状态的价值函数拿来迭代。第二迭代终止条件用的是“最大变动量 delta 小于 theta”这是一种经验性收敛判断不会保证“已收敛到数学最优”但工程上足够用。3.3 策略改进与完整策略迭代策略评估算完 V 之后就可以做策略改进了。所谓改进就是对每个非终止状态遍历四个动作看哪个动作对应的“即时奖励 下一状态价值”最大就把它作为新策略。这一步用 np.argmax 很方便def policy_improvement(V, gammaGAMMA): 策略改进根据当前价值函数贪心选择最优动作 policy np.zeros(16, dtypeint) for s in range(16): if s in TERMINAL_STATES: policy[s] -1 # 终止状态的动作无实际意义 continue q_values [] for a in range(4): s_next step(s, a) q_values.append(-1 gamma * V[s_next]) policy[s] int(np.argmax(q_values)) return policy有了评估和改进两个函数策略迭代主循环就很清晰了随机初始化一个策略然后反复“评估 → 改进”直到策略不再发生变化。因为策略空间有限且每一步都保证策略不劣化最终一定会停在某个稳定策略上这个策略就是最优策略。np.random.seed(42) V np.zeros(16) policy np.random.randint(0, 4, size16) while True: V policy_evaluation(policy, V) new_policy policy_improvement(V) if np.array_equal(policy, new_policy): policy new_policy break policy new_policy print(最优策略0上,1下,2左,3右) print(policy.reshape(4, 4)) print(最优价值函数) print(V.reshape(4, 4))跑完这段代码你会看到策略在终止状态周围的区域很快变为“朝终点走”离终点远的角落则需要多迭代几轮。最优价值函数在目标状态附近数值高在起点附近数值低——在这个奖励全为 -1 的环境里价值越负说明离终点越远。观察价值函数从随机初始化到收敛的过程能非常直观地感受到“价值在状态之间传播”这件事。3.4 价值迭代更简洁的变体价值迭代写在代码上比策略迭代更短因为它在同一轮更新里同时完成了“评估”和“改进”——直接把贝尔曼最优方程作为更新规则def value_iteration(V, gammaGAMMA, theta1e-4): 价值迭代直接迭代贝尔曼最优方程 while True: delta 0 new_V V.copy() for s in range(16): if s in TERMINAL_STATES: continue q_values [] for a in range(4): s_next step(s, a) q_values.append(-1 gamma * V[s_next]) new_V[s] max(q_values) delta max(delta, abs(new_V[s] - V[s])) V new_V if delta theta: break return V你不需要显式维护策略只需要不断对价值函数做“贝尔曼备份”。等到价值函数稳定后再调用一次 policy_improvement就能从最优价值函数中提取出最优策略。由于价值迭代的每轮更新幅度通常比策略评估更大所以迭代收敛所需的轮数往往更少但是单轮计算量并不比别人少太多整体效率取决于具体环境。实际调试时我建议先跑通价值迭代再用同样的环境跑策略迭代对比两种算法在每一个状态上生成的价值函数是否一致。如果数值一致说明你的实现没有大问题如果不一致大概率是策略评估里没有做同步更新或者终止状态没有正确处理。4. 常见问题与调试陷阱这些坑我基本都踩过4.1 收敛阈值与折扣因子设置不当导致的假收敛策略评估和价值迭代里的 theta 参数是个非常容易被忽略的坎。我第一次写DP代码时把 theta 设成 0.1结果策略迭代几轮就停了看起来也“收敛”了但价值函数和最优策略其实差得很远。原因很简单阈值越大迭代越早停止价值函数精度越差。后来我养成一个习惯先设一个比较小的 theta比如 1e-4跑通再打印 delta 下降曲线观察它是不是单调下降、有没有震荡。如果震荡说明步长太大或同步更新没做对。折扣因子 GAMMA 的影响也值得单独说。γ 1 时远期回报会被指数级折损这保证了无限步骤场景下累积回报有限数学上更容易收敛。但 γ 太小的代价是智能体会“短视”——比如 γ0.5 时两步之外的奖励已经衰减到四分之一它可能宁愿选择眼前小利也不愿意为远期目标多走几步。反过来γ1 在没有终止状态的循环环境中会导致累积回报发散所以这类场景必须设置小于1的折扣。经验值上一般任务从 0.9 开始调需要远见的长周期任务用 0.99 甚至更高。4.2 同步更新与异步更新的真实差异这个坑我在3.2节里已经提过一半。很多教科书代码为了简洁会直接写 V[s] -1 gamma * V[s_next]这就是异步更新。异步更新的收敛速度通常更快因为它“充分利用”了新信息但代价是理论分析更麻烦调试时如果出现奇怪的震荡就很难判断是环境设计问题还是更新顺序问题。我的建议分两步先把同步更新版本跑通确认逻辑没问题再根据性能需要改成异步更新对比两种写法的收敛轮数和最终结果。还有一个容易被忽略的细节异步更新里如果遍历状态的顺序固定结果可能受“状态编号顺序”的隐性影响导致某些状态更快更新、某些状态被滞后。这并不算bug但会让调试时观察到的中间过程不那么“对称”。如果你发现价值函数的中间状态分布有偏优先怀疑遍历顺序而不是以为自己写错了公式。4.3 动态规划的适用边界状态爆炸与环境建模成本动态规划虽然优雅但它有一个硬伤时间复杂度和状态空间规模直接挂钩。策略评估每轮要扫描所有状态每个状态要遍历所有动作和所有可能的下一个状态复杂度大概在 O(|S|²|A|) 量级。状态数一旦超过十万、百万每轮迭代的时间就不可接受了状态数到十亿以上连存储价值函数都成问题。很多人觉得“DP太天真、深度强化学习才是正道”本质就是被状态爆炸吓怕了。但我想提醒的是另一面很多时候真正卡住你的不是算法本身而是模型获取成本。DP要求已知 P(s|s,a) 和 R(s,a)但在真实系统里想拿到精确的转移概率往往比训练一个策略网络还难。所以项目里选择DP还是深度RL我一般先问三个问题状态空间能不能离散化到可承受范围转移概率能不能通过规则或仿真拿到对最优性的要求是精确还是近似如果前两个回答“是”第三个回答“精确”那DP就是不二之选否则再考虑无模型算法。5. 从动态规划出发能走多远5.1 从DP到蒙特卡洛与时序差分模型不存在时怎么办动态规划要求已知 P 和 R这个条件太奢侈于是就有了蒙特卡洛方法直接让智能体与环境交互收集完整回合的回报用多个回合的平均回报来估计价值函数。它完全不需要模型只要知道“从某个状态出发最终累积拿到了多少奖励”就够了。代价是方差大、训练慢因为必须等到回合结束才能获得一次更新信号。时序差分TD则折中了两者它既不像DP那样用完整期望也不像蒙特卡洛那样等回合结束而是用“当前即时奖励 下一状态当前估计值”来更新当前状态。这就是 Q-Learning 和 SARSA 的基本思想。你看从DP到TD变化的只是“如何估计期望”这一步——DP做全期望MC做采样平均TD做了一步采样自举。价值迭代里那个 update 式子在Q-Learning里几乎原封不动地保留着只是把 max_a 后面从全期望换成了 samples 而已。5.2 动态规划思想在组合优化问题中的再应用动态规划不只在强化学习里重要它本身就是算法世界里的重头戏。01背包问题、最少硬币找零、车辆路径规划这些经典题目全都有“状态转移方程 子问题最优解”的影子。01背包的状态可以定义为“前 i 件物品、背包容量为 j 时的最大价值”然后根据“装或不装第 i 件物品”做二元决策这个递归结构和贝尔曼方程如出一辙。车辆路径问题VRP小规模时也可以写状态DP来精确求解但一旦客户点增多状态爆炸得厉害这时人们就开始寻找启发式、元启发式或者用强化学习辅助求解。你会发现动态规划既是一种算法更是一种“把序贯决策递归化”的思维方式。理解了它你在看MILP混合整数线性规划和强化学习的结合时就知道二者其实在解决同一类问题带约束的序贯决策只是一个习惯用分支定界一个习惯用价值迭代。5.3 再往后图强化学习、离线强化学习与联邦强化学习从DP这个锚点出发往深走眼前会展开好几条路。图强化学习用图神经网络结构来建模状态关系适合状态本身带拓扑结构的场景离线强化学习比如IQL解决的是“只用历史日志学习、不与环境在线交互”的问题核心还是怎么在价值函数估计中加约束、避免过估计联邦深度强化学习则是把训练过程分布到多个节点隐私和通信效率成了主要矛盾。这些方向听起来高大上但剥开外壳底层落点基本还是同一个问题“如何更高效更准确地逼近价值函数”。理解了DP你看这些新名词时会多一种“哦原来它就是把这一步换了一种近似方式”的熟悉感而不是被术语吓退。最后分享一个我从踩坑中得到的经验别急着上深度强化学习框架先把策略迭代和价值迭代在几个十几状态的小环境里完整写一遍亲眼看看价值函数是怎么从全零一点点“长”成有结构的形状。这个手感一旦建立起来之后看任何高级算法你都会习惯性地问一句“它的价值函数是怎么更新的、策略是怎么改进的”自然就不容易被训练技巧和调参玄学带偏。想把这个主题往深走的话建议把Sutton那本书的第四章和第十七章放在一起读——前者讲表格型DP后者讲策略梯度中间穿插蒙特卡洛和时序差分连起来看你会对强化学习的方法谱系有一个特别完整的认知。