简介这是一份面向强化学习入门者的Q-Learning实战代码资源用Pygame搭建5x5网格环境训练蓝色智能体绕开中心障碍物、以最大概率抵达右下角绿色目标点。代码完整实现了Q值表、ε-greedy策略与奖励机制撞墙扣10分、到达目标加50分、普通移动扣1分适合想通过可视化直观理解强化学习决策过程的Python学习者。压缩包共3个文件包含1个py主程序、1个md说明文档和1个gif运行演示整体仅46KB轻量易读下载后可直接运行观察智能体从随机探索到逐步收敛的全过程。目前已有1043人学习下载。读者可借此掌握Q表更新公式、探索与利用的平衡技巧并对照gif演示快速验证训练效果是入门强化学习不可多得的练手项目。1. 用 Q-Learning 让智能体自己走到目标点为什么值得从这个小场景入门强化学习很多人第一次接触强化学习是从 CartPole 这类现成环境开始的跑几行代码就能看到奖励曲线往上走但心里其实是懵的——状态是什么、动作怎么选、Q 表到底在更新什么全被封装在环境里了。如果你想真正搞懂「智能体怎么学会走路」这件事我更推荐一个自己搭的极简场景一个网格地图一个智能体一个目标点用 Q-Learning 训练它一步步走过去再用 Pygame 把每一步实时画出来。这个标题讲的正是这件事——Q-Learning 是算法Python 是语言Pygame 是可视化窗口智能体移动到目标点是任务目标。它解决的问题很具体把强化学习里最核心的「状态-动作-奖励-更新」闭环用几十行能看懂的代码摊开在你面前。适合两类人一类是刚学完 Python 基础语法、想找个能跑起来又有成就感的项目练手的另一类是做智能体开发、想补上强化学习底层直觉的从业者。跑通之后你会对 Q 表、学习率、折扣因子这些概念有肌肉记忆再去看 DQN、多智能体强化学习这些进阶方向就不会觉得是黑匣子了。2. 把 Q-Learning 拆开看Q 表、奖励函数和 Pygame 渲染循环怎么配合2.1 Q-Learning 到底在更新什么Q-Learning 的核心是一张表行是状态列是动作格子里存的是「在某个状态做某个动作未来能拿到的累计奖励期望」也就是 Q 值。智能体每一步根据当前状态查表选 Q 值最大的动作执行环境返回新状态和即时奖励然后用贝尔曼方程更新刚才那个格子的值Q(s, a) Q(s, a) α × [r γ × max Q(s, a) − Q(s, a)]这里 α 是学习率控制新信息覆盖旧经验的幅度γ 是折扣因子决定智能体多看重远期奖励。这个公式是整个算法的全部没有神经网络没有反向传播就是一次查表和一次赋值。理解这一点很关键因为后面所有深度强化学习算法本质都是把这张表换成一个函数逼近器。在我们的场景里状态就是智能体在地图上的坐标 (row, col)动作是上下左右四个方向奖励设计成走到目标点 10撞墙或越界 −1普通移动 −0.01。最后那个小负值是为了逼智能体尽快到达不然它可能在地图里绕圈也不亏。2.2 环境、智能体、渲染三层怎么分写这类项目最容易翻车的地方是把游戏逻辑、算法逻辑和渲染逻辑全塞进一个 while 循环里改一处崩三处。我一般会分成三层环境层负责地图、状态转移、奖励计算智能体层只负责选动作和更新 Q 表渲染层用 Pygame 把当前状态画出来。三层之间只通过状态和动作通信。先定义环境用 NumPy 存地图0 表示可走1 表示障碍2 表示目标import numpy as np class GridEnv: def __init__(self, size8): self.size size # 0 可走1 障碍2 目标 self.grid np.zeros((size, size), dtypeint) self.grid[2, 3] 1 self.grid[3, 3] 1 self.grid[4, 3] 1 self.goal (size - 1, size - 1) self.grid[self.goal] 2 self.start (0, 0) self.state self.start def reset(self): self.state self.start return self.state def step(self, action): # 动作映射0 上1 下2 左3 右 moves [(-1, 0), (1, 0), (0, -1), (0, 1)] dr, dc moves[action] nr, nc self.state[0] dr, self.state[1] dc # 越界或撞障碍原地不动并给负奖励 if not (0 nr self.size and 0 nc self.size): return self.state, -1.0, False if self.grid[nr, nc] 1: return self.state, -1.0, False self.state (nr, nc) if self.state self.goal: return self.state, 10.0, True return self.state, -0.01, False这段代码里step返回三个值新状态、即时奖励、是否结束。动作用整数 0 到 3 表示避免字符串比较带来的性能损耗。奖励值 −1、−0.01、10 是我反复调过的你也可以改但要注意撞墙惩罚不能太轻否则智能体会贴着墙走。2.3 训练循环和 ε-greedy 探索智能体如果永远选 Q 值最大的动作前期 Q 表全是零它会一直往上撞墙学不到东西。所以要用 ε-greedy以 ε 的概率随机选动作探索以 1−ε 的概率选当前最优。ε 从 1.0 开始逐渐衰减到 0.05前期多探索后期多利用。import random class QAgent: def __init__(self, n_states, n_actions, alpha0.1, gamma0.95, epsilon1.0, epsilon_min0.05, epsilon_decay0.995): self.q_table np.zeros((n_states, n_actions)) self.alpha alpha self.gamma gamma self.epsilon epsilon self.epsilon_min epsilon_min self.epsilon_decay epsilon_decay self.n_actions n_actions def state_to_index(self, state, size): return state[0] * size state[1] def choose_action(self, state_idx): if random.random() self.epsilon: return random.randint(0, self.n_actions - 1) return int(np.argmax(self.q_table[state_idx])) def update(self, s_idx, action, reward, next_s_idx, done): best_next 0 if done else np.max(self.q_table[next_s_idx]) td_target reward self.gamma * best_next td_error td_target - self.q_table[s_idx, action] self.q_table[s_idx, action] self.alpha * td_error def decay_epsilon(self): self.epsilon max(self.epsilon_min, self.epsilon * self.epsilon_decay)state_to_index把二维坐标压成一维方便查表。update里done为真时不再加未来奖励这是终止状态的正确写法漏掉这个判断是新手最常见的错误之一。ε 衰减用乘法而不是减法衰减曲线更平滑。2.4 用 Pygame 把训练过程画出来Pygame 的渲染循环和训练循环要分开。训练可以跑几千轮但没必要每轮都画我一般每训练 N 轮渲染一次或者训练完用最优策略跑一遍演示。下面是最小渲染代码import pygame CELL 60 FPS 8 def draw(screen, env, agent_pos): screen.fill((255, 255, 255)) for r in range(env.size): for c in range(env.size): rect pygame.Rect(c * CELL, r * CELL, CELL, CELL) if env.grid[r, c] 1: pygame.draw.rect(screen, (60, 60, 60), rect) elif env.grid[r, c] 2: pygame.draw.rect(screen, (0, 200, 0), rect) pygame.draw.rect(screen, (200, 200, 200), rect, 1) # 画智能体 r, c agent_pos pygame.draw.circle(screen, (30, 100, 255), (c * CELL CELL // 2, r * CELL CELL // 2), CELL // 3) pygame.display.flip()CELL是每格像素FPS控制演示速度调成 8 左右肉眼能看清每一步。渲染函数只读状态不修改任何训练数据这样即使关掉窗口训练也能继续。3. 从零跑通环境搭建、参数设置和一次完整训练3.1 Python 环境和 Pygame 安装如果你还没配好 Python 环境建议用 3.9 以上版本虚拟环境隔离依赖。Windows 和 Linux 下命令略有差别但核心就三步建虚拟环境、激活、装包。# 创建虚拟环境 python -m venv rl_env # 激活Windows rl_env\Scripts\activate # 激活Linux / macOS source rl_env/bin/activate # 安装依赖 pip install pygame numpy装完可以用python -c import pygame; print(pygame.ver)验证。如果报ModuleNotFoundError八成是虚拟环境没激活或者 pip 装到了全局环境。VS Code 里记得把解释器切到rl_env那个不然跑起来还是找不到包。3.2 训练主循环怎么写把前面三层拼起来主循环结构是外层跑 episode内层跑单局直到到达目标或超过最大步数。每步选动作、执行、更新、衰减 ε。def train(episodes2000, max_steps200): env GridEnv(size8) agent QAgent(n_statesenv.size * env.size, n_actions4) rewards_history [] for ep in range(episodes): state env.reset() s_idx agent.state_to_index(state, env.size) total_reward 0 for _ in range(max_steps): action agent.choose_action(s_idx) next_state, reward, done env.step(action) next_idx agent.state_to_index(next_state, env.size) agent.update(s_idx, action, reward, next_idx, done) s_idx next_idx total_reward reward if done: break agent.decay_epsilon() rewards_history.append(total_reward) if (ep 1) % 200 0: avg np.mean(rewards_history[-200:]) print(fEpisode {ep1}, avg reward: {avg:.2f}, epsilon: {agent.epsilon:.3f}) return agent, envmax_steps是单局步数上限防止智能体在某个状态卡死导致死循环。rewards_history用来观察收敛趋势正常情况平均奖励会从很负逐渐逼近 10 左右。如果跑了几百轮还在 −5 附近晃多半是奖励设计或 ε 衰减有问题。3.3 关键参数怎么调参数没有万能值但有一套我常用的起点你可以照着改参数含义推荐起点调大后的影响alpha学习率0.1学得快但不稳定可能震荡gamma折扣因子0.95更看重远期收敛慢但路径更优epsilon初始探索率1.0前期探索充分训练前期奖励低epsilon_decay衰减速度0.995衰减慢探索久训练轮数要加epsilon_min最小探索率0.05太大后期还在乱走太小容易卡局部最优调参顺序建议先固定 gamma0.95、alpha0.1只调 ε 衰减看多少轮能稳定到达目标再微调 alpha如果奖励曲线抖得厉害就降到 0.05最后动 gamma一般 0.9 到 0.99 之间。3.4 训练完怎么验证策略真的学会了训练结束后把 ε 设成 0跑一遍贪心策略看智能体能不能从起点走到目标。这一步很重要因为训练时的平均奖励高不代表策略稳定可能是探索带来的运气。def evaluate(agent, env, max_steps100): state env.reset() s_idx agent.state_to_index(state, env.size) path [state] for _ in range(max_steps): action int(np.argmax(agent.q_table[s_idx])) state, reward, done env.step(action) s_idx agent.state_to_index(state, env.size) path.append(state) if done: print(到达目标路径长度:, len(path)) return path print(未到达目标) return path如果验证时走不到先看 Q 表里目标点附近的值是不是明显高于其他格子。如果整张表数值都很接近说明训练不充分或奖励信号太弱。4. 避坑指南Q-Learning 加 Pygame 最容易翻车的 5 个地方现象一训练几千轮智能体还是原地打转或撞墙。原因通常是奖励设计有问题比如普通移动奖励是 0撞墙也是 0智能体没有动力避开障碍。解决是把撞墙设成明显负值−1普通移动给极小负值−0.01让「不动」和「乱走」都比「走向目标」差。现象二Pygame 窗口卡死点关闭没反应。原因是渲染循环里没有处理事件队列操作系统认为程序无响应。解决是在每帧渲染后加pygame.event.get()消费事件检测到 QUIT 就退出。训练和渲染如果在同一个线程还要注意别让渲染阻塞训练。现象三Q 表数值越来越大最后溢出或变成 nan。这是奖励没有归一化、学习率又偏大导致的。解决是把奖励缩放到 −1 到 1 区间或者把 alpha 降到 0.01 到 0.05。另外检查done为真时有没有错误地加上未来奖励那个 bug 会让终止状态的值无限累积。现象四换了地图尺寸后代码报索引越界。state_to_index用的是row * size col如果环境 size 和智能体初始化时的 n_states 不一致就会越界。解决是把 size 作为参数统一传递别在多个地方硬编码 8。现象五训练时奖励曲线很好看演示时却走不到目标。这是典型的过拟合到探索路径或者 ε 没降到足够低。解决是验证时把 ε 设为 0并且多跑几次不同起点如果支持的话看策略是否稳定。如果只在起点固定时能走通说明 Q 表对状态覆盖不够需要增加训练轮数或调整 ε 衰减。5. 进阶技巧把 Q 表可视化一眼看出智能体学到了什么跑通基础版之后我最推荐的进阶动作是把 Q 表画成热力图。原因很简单奖励曲线只能告诉你「学得好不好」热力图能告诉你「它到底学到了什么」。你会看到目标点周围的值像水波一样往外扩散障碍物后面出现明显的低值区这种直观感受是看数字给不了的。具体做法是取每个状态四个动作里最大的 Q 值reshape 成地图尺寸用 matplotlib 画出来import matplotlib.pyplot as plt def plot_q_table(agent, env): q_max np.max(agent.q_table, axis1) q_grid q_max.reshape(env.size, env.size) plt.figure(figsize(6, 6)) plt.imshow(q_grid, cmapviridis) plt.colorbar(labelMax Q value) plt.title(Q-table value heatmap) # 标出障碍和目标 for r in range(env.size): for c in range(env.size): if env.grid[r, c] 1: plt.text(c, r, X, hacenter, vacenter, colorred) elif env.grid[r, c] 2: plt.text(c, r, G, hacenter, vacenter, colorwhite) plt.show()跑出来之后重点看三件事目标点那格是不是全图最高障碍物背面的格子值是不是明显偏低从起点到目标是不是有一条值逐渐升高的「走廊」。如果走廊断了说明智能体还没学会绕障碍可以回去加训练轮数或者把障碍布局改简单点再逐步加难。另一个值得试的技巧是动态调整地图难度。先用 5×5 无障碍跑通确认算法没问题再换成 8×8 带障碍最后可以随机生成障碍。每次只改一个变量这样出问题时你能立刻定位是算法还是环境。我自己的习惯是每改一次地图就重新跑 2000 轮把平均奖励和路径长度都记下来对比着看。强化学习这玩意儿玄学的地方就在于同样的参数换个地图可能就翻车所以留好对照组比什么都重要。最后说个我踩过的坑别一上来就追求最优路径。Q-Learning 在网格地图上收敛到的是「能到达目标」的策略不一定是最短路径因为奖励设计里没有显式惩罚步数。如果你要最短路径把每步的负奖励调大一点或者加一个步数惩罚项但别调太狠否则智能体会因为怕惩罚而不敢动。这个平衡点得自己试希望帮到你。本文还有配套的精品资源点击获取
