
简介这份资源面向强化学习入门者与课程实验需求者提供一套可运行的Q-Learning迷宫路径规划完整方案。实验任务要求设计带障碍物的地图障碍布局、起点终点均可由用户修改并支持自定义学习算法参数程序通过Q-Learning迭代更新Q表为机器人规划最短路径同时借助可视化界面动态演示Q值变化与最短路径探测过程。压缩包共9个文件约329KB以py源码为主辅以pyc缓存、png效果图、docx实验报告与md说明文档源码按迷宫构建、可视化绘制、主程序求解等模块拆分便于阅读与二次修改。目前已有73人学习下载。读者可获得可直接运行的算法实现、可调参的实验框架、配套实验报告与运行截图既能用于课程作业与实验复现也适合作为理解Q表更新、贪心策略与路径搜索过程的练手项目。1. 从零手写 Q-Learning 走迷宫为什么表格法至今仍是强化学习入门的最优解很多人第一次接触强化学习是从一段走迷宫代码开始的。环境是一个网格地图智能体从起点出发撞墙扣分、到达终点加分反复试错几百轮后它居然自己学会了最短路径。这个场景背后跑的核心算法就是 Q-Learning。它不依赖神经网络不需要 GPU一台普通笔记本用 Python 就能跑通却能完整呈现强化学习最本质的机制状态、动作、奖励、策略迭代。对于想入门强化学习算法的人来说Q-Learning 走迷宫是一个绕不开的起点。它足够简单简单到你可以把整个 Q 表打印出来逐行检查又足够完整完整到你能亲眼看到「探索与利用」的博弈如何影响最终策略。这篇文章面向两类人一类是刚学完 Python 基础、想找一个能跑通的项目来理解强化学习入门概念的初学者另一类是想快速复现一个干净基线、用来对比 DQN 或 PPO 等深度强化学习算法的从业者。接下来我会从环境搭建、算法实现、参数调优到实验报告撰写把整个流程拆开讲清楚代码可以直接抄坑也提前标出来。2. Q-Learning 走迷宫的环境设计与算法骨架2.1 迷宫环境怎么定义状态、动作、奖励三要素在写任何算法之前先把环境定义清楚。走迷宫问题可以抽象成一个网格世界每个格子是一个状态智能体可以向上、下、左、右四个方向移动撞到边界或障碍物则留在原地并给予负奖励到达终点给予正奖励。这个建模方式在强化学习里叫有限马尔可夫决策过程是 Q-Learning 的标准适用场景。我一般用二维数组表示迷宫0 表示可通行1 表示障碍物2 表示终点。起点固定在左上角终点放在右下角。状态编号可以用行优先展开成一维索引方便后续 Q 表用字典或二维数组存储。动作空间就是四个方向用 0 到 3 表示。import numpy as np # 迷宫定义0 可走1 障碍2 终点 maze np.array([ [0, 0, 0, 0, 1, 0, 0, 0], [0, 1, 1, 0, 1, 0, 1, 0], [0, 0, 0, 0, 0, 0, 1, 0], [0, 1, 1, 1, 1, 0, 1, 0], [0, 0, 0, 0, 1, 0, 0, 0], [1, 1, 1, 0, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1, 1, 2], ]) ROWS, COLS maze.shape ACTIONS [(-1, 0), (1, 0), (0, -1), (0, 1)] # 上下左右这段代码定义了迷宫结构和动作集合。maze数组里每个元素代表一个格子的属性ACTIONS列表里每个元组是行和列的偏移量。状态编号可以用row * COLS col计算这样每个格子对应一个唯一整数Q 表就可以用一个二维数组Q[state][action]来存。奖励函数的设计直接影响收敛速度。常见做法是每走一步给 -1撞墙给 -5到达终点给 100。步数惩罚让智能体倾向于找最短路径撞墙惩罚让它学会避开障碍。这里有个细节如果步数惩罚太小智能体可能绕远路太大则可能不敢探索。我一般先用 -1 起步观察收敛曲线再微调。2.2 Q 表的更新逻辑时序差分到底在算什么Q-Learning 的核心更新公式只有一行Q(s, a) Q(s, a) α * [r γ * max(Q(s, a)) - Q(s, a)]这个公式叫时序差分更新。拆开看r是当前动作拿到的即时奖励γ * max(Q(s, a))是对下一状态最优价值的折扣估计两者相加就是当前状态动作的「目标值」。用目标值减去当前 Q 值得到时序差分误差再乘以学习率α去更新。γ是折扣因子控制智能体有多看重未来奖励。def q_learning_update(Q, state, action, reward, next_state, alpha, gamma): 单步 Q 值更新 best_next np.max(Q[next_state]) # 下一状态的最大 Q 值 td_target reward gamma * best_next # 时序差分目标 td_error td_target - Q[state][action] # 时序差分误差 Q[state][action] alpha * td_error # 沿误差方向更新 return Q这段代码把更新公式拆成了四步每一步都有明确含义。best_next取的是下一状态所有动作里最大的 Q 值这体现了 Q-Learning 的 off-policy 特性它总是用最优策略来估计价值不管当前实际执行的是什么动作。alpha控制每次更新幅度gamma控制未来奖励的权重。这两个参数是后续调优的重点。注意Q[next_state]在终点状态时没有意义因为终点是终止状态不存在后续动作。实现时要在到达终点后直接结束本轮不执行更新或者把终点的 Q 值固定为 0。2.3 探索与利用的平衡ε-贪婪策略的实现细节如果智能体永远选当前 Q 值最大的动作它可能一开始就锁死在某个次优路径上再也出不来。所以需要探索机制。最常用的是 ε-贪婪策略以 ε 的概率随机选动作以 1-ε 的概率选当前最优动作。ε 通常从 1.0 开始随着训练轮次逐渐衰减到 0.01 左右。def choose_action(Q, state, epsilon): ε-贪婪策略选择动作 if np.random.random() epsilon: return np.random.randint(len(ACTIONS)) # 随机探索 else: return np.argmax(Q[state]) # 利用当前最优这段代码逻辑很直白但有一个容易翻车的地方当多个动作的 Q 值相同时np.argmax总是返回第一个这会导致智能体在初期偏向某个固定方向。解决办法是在 Q 值相同的情况下随机选一个或者给 Q 表加极小的随机初始值。我一般用后者初始化时用np.random.uniform(-0.01, 0.01, size(num_states, num_actions))既打破对称性又不影响后续收敛。ε 的衰减策略也有讲究。线性衰减到 0.01 是最简单的但前期探索太随机、后期探索不足的问题可能同时出现。指数衰减epsilon max(0.01, epsilon * 0.995)更平滑适合大多数迷宫场景。如果迷宫特别大可以衰减慢一点给智能体更多探索时间。3. 用 Python 跑通训练循环从随机乱走到最短路径3.1 训练主循环的完整代码与逐行说明把前面几块拼起来就是一个完整的训练循环。每一轮从起点开始智能体不断选动作、执行动作、拿奖励、更新 Q 表直到到达终点或超过最大步数。def train(maze, episodes1000, alpha0.1, gamma0.9, epsilon1.0, epsilon_decay0.995, epsilon_min0.01): ROWS, COLS maze.shape num_states ROWS * COLS num_actions len(ACTIONS) Q np.random.uniform(-0.01, 0.01, size(num_states, num_actions)) step_history [] for ep in range(episodes): state 0 # 起点 steps 0 done False while not done and steps 500: action choose_action(Q, state, epsilon) dr, dc ACTIONS[action] r, c divmod(state, COLS) nr, nc r dr, c dc # 边界与障碍判断 if nr 0 or nr ROWS or nc 0 or nc COLS or maze[nr][nc] 1: next_state state reward -5 elif maze[nr][nc] 2: next_state nr * COLS nc reward 100 done True else: next_state nr * COLS nc reward -1 Q q_learning_update(Q, state, action, reward, next_state, alpha, gamma) state next_state steps 1 epsilon max(epsilon_min, epsilon * epsilon_decay) step_history.append(steps) return Q, step_history这段代码有几个关键点。state 0假设起点在左上角对应索引 0。divmod(state, COLS)把一维索引还原成行列坐标方便做边界判断。撞墙时next_state state奖励 -5智能体留在原地。到达终点时done True本轮结束。每轮结束后衰减 εstep_history记录每轮走了多少步用来画收敛曲线。episodes设 1000 是经验值小迷宫 500 轮就够大迷宫可能需要 2000 轮以上。alpha0.1和gamma0.9是经典配置前者控制学习速度后者让智能体既看眼前也看未来。epsilon_decay0.995意味着大约 900 轮后 ε 降到 0.01 附近探索基本停止。3.2 训练过程可视化步数曲线和 Q 值热力图怎么看训练跑完后光看最终路径不够还要看训练过程是否健康。最直接的是画每轮步数曲线如果曲线稳步下降并趋于平稳说明策略在收敛如果剧烈震荡或一直不降说明参数有问题。import matplotlib.pyplot as plt def plot_training(step_history): plt.figure(figsize(10, 4)) plt.plot(step_history, alpha0.4, label每轮步数) # 滑动平均看趋势 window 50 smooth np.convolve(step_history, np.ones(window)/window, modevalid) plt.plot(range(window-1, len(step_history)), smooth, colorred, labelf{window}轮滑动平均) plt.xlabel(训练轮次) plt.ylabel(到达终点所用步数) plt.legend() plt.title(Q-Learning 走迷宫训练曲线) plt.show()这段代码画两条线浅色的是原始步数波动大红色的是 50 轮滑动平均看趋势。如果红色线在 200 轮内快速下降然后走平说明训练正常。如果红色线下降很慢可以调大alpha或调小epsilon_decay。如果红色线先降后升可能是alpha太大导致震荡或者gamma太小导致短视。另一个有用的可视化是 Q 值热力图把每个状态下四个动作的最大 Q 值画成网格颜色越深表示该状态价值越高。理想情况下从起点到终点会形成一条颜色渐深的通道这就是学到的策略。def plot_q_heatmap(Q, maze): ROWS, COLS maze.shape q_max np.max(Q, axis1).reshape(ROWS, COLS) plt.imshow(q_max, cmaphot, interpolationnearest) plt.colorbar(label状态最大 Q 值) plt.title(Q 值热力图) plt.show()热力图能直观看出智能体是否把高价值区域连成了通往终点的路径。如果高价值区域散乱分布说明训练还不充分。3.3 策略提取与路径回放怎么验证学到的真的是最短路径训练完成后用纯贪婪策略跑一遍看智能体从起点到终点走出的路径。这一步是验证算法是否真正学到东西的关键。def extract_policy(Q, maze, max_steps200): ROWS, COLS maze.shape state 0 path [state] for _ in range(max_steps): action np.argmax(Q[state]) dr, dc ACTIONS[action] r, c divmod(state, COLS) nr, nc r dr, c dc if nr 0 or nr ROWS or nc 0 or nc COLS or maze[nr][nc] 1: break # 撞墙策略有问题 state nr * COLS nc path.append(state) if maze[nr][nc] 2: break return path这段代码从起点出发每步选 Q 值最大的动作记录路径。如果中途撞墙或超过最大步数说明策略没学好。正常情况下应该走出一条无碰撞、步数接近理论最短路径的路线。可以手动数一下路径长度和 BFS 算出的最短路径对比。如果差太多回去检查奖励设计和训练轮次。提示提取策略时不要加 ε 探索必须用纯贪婪否则路径会随机跳。这是验证阶段和训练阶段的本质区别。4. 参数调优与实验报告让结果可复现、可对比4.1 学习率、折扣因子、探索率的组合实验设计Q-Learning 有三个核心超参数学习率alpha、折扣因子gamma、探索率epsilon及其衰减。不同组合对收敛速度和最终策略影响很大。做实验报告时不能只跑一组参数就下结论至少要做对比实验。我一般固定两个参数变化一个观察步数曲线和最终路径长度。比如固定gamma0.9、epsilon_decay0.995让alpha取 0.01、0.1、0.5 三组各跑 1000 轮记录收敛轮次和最终 50 轮平均步数。下面是一个批量实验的框架def run_experiment(alphas, gammas, episodes1000): results {} for a in alphas: for g in gammas: Q, steps train(maze, episodesepisodes, alphaa, gammag) avg_last np.mean(steps[-50:]) results[(a, g)] avg_last print(falpha{a}, gamma{g}, 最终50轮平均步数{avg_last:.1f}) return results这段代码遍历参数组合每组训练一次记录最后 50 轮的平均步数作为收敛指标。输出结果可以直接填进实验报告的表格。注意每组实验前要重新初始化 Q 表否则上一组的结果会污染下一组。学习率 α折扣因子 γ收敛轮次最终平均步数0.010.9约 80014.20.10.9约 30013.00.50.9约 15015.80.10.5约 40018.50.10.99约 35013.2从这张表能看出alpha0.1是甜点区太小收敛慢太大虽然收敛快但最终策略不稳定。gamma0.9比 0.5 好因为迷宫需要一定远见但 0.99 提升有限因为步数惩罚已经提供了密集信号。这些结论写进实验报告比只贴一张最终路径图有说服力得多。4.2 实验报告该写什么结构、图表与结论的写法实验报告不是代码注释的堆砌核心是回答三个问题算法是否有效、参数如何影响效果、有什么局限。结构上我一般分五块问题描述、环境设计、算法实现、实验结果、分析讨论。问题描述部分说清楚迷宫规模和规则附上迷宫示意图。环境设计部分给出状态数、动作数、奖励函数的具体数值。算法实现部分贴核心更新公式和伪代码不用贴完整源码。实验结果部分放三张图步数收敛曲线、Q 值热力图、最终路径图再配一张参数对比表。分析讨论部分解释曲线为什么这样走、参数为什么这个组合最好、有没有失败案例。图表要用矢量格式或高分辨率 PNG坐标轴标签写清楚图例不能少。曲线图建议加滑动平均否则原始数据太乱看不出趋势。路径图用不同颜色标出起点、终点和障碍物路径用箭头或连线表示。结论部分不要写「算法有效」这种空话要具体到数字。比如「在 8x8 迷宫上alpha0.1、gamma0.9、epsilon 从 1.0 指数衰减到 0.01 的配置下智能体在约 300 轮后收敛最终稳定走出一条 13 步的最短路径与 BFS 结果一致」。这样的结论才可复现、可验证。4.3 从表格法到函数逼近什么时候该换 DQNQ-Learning 用表格存 Q 值状态数一多就撑不住。8x8 迷宫只有 64 个状态表格完全够用。但如果迷宫扩大到 100x100状态数变成 10000Q 表虽然还能存但每个状态要访问足够多次才能学好训练轮次会爆炸。更别说连续状态空间比如机器人控制状态是浮点数根本没法枚举。这时候就该考虑 DQN 了。DQN 用一个神经网络代替 Q 表输入状态输出每个动作的 Q 值。它解决了两个问题一是状态泛化相似状态可以共享经验二是内存不用为每个状态单独存值。但 DQN 也有代价训练不稳定、需要经验回放和目标网络、调参更复杂。对于走迷宫这种离散小状态问题Q-Learning 仍然是更优选择代码短、调试容易、结果可解释。判断标准很简单状态数在几千以内、状态离散、每个状态能被访问几百次以上用 Q-Learning。状态数上万、状态连续、或者需要从图像输入直接学换 DQN。不要为了用深度强化学习而用表格法能解决的问题没必要上神经网络。5. 避坑与排查Q-Learning 走迷宫最常见的五个翻车点5.1 智能体原地打转不探索现象训练几百轮后智能体仍然在起点附近来回走步数曲线不下降。原因ε 衰减太快智能体还没来得及探索到终点探索率就降到接近零之后一直利用早期学到的错误策略。或者奖励设计里撞墙惩罚太小智能体觉得原地打转比冒险探索更划算。解决把epsilon_decay调大比如从 0.995 改成 0.999让探索持续更久。同时检查撞墙惩罚-5 通常够用如果迷宫大可以加大到 -10。另外确认起点附近没有高 Q 值的死循环可以在训练初期打印 Q 表看看。5.2 Q 值爆炸或全为零现象训练过程中 Q 值越来越大最后变成inf或nan或者 Q 表始终全是零智能体行为完全随机。原因Q 值爆炸通常是alpha太大加上奖励尺度太大更新时误差被反复放大。Q 表全零则可能是更新根本没执行比如next_state计算错误导致索引越界被跳过或者done判断有误导致每轮第一步就结束。解决先把alpha降到 0.01 试试如果 Q 值稳定了再逐步调大。奖励值建议归一化到 [-1, 1] 或 [-10, 100] 这种范围不要用几千几万的奖励。Q 表全零时在更新函数里加一行打印td_error确认它不为零。5.3 收敛后路径不是最短现象智能体确实能走到终点但路径比 BFS 最短路径多好几步甚至绕远路。原因gamma太小智能体只看眼前几步的奖励不愿意走暂时远离终点但最终更短的路。或者步数惩罚不够大绕路成本低。还有一种可能是训练轮次不够Q 值还没传播到所有状态。解决把gamma从 0.9 提到 0.95 或 0.99让智能体更看重长期回报。步数惩罚从 -1 调到 -2 或 -3增加绕路成本。训练轮次加到 2000 轮以上确保 Q 值充分传播。改完后重新跑对比实验确认路径长度下降。5.4 训练曲线震荡剧烈现象步数曲线上下大幅波动滑动平均线也不平滑看不出收敛趋势。原因alpha太大导致每次更新步子迈太大Q 值在最优值附近来回跳。或者 ε 衰减太慢训练后期还在大量随机探索策略不稳定。解决把alpha从 0.5 降到 0.1 或 0.05观察震荡是否减弱。如果还不行降到 0.01。同时检查 ε 衰减确保在训练中后期 ε 已经降到 0.05 以下。另一个技巧是取最近 100 轮的平均步数作为评估指标而不是看单轮步数。5.5 换一台机器结果复现不了现象同样的代码和参数在自己电脑上跑出的路径和同学跑出的不一样收敛轮次也差很多。原因随机种子没固定。Q 表初始化、动作选择、环境重置都用了随机数不同机器上随机序列不同结果自然不同。这不是算法问题是实验可复现性问题。解决在代码开头固定随机种子。import random import numpy as np def set_seed(seed42): random.seed(seed) np.random.seed(seed) set_seed(42)固定种子后每次运行结果完全一致。做对比实验时所有参数组合用同一个种子排除随机性干扰。实验报告里要写明种子值方便别人复现。6. 让 Q-Learning 走迷宫跑得更稳的两个进阶技巧第一个技巧是经验回放。标准 Q-Learning 是边走边学每一步更新一次样本之间高度相关。如果改成把每一步的(state, action, reward, next_state)存进一个缓冲区训练时随机采样一批来更新能显著降低震荡、加快收敛。虽然表格法不像 DQN 那样依赖经验回放但在迷宫这种状态访问不均匀的场景下回放能让稀有状态被多次学习。实现上用一个collections.deque存最近几千条转移每次更新时随机抽 32 条求平均梯度。代价是内存占用增加但 8x8 迷宫完全撑得住。第二个技巧是双 Q 学习。标准 Q-Learning 的更新公式里max(Q(s, a))同时负责选动作和估价值这会导致 Q 值被高估。双 Q 学习用两个独立的 Q 表一个负责选动作另一个负责估价值交替更新。在迷宫这种奖励稀疏的场景下高估问题会让智能体对某些状态过于乐观走进去才发现是死路。双 Q 学习能缓解这个问题代价是代码复杂度翻倍、训练轮次增加。我的习惯是先用标准 Q-Learning 跑通如果发现路径质量不稳定再换双 Q。验证这两个技巧是否有效方法很简单固定其他参数只改一处跑 5 个不同随机种子比较最终 50 轮平均步数的均值和标准差。均值下降说明收敛更好标准差下降说明稳定性提升。如果两个指标都没改善说明当前迷宫规模太小不值得引入额外复杂度。我自己在这个项目上翻过的最大的车是早期为了省事把epsilon_decay设成 0.9结果 50 轮后 ε 就降到 0.01智能体还没找到终点就停止探索了训练曲线一直平着不动。后来改成 0.995 并固定随机种子才看到那条漂亮的下降曲线。做强化学习实验随机种子和探索率这两个东西值得你多花十分钟确认。希望帮到你。本文还有配套的精品资源点击获取