简介:这是一份面向强化学习入门者与Python开发者的Q Learning算法完整实现代码包,聚焦在无法预知全部状态与动作的场景下,通过构建Q表并迭代更新,让智能体逐步逼近最优策略。包内共5个文件,以py核心脚本为主,辅以md说明文档、pdf报告、license授权文件及临时文件,整体约852KB,结构轻量,便于直接阅读与二次修改。代码围绕Q函数更新公式展开,涵盖学习率、折扣因子、epsilon-greedy探索策略等关键参数,并演示了与gym环境(如CartPole-v1)结合的训练流程,同时涉及状态离散化处理思路。目前已有1108人学习下载,适合希望从零理解Q表构建、episode训练循环与收敛调参的读者,可据此快速搭建实验、观察奖励曲线并迁移到自身任务中。
1. 从一份 Q Learning 完整代码说起:为什么表格法至今仍是强化学习最值得先跑通的那一版
很多人第一次接触强化学习,是从一份python实现Q Learning强化学习(完整代码).zip开始的。解压之后通常是一个几百行的脚本,跑起来在控制台里打印一串回合奖励,最后画出一条慢慢爬升的曲线。看起来朴素,但它恰好是理解整个强化学习算法族最省事的一扇门。Q Learning 属于 value-based、model-free、off-policy 的时序差分方法,核心就是一张 Q 表:状态做行、动作做列,每个格子记录「在这个状态下做这个动作,长期能拿多少回报」。它不需要环境模型,不需要神经网络,甚至不需要 GPU,一台装了 python 的普通笔记本就能跑。适合谁?适合刚学完 python 基础、想搞懂强化学习到底在优化什么的人,也适合需要快速验证某个环境奖励设计是否合理的工程师——因为表格法没有函数逼近带来的玄学,出问题基本都能定位到具体某个格子。这一章先把「它是什么、为什么还值得学」讲清楚,后面几章再落到代码、参数和踩坑上。
2. Q Learning 的更新逻辑:从贝尔曼方程到那张会收敛的表
2.1 为什么是「离线学、在线用」的 off-policy
Q Learning 最容易被忽略的一点,是它更新时用的不是当前正在执行的动作,而是下一状态里 Q 值最大的那个动作。这就是它 off-policy 的来源。行为策略可以是 ε-greedy,一边探索一边利用;而目标策略始终是 greedy,也就是「假设我之后都选最好的」。两者分开,意味着它可以边试错边学一个更优的策略,哪怕当前动作选得很烂,更新目标依然朝着最优方向走。
更新公式写成代码就是一行:
# Q(s,a) <- Q(s,a) + alpha * (r + gamma * max(Q(s',:)) - Q(s,a)) q_table[state, action] += alpha * ( reward + gamma * np.max(q_table[next_state, :]) - q_table[state, action] )这里的max就是 off-policy 的体现。如果换成q_table[next_state, next_action],那就变成了 SARSA,是 on-policy。这个差别在悬崖行走(Cliff Walking)环境里会非常明显:Q Learning 学到的策略敢贴着悬崖走,因为它假设后续都走最优;SARSA 会绕远,因为它把探索时的风险也算进去了。选哪个取决于你的场景能不能承受试错代价。
2.2 一张 Q 表要多大,什么时候该放弃表格法
Q 表的形状是(状态数, 动作数)。状态数如果是 100,动作 4 个,那就是 400 个浮点数,随便跑。但如果状态是连续值,比如小车的位置、速度都是浮点,那状态空间是无限的,表格法直接失效。常见的离散化做法是把每个维度切成若干桶:
# 把连续观测离散成有限状态 def discretize(obs, bins): # bins 是每个维度的分桶边界列表 return tuple(np.digitize(o, b) for o, b in zip(obs, bins))分桶数量是个权衡:桶太少,不同情况被合并,学出来的策略粗糙;桶太多,每个格子被访问的次数骤降,收敛慢甚至学不动。经验上单个维度 10 到 20 个桶是常见起点,维度超过 4 到 5 个就要警惕维度灾难——状态数会指数膨胀。这时候就该考虑 DQN 这类用神经网络逼近 Q 值的方法了。判断标准很简单:如果 Q 表里大部分格子长期没被访问过,那表格法已经不适合你当前的问题。
2.3 一个能跑通的最小实现骨架
下面这段是 Q Learning 训练循环的通用骨架,环境用 Gym 风格的接口,换成任何离散动作环境都能套:
import numpy as np def train_q_learning(env, episodes=5000, alpha=0.1, gamma=0.99, epsilon=1.0, epsilon_min=0.01, epsilon_decay=0.995): # 初始化 Q 表,状态数 x 动作数 n_states = env.observation_space.n n_actions = env.action_space.n q_table = np.zeros((n_states, n_actions)) for ep in range(episodes): state, _ = env.reset() done = False while not done: # ε-greedy:以 epsilon 概率随机探索 if np.random.rand() < epsilon: action = env.action_space.sample() else: action = np.argmax(q_table[state, :]) next_state, reward, terminated, truncated, _ = env.step(action) done = terminated or truncated # 核心更新,注意用的是 next_state 的最大 Q 值 best_next = np.max(q_table[next_state, :]) q_table[state, action] += alpha * ( reward + gamma * best_next - q_table[state, action] ) state = next_state # 每回合结束后衰减探索率 epsilon = max(epsilon_min, epsilon * epsilon_decay) return q_table逻辑上分四步:选动作、执行、用贝尔曼最优方程更新、衰减探索率。参数含义逐个说:alpha是学习率,控制新信息覆盖旧估计的幅度,太大震荡、太小收敛慢;gamma是折扣因子,越接近 1 越看重长期回报;epsilon是探索概率,从 1 衰减到epsilon_min,让前期多试、后期多利用;epsilon_decay决定衰减速度,0.995 意味着大约几百回合后探索率降到很低。这套默认值在 FrozenLake、Taxi 这类小环境上基本都能收敛,换环境时优先调alpha和epsilon_decay。
3. 把代码真正跑起来:环境、依赖和三个必调参数
3.1 环境准备与依赖安装
拿到一份python实现Q Learning强化学习(完整代码).zip,第一步不是急着运行,而是确认 python 版本和依赖。这类代码通常依赖 numpy,如果带环境演示还会依赖 gym 或 gymnasium。python 3.8 及以上都能跑,安装命令:
# 建议先建虚拟环境,避免污染全局 python -m venv ql_env source ql_env/bin/activate # Windows 用 ql_env\Scripts\activate # 安装核心依赖 pip install numpy gymnasium如果代码里用的是老版gym,接口是env.step()返回四个值(next_state, reward, done, info);新版gymnasium返回五个值,多了truncated。这是最常见的翻车点之一,报错通常是「too many values to unpack」。解决办法要么装对应版本,要么在代码里改成五元组解包。先跑通再谈优化,别一上来就改算法。
3.2 三个必调参数:alpha、gamma、epsilon_decay
| 参数 | 作用 | 常见起点 | 调大后果 | 调小后果 |
|---|---|---|---|---|
| alpha | 学习率 | 0.1 | 更新激进,Q 值震荡不收敛 | 收敛极慢,像没在学 |
| gamma | 折扣因子 | 0.99 | 更看重长期,方差大 | 短视,学不到长线策略 |
| epsilon_decay | 探索衰减 | 0.995 | 探索久,收敛慢但更充分 | 过早利用,可能卡在次优 |
调参顺序建议:先固定gamma=0.99,把alpha从 0.1 试到 0.5 看曲线是否稳定;再调epsilon_decay,如果奖励曲线前期一直很低、后期才起来,说明探索不够,把 decay 调大(更慢衰减);如果曲线早早平了但没到最优,说明探索不足就过早利用,同样调大 decay。gamma一般最后动,除非你的任务回报周期特别长。
3.3 怎么判断它真的在学:看奖励曲线和 Q 表
不要只看最后一行打印的奖励。健康的训练过程,奖励曲线应该是前期低、中期快速上升、后期在高位小幅波动。如果曲线一直贴地,先检查奖励函数是不是太稀疏——很多环境只有到达目标才给 1,其余全是 0,这种稀疏奖励下 Q Learning 收敛很慢,需要更多回合或者做奖励塑形。另一个验证手段是直接看 Q 表:在关键状态下,最优动作对应的 Q 值应该明显高于其他动作。如果所有格子数值都差不多,说明还没学到东西。把 Q 表打印出来看几行,比盯着曲线猜有用得多。
4. 避坑与排查:Q Learning 跑不动时先查这几条
4.1 现象:奖励曲线长期不上升
原因通常有三个:探索率衰减太快、学习率太小、奖励太稀疏。先确认epsilon是不是几回合就掉到 0.01 了,如果是,把epsilon_decay调到 0.999 让它多探索。再看alpha,0.01 这种小值在小环境上会慢得让人怀疑人生,试 0.1 到 0.3。如果奖励本身只有终点给分,考虑加中间引导奖励,但要小心塑形过度导致策略跑偏。
4.2 现象:Q 值越来越大甚至溢出
这是漏了折扣或者更新公式写错的典型症状。检查更新里有没有gamma * best_next,如果写成reward + best_next,Q 值会随回合数线性累加,几百回合后就是天文数字。另一个可能是gamma设成了 1 且任务没有终止状态,回报无限累积。把gamma压到 0.99 以下,并确认环境有明确的终止条件。
4.3 现象:训练时好时坏,换个随机种子就崩
Q Learning 对初始化和探索顺序敏感,单次运行的结果不能说明问题。正确做法是固定多个随机种子各跑一遍,看平均表现。如果不同种子差异巨大,说明当前参数处在一个不稳定区间,通常是alpha偏大。把学习率降下来,或者引入学习率衰减,让后期更新更稳。别拿一次跑出来的漂亮曲线当结论。
4.4 现象:状态离散化后维度爆炸
连续状态做分桶时,如果每个维度都切 50 个桶,4 个维度就是 625 万种状态,Q 表内存直接吃满,而且绝大多数格子永远访问不到。解决办法是降低分桶数、做特征选择(去掉无关维度),或者干脆换 DQN。判断依据是:训练几千回合后,Q 表里非零格子的比例如果低于百分之几,说明状态空间相对样本量太大了。
5. 从表格法往前走:经验回放、目标网络和什么时候该换 DQN
跑通 Q Learning 之后,最自然的进阶是理解 DQN 为什么需要那两个额外组件。Q Learning 用max做自举更新,本身就有高估倾向;当 Q 值用神经网络表示时,又多了样本相关性和训练不稳定的问题。经验回放(experience replay)把(s, a, r, s')存进缓冲区随机采样,打破时间相关性;目标网络(target network)用一份延迟更新的参数算best_next,缓解自举带来的震荡。这两点本质上是在补表格法天然没有的坑——表格法里每个格子独立更新,不存在这些问题。
一个实用的迁移路径是:先在离散小环境上用 Q Learning 把奖励设计和超参感觉建立起来,再换到连续状态环境,把 Q 表替换成两层全连接网络,加上回放缓冲区和目标网络。判断该不该换的信号很明确:状态离散化后 Q 表访问率过低、或者动作本身是连续的(表格法无法处理连续动作)。我自己的习惯是,任何新任务先用 Q Learning 跑一个最小版本,哪怕只能跑简化后的离散环境,也能快速验证奖励函数合不合理。这一步省下来的时间,远比直接上 DQN 调参要划算。希望帮到你。
本文还有配套的精品资源,点击获取