从零手写Q-Learning走迷宫:Python强化学习入门实战

发布时间:2026/10/5 0:28:06
从零手写Q-Learning走迷宫:Python强化学习入门实战 简介这份资源面向强化学习入门者与课程实验需求者提供一套用Python实现Q-Learning算法完成迷宫路径规划的完整源码与实验报告。实验任务包括设计带障碍物的地图并支持用户修改布局、指定起点终点编程实现Q-Learning求解最短路径且学习参数可调并通过可视化界面演示Q值变化与最短路径探测过程。压缩包共9个文件约329KB以3个py源码文件为主另有2个pyc缓存、2张png效果图、1份docx实验报告和1份md说明分别承担算法实现、运行缓存、结果展示与文档记录。源码按迷宫类、可视化绘制、主程序求解三部分组织涵盖Q值计算、贪心策略选动作与Q-Table迭代更新等核心逻辑实验报告则梳理了实验流程与结果分析。目前已有73人学习适合希望快速跑通Q-Learning迷宫案例、对照代码理解算法细节并完成课程实验的读者参考。1. 从零手写 Q-Learning 走迷宫为什么它是强化学习入门最值得啃的第一个项目很多人学强化学习第一反应是去装 Stable-Baselines3、跑 CartPole几行代码就出结果但问他 Q 表长什么样、Bellman 更新到底在更新什么答不上来。真正能让你把强化学习「想明白」的反而是最土的一个项目用 Python 从零实现 Q-Learning让一个智能体在网格迷宫里从起点走到终点。它没有神经网络、没有 GPU、没有复杂环境封装全部逻辑加起来不到两百行但 TD 误差、探索与利用、折扣因子、收敛判据这些核心概念一个不少。这篇笔记就围绕「基于 Python 实现 Q-Learning 强化学习算法走迷宫」这件事把环境怎么搭、Q 表怎么更新、参数怎么调、实验报告里的曲线怎么画一步步讲透。适合刚入门强化学习、想真正动手写一遍而不是调库的 Python 开发者也适合要交课程实验报告的学生。2. 迷宫环境与 Q-Learning 的数学骨架先把账算清楚再写代码2.1 把迷宫抽象成 MDP状态、动作、奖励怎么定Q-Learning 跑在马尔可夫决策过程MDP上落到迷宫这个具体场景四元组要一一对应清楚否则后面代码一定乱。状态State每个可通行的格子就是一个状态用(row, col)表示。墙不是状态智能体永远不会「站在墙上」。假设迷宫是 5×5去掉若干墙格剩下的就是状态空间。状态总数直接决定 Q 表的行数。动作Action上下左右四个离散动作编号 0/1/2/3。动作空间固定为 4这是 Q 表列数的来源。奖励Reward这是最容易被新手写错的地方。常见做法是——每走一步给 -1 的小惩罚撞墙或越界给 -5到达终点给 100。为什么每步给负奖励因为如果每步给 0智能体可能学会在原地打转也不亏永远不急着到终点给负值相当于给时间加成本逼它找最短路径。转移Transition在确定性迷宫里给定状态和动作下一个状态是确定的。撞墙时状态不变或按你的设计停在原地这就是「无效动作」。提示奖励设计是 Q-Learning 里最玄学的部分。同一张迷宫奖励从「每步 -1」改成「每步 -0.01」收敛出来的路径可能一样但收敛速度差好几倍。先把奖励量级定好再调学习率。2.2 Q 表与 Bellman 更新一行公式背后的直觉Q 表是一个二维数组Q[state][action]表示「在状态 s 下做动作 a之后一直按最优策略走能拿到的累计折扣回报的估计」。它一开始全是 0靠不断试错把真实值填进去。更新公式是 Q-Learning 的灵魂Q(s, a) ← Q(s, a) α · [r γ · max Q(s, a) − Q(s, a)]拆开看每一项r γ · max Q(s, a)叫 TD 目标target是「这一步的真实奖励 对未来的乐观估计」。max Q(s, a)用的是下一个状态里最大的那个 Q 值注意是 max不是实际执行的动作。这正是 Q-Learning 属于 off-policy 的原因——它学的是最优策略跟当前实际怎么走没关系。r γ · max Q(s, a) − Q(s, a)是 TD 误差衡量「我原来的估计」和「新证据」差多少。α是学习率控制每次往新证据靠多少。α1 就是完全信新证据α0.1 就是慢慢挪。γ 是折扣因子取值 0 到 1。γ 越接近 1智能体越看重长远回报越接近 0越短视。迷宫这种需要走十几步才能到终点的任务γ 一般设 0.9 到 0.99。2.3 探索与利用ε-greedy 为什么不能一上来就贪心如果智能体一开始就总选当前 Q 值最大的动作而 Q 表全是 0它就会一直选动作 0或第一个被更新的动作永远发现不了终点。所以必须留一部分概率去随机探索。ε-greedy 策略以 ε 的概率随机选动作以 1−ε 的概率选当前最优动作。常见做法是 ε 从 1.0 开始衰减到 0.05 左右。前期疯狂探索把 Q 表铺开后期收敛到贪心策略走最优路径。import numpy as np def epsilon_greedy(q_table, state, epsilon, n_actions): # 以 epsilon 概率随机探索否则选当前 Q 值最大的动作 if np.random.rand() epsilon: return np.random.randint(n_actions) return int(np.argmax(q_table[state]))这段逻辑很短但参数含义要清楚epsilon是探索率n_actions是动作数迷宫固定为 4。np.argmax在 Q 值相等时会返回第一个索引这是新手常忽略的细节——如果 Q 表初始化全 0argmax 永远返回 0所以前期必须靠随机探索打破这个对称性。3. 用 Python 把 Q-Learning 走迷宫跑通从环境到训练循环3.1 迷宫环境类状态编码与奖励函数先把环境写成一个类负责维护智能体位置、判断动作合法性、返回奖励和下一状态。状态用一维整数编码方便直接做 Q 表索引。import numpy as np class MazeEnv: def __init__(self): # 0 可走1 墙2 终点 self.maze np.array([ [0, 0, 0, 0, 1], [1, 1, 0, 1, 0], [0, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 0, 0, 0, 2], ]) self.n_rows, self.n_cols self.maze.shape self.start (0, 0) self.goal (4, 4) self.state self.start # 动作0 上1 下2 左3 右 self.actions [(-1, 0), (1, 0), (0, -1), (0, 1)] def reset(self): self.state self.start return self._encode(self.state) def _encode(self, pos): # 把 (row, col) 编码成一维状态编号 return pos[0] * self.n_cols pos[1] def step(self, action): dr, dc self.actions[action] nr, nc self.state[0] dr, self.state[1] dc # 越界或撞墙状态不变给惩罚 if not (0 nr self.n_rows and 0 nc self.n_cols) \ or self.maze[nr, nc] 1: return self._encode(self.state), -5, False self.state (nr, nc) if self.state self.goal: return self._encode(self.state), 100, True return self._encode(self.state), -1, False逻辑说明_encode把二维坐标压成一维Q 表就能用q_table[state][action]直接索引省去字典查找。step里先算目标格越界或撞墙时返回原状态和 -5 惩罚这是「无效动作」的标准处理。到达终点返回 100 并置doneTrue训练循环据此结束本回合。参数说明奖励值 -5 / -1 / 100 是经验值量级差距要拉开否则终点奖励淹没在步数惩罚里。如果迷宫更大比如 20×20每步惩罚可以调小到 -0.1避免累计惩罚过大导致 Q 值发散。3.2 训练主循环Q 表更新与 ε 衰减def train(env, episodes2000, alpha0.1, gamma0.95, eps_start1.0, eps_end0.05, eps_decay0.995): n_states env.n_rows * env.n_cols n_actions 4 q_table np.zeros((n_states, n_actions)) epsilon eps_start rewards_history [] for ep in range(episodes): state env.reset() total_reward 0 done False while not done: action epsilon_greedy(q_table, state, epsilon, n_actions) next_state, reward, done env.step(action) # Q-Learning 核心更新 best_next np.max(q_table[next_state]) td_target reward gamma * best_next q_table[state, action] alpha * (td_target - q_table[state, action]) state next_state total_reward reward rewards_history.append(total_reward) epsilon max(eps_end, epsilon * eps_decay) return q_table, rewards_history逻辑说明外层循环是回合episode内层是单回合内的每一步。每走一步就做一次 Q 更新这是时序差分TD学习的典型结构——不需要等整条路径走完再更新。epsilon每回合乘一次衰减系数从 1.0 慢慢降到 0.05。参数说明episodes2000对 5×5 迷宫足够20×20 可能要上万。alpha0.1是稳妥起点太大如 0.9会导致 Q 值震荡不收敛太小如 0.01收敛慢。gamma0.95让智能体看得足够远。eps_decay0.995意味着大约 600 回合后 ε 降到 0.05 附近前期探索、后期利用的节奏比较均衡。3.3 策略提取与路径可视化训练完 Q 表用纯贪心策略走一遍把路径打印出来验证。def extract_path(env, q_table): state env.reset() path [env.state] done False steps 0 while not done and steps 100: action int(np.argmax(q_table[state])) state, _, done env.step(action) path.append(env.state) steps 1 return path q_table, rewards train(MazeEnv()) env MazeEnv() path extract_path(env, q_table) print(路径长度:, len(path)) print(路径:, path)逻辑说明extract_path不再探索每步都选 Q 值最大的动作这就是训练出来的最终策略。steps 100是安全上限防止策略没收敛时死循环。打印路径能直观看到智能体是否绕开了墙、是否走了最短路线。参数说明如果路径长度明显大于理论最短步数说明训练不充分或 ε 衰减太快可以增加回合数或放慢衰减。4. 参数调优与实验报告让曲线和结论站得住脚4.1 四个关键参数怎么调α、γ、ε、奖励量级这四个参数没有万能值但有一套可复现的调参顺序。参数作用推荐起点调大后果调小后果α 学习率每次更新步长0.1Q 值震荡、难收敛收敛极慢γ 折扣因子看重未来程度0.95易发散、方差大短视、绕远路ε 起始值初始探索强度1.0前期太乱探索不足、卡局部ε 衰减探索退火速度0.995收敛慢过早贪心、学不全调参顺序建议先固定 γ0.95、α0.1把 ε 衰减调到能收敛再微调 α最后动 γ。每次只改一个参数否则实验报告里说不清是谁的功劳。注意如果 Q 值出现 NaN 或爆炸到极大值八成是奖励量级和 α 不匹配。把奖励缩放到 [-1, 1] 区间或者把 α 降到 0.01通常能救回来。4.2 用 Python 画收敛曲线回报、步数与成功率实验报告里最有说服力的图是「每回合累计回报随回合数变化」。原始曲线抖动很大通常要做滑动平均。import matplotlib.pyplot as plt def moving_average(data, window50): return np.convolve(data, np.ones(window)/window, modevalid) plt.plot(rewards, alpha0.3, labelraw reward) plt.plot(moving_average(rewards), labelmoving avg (50)) plt.xlabel(Episode) plt.ylabel(Total Reward) plt.legend() plt.title(Q-Learning Maze Convergence) plt.savefig(convergence.png, dpi150)逻辑说明alpha0.3让原始曲线半透明突出滑动平均线。window50是常用窗口太小看不出趋势太大丢失细节。保存成 PNG 直接放进实验报告。参数说明如果曲线在某个负值附近长期横盘不上升说明智能体没找到终点检查奖励设计或 ε 是否衰减太快。如果曲线上升后又剧烈震荡说明 α 偏大。4.3 实验报告该写哪几块从现象到归因一份能拿得出手的实验报告结构比篇幅重要。建议包含问题定义迷宫尺寸、状态数、动作数、奖励规则用表格列清楚。算法描述Q 表维度、更新公式、ε-greedy 策略公式要写出来。参数设置α、γ、ε 起始与衰减、回合数做成参数表。结果展示收敛曲线、最终路径图、路径长度。对比实验至少做一组对照比如 α0.1 vs α0.5或 ε 衰减快慢对比用同一张图叠加两条曲线。结论与归因不要只写「收敛了」要写「α0.5 时曲线前 200 回合震荡明显因为步长过大导致 Q 值在最优值附近反复横跳」。对比实验是拉开报告质量的关键。只跑一组参数谁都会能说清「为什么这组参数更好」才是工程能力。5. 避坑与排查Q-Learning 走迷宫最常见的五个翻车点5.1 智能体原地打转永远走不到终点现象训练几千回合累计回报一直卡在某个负值路径提取出来是原地循环。原因奖励设计里每步惩罚太小甚至为 0智能体发现「不动也不亏」或者 ε 衰减太快前期没探索到终点就被贪心策略锁死。解决把每步惩罚设为明显负值如 -1终点奖励设为 100 拉开量级同时确认 ε 起始值为 1.0衰减系数不要小于 0.99。5.2 Q 表全是 0argmax 永远返回动作 0现象训练前后 Q 表几乎没变化智能体行为固定。原因Q 表初始化为全 0np.argmax在相等时返回索引 0如果探索率又很低动作 0 被反复选中其他动作的 Q 值永远得不到更新。解决确保 ε 起始为 1.0让所有动作都有机会被尝试或者给 Q 表加一点随机初始化打破对称性。5.3 收敛曲线剧烈震荡Q 值发散现象累计回报忽高忽低Q 值出现极大值或 NaN。原因学习率 α 太大或奖励量级过大比如终点给 10000导致 TD 目标远超当前估计更新步子迈太大。解决把 α 降到 0.01~0.1奖励缩放到 [-1, 1] 或 [-10, 10] 区间γ 不要设成 1.0。5.4 训练能收敛但提取的路径不是最短现象智能体确实到终点了但绕了远路。原因γ 偏小导致智能体短视或者训练回合不够Q 值还没传播到起点附近的状态。解决把 γ 提到 0.95 以上增加训练回合数。Q 值的传播是从终点往回逐格扩散的迷宫越大需要的回合越多。5.5 换一张迷宫就要重调所有参数现象在 5×5 上调好的参数换到 10×10 就完全不收敛。原因状态空间变大Q 表需要更多回合才能填满原来的回合数和 ε 衰减速度不够。解决回合数按状态数比例放大ε 衰减系数调大如 0.999每步惩罚适当减小避免长路径累计惩罚过大。6. 进阶技巧把 Q-Learning 从「能跑」推到「跑得好」跑通基础版之后有几个技巧能让你的实现更接近工程水准也能让实验报告更有深度。第一个是双 Q 表Double Q-Learning。标准 Q-Learning 用max估计下一状态价值会系统性高估 Q 值迷宫小的时候看不出来状态一多就会导致策略偏保守。做法是维护两张 Q 表一张负责选动作、另一张负责估值交替更新。改动不大但能明显缓解高估问题。第二个是奖励塑形Reward Shaping。在稀疏奖励迷宫里智能体可能很久都碰不到终点。可以在每步奖励里加一项「离终点的曼哈顿距离减少就给正奖励」引导它朝终点方向走。但要注意塑形奖励不能太大否则智能体会为了拿塑形奖励而绕圈反而学不到真正的最短路径。第三个是用热力图看 Q 值分布。把每个状态的最大 Q 值画成网格热力图能直观看到 Q 值从终点向四周扩散的过程。如果某个区域 Q 值长期为 0说明智能体从没探索到那里需要检查 ε 策略或迷宫连通性。def plot_q_heatmap(q_table, env): grid np.max(q_table, axis1).reshape(env.n_rows, env.n_cols) plt.imshow(grid, cmapviridis) plt.colorbar(labelMax Q Value) plt.title(Q Value Heatmap) plt.savefig(q_heatmap.png, dpi150)这段代码把 Q 表每行的最大值还原成迷宫网格reshape依赖状态编码顺序和迷宫行列一致。热力图颜色越亮表示该状态价值越高正常情况下终点附近最亮向起点逐渐变暗。最后一个习惯每次改参数都固定随机种子np.random.seed(42)否则两次实验的曲线没法对比实验报告里的结论就站不住脚。我自己早期写实验报告时吃过这个亏同一组参数跑两次曲线差很多被追问才发现是随机性没控制。把种子固定、把参数表列清楚、把对比实验做扎实这份 Q-Learning 走迷宫的源码和报告才真正经得起看。希望帮到你。本文还有配套的精品资源点击获取