简介本资源是一份面向强化学习初学者与Python实践者的Q Learning算法完整实现方案聚焦于离散状态-动作空间下的智能体策略学习问题适用于高校课程实验、AI自学项目及算法原理验证场景。压缩包共5个文件包含核心算法脚本qlearningAgents.py、环境说明与使用指南的README.md、技术原理简述的PDF报告、开源许可证LICENSE及临时文件tmp整体体积852KB结构精炼便于快速上手与代码复用。已有1108人学习下载体现了较强的教学参考价值与实践热度。读者可直接运行Python脚本在经典CartPole等gym环境上实操Q表初始化、epsilon-greedy动作选择、Q值迭代更新及收敛过程可视化代码严格遵循Q函数更新公式清晰呈现学习率α、折扣因子γ与奖励r的作用机制并对状态表示state处理逻辑作了适配说明是理解强化学习基础思想不可多得的可执行范例。1. 一个不靠预设规则、只靠试错就能学会走迷宫的AIQ Learning实战项目解析你有没有试过写一个“会自己学走路”的程序不是靠硬编码每一步该往哪走而是让它在迷宫里撞南墙、掉陷阱、绕远路撞够了、疼多了突然某天就抄近道直奔终点——这个过程就是Q Learning最迷人的地方。这个Python实现的Q Learning强化学习项目就是一个完整可运行的“迷宫求解AI”最小闭环从环境建模5×5网格迷宫、智能体初始化、奖励函数设计、Q表更新逻辑到训练收敛可视化全部打包在一个不到300行的q_learning.py里。它不依赖TensorFlow或PyTorch纯NumPy Matplotlib实现新手能3分钟跑通熟手能5分钟看懂状态转移与贝尔曼方程落地细节。适合想真正理解“强化学习不是调参而是建模决策过程”的开发者——尤其当你刚学完马尔可夫决策过程MDP却卡在“怎么把公式变成for循环”时这份代码就是那张缺了的拼图。2. 项目结构与技术栈轻量但完整拒绝黑匣子这个项目虽小但麻雀虽小五脏俱全。它没用任何深度学习框架刻意规避了DQN这类“带神经网络的Q Learning变种”回归强化学习最原始、最透明的内核一张二维Q表state-action value table。所有逻辑都暴露在阳光下没有隐藏层、没有自动微分、没有梯度下降——只有状态索引、动作选择、奖励叠加和贝尔曼更新。这种设计不是偷懒而是教学级的诚实你想知道Q值怎么更新直接看update_q_table()想知道探索-利用怎么平衡翻epsilon_greedy_action()连迷宫怎么渲染成热力图都在plot_q_table()里一行行画出来。整个项目就两个核心文件q_learning.py主逻辑和maze_env.py环境封装外加一个requirements.txt。没有配置文件、没有数据库、不连网络下载解压后python q_learning.py就能看到AI在终端里打印每轮步数再等30秒Matplotlib弹出Q表热力图——你亲眼见证“知识”如何从空表里长出来。2.1 目录结构与文件职责项目解压后目录结构极简q_learning_project/ ├── q_learning.py # 主训练脚本定义Q表、训练循环、更新逻辑、可视化 ├── maze_env.py # 环境类封装迷宫状态、动作空间、奖励函数、终止条件 ├── requirements.txt # 仅3个依赖numpy, matplotlib, tqdm用于进度条 └── README.md # 一行说明“运行q_learning.py即可启动训练”提示maze_env.py不是玩具——它实现了标准OpenAI Gym风格的step(action)接口返回next_state, reward, done, info这意味着你未来想把它替换成CartPole或MountainCar环境只需改1处导入1处初始化其余训练逻辑完全复用。这是刻意为之的抽象设计不是过度工程。2.2 技术选型深意为什么不用PyTorch很多初学者一听说“强化学习”就默认要上深度学习但这个项目反其道而行之。它用纯NumPy实现Q表原因有三第一可调试性Q表是np.zeros((state_size, action_size))你可以随时print(q_table[2, 3])看某个状态-动作对的当前估值而神经网络输出是个黑匣子向量你得先做前向传播再argmax中间还可能有softmax干扰第二数学透明性贝尔曼最优方程Q(s,a) ← Q(s,a) α[r γ·max_a Q(s,a) − Q(s,a)]在代码里就是一行赋值见2.3节没有autograd帮你隐式求导你必须亲手写出每个符号对应哪个变量第三性能足够5×5迷宫共25个状态4个动作上下左右Q表仅100个浮点数内存占用1KBNumPy更新比PyTorch快3倍——这里不需要GPU加速需要的是“一眼看穿”。我一般会告诉新人先用NumPy把Q Learning跑通10遍再碰DQN。否则你连Q值发散是因为学习率α太大还是折扣因子γ太小都分不清就去调神经网络的batch size纯属玄学。2.3 核心数据结构Q表与状态编码Q表是整个项目的灵魂它被定义为# q_learning.py 第32行 q_table np.zeros((state_size, action_size)) # state_size 25 (5x5网格展平为0~24) # action_size 4 (0:上, 1:右, 2:下, 3:左)关键在于状态如何编码。迷宫不是用(row, col)元组表示而是映射为单整数# maze_env.py 第47行状态编码函数 def state_to_int(self, state): row, col state return row * self.width col # 5*row col → 0~24比如机器人在(2,3)位置第2行第3列索引从0开始状态ID就是2*53 13。这个设计让Q表能用二维数组直接索引q_table[13, 1]就是“在位置13时向右走”的预期收益。反过来整数转坐标也很简单# maze_env.py 第51行 def int_to_state(self, state_int): return (state_int // self.width, state_int % self.width)注意状态ID不是随机分配而是严格按行优先顺序row-major order排列。这保证了state_to_int和int_to_state互为逆运算且所有状态ID连续无空洞——这对Q表初始化和后续索引至关重要。如果这里用哈希映射或字典后续np.argmax(q_table[state_id])就会报错。2.4 动作空间与奖励函数设计动作空间被硬编码为4个离散方向# maze_env.py 第22行 self.actions [up, right, down, left] self.action_to_idx {up:0, right:1, down:2, left:3}奖励函数reward function是策略成败的关键它不来自外部标注而是由环境规则定义# maze_env.py 第89行step()方法中的奖励逻辑 if next_state self.goal_state: reward 100.0 # 到达终点大正向奖励 done True elif next_state in self.obstacle_states: reward -10.0 # 撞墙惩罚 done True else: reward -1.0 # 每走一步小负向奖励鼓励尽快结束这个设计有深意-1.0的步间惩罚迫使AI寻找最短路径走10步得-10分走5步得-5分后者更优而100.0的终点奖励远大于任何绕路成本确保收敛目标明确。如果你把终点奖励设成1.0AI可能宁愿永远绕圈也不愿冒险靠近墙壁——这就是奖励塑形reward shaping的血泪经验。3. 训练流程拆解从初始化到收敛的6个关键步骤Q Learning训练不是一蹴而就而是一个状态-动作价值逐步逼近最优解的过程。这个项目把整个流程压缩在train_agent()函数中共6个不可跳过的阶段。我们逐行拆解重点看每个阶段的输入、输出和易错点。3.1 步骤1超参数初始化决定算法性格# q_learning.py 第68行 alpha 0.1 # 学习率新经验覆盖旧知识的比例 gamma 0.99 # 折扣因子未来奖励的“贬值率” epsilon 1.0 # 初始探索率100%随机选动作 epsilon_decay 0.999 # 每轮衰减让AI逐渐从探索转向利用 min_epsilon 0.01 # 最小探索率保留1%随机性防局部最优这些数字不是随便写的。alpha0.1意味着每次更新只采纳10%的新信息避免单次错误奖励剧烈震荡Q值gamma0.99表示100步后的奖励只值现在的0.99^100 ≈ 0.37符合“远期收益不确定性高”的直觉epsilon_decay0.999对应约7000轮后降到min_epsilon给足探索时间。我一般会建议新手先固定alpha0.1, gamma0.9, epsilon0.9跑通再调参——别一上来就动gamma0.999那Q值更新慢得像树懒。3.2 步骤2主训练循环episode-by-episode# q_learning.py 第75行 for episode in range(num_episodes): state env.reset() # 重置到起点 (0,0) total_reward 0 done False while not done: # 选择动作epsilon-greedy action epsilon_greedy_action(q_table, state, epsilon, env.action_space) # 执行动作获取反馈 next_state, reward, done, _ env.step(action) # 更新Q表核心 q_table update_q_table(q_table, state, action, reward, next_state, done, alpha, gamma) state next_state total_reward reward注意两点第一env.reset()每次从固定起点出发保证可复现第二while not done内是单集episode的完整轨迹长度不固定可能3步到终点也可能20步撞墙。这里没有“批量更新”每个(s,a,r,s)四元组立刻触发一次Q表更新——这是在线学习online learning的典型特征。3.3 步骤3epsilon-greedy动作选择探索与利用的平衡术# q_learning.py 第112行 def epsilon_greedy_action(q_table, state, epsilon, action_space): if np.random.random() epsilon: return np.random.choice(action_space) # 随机探索 else: return np.argmax(q_table[state]) # 贪心利用这个函数是Q Learning的“大脑开关”。当epsilon1.0时100%随机AI像无头苍蝇当epsilon0.01时99%按Q表最大值走只留1%概率瞎走防死锁。关键细节np.argmax(q_table[state])返回的是动作索引0~3不是动作名所以env.step()接收的是整数不是字符串。如果这里误传upenv.step()会抛TypeError——这是新手最常翻车的第一处。3.4 步骤4Q表更新贝尔曼方程的代码实现# q_learning.py 第122行 def update_q_table(q_table, state, action, reward, next_state, done, alpha, gamma): if done: # 终止状态Q值 当前奖励无未来收益 q_target reward else: # 非终止状态Q值 当前奖励 γ × max_a Q(s,a) q_target reward gamma * np.max(q_table[next_state]) # Q学习更新公式Q(s,a) ← Q(s,a) α [q_target − Q(s,a)] q_table[state, action] q_table[state, action] alpha * (q_target - q_table[state, action]) return q_table这就是教科书里的贝尔曼最优方程落地q_target是“理想目标值”q_table[state, action]是“当前认知值”差值q_target - q_table[state, action]叫TD误差Temporal Difference Erroralpha控制修正步长。注意np.max(q_table[next_state])取的是下一个状态所有动作中的最大Q值这体现了“最优策略假设”——我们相信AI在s时会选择最好的动作哪怕它现在还没学会。3.5 步骤5探索率衰减让AI从莽撞到老练# q_learning.py 第88行主循环内 epsilon max(min_epsilon, epsilon * epsilon_decay)max()函数防止epsilon跌破min_epsilon这是安全兜底。衰减不是线性的如epsilon - 0.001而是指数衰减* 0.999因为前期需要大胆探索后期需要稳定利用。如果你用线性衰减前100轮epsilon从1.0降到0.9变化太慢后100轮从0.1降到0.0又太急——指数衰减能保持探索强度与训练进度匹配。3.6 步骤6收敛判断与日志何时停止训练项目没设硬性收敛条件如Q值变化1e-5而是用经验法则# q_learning.py 第92行 if episode % 100 0: print(fEpisode {episode}, Total Reward: {total_reward:.1f}, Epsilon: {epsilon:.3f})每100轮打印一次总奖励。你会看到初期total_reward在-20 ~ -5反复撞墙中期升到-5 ~ 10偶尔摸到终点后期稳定在90 ~ 100几乎每次都直达终点。当连续500轮total_reward 95基本可认为收敛。这不是数学证明而是工程实践中的“眼见为实”——毕竟Q Learning的收敛性依赖于访问所有状态-动作对而迷宫环境是有限且连通的必然收敛。4. 常见问题排查那些让我重启IDE三次的坑Q Learning看着简单实操时一堆坑能让你怀疑人生。以下是我在本地复现这个项目时踩过的5个真实问题按“现象→原因→解决”结构整理全是血泪经验。4.1 现象训练1000轮后Q表全是0total_reward恒为-10原因状态编码错误。maze_env.py中state_to_int()把(row, col)算成row col * width列优先但Q表初始化是按行优先索引的导致所有q_table[state_id]访问的都是错误内存地址实际更新的是一片未初始化的野指针区域。解决检查state_to_int()是否严格为row * width col并用assert state_to_int((0,0)) 0 and state_to_int((4,4)) 24验证边界。4.2 现象AI永远不往右走只在左上角打转原因动作空间索引错位。env.step()内部把动作0映射为left但epsilon_greedy_action()返回的np.argmax()结果被直接传入而Q表训练时假设动作0是up。动作语义和物理方向不匹配导致“向Q值最大的方向走”实际是往墙里钻。解决统一动作定义在maze_env.py顶部加注释# action 0: up, 1: right, 2: down, 3: left并在step()开头加assert action in [0,1,2,3]断言。4.3 现象训练到500轮total_reward突然从95暴跌到-20之后再也上不去原因epsilon衰减过快。epsilon_decay0.99导致100轮后epsilon0.36200轮后epsilon0.13AI过早放弃探索困在局部最优路径比如一条绕远但安全的路再也无法发现更短的捷径。解决将epsilon_decay调小至0.999并监控epsilon值——它应在3000轮后才降到0.3以下。4.4 现象Matplotlib热力图显示Q值全为蓝色低值无红色热点原因Q表更新逻辑错误。update_q_table()中误写成q_table[state, action] q_target直接赋值而非q_table[state, action] alpha * (q_target - q_table[state, action])增量更新。这导致Q值被粗暴覆盖失去历史记忆无法渐进优化。解决核对更新公式确保是而非并打印q_table[0,0]在第1/10/100轮的值确认它在缓慢增长而非跳变。4.5 现象env.step()抛IndexError: index 25 is out of bounds for axis 0 with size 25原因状态ID越界。迷宫是5×5状态ID应为0~24但state_to_int()在计算(5,0)时得到5*5025超出了Q表维度。这是因为env.reset()返回的起点被设为(5,0)第5行第0列但数组索引最大是4。解决检查env.__init__()中self.start_state (0,0)是否被误改为(5,0)并确认所有状态坐标都在[0, width-1] × [0, height-1]范围内。5. 核心模块代码逐行详解Q表更新与可视化现在我们聚焦最核心的两段代码Q表更新函数算法心脏和Q表热力图可视化效果验证。它们短小精悍但每一行都承载着强化学习的本质逻辑。我会逐行解释并标出哪些参数可以安全修改哪些动了必翻车。5.1update_q_table()12行代码里的强化学习哲学def update_q_table(q_table, state, action, reward, next_state, done, alpha, gamma): 更新Q表的核心函数 参数说明 q_table: np.ndarray, shape(25,4), 当前Q值表 state: int, 当前状态ID (0~24) action: int, 当前执行的动作 (0~3) reward: float, 本次动作获得的即时奖励 next_state: int, 下一状态ID (0~24)若doneTrue则无效 done: bool, 是否到达终止状态终点或障碍 alpha: float, 学习率 (建议0.05~0.2) gamma: float, 折扣因子 (建议0.9~0.99) # 行1计算目标Q值target if done: q_target reward # 终止状态无未来收益目标就是当前奖励 else: # 行2非终止状态目标 即时奖励 γ × 下一状态最优Q值 # 注意np.max(q_table[next_state]) 取的是next_state行的最大值 q_target reward gamma * np.max(q_table[next_state]) # 行3计算当前Q值与目标的差距TD误差 # 这是强化学习的“误差驱动”本质只修正认知偏差不推倒重来 td_error q_target - q_table[state, action] # 行4用学习率α缩放误差更新Q表 # 关键这里是 不是 。保留历史知识只吸收新信息 q_table[state, action] alpha * td_error return q_table提示alpha是唯一需要调参的超参数。alpha0.01更新太慢alpha0.5容易震荡。我一般从0.1起步观察Q值变化曲线——如果q_table[0,1]起点向右在100轮内从0跳到50再跌回10说明alpha太大如果1000轮才从0到1说明alpha太小。5.2plot_q_table()把抽象价值变成可视决策地图def plot_q_table(q_table, env, titleQ Table Heatmap): 将Q表可视化为热力图直观展示每个状态各动作的价值 每个格子显示4个颜色块对应4个动作颜色越红表示Q值越高 # 行1创建5x5网格每个格子放4个动作的Q值 fig, axes plt.subplots(5, 5, figsize(12, 12)) fig.suptitle(title, fontsize16) # 行2遍历每个状态 for state_id in range(25): row, col env.int_to_state(state_id) ax axes[row, col] # 行3提取该状态4个动作的Q值 q_values q_table[state_id] # shape(4,) # 行4用4个扇形块表示4个动作上右下左 # 颜色映射Q值越大红色越深viridis反转 colors plt.cm.viridis_r(q_values / np.max(q_table) if np.max(q_table) 0 else 1) # 行5绘制4个扇形角度0°,90°,180°,270°对应上右下左 wedges, texts ax.pie([1,1,1,1], colorscolors, startangle90) # 行6标记状态ID和最大Q值 ax.set_title(fs{state_id}\nmaxQ{np.max(q_values):.1f}, fontsize8) ax.axis(equal) plt.tight_layout() plt.show()这段代码的妙处在于它没用传统热力图每个格子一个值而是用四象限饼图同时展示一个状态的所有动作价值。你看(0,0)左上角起点的饼图如果右下角对应动作2:down最红说明AI认为“先往下走”最有利——这和迷宫设计一致终点在右下角。这种可视化让你一眼看穿AI的“思考过程”而不是只看最终路径。注意plt.cm.viridis_r是反转的viridis色图确保高Q值为红色直觉上“好”红“坏”蓝。如果用默认viridis高值是黄色容易和警告色混淆。6. 二次开发指南3个可立即上手的改造实验现在你已经跑通、看懂、调过这个项目下一步就是动手改。别急着加神经网络先做3个经典改造实验——每个都能加深你对Q Learning本质的理解且5分钟内就能看到效果。我按难度递进排列最后一个实验直接关联工业场景。6.1 实验1动态迷宫——让墙壁随时间移动原迷宫是静态的但现实世界充满变化。我们让障碍物每100轮移动一次# 在maze_env.py的step()方法末尾添加 if self.episode_count % 100 0 and self.episode_count 0: # 随机选择一个障碍物移到新位置 old_obs self.obstacle_states.pop() new_obs (np.random.randint(0,5), np.random.randint(0,5)) # 确保不移到起点/终点 while new_obs in [(0,0), (4,4)]: new_obs (np.random.randint(0,5), np.random.randint(0,5)) self.obstacle_states.append(new_obs)改造效果AI不再能记住“某处有墙”必须持续探索。你会看到total_reward周期性下跌墙移动时然后缓慢回升。这验证了Q Learning的在线适应能力——只要epsilon不衰减到0它就能跟上环境变化。从那以后我每次做动态环境项目都强制在训练循环里加if episode % 100 0: env.update_obstacles()这是我的后悔药。6.2 实验2多目标奖励——从“找终点”到“收集金币”把单一终点奖励改成多个金币点每个金币10分终点100分# 修改maze_env.py的reward逻辑 self.coin_states [(1,1), (2,3), (3,1)] # 3个金币位置 ... if next_state self.goal_state: reward 100.0 elif next_state in self.coin_states: reward 10.0 self.coin_states.remove(next_state) # 金币被拾取后消失 elif next_state in self.obstacle_states: reward -10.0 else: reward -1.0改造效果AI会先绕路捡金币再冲终点路径变长但总分更高。这揭示了奖励函数即策略——你定义什么值得追求AI就优化什么。很多业务场景如推荐系统的“用户停留时长”和“付费转化”就是一对需要权衡的多目标这个实验就是它的最小原型。6.3 实验3离散动作→连续动作用Q值指导方向向量原项目动作是离散的4方向但机器人实际运动是连续的。我们用Q值生成方向向量# 在q_learning.py新增函数 def get_continuous_action(q_table, state): 将离散Q值映射为2D方向向量 动作0(up)→(0,1), 1(right)→(1,0), 2(down)→(0,-1), 3(left)→(-1,0) 输出加权平均方向 Σ(Q_value[i] * direction_vector[i]) directions np.array([[0,1], [1,0], [0,-1], [-1,0]]) # 4个单位向量 q_values q_table[state] # shape(4,) # 归一化Q值为权重避免负值干扰 weights np.exp(q_values - np.max(q_values)) # softmax weights / np.sum(weights) # 加权平均 action_vec np.sum(weights[:, None] * directions, axis0) return action_vec / (np.linalg.norm(action_vec) 1e-8) # 单位化 # 在训练循环中替换动作选择 # action epsilon_greedy_action(...) → action_vec get_continuous_action(q_table, state) # 然后env.step_continuous(action_vec) # 需扩展env支持改造意义这是通往Actor-Critic架构的桥梁。离散Q Learning输出“选哪个动作”连续Q Learning输出“往哪走”后者才是机械臂、无人机等真实系统的输入。这个实验不追求完美而是让你亲手触摸“离散到连续”的鸿沟——你会发现单纯加权平均方向不够需要引入策略梯度Policy Gradient而这正是DQN之后的进阶路径。希望帮到你。本文还有配套的精品资源点击获取