强化学习入门:MDP与贝尔曼方程从原理到代码实现
1. 为什么MDP是强化学习的“地基”很多刚接触强化学习的朋友在看完环境搭建和基础概念之后都会卡在同一个地方马尔可夫决策过程和贝尔曼方程。这两个词听起来像是数学系研究生的专属但实际上它们就是强化学习最底层的“游戏规则说明书”。你后面要学的Q-Learning、DQN、策略梯度甚至现在火热的大模型对齐技术全都建立在这套规则之上。我见过不少同学直接跳到代码实现结果遇到状态转移、折扣因子、值函数迭代这些概念时只能靠死记硬背。这样做的后果就是一旦环境稍微复杂一点或者需要自己设计奖励函数就完全不知道从何下手。所以这一篇我打算用最直白的方式把MDP和贝尔曼方程拆开揉碎让你不仅知道公式长什么样更知道它为什么长这样以及在实际写代码时怎么用。这篇文章适合已经了解强化学习基本概念智能体、环境、奖励、策略的读者。如果你还没看过前两篇建议先补一下基础。我会从MDP的五个核心要素讲起然后推导贝尔曼方程最后用一个完整的网格世界例子手把手带你实现值迭代和策略迭代。整个过程我会穿插自己踩过的坑和实操心得保证你看完能自己写出一个可运行的MDP求解器。2. 马尔可夫决策过程到底在描述什么2.1 从“马尔可夫性”说起马尔可夫性用一句话概括就是未来的状态只取决于当前状态与过去无关。这听起来像是一句废话但在强化学习里它是一个极其强力的假设。有了它我们才能把复杂的历史交互压缩成一个当前状态从而用有限的数学工具去描述无限的时间序列。举个生活化的例子。你玩一个迷宫游戏角色现在站在某个格子里。下一步能走到哪里、会得到什么奖励只跟这个格子的位置以及你选择的动作有关跟你之前是怎么绕到这个格子的完全没关系。这就是马尔可夫性。如果游戏设计成“必须按照特定路径才能触发隐藏奖励”那它就不满足马尔可夫性标准的MDP框架就不适用了。在实际建模时我们通常假设环境满足马尔可夫性。如果不满足常见的做法是把历史信息也编码进状态里强行让它满足。比如把最近几帧画面堆叠起来作为状态输入这也是DQN处理Atari游戏时的标准操作。2.2 MDP的五元组拆解一个标准的MDP由五个元素组成我习惯把它们记成“状态、动作、转移、奖励、折扣”状态集合 S环境所有可能状态的集合。在网格世界里就是所有格子的坐标。动作集合 A智能体在每个状态下可以采取的动作。比如上下左右四个方向。转移概率 P(s|s,a)在状态s采取动作a后转移到状态s的概率。确定性环境下这个概率是1或0随机环境下则是分布。奖励函数 R(s,a,s)从状态s经过动作a转移到s时获得的即时奖励。折扣因子 γ取值在0到1之间用来衡量未来奖励的重要性。这五个元素一旦确定整个强化学习问题就形式化了。智能体的目标就是找到一个策略π(a|s)使得从任意状态出发累积折扣奖励的期望最大。注意很多教程会把奖励写成R(s,a)或者R(s)这取决于具体问题的建模方式。只要前后一致不影响最终结果。但在推导贝尔曼方程时用R(s,a,s)最严谨也最不容易出错。2.3 回报、值函数与策略回报是从当前时刻开始未来所有奖励的折扣累加G_t R_{t1} γR_{t2} γ²R_{t3} ...折扣因子γ的作用有两个一是让无穷级数收敛二是调节智能体的“短视”程度。γ接近0时智能体只看眼前利益γ接近1时它会考虑长远收益。实际项目中γ通常取0.9到0.99之间。如果任务步数很长比如上千步γ可以取0.99甚至0.999但要注意数值稳定性。状态值函数 Vπ(s)表示在状态s下按照策略π行动所能获得的期望回报。动作值函数 Qπ(s,a)则表示在状态s下先采取动作a之后再按照策略π行动所能获得的期望回报。这两个函数是强化学习的核心贝尔曼方程就是描述它们之间递归关系的等式。3. 贝尔曼方程把递归思想装进公式里3.1 贝尔曼期望方程是怎么来的贝尔曼方程的本质是递归。当前状态的价值等于即时奖励加上折扣后的下一状态价值。用公式写出来就是Vπ(s) Σ_a π(a|s) Σ_{s} P(s|s,a) [ R(s,a,s) γ Vπ(s) ]这个公式看起来复杂但拆开看非常直观。对于每个可能的动作a我们按照策略π的概率去选择它对于每个动作环境可能转移到不同的s我们按照转移概率加权每次转移获得即时奖励R再加上折扣后的下一状态价值γVπ(s)。全部加起来就是当前状态的价值。同理动作值函数的贝尔曼期望方程是Qπ(s,a) Σ_{s} P(s|s,a) [ R(s,a,s) γ Σ_{a} π(a|s) Qπ(s,a) ]这两个方程是等价的可以互相推导。在实际算法中我们通常根据具体情况选择用V还是Q。比如值迭代用V更方便而Q-Learning直接用Q。3.2 贝尔曼最优方程与最优策略贝尔曼期望方程描述的是“给定策略下的价值”而贝尔曼最优方程描述的是“所有策略中最好的那个价值”V*(s) max_a Σ_{s} P(s|s,a) [ R(s,a,s) γ V*(s) ]注意这里的max操作。它表示在状态s下我们不再按照某个固定策略选择动作而是直接选择那个能让价值最大的动作。对应的最优策略就是π*(s) argmax_a Σ_{s} P(s|s,a) [ R(s,a,s) γ V*(s) ]贝尔曼最优方程是非线性的因为里面有max操作。这导致它不能像期望方程那样直接解线性方程组而需要迭代求解。值迭代和策略迭代就是两种经典的迭代方法。实操心得很多同学在推导时会混淆V和Q的贝尔曼方程。我的建议是先记住V的版本然后利用Q(s,a) R γV(s)的关系去推导Q的版本。这样不容易记错。3.3 折扣因子的实际影响折扣因子γ的选择会直接影响智能体的行为。我做过一个简单的实验在一个需要绕路才能到达终点的网格世界里γ0.5时智能体宁愿原地打转也不愿意绕远路γ0.9时它能找到最优路径γ0.99时路径更倾向于最短但收敛速度明显变慢。从数学上看γ决定了有效视野长度。大约1/(1-γ)步之后的奖励会被折扣到可以忽略不计。γ0.9对应约10步γ0.99对应约100步。所以如果你的任务需要智能体考虑100步以后的奖励γ至少要设到0.99。另外γ1时如果任务没有终止状态回报可能发散导致值函数无穷大。所以实际项目中除非有明确的终止条件否则不要设γ1。4. 手把手实现值迭代与策略迭代4.1 环境设计一个4x4网格世界为了让你能直接复现我用一个经典的4x4网格世界作为例子。规则如下状态16个格子用0到15编号。动作0上1下2左3右。转移确定性转移撞墙则停在原地。奖励到达终点格子15奖励1其他转移奖励-0.01。折扣因子γ0.99。终止状态格子15。这个环境足够简单但包含了MDP的所有核心要素。你可以把它复制到任何Python环境里运行。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.gamma 0.99 self.reward -0.01 self.goal_reward 1.0 def step(self, state, action): row, col divmod(state, self.size) if action 0: # 上 row max(row - 1, 0) elif action 1: # 下 row min(row 1, self.size - 1) elif action 2: # 左 col max(col - 1, 0) elif action 3: # 右 col min(col 1, self.size - 1) next_state row * self.size col if next_state self.goal: return next_state, self.goal_reward, True return next_state, self.reward, False这段代码里step函数返回下一个状态、即时奖励和是否终止。注意撞墙时状态不变但仍然会得到-0.01的惩罚这鼓励智能体尽快到达终点。4.2 值迭代的实现与参数选择值迭代的核心思想是反复应用贝尔曼最优方程直到值函数收敛。伪代码如下初始化V(s)0对所有s。重复直到收敛对每个状态s计算所有动作的Q值。更新V(s) max_a Q(s,a)。输出最优值函数和最优策略。def value_iteration(env, theta1e-6, max_iter10000): V np.zeros(env.n_states) for i in range(max_iter): delta 0 for s in range(env.n_states): if s env.goal: continue q_values [] for a in range(env.n_actions): s_next, r, done env.step(s, a) q_values.append(r env.gamma * V[s_next]) best max(q_values) delta max(delta, abs(best - V[s])) V[s] best if delta theta: print(f值迭代在第{i1}轮收敛) break policy np.zeros(env.n_states, dtypeint) for s in range(env.n_states): if s env.goal: continue q_values [] for a in range(env.n_actions): s_next, r, done env.step(s, a) q_values.append(r env.gamma * V[s_next]) policy[s] np.argmax(q_values) return V, policy这里theta是收敛阈值我设的是1e-6。实际测试中这个4x4网格大约在30到50轮就收敛了。如果你把γ改成0.999收敛轮数会明显增加可能需要几百轮。注意事项值迭代每一轮都要遍历所有状态和动作复杂度是O(|S|·|A|)。状态数很大时比如连续状态空间就需要用函数逼近那就是DQN的范畴了。4.3 策略迭代的实现与对比策略迭代分为两步策略评估和策略改进。策略评估是给定策略π反复应用贝尔曼期望方程直到V收敛策略改进是根据V选择贪心动作得到新策略。重复这两步直到策略不再变化。def policy_evaluation(env, policy, theta1e-6, max_iter1000): V np.zeros(env.n_states) for _ in range(max_iter): delta 0 for s in range(env.n_states): if s env.goal: continue a policy[s] s_next, r, done env.step(s, a) new_v r env.gamma * V[s_next] delta max(delta, abs(new_v - V[s])) V[s] new_v if delta theta: break return V def policy_iteration(env, max_iter100): policy np.zeros(env.n_states, dtypeint) for i in range(max_iter): V policy_evaluation(env, policy) stable True for s in range(env.n_states): if s env.goal: continue old_action policy[s] q_values [] for a in range(env.n_actions): s_next, r, done env.step(s, a) q_values.append(r env.gamma * V[s_next]) policy[s] np.argmax(q_values) if old_action ! policy[s]: stable False if stable: print(f策略迭代在第{i1}轮收敛) break return V, policy策略迭代通常比值迭代收敛更快因为每次策略评估后策略改进的幅度更大。但每次策略评估本身需要迭代很多轮所以单轮计算量更大。在我的测试中4x4网格上策略迭代大约5到8轮就收敛了而值迭代需要30多轮。但总计算时间两者差不多因为策略迭代每轮更耗时。4.4 结果可视化与策略解读运行完两个算法后你可以把最优策略打印出来。用箭头表示动作你会看到智能体从任意格子出发都会沿着最短路径走向右下角的终点。如果某个格子有多个最优动作argmax会返回第一个所以策略可能不是唯一的。我建议你把V值也打印出来观察一下数值分布。靠近终点的格子V值接近1远离终点的格子V值接近-0.1左右。这是因为每走一步都有-0.01的惩罚折扣因子又让未来奖励打折扣。这个分布直观地反映了“距离终点越远价值越低”的规律。如果你想进一步验证可以手动计算某个格子的V值然后跟程序输出对比。比如终点左边那个格子向右走到终点获得1所以V -0.01 0.99*1 0.98。程序输出应该非常接近这个值。5. 常见问题与排查技巧实录5.1 值函数不收敛或发散怎么办这是新手最常见的问题。我总结了几种典型情况和对应的排查思路现象可能原因解决方法V值越来越大最终变成inf折扣因子γ≥1且没有终止状态把γ设为0.99或更小V值震荡不收敛学习率过大或转移概率有误检查转移函数确保概率和为1收敛极慢γ太接近1或状态空间太大适当降低γ或使用函数逼近策略在多个动作间跳变存在多个等价最优动作这是正常现象固定一个即可我踩过的一个坑是在写转移函数时忘记处理撞墙情况导致智能体可以“穿墙”到达终点V值异常高。后来加上边界检查才正常。所以写环境时一定要仔细检查边界条件。5.2 贝尔曼方程推导中的常见误区很多同学在推导时会犯两个错误。第一个是把V和Q的方程搞混第二个是忘记对动作和下一状态求和。我的建议是推导时先把所有可能的动作和下一状态列出来然后逐个加权求和不要跳步。另一个误区是认为贝尔曼方程只能用于确定性环境。实际上它天然支持随机环境只要把转移概率写进去就行。随机环境下的值迭代和策略迭代代码几乎不用改只需要把step函数改成返回概率分布即可。5.3 实际项目中的参数调优经验在实际项目中γ和奖励函数的设计往往比算法本身更重要。我做过一个路径规划项目一开始γ0.9智能体总是选择绕远路去捡小奖励而不愿意走最短路径到达终点。后来把γ提高到0.99同时把每步惩罚从-0.01调到-0.05智能体才学会了直奔终点。奖励函数的设计原则是让智能体的目标与你的目标一致。如果你希望它尽快到达终点就给每步一个小的负奖励如果你希望它探索更多区域就给探索新状态一个正奖励。但要注意奖励太稀疏会导致学习困难太密集又容易让智能体“刷分”。实操心得调试MDP时先用小规模环境比如3x3验证算法正确性再扩展到大规模。小环境下手动计算几个V值跟程序输出对比能快速发现公式实现错误。5.4 从MDP到深度强化学习的过渡当你理解了MDP和贝尔曼方程再去看DQN就会觉得顺理成章。DQN本质上就是用神经网络来近似Q(s,a)然后用贝尔曼最优方程构造损失函数L E[(r γ max_{a} Q(s,a) - Q(s,a))²]这个损失函数就是贝尔曼最优方程的残差。值迭代是直接迭代V而DQN是迭代Q网络的参数。理解了这一点你就知道为什么DQN需要目标网络、经验回放这些技巧了——它们都是为了让这个迭代过程更稳定。如果你已经能熟练实现值迭代和策略迭代我建议下一步尝试用Q表格实现Q-Learning然后再过渡到DQN。这个路径比直接看DQN论文要平滑得多。6. 从公式到代码的完整闭环6.1 一个可复现的完整示例把前面的代码片段拼起来加上可视化和测试就是一个完整的MDP求解器。我建议你按照以下步骤操作创建GridWorld类实现step方法。实现value_iteration函数设置theta1e-6。实现policy_iteration函数设置max_iter100。分别运行两个算法打印收敛轮数和最终策略。用箭头可视化策略检查是否符合预期。如果你运行结果跟我的描述有出入先检查step函数的边界处理再检查γ和奖励值是否一致。这两个地方最容易出错。6.2 扩展思路从确定性到随机性把确定性环境改成随机环境只需要改一处step函数不再返回单个下一状态而是返回一个列表包含所有可能的下一状态及其概率。比如向右走有80%概率到达右边格子20%概率停在原地。然后值迭代和策略迭代的代码只需要把V[s_next]改成加权求和即可。这个扩展能帮你理解为什么随机环境下的值函数更平滑。因为每个动作的结果不确定智能体不能依赖某个特定结果所以值函数会综合考虑所有可能性。6.3 我个人的一些经验体会最后分享几个我在实际项目中总结的小技巧。第一先验证再优化。不要一上来就调参先用小环境确认算法逻辑正确。第二打印中间结果。值迭代每一轮的V值变化、策略迭代每一轮的动作变化都能帮你快速定位问题。第三善用可视化。把V值画成热力图把策略画成箭头图一眼就能看出哪里不对。贝尔曼方程和MDP是强化学习的“内功”练好这部分后面学任何算法都会觉得有迹可循。我见过太多人跳过这部分直接学DQN结果遇到问题只能靠猜。花时间把这一篇的内容吃透绝对值回票价。

相关新闻

SkillSpector扫描LLM项目:高危告警为何总评SAFE?评分机制与应对策略

SkillSpector扫描LLM项目:高危告警为何总评SAFE?评分机制与应对策略

1. 从一次告警面板的诡异现象说起第一次用 SkillSpector 扫完一个自研的 LLM Agent 项目,结果页面让我愣了好几秒:风险列表里明晃晃挂着三条高危告警,可顶部的总评却是一个绿色的 SAFE。当时第一反应是工具坏了,第二反应是——是不…

2026/10/13 12:13:02 阅读更多 →
PHP刷华为机试全攻略:高频算法模板与在线训练实战指南

PHP刷华为机试全攻略:高频算法模板与在线训练实战指南

说实话,用 PHP 去刷大厂机试,很多人的第一反应是“不太行”。但我自己用 PHP 刷完几百道在线训练题之后,反而觉得这套语言在机试场景下非常顺手:字符串处理是看家本领,关联数组天然就是哈希表,写出来的代码…

2026/10/13 12:13:05 阅读更多 →
遮挡行人重识别实战:锚点重建与Mamba时序聚合提升10.6个点

遮挡行人重识别实战:锚点重建与Mamba时序聚合提升10.6个点

1. 遮挡行人重识别这个坑,为什么值得认真填行人重识别(ReID)说白了就是给系统一堆摄像头拍到的行人图,让它判断哪些图里是同一个人。这个任务在安防、智慧园区、商场客流分析里都是刚需,但真正落地的时候,最…

2026/10/13 12:13:08 阅读更多 →

最新新闻

Cursor额度重置教程:设备指纹清理实现合规续杯

Cursor额度重置教程:设备指纹清理实现合规续杯

简介:本资源是面向软件开发者的 Cursor 编辑器 11 月续杯实战源码包,聚焦解决免费用户模型调用受限、账号续期繁琐等实际痛点,适用于中初级开发者快速提升 AI 编程效率。压缩包共 3 个文件,含 .inscode(Cursor 插件配置…

2026/10/13 19:38:20 阅读更多 →
用Claude Code摸清老项目技术栈:三轮提示词框架与实战记录

用Claude Code摸清老项目技术栈:三轮提示词框架与实战记录

“只管去写”系列到了第二天。今天的任务很明确:用Claude分析一个老项目的技术栈。这件事听起来远没有写一个新功能那么有成就感,但只要你接过大半年之前自己写的代码、接过离职同事留下的半成品项目、或者刚入职需要快速搞清一套业务系统的底细&#xf…

2026/10/13 19:38:20 阅读更多 →
开源协作的隐形基石:高效代码评审实践指南

开源协作的隐形基石:高效代码评审实践指南

做开源项目这几年,我越来越确信一件事:代码评审(code review)是整个协作链条里最被低估的一环。很多人觉得代码写完、测试跑通、合并进去就完事了,可真正把一个项目推过几个版本之后才发现,评审才是让代码质…

2026/10/13 19:38:20 阅读更多 →
Python pickle序列化实战:从协议版本到安全红线全解析

Python pickle序列化实战:从协议版本到安全红线全解析

如果你写过 Python,一定遇到过这类需求:程序跑完把内存里的字典、列表或者某个自定义类实例保存到磁盘,下次启动再原封不动读回来。Python 为此内置了一个序列化模块叫 pickle,标题说得很直白——Pickle 是 Python 内置的序列化模…

2026/10/13 19:38:20 阅读更多 →
AnyPS5:不越狱的PS5增强方案与边界协同架构

AnyPS5:不越狱的PS5增强方案与边界协同架构

项目标题:“AnyPS5”这个名称本身带有强烈的指向性与模糊性并存的特征——它既像一个技术代号,又像一句口号;既暗示兼容性、泛用性(“Any”),又锚定在特定硬件生态(“PS5”)。但问题…

2026/10/13 19:38:20 阅读更多 →
互联网大厂薪酬结构全解析:总包、级别、绩效与谈薪技巧

互联网大厂薪酬结构全解析:总包、级别、绩效与谈薪技巧

1. 先说结论:今年这个薪酬盘子到底什么情况每年这个节点,总有人拿着某头部互联网平台的薪酬截图或者 Offer 在各个群里问“这个数正常吗”。说实话,我在这一行摸爬滚打了十几年,带过团队、做过招聘、也背过绩效,今年这…

2026/10/13 19:37:19 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →