我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

纯Python实现Q-Learning强化学习:从Q表到贝尔曼方程的完整代码实战

纯Python实现Q-Learning强化学习:从Q表到贝尔曼方程的完整代码实战 简介这份资源是面向强化学习入门者与Python开发者的Q Learning算法完整实现包帮助读者在无法预知全部状态与动作的环境下通过构建Q表并迭代更新让智能体逐步学到最优策略。内容围绕Q函数更新公式展开涵盖学习率、折扣因子、epsilon-greedy探索策略等关键概念并给出从初始化Q表到多轮episode收敛的完整流程适合想动手理解强化学习原理、需要可运行代码参考的读者。压缩包共5个文件包含py源码、md说明、pdf报告、license及临时文件整体约852KB其中py文件承载核心算法实现md与pdf用于梳理思路与实验记录。目前已有1108人学习下载读者可据此掌握Q表字典结构设计、状态离散化处理与训练调参思路并借助gym环境如CartPole-v1快速验证算法效果形成从理论公式到代码落地的完整认知。1. 从一张 Q 表说起这个 200 行不到的强化学习包为什么值得你花一晚上跑通很多人第一次接触强化学习是从 DQN、PPO 这些深度强化学习算法入门的结果环境配了三天CUDA 版本对不上最后连一个能收敛的 demo 都没跑出来。其实强化学习最朴素的思想——用一张表记录「在什么状态下做什么动作最划算」——根本不需要神经网络。这份python实现Q Learning强化学习(完整代码).zip走的就是这条最朴素的路线用纯 Python 加 NumPy把 Q-Learning 从环境建模、Q 表更新、ε-greedy 探索到训练收敛的完整链路摊开给你看。它适合两类人一类是刚学完 Python 基础、想找一个能真正跑起来、能改参数的强化学习入门项目另一类是已经用过深度强化学习框架、但想回头把表格型方法Tabular RL的底层逻辑补扎实的从业者。整个包不依赖 Gym、不依赖 PyTorch解压就能跑这一点在动辄要装十几个依赖的今天反而成了稀缺品。2. Q-Learning 的数学骨架贝尔曼方程到底在代码里长什么样2.1 时序差分更新与那张被反复覆盖的 Q 表Q-Learning 的核心就一个公式但很多人背得出公式却写不对代码。公式是Q(s, a) ← Q(s, a) α · [r γ · max Q(s, a) − Q(s, a)]这里的每一项在代码里都有明确对应。Q(s, a)是当前状态动作对的估值α是学习率代码里通常叫alpha或lrr是执行动作后环境给的即时奖励γ是折扣因子gammamax Q(s, a)是下一状态所有动作里最大的那个估值。方括号里那一整块叫 TD 误差时序差分误差它衡量的是「我原来的估计」和「实际走一步之后看到的更好估计」之间的差距。关键点在于Q-Learning 是 off-policy 的。它在更新时用的是max Q(s, a)也就是假设下一步一定选最优动作而不管实际探索时用的是不是 ε-greedy 随机动作。这个细节决定了它和 SARSA 的分道扬镳——SARSA 用的是实际执行的那个动作的 Q 值。如果你在代码里把max换成实际动作对应的 Q 值算法就悄悄变成了 SARSA收敛行为会不一样。这是新手最容易在改代码时踩的坑。Q 表本身就是一个二维数组行是状态数列是动作数。状态空间小的时候比如几百个格子这张表用 NumPy 数组存完全够用状态一多表就爆炸这也是为什么后来有了 DQN 用神经网络来拟合这张表。但理解表格版本是理解后面一切函数逼近方法的前提。2.2 用 NumPy 初始化 Q 表和超参数下面这段是典型的 Q 表初始化写法我按常见实现整理出来你可以对照包里的代码看结构是否一致import numpy as np # 状态数比如一个 5x5 的网格世界共 25 个状态 n_states 25 # 动作数上下左右 4 个动作 n_actions 4 # Q 表初始化为全零也可以用小随机数打破对称 Q_table np.zeros((n_states, n_actions)) # 超参数 alpha 0.1 # 学习率太大震荡太小收敛慢 gamma 0.9 # 折扣因子越接近 1 越看重长远奖励 epsilon 1.0 # 初始探索率从完全随机开始 epsilon_min 0.01 epsilon_decay 0.995 episodes 1000 # 训练轮数逻辑说明Q_table用np.zeros初始化是最常见的做法因为初始时我们对任何状态动作对都没有偏好。alpha取 0.1 是一个稳妥的起点如果你发现训练曲线剧烈抖动先把它降到 0.05 试试。gamma取 0.9 意味着大约看未来 10 步的回报网格世界这类任务通常 0.9 到 0.99 之间都合理。epsilon从 1.0 开始保证前期充分探索然后按epsilon_decay逐步衰减到epsilon_min这个衰减策略直接决定了「探索」和「利用」的平衡点。参数怎么改如果你的环境奖励稀疏比如走到终点才给奖励gamma要调高到 0.95 以上否则远处的奖励传不回来如果训练几百轮就收敛了但效果不稳定把epsilon_decay调慢一点比如 0.999让探索持续更久。2.3 ε-greedy 策略与训练主循环训练循环是整份代码的心脏结构如下for episode in range(episodes): state env.reset() # 每轮重置环境回到起点 done False while not done: # ε-greedy以 epsilon 概率随机探索否则选 Q 值最大的动作 if np.random.rand() epsilon: action np.random.randint(n_actions) else: action np.argmax(Q_table[state]) next_state, reward, done env.step(action) # 贝尔曼更新注意用的是 max这是 off-policy 的标志 best_next np.max(Q_table[next_state]) td_target reward gamma * best_next td_error td_target - Q_table[state, action] Q_table[state, action] alpha * td_error state next_state # 每轮结束衰减 epsilon if epsilon epsilon_min: epsilon * epsilon_decay逻辑说明np.random.rand() epsilon这一行控制探索概率注意是「小于」而不是「大于」写反了会变成前期几乎不探索。np.argmax(Q_table[state])在 Q 值全为零时会返回索引 0这意味着初始阶段如果 epsilon 衰减太快智能体会一直往同一个方向撞墙这是很多人训练不收敛的隐藏原因。td_target和td_error拆开写是为了方便你打印调试实际部署时可以合并成一行。参数说明env.reset()和env.step()是环境接口包里的环境实现决定了状态怎么编号、奖励怎么给。如果你要换成自己的环境只要保证reset返回一个整数状态、step返回(next_state, reward, done)三元组即可。done标志位必须在到达终止状态或超过最大步数时置为 True否则会死循环。3. 把代码跑起来环境搭建、训练与结果验证3.1 运行环境与依赖确认这份代码对环境的宽容度很高Python 3.6 以上都能跑核心依赖只有 NumPy。如果你机器上还没装一条命令解决pip install numpy如果你用的是 Python 3.8 或更新的版本NumPy 默认版本就够用不需要指定版本号。有些同学的环境里 NumPy 版本过旧导致np.random.rand行为异常这种情况先升级pip install --upgrade numpy解压后的目录结构通常是主程序文件加一个环境定义文件直接python 主文件名.py就能启动训练。如果你在 VS Code 里跑注意选对解释器别用系统自带的 Python 2否则print语句会直接报语法错误。3.2 训练过程观察与收敛判断跑起来之后终端一般会打印每轮的累计奖励或步数。判断有没有收敛看两个信号一是每轮步数是否稳定下降到接近最优路径长度二是累计奖励是否趋于平稳。如果跑了 1000 轮步数还在大幅波动大概率是超参数没调好按下面的顺序排查先看epsilon衰减是不是太快导致后期几乎不探索卡在局部最优再看alpha是不是太大Q 值在目标附近来回跳最后看gamma是不是太低导致智能体只顾眼前奖励。我一般会在训练循环里每 100 轮打印一次平均步数这样趋势一目了然。if episode % 100 0: print(fEpisode {episode}, epsilon{epsilon:.3f}, avg_steps{np.mean(recent_steps):.1f})这段打印代码建议你自己加上包里不一定有但它能帮你快速判断训练状态比盯着单轮结果有用得多。3.3 用贪心策略验证学到的 Q 表训练完之后把epsilon设为 0跑几轮纯贪心策略看智能体能不能稳定走到终点epsilon 0 # 关闭探索完全按 Q 表走 state env.reset() done False path [state] while not done: action np.argmax(Q_table[state]) state, reward, done env.step(action) path.append(state) print(最优路径:, path)逻辑说明这段验证代码是检验训练成果的关键。如果路径长度明显短于随机策略说明 Q 表学到了东西如果路径还是绕来绕去说明训练不充分或者奖励设计有问题。path列表记录了完整轨迹你可以把它画在网格上直观检查。注意验证时一定要把epsilon置零否则还会随机乱走看起来像是没学会。4. 避坑与排查那些让 Q-Learning 不收敛的细节4.1 现象Q 值全部趋同或爆炸原因学习率alpha设置过大或者奖励没有做归一化。如果每步奖励是 100 这种量级TD 误差会非常大Q 值更新幅度过猛直接发散。解决把奖励缩放到 -1 到 1 之间alpha降到 0.01 到 0.1 之间重新训练。这是血泪经验很多人第一次跑就是被奖励量级坑了。4.2 现象智能体在原地打转永远走不到终点原因epsilon衰减过快前期没探索够Q 表里大量状态动作对还是零argmax永远返回索引 0。解决把epsilon_decay调大比如从 0.995 改成 0.999或者设置一个探索预热期前 100 轮强制epsilon1.0。4.3 现象训练轮数加了一倍效果反而变差原因过拟合到某条次优路径后epsilon已经降到很低智能体没有机会跳出局部最优。解决不要一味增加轮数而是检查epsilon_min是不是设得太小留 0.05 左右的持续探索往往比 0.01 更稳。4.4 现象换了自己的环境后直接报索引越界原因env.reset()返回的状态编号超出了Q_table的行数或者env.step()返回的next_state是负数。解决在环境接口里加断言确保状态编号在[0, n_states)范围内。这个黑匣子问题不打印出来很难发现建议在step函数里加一行assert 0 next_state n_states。4.5 现象训练时奖励正常验证时路径完全不对原因验证代码里忘了把epsilon置零或者验证时用的环境实例和训练时不是同一个状态编号规则变了。解决验证前显式设置epsilon 0并确认环境初始化参数一致。5. 进阶技巧把 Q-Learning 用到你自己的场景里5.1 状态离散化连续状态怎么塞进 Q 表Q 表只能处理离散状态但很多实际问题的状态是连续的比如小车的位置和速度。常见做法是做分桶binning把每个连续维度切成若干区间用np.digitize把连续值映射成桶索引再把多个维度的桶索引组合成一个整数状态。桶的数量要权衡切太细 Q 表太大训练慢切太粗精度不够。我一般先用 10 个桶起步观察收敛情况再调整。5.2 奖励塑形稀疏奖励下的生存指南如果只有终点给奖励Q-Learning 在稍大的网格里几乎学不动。奖励塑形reward shaping是常用手段每走一步给一个小的负奖励靠近终点时给递增的正奖励。但要注意塑形奖励设计不当会引入偏差让智能体学会「刷奖励」而不是真正完成任务。一个稳妥的原则是塑形奖励的总量不要超过最终目标奖励的量级。5.3 从 Q 表到函数逼近的过渡信号什么时候该放弃 Q 表当你的状态数超过几万、内存开始吃紧或者训练速度明显变慢时就该考虑用神经网络来拟合 Q 值了也就是 DQN。但不要急着上 DQN先把这份表格版代码跑透理解 TD 误差、经验回放虽然表格版不一定用、目标网络这些概念在表格场景下的对应物后面看 DQN 代码会顺畅很多。5.4 一个我常用的调试习惯每次改完超参数或环境我会先跑 50 轮把 Q 表的最大值、最小值、非零元素占比打印出来。如果非零占比一直很低说明探索不够如果最大值和最小值差距极大说明更新不稳定。这个习惯帮我省下了大量盲目调参的时间。从那以后我每次接手新的强化学习代码都强制先跑一遍这个「体检」再谈效果。希望这份 Q-Learning 代码包能帮你把强化学习的第一块基石踩实后面再学深度强化学习心里就有底了。本文还有配套的精品资源点击获取
返回列表