马尔可夫决策过程与贝尔曼方程:从理论推导到代码实现
发布时间:2026/10/13 10:49:07 锦皓数字建站

1. 从“只看眼前”到“放眼未来”为什么需要马尔可夫决策过程很多刚接触强化学习的朋友在啃完多臂老虎机那一章之后都会产生一种“我好像懂了”的错觉。毕竟老虎机问题里你只需要考虑当前这一步选哪个动作能拿到最高回报环境不会因为你选了某个动作就发生状态迁移下一次还是同样的局面。这种“单步决策”的设定确实好理解也好实现。但真实世界里的决策问题几乎都不是这样的。你走一步棋棋盘局面就变了你开一局游戏角色位置、血量、资源全都跟着变你调整一次推荐策略用户的后续行为分布也会发生偏移。换句话说动作会改变状态而状态又会影响后续能拿到的回报。这时候老虎机那套“只看即时收益”的思路就彻底不够用了。你需要一个能描述“状态迁移”和“长期回报”的数学框架这就是马尔可夫决策过程Markov Decision ProcessMDP要解决的问题。我见过不少初学者在这一块卡住不是因为数学有多难而是因为脑子里没有建立起“序贯决策”的直觉。他们能背出贝尔曼方程的公式但问他“为什么要有折扣因子”或者“状态价值函数和动作价值函数到底差在哪”就答不上来了。这其实很正常因为大部分教程一上来就甩定义、甩公式缺少从直觉到形式的过渡。这篇文章我就想换个讲法先把“为什么”讲透再落到“怎么写代码验证”让你不光会推导还能亲手跑出结果来。这篇文章适合谁如果你已经了解强化学习的基本概念知道什么是智能体、环境、奖励也写过一个简单的老虎机实验那接下来的内容会非常顺。如果你连老虎机都还没碰过建议先补一下前一篇不然直接看MDP会有点吃力。整篇内容我会围绕三个核心问题展开MDP到底在描述什么、贝尔曼方程是怎么把“长期回报”拆解成“当前奖励加未来价值”的、以及怎么用代码把这两样东西真正算出来。2. 马尔可夫决策过程的五元组每个符号背后到底在说什么2.1 状态、动作、奖励、转移、折扣拆开来看MDP的标准定义是一个五元组 $(S, A, P, R, \gamma)$。教科书上通常一行就写完了但每个符号背后其实都藏着不少设计上的考量。我逐个拆开讲顺便说说实际建模时容易踩的坑。状态集合 $S$描述的是环境所有可能处于的情况。这里有个关键假设叫“马尔可夫性”当前状态已经包含了做出最优决策所需的全部历史信息。换句话说只要知道现在处于哪个状态过去怎么走到这里的并不重要。这个假设听起来很美好但实际建模时经常不成立。比如你用一个截图作为游戏状态单帧画面里看不出角色的速度方向那它就不满足马尔可夫性。解决办法通常是把连续几帧叠在一起作为状态或者用循环网络来编码历史。这一点在后面的实战里我会再展开。动作集合 $A$是智能体在每个状态下可以采取的操作。离散动作空间好办上下左右四个方向就是四个动作。连续动作空间就麻烦一些比如机械臂的关节角度是连续值这时候策略网络输出的就不是某个动作的概率而是一个分布的参数。初学者常犯的错误是把动作空间设计得过大导致探索效率极低。我的经验是能离散化就先离散化实在不行再上连续控制的方法。转移概率 $P(s|s,a)$是MDP区别于老虎机的核心。它告诉你在状态 $s$ 执行动作 $a$ 之后有多大概率转移到状态 $s$。这里要注意转移可以是确定性的也可以是随机性的。确定性转移就是执行动作后必然到达某个状态随机性转移则是一个概率分布。很多实际环境都是随机的比如你让机器人往前走轮子可能打滑最终位置会有偏差。建模时如果把随机性忽略掉训出来的策略在真实环境里往往很脆弱。奖励函数 $R(s,a,s)$定义了智能体在转移过程中获得的即时反馈。这里有个设计上的细节奖励到底是定义在 $(s,a)$ 上还是 $(s,a,s)$ 上两种写法都有区别在于奖励是否依赖于转移后的状态。大多数教材用 $R(s,a)$ 的简写但严格来说如果奖励和下一状态有关就得写成三元组的形式。实际写代码时环境返回的通常是一个标量奖励具体依赖哪些变量由你自己决定。折扣因子 $\gamma \in [0,1)$是我最想强调的一个参数。它的作用是让未来的奖励打折扣避免无限期累积导致数值发散同时也体现了“近期奖励比远期奖励更重要”的偏好。$\gamma$ 越接近1智能体越有远见越接近0越短视。很多人调参时把 $\gamma$ 设成0.99就完事了但其实这个值需要根据任务的步数尺度来定。如果一个回合平均只有几十步0.99已经足够让智能体看到很远的未来如果回合长达几千步可能要用0.999甚至更高否则远处的奖励几乎被折扣没了。2.2 策略、回报与价值函数三个容易混淆的概念策略 $\pi(a|s)$ 是智能体的行为准则它定义了在每个状态下选择各个动作的概率。确定性策略直接输出一个动作随机性策略输出一个概率分布。为什么要有随机性策略因为探索。如果策略是确定性的智能体可能永远困在某个次优动作上没法发现更好的选择。回报 $G_t$ 是从时刻 $t$ 开始往后所有奖励的折扣累积和$$G_t R_{t1} \gamma R_{t2} \gamma^2 R_{t3} \cdots$$注意这里的下标$R_{t1}$ 是执行动作后立刻拿到的奖励从下一时刻开始算。这个细节在推导贝尔曼方程时很关键写错了整个公式就乱了。价值函数分两种。状态价值函数 $V^\pi(s)$ 表示从状态 $s$ 出发按照策略 $\pi$ 行动所能获得的期望回报。动作价值函数 $Q^\pi(s,a)$ 则表示在状态 $s$ 先执行动作 $a$之后再按照策略 $\pi$ 行动所能获得的期望回报。两者的关系是$$V^\pi(s) \sum_a \pi(a|s) Q^\pi(s,a)$$这个式子说白了就是状态价值等于在该状态下所有可能动作的价值按策略选择各动作的概率加权平均。理解这一点后面推导贝尔曼方程就顺了。我刚开始学的时候总把 $V$ 和 $Q$ 搞混后来用一句话记住了$V$ 是“站在状态上看未来”$Q$ 是“站在动作上看未来”。$Q$ 比 $V$ 多了一层“我已经选定了某个动作”的信息所以它更具体也更有用——很多算法直接学 $Q$ 函数然后选 $Q$ 最大的动作就行不需要显式的策略。3. 贝尔曼方程把无限长的回报折叠成一个递归3.1 从回报的定义直接推导贝尔曼方程不是什么新东西它就是对回报定义的一个递归展开。我们来看$$G_t R_{t1} \gamma R_{t2} \gamma^2 R_{t3} \cdots$$把第一项拿出来剩下的部分提取一个 $\gamma$$$G_t R_{t1} \gamma (R_{t2} \gamma R_{t3} \cdots) R_{t1} \gamma G_{t1}$$这就是贝尔曼方程最本质的形式当前回报等于即时奖励加上折扣后的下一时刻回报。你看推导过程没有任何魔法就是把定义式重新组合了一下。但这个简单的递归关系是整个强化学习算法的基石。对价值函数取期望就得到状态价值函数的贝尔曼方程$$V^\pi(s) \sum_a \pi(a|s) \sum_{s} P(s|s,a) [R(s,a,s) \gamma V^\pi(s)]$$这个式子的含义是状态 $s$ 的价值等于在该状态下按策略选动作、按环境动态转移到下一状态、拿到即时奖励、再加上折扣后的下一状态价值所有这些的期望。动作价值函数的贝尔曼方程类似$$Q^\pi(s,a) \sum_{s} P(s|s,a) [R(s,a,s) \gamma \sum_{a} \pi(a|s) Q^\pi(s,a)]$$对比一下就能看出$Q$ 的方程里多了一层对下一动作的求和因为执行完当前动作后还要继续按策略选下一个动作。3.2 贝尔曼最优方程从“评估策略”到“寻找最优”上面讲的是给定策略 $\pi$ 下的贝尔曼方程叫贝尔曼期望方程。但强化学习的终极目标是找到最优策略所以还需要贝尔曼最优方程。最优状态价值函数 $V^*(s)$ 定义为在所有策略中能取得的最大状态价值$$V^*(s) \max_\pi V^\pi(s)$$对应的贝尔曼最优方程是$$V^(s) \max_a \sum_{s} P(s|s,a) [R(s,a,s) \gamma V^(s)]$$注意这里的变化对动作的求和变成了取最大值。因为最优策略在每个状态都会选价值最高的动作所以不需要按概率加权直接取 max 就行。同理最优动作价值函数的贝尔曼最优方程是$$Q^(s,a) \sum_{s} P(s|s,a) [R(s,a,s) \gamma \max_{a} Q^(s,a)]$$这个式子是 Q-learning 算法的直接来源。Q-learning 就是用采样来估计这个期望然后用 $\max$ 来更新 Q 值。理解了贝尔曼最优方程Q-learning 的更新公式就变得非常自然了。这里有个容易混淆的点贝尔曼期望方程描述的是“给定策略下的价值”它是线性的可以用矩阵求逆直接解。贝尔曼最优方程描述的是“最优策略下的价值”它带有 max 操作是非线性的一般没有解析解只能用迭代方法逼近。这个区别决定了后面两类算法的分野策略评估类方法解期望方程价值迭代和策略迭代类方法解最优方程。3.3 折扣因子的作用不只是防止发散前面提到 $\gamma$ 是为了让无穷级数收敛但它的作用远不止于此。从数学上看只要 $\gamma 1$ 且奖励有界回报 $G_t$ 就一定有界因为$$|G_t| \leq R_{max} (1 \gamma \gamma^2 \cdots) \frac{R_{max}}{1-\gamma}$$这个上界在理论分析里经常用到。但从实践角度看$\gamma$ 其实是在调节智能体的“时间视野”。有效视野大约是 $1/(1-\gamma)$ 步。$\gamma0.9$ 对应大约10步$\gamma0.99$ 对应大约100步$\gamma0.999$ 对应大约1000步。我踩过的一个坑是在一个回合长度约200步的任务里我一开始把 $\gamma$ 设成了0.9结果智能体完全学不会需要长期规划的策略因为它只能看到10步以内的奖励。后来改成0.99效果立刻好了很多。所以选 $\gamma$ 的时候先估算一下任务的平均回合长度让有效视野覆盖到关键决策的时间跨度这个经验比盲目调参靠谱得多。4. 用代码把贝尔曼方程跑起来一个网格世界的完整实现4.1 环境设计4x4网格世界的规则光看公式容易飘我们用一个具体的网格世界来把前面讲的东西落地。规则很简单一个4x4的格子左上角是起点右下角是终点。智能体每次可以上下左右移动一格移出边界则原地不动。到达终点获得奖励1其余每步奖励-0.04这个小的负奖励是为了鼓励智能体尽快到达终点不要磨蹭。这个环境的状态空间是16个格子动作空间是4个方向转移是确定性的执行动作后必然到达预期格子除非撞墙。虽然简单但足够展示贝尔曼方程的所有核心概念。先定义环境import numpy as np class GridWorld: def __init__(self, size4): self.size size self.n_states size * size self.n_actions 4 # 上右下左 self.goal self.n_states - 1 self.start 0 # 动作对应的行列偏移 self.moves [(-1, 0), (0, 1), (1, 0), (0, -1)] def step(self, state, action): row, col divmod(state, self.size) dr, dc self.moves[action] new_row min(max(row dr, 0), self.size - 1) new_col min(max(col dc, 0), self.size - 1) next_state new_row * self.size new_col if next_state self.goal: reward 1.0 else: reward -0.04 done (next_state self.goal) return next_state, reward, done这段代码里有个细节值得说我用min(max(...))来处理边界效果是撞墙后停在原地。另一种常见做法是撞墙后回到原状态并给一个负奖励两种都行但要在文档里写清楚不然复现的人会对不上。4.2 策略评估迭代求解贝尔曼期望方程有了环境第一步是给定一个策略算出它的状态价值函数。这里用迭代法初始化 $V(s)0$然后反复用贝尔曼期望方程更新每个状态的价值直到收敛。def policy_evaluation(env, policy, gamma0.99, theta1e-6): V np.zeros(env.n_states) while True: delta 0 for s in range(env.n_states): if s env.goal: continue v_old V[s] v_new 0 for a in range(env.n_actions): prob policy[s][a] if prob 0: continue next_s, reward, _ env.step(s, a) v_new prob * (reward gamma * V[next_s]) V[s] v_new delta max(delta, abs(v_old - v_new)) if delta theta: break return V这里的theta是收敛阈值设成 $10^{-6}$ 通常够用。注意我跳过了终点状态的更新因为终点是终止状态没有后续价值。如果忘了这个处理终点会被反复更新虽然最终也会收敛但物理意义就错了。用一个均匀随机策略跑一下你会看到每个格子的价值都是负数越靠近终点越接近0。这符合直觉随机乱走的话每步都在扣分离终点越远扣得越多。4.3 价值迭代直接逼近最优价值函数策略评估是给定策略算价值价值迭代则是直接算最优价值。区别在于更新时用 max 代替了按策略加权def value_iteration(env, gamma0.99, theta1e-6): V np.zeros(env.n_states) while True: delta 0 for s in range(env.n_states): if s env.goal: continue v_old V[s] q_values [] for a in range(env.n_actions): next_s, reward, _ env.step(s, a) q_values.append(reward gamma * V[next_s]) V[s] max(q_values) delta max(delta, abs(v_old - V[s])) if delta theta: break # 提取最优策略 policy np.zeros((env.n_states, env.n_actions)) for s in range(env.n_states): if s env.goal: continue q_values [] for a in range(env.n_actions): next_s, reward, _ env.step(s, a) q_values.append(reward gamma * V[next_s]) best_action np.argmax(q_values) policy[s][best_action] 1.0 return V, policy跑完之后你会发现最优策略就是从每个格子指向终点的最短路径方向。价值函数则呈现出从终点向外递减的梯度越远的格子价值越低。这里有个实操经验价值迭代的收敛速度通常比策略迭代快但每次迭代的计算量稍大因为要算所有动作的 Q 值。在状态空间不大的情况下两者差别不明显。但如果状态空间很大策略迭代可能因为每次都要完整评估策略而变慢。我的建议是先用价值迭代快速拿到一个基准再根据具体问题选择后续算法。4.4 结果验证怎么确认自己算对了代码跑通不代表算对了。我一般会用几个方法交叉验证第一检查价值函数的单调性。在网格世界里离终点越近的状态价值应该越高。如果出现某个远处的格子价值反而更高那多半是更新逻辑写错了。第二手动算一个简单情况。比如终点左边那个格子最优动作是向右走价值应该是 $1.0 \gamma \times 0 1.0$因为下一步就到终点终点价值为0。如果代码算出来不是1.0就要检查奖励和折扣的计算顺序。第三对比不同 $\gamma$ 下的结果。$\gamma$ 越大远处格子的价值应该越接近终点价值$\gamma$ 越小远处格子价值应该越接近每步奖励的累积。这个趋势如果对不上说明折扣因子没起作用。我见过一个常见的 bug 是把奖励和折扣的顺序搞反写成 $\gamma \times reward V[next_s]$这样折扣就加在即时奖励上了完全错误。正确的写法是 $reward \gamma \times V[next_s]$即时奖励不打折只有未来价值打折。这个细节看起来小但影响很大一定要检查清楚。5. 那些公式没告诉你的事实操中的坑与经验5.1 马尔可夫性不成立时怎么办前面提过马尔可夫性是MDP的核心假设但实际环境经常不满足。最典型的例子是用单帧图像作为状态。单张图片里没有速度信息智能体无法判断自己是静止还是在移动。这时候有两种处理思路。一种是状态工程把最近几帧拼在一起作为状态。比如用4帧堆叠状态维度变成原来的4倍但马尔可夫性就基本满足了。这个方法在早期深度强化学习里很常用实现简单效果稳定。缺点是状态维度变高网络输入变大训练变慢。另一种是模型化历史用循环神经网络或 Transformer 来编码历史观测让网络自己学会提取需要的记忆。这个方法更灵活但训练难度也更高容易出现梯度问题。我的建议是如果任务不复杂优先用帧堆叠如果观测本身是高维的比如自然语言指令再考虑用循环网络。5.2 奖励设计稀疏奖励与奖励塑形网格世界里的奖励设计还算友好因为每步都有一个小负奖励智能体至少能感知到“活着就有代价”从而倾向于尽快结束。但很多真实任务的奖励是稀疏的只有到达目标才有奖励中间过程完全没有反馈。这种情况下随机探索几乎不可能碰巧到达目标学习会非常困难。解决办法之一是奖励塑形人为设计一些中间奖励引导智能体朝目标前进。比如在网格世界里可以给“离终点更近”这个行为一个小的正奖励。但奖励塑形有个风险如果设计不当智能体可能学会刷中间奖励而不去完成真正的任务。我见过一个案例设计者给“靠近目标”加了奖励结果智能体学会了在目标附近来回移动因为这样能持续获得靠近奖励而不进入目标状态。所以奖励塑形的原则是中间奖励要足够小不能盖过最终奖励同时要确保完成任务的累积奖励仍然是最高的。一个实用的技巧是用势函数塑形把中间奖励设计成势函数的差分这样理论上不改变最优策略只是加速学习。5.3 折扣因子与回合终止的配合折扣因子和回合终止termination的配合也有讲究。在有限回合的任务里终止状态的价值应该定义为0因为它后面没有奖励了。但有些实现里会忘记这一点让终止状态也参与价值更新导致终止状态的价值被反复迭代最终收敛到一个非零值。这个错误不会让程序崩溃但会让学到的策略有偏差。另一个细节是如果任务有最大步数限制超时截断和真正终止要区分开。超时截断时虽然回合结束了但状态本身不是终止状态它的价值不应该被当作0。这个区别在自举bootstrapping时很重要。很多开源实现里用done和truncated两个标志来区分就是出于这个考虑。我在实际项目里遇到过因为没区分这两个标志导致智能体在接近最大步数时行为异常的问题。它学会了在快超时的时候故意拖延因为超时截断的价值估计被错误地设成了0反而比正常终止的价值还高。修正之后行为就正常了。5.4 数值稳定性价值溢出与归一化当状态空间很大、回合很长时价值函数可能变得非常大导致数值溢出。尤其是 $\gamma$ 接近1的时候回报的上界 $R_{max}/(1-\gamma)$ 会很大。如果奖励本身也大浮点数可能扛不住。解决办法有几个一是对奖励做归一化把奖励缩放到一个合理范围比如 $[-1, 1]$二是对价值函数做裁剪限制在一个区间内三是用对数空间表示价值但这会改变贝尔曼方程的形式不太常用。我的经验是奖励归一化是最简单也最有效的。在训练前统计一下奖励的均值和标准差然后做标准化能让训练稳定很多。但要注意归一化后的奖励会改变最优策略的尺度如果任务对奖励的绝对值敏感就要谨慎使用。6. 从MDP到深度强化学习这条路的下一步把MDP和贝尔曼方程吃透之后你会发现后面那些深度强化学习算法其实都是在这个框架上做文章。Q-learning 是用采样估计贝尔曼最优方程的期望DQN 是用神经网络来拟合 Q 函数策略梯度则是直接对策略参数求梯度而贝尔曼方程提供了梯度估计的基准。如果你现在能独立推导出贝尔曼期望方程和最优方程能说清楚 $V$ 和 $Q$ 的区别与联系能写出策略评估和价值迭代的代码并验证结果那这一关就算过了。接下来可以往两个方向走一个是深入动态规划类方法把策略迭代、广义策略迭代这些概念搞清楚另一个是直接跳到时序差分学习看怎么用采样代替全期望这是通往 Q-learning 和 SARSA 的必经之路。我个人建议先把时序差分学好因为它是深度强化学习里最常用的估计方法。贝尔曼方程给了你理论上的递归关系时序差分则告诉你如何用单步采样来逼近这个关系。两者结合才能理解为什么 Q-learning 可以在不知道环境转移概率的情况下工作。最后分享一个我自己的学习习惯每学完一个公式就写一段最小代码去验证它。不需要复杂的网络哪怕只是一个几行的小例子跑出来的数字和手算对上了心里才踏实。MDP 和贝尔曼方程尤其如此因为它们看起来简单但细节里全是坑。你亲手踩过一遍比看十遍推导都管用。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。