news 2026/10/14 17:27:22

从零实现Q-Learning:Q表更新原理与Python网格世界实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零实现Q-Learning:Q表更新原理与Python网格世界实践

刚接触强化学习的时候,我最深的感受是:一堆术语在脑子里打架。策略、价值函数、时序差分、免模型……每个词单独看都能查到解释,合在一起就不知道它在解决什么问题。直到我亲手用Q-Learning写了一个“4×4网格找宝藏”的小智能体,跑完一千个回合,看着Q表从全零慢慢变成一条清晰路线,才真正把机器学习里这个最经典的强化学习算法吃透。

这篇分享不讲复杂数学,就从零开始讲清楚几件事:Q-Learning到底解决什么问题、Q表里的数字从哪里来、怎么用几十行Python让它跑起来,以及训练过程中最容易翻车的三个细节。最后再聊聊从Q表到深度Q网络(DQN)的进化逻辑。适合刚入门强化学习的人,也适合想动手写第一个智能体但不知道从哪下手的开发者。

1. Q-Learning到底在解决什么问题

1.1 从一个网格找宝藏的小例子说起

假设有一个4×4的网格世界,一共16个格子。智能体站在左上角的格子,目标是到达右下角的宝藏格子。地图上还有几个特殊位置:踩上去会掉血的陷阱,以及一个提前退出的“小出口”。智能体每一步可以选择四个动作:上、右、下、左。每走一步,环境给一个-1的奖励,意思是“你多走一步就多消耗一点体力”;踩到陷阱给-5;找到宝藏给+10。

问题是:智能体一开始根本不知道地图长什么样,也不知道陷阱在哪。它只能靠一次次试错,不断走、不断被打分,慢慢形成“哪个状态下做哪个动作比较划算”的经验。这其实就是强化学习最朴素的原型:一个智能体(Agent)在环境(Environment)里采取动作(Action),环境返回新的状态(State)和奖励(Reward),智能体根据奖励调整后续动作,目标是让累积奖励最大。

别小看这个例子,它背后藏着一类真实问题的共性结构。比如外卖骑手在不知道商圈拥堵规律时摸索路线,仓储机器人在货架之间学习走线,游戏AI学习闯关策略,本质上都是“状态-动作-奖励”的循环。只要问题能被抽象成这个框架,Q-Learning就有用武之地。

1.2 积累奖励不是“回看过去”,而是“盯住未来”

刚开始我犯过一个理解上的错误:以为强化学习就是让智能体学会“记住哪一步得分高”。后来才发现,真正的核心是“长期累积回报”,不是“当前这一步回报”。

举个岔路例子:左边一条路看起来近,但后半段全是小坑;右边一条路远一些,但全程安全。如果只看眼前一步,智能体会倾向走左边,因为左转的立即奖励可能稍微高一点。但从长期累积奖励的角度算,右边才是更优选择。Q-Learning的价值就在于评估“动作的未来累积收益预期”,它学的不是“这一步爽不爽”,而是“从这个状态出发,后续还能拿多少”。

这也是它和普通分类、回归任务最大的不同。常见机器学习任务里,输入输出是一一对应的,给一张猫的图片,模型输出“猫”,对错立刻可知。但强化学习里,你在第3步做出的决定,可能到第20步才看到真正后果,中间隔着一连串后续动作和奖励。这种“延迟反馈”问题,才是强化学习真正要面对的核心难点。

1.3 和别的机器学习方法划清界限

为了不让自己混淆,我当时把三类方法放在一起对比:

  • 监督学习:有标签样本,学一个输入到输出的映射,比如图片分类、房价预测。
  • 无监督学习:没有标签,靠数据自身结构找出规律,比如聚类、降维。
  • 强化学习:没有直接答案,只有一个奖励信号。模型要通过试错找出“怎么做才能让长期回报最大化”。

Q-Learning在其中属于“免模型”(Model-Free)算法,意思是它不显式学习环境的转移规律,不关心“从格子A往上走一定会到格子B”这种内部机制,而是直接估计每个状态下每个动作的价值。同时它也是一种“离策略”(Off-Policy)时序差分算法,这个“离策略”的含义后面讲更新公式时会再展开。

理解这个区别最大的好处是:遇到问题时,你不会再条件反射式地拿分类模型去硬套。先问一句“这个任务有直接答案吗?还是需要靠试错交互来学?”如果是后者,就可以考虑上强化学习。

2. Q表的核心逻辑与更新公式从哪来

2.1 Q表长什么样

Q-Learning最直观的载体就是一张表,叫Q表。行是状态,列是动作,表格里的每个值记为Q(s, a),表示“在状态s下执行动作a,并在此基础上继续按最优策略走下去,能拿到的期望累积折扣奖励”。

拿刚才的4×4网格来说,16个状态,4个动作,Q表就是个16×4的矩阵。如果训练成功,某个状态行里的4个值会有明显差异,最大的那个值对应的动作就是当前状态下的最优动作。

我一般会把训练好的Q表打印出来看,它长这样:

状态上右下左
0-3.2-2.1-1.8-3.4
1-2.8-1.2-2.3-3.1
2-2.5-0.9-2.8-2.7
3-1.8T-2.4-2.6

这里“T”表示该状态是终止状态,不需要再选动作。你注意看,状态0往下走的Q值最大(-1.8),智能体就会优先往下走。这就是Q表的意义:它不是规则,不是公式,而是一张“经验价值地图”。

更关键的是,Q值综合了“即时奖励”和“未来奖励”。即使两个动作在下一步拿到的即时奖励一模一样,如果其中一个动作通往更好的后续状态,它的Q值也会更高。这也解释了为什么只盯着眼前一步走,得不到最优策略。

2.2 从贝尔曼方程到Q-Learning更新式

Q表里的值不是凭空拍的,它来自一个叫贝尔曼方程(Bellman Equation)的思想:当前状态的价值,等于“立即得到的奖励 + 下一状态下最优动作的折扣价值”。

写成公式就是:

Q*(s, a) = r + γ × max_{a'} Q*(s', a')

其中:

  • r是执行动作a后环境给的即时奖励;
  • s'是转移后的新状态;
  • γ是折扣因子,取值0到1,表示未来奖励值多少钱;
  • max_{a'} 表示在状态s'下所有可选动作里,挑那个能让Q值最大的动作。

如果已经有正确的Q值,这个等式是精确成立的。但训练开始时我们的Q表全是错的,所以不能直接用贝尔曼方程,而是要用它不断修正当前估计。修正方式就是经典的时间差分(TD)更新公式:

Q(s, a) ← Q(s, a) + α × [r + γ × max_{a'} Q(s', a') − Q(s, a)]

这个式子看起来复杂,拆开看就三部分:

  • “r + γ × max Q(s', a')”叫目标值,意思是“这次实际经历告诉我,从这一步出发大概值多少”;
  • 减掉“当前Q表里的旧估计值”,得到两者之差;
  • 再用学习率α控制“朝这个差距迈多大的步子”,把旧估计往目标值拉近一点。

整个过程有点像健身时称体重:每天电子秤给一个读数,你不会立刻从60公斤跳到70公斤,而是往趋势方向微调一点。这里的α就是微调幅度。

为什么要取max而不是当前策略实际会走的动作?这是最容易迷糊的地方。Q-Learning想学的价值,始终是“面向最优策略”的价值。即使探索时你可能在随机乱走,更新目标依然假设“接下来每一步都走最优动作”。这种“做一步看一步,但展望时脑子放最好情况”的思路,就是它被称为离策略(Off-Policy)的原因。

为了对比,可以看一下另一种经典算法SARSA。SARSA更新目标里用的是当前策略真实选择的动作,而不是max,所以叫在线策略(On-Policy)算法。在“悬崖行走”这类环境里,Q-Learning会因为展望最优路径而表现出“更勇敢”的行为,SARSA则更保守。两者没有绝对好坏,只是适用场景不同。

2.3 学习率、折扣因子、探索率这对三角关系

Q-Learning里有三个超参数需要一起理解,它们相互牵制:

超参数范围取值偏大取值偏小
学习率 α0到1更新快但震荡剧烈,可能不收敛更新慢但稳定,训练时间长
折扣因子 γ0到1有远见,看重长期收益,但方差大目光短浅,只看眼前,容易近视
探索率 ε0到1探索多,能发现全局最优,但收敛慢收敛快,但容易陷入局部最优

探索率通常配合ε-贪心(ε-Greedy)策略使用:每次决策时,智能体以ε的概率随机探索一个动作,以1−ε的概率选择当前Q值最大的动作。训练初期ε很高,相当于小白乱逛;后期ε降下来,相当于专家稳定输出。

我常用的初始组合是:α=0.1,γ=0.9,ε从1.0开始,每回合乘以0.995衰减,最低降到0.01。这个组合在简单网格类环境里很少翻车。但要注意,这不是万能配方,环境变复杂后要重调。调参时我的顺序是先固定γ,把α和ε衰减节奏试到稳定,再去微调γ观察长期收益的变化。

3. 用Python从零实现一个Q-Learning智能体

3.1 搭建一个4×4网格环境

先说环境。环境负责给智能体提供状态、执行动作、返回奖励和是否结束。我建议不要偷懒直接用现成库,而是手写一个,这样你能完全掌控每一步。

import numpy as np class GridWorld: def __init__(self, size=4): self.size = size self.state = 0 # 终止状态:5和11是陷阱,15是宝藏终点 self.terminal = {5: -5, 7: -5, 11: -5, 15: +10} self.action_space = 4 # 0上、1右、2下、3左 def reset(self): self.state = 0 return self.state def step(self, action): row, col = divmod(self.state, self.size) if action == 0: row = max(row - 1, 0) elif action == 1: col = min(col + 1, self.size - 1) elif action == 2: row = min(row + 1, self.size - 1) elif action == 3: col = max(col - 1, 0) next_state = row * self.size + col done = False reward = -1 # 默认每走一步扣一分 if next_state in self.terminal: reward = self.terminal[next_state] done = True self.state = next_state return next_state, reward, done

几个细节解释一下。divmod(state, size)用来把一维的状态编号转成二维坐标,比如状态5就是第1行第1列。走到边缘时用max/min把动作限制住,保证智能体不会“撞墙出界”。每走一步默认给-1,让智能体学会用尽可能少的步数完成任务。终止状态用字典存,就是为了在step里快速判断是否结束并返回对应奖励。

3.2 智能体的决策逻辑与训练循环

智能体的核心代码分两块:一块是ε-贪心选动作,一块是更新Q表。

def choose_action(state, q_table, epsilon): if np.random.random() < epsilon: return np.random.randint(4) # 探索:随机动作 return int(np.argmax(q_table[state])) # 利用:选当前最优动作 def update_q(q_table, state, action, next_state, reward, done, alpha=0.1, gamma=0.9): best_next = np.max(q_table[next_state]) if not done else 0 target = reward + gamma * best_next q_table[state][action] += alpha * (target - q_table[state][action])

有个细节千万注意:当done为True时,next_state之后不再有奖励,所以best_next必须取0,而不是再去查Q表的那一行。如果你漏掉这个判断,终止状态会被赋予额外的高价值,策略就会变得古怪,甚至会绕着终点不进去。

训练循环就是反复让智能体与环境交互:

env = GridWorld() q_table = np.zeros((16, 4)) epsilon = 1.0 decay = 0.995 min_epsilon = 0.01 episodes = 1000 for episode in range(episodes): state = env.reset() done = False total_reward = 0 while not done: action = choose_action(state, q_table, epsilon) next_state, reward, done = env.step(action) update_q(q_table, state, action, next_state, reward, done) total_reward += reward state = next_state epsilon = max(min_epsilon, epsilon * decay) if (episode + 1) % 100 == 0: avg_return = total_reward # 这里可以改成滑动平均 print(f"Episode {episode + 1}, Return: {avg_return}, Epsilon: {epsilon:.3f}")

整个训练流程可以总结成一句话:每一次交互都用实际得到的奖励和下一个状态的最大Q值,去修正当前状态动作的Q值;一整个回合结束后,稍微降低探索率;重复足够多回合后,Q表就成了策略。

3.3 训练结果怎么解读

训练结束后,我会把学到的策略打印出来,一眼就能看出智能体是不是学“通”了:

def show_policy(q_table): actions = ['↑', '→', '↓', '←'] policy = [] for s in range(16): if s in env.terminal: policy.append("T") else: policy.append(actions[int(np.argmax(q_table[s]))]) for row in range(4): print(" ".join(policy[row * 4:(row + 1) * 4])) show_policy(q_table)

一次典型输出可能是:

↓ → ↓ T → T ↓ T → → ↓ T → → → T

这种输出说明智能体在每个非终止状态都选出了自己的最优动作。配一张热力图看Q值差异会更直观,Q值越大的格子颜色越亮,从起点到宝藏会形成一条明显的“亮色走廊”。

我一般还会额外记录两个指标:平均回合回报和平均回合步数。训练刚开始那几十个回合里,回合步数通常很长,因为智能体在乱走;后面步数会慢慢缩短并稳定在一个值附近。如果这两个指标都稳定了,基本可以判断训练到位了。记得跑之前设一个随机种子,比如np.random.seed(42),否则每次结果波动会干扰你判断调参效果。

4. 训练过程中最容易翻车的三个细节

4.1 奖励太稀疏,智能体根本学不动

第一次自己加了一个更大的网格,比如6×6,结果发现智能体训练效果极差,几百轮下来回合一变策略都学不会。当时的症状是:回合步数一直很高,累计奖励没有明显上升趋势。

排查链路是这样的:

  1. 先看是不是探索不够。调大初始ε,把衰减系数从0.995改成0.999,结果有改善,但依然很慢。
  2. 再检查奖励分布。发现除了终点和陷阱之外,其他地方一律-1。在36格的网格里,终点只占1格,随机探索几乎碰不到终点。碰不到终点,就没有有效的“目标信号”反向传播给Q表。
  3. 最后我做了奖励塑形(Reward Shaping):除了每步-1之外,如果新状态离终点曼哈顿距离更近,额外给+0.1的引导奖励。

改完之后学习速度明显加快。这里有个重要警告:奖励塑形不能随便给,否则会改变最优策略,让智能体学会“刷引导奖励”而不去真正完成任务。严谨的做法是用基于势能的塑形(Potential-Based Shaping),公式是F(s, s') = γΦ(s') − Φ(s),这样能保证最优策略不变。我的经验是,能用环境本身的稀疏奖励就尽量用,非用塑形不可时,幅度要小,而且要验证最终策略没有偏离。

4.2 判断收敛不能只看累计奖励曲线

还有一个特别常见的坑:累计奖励曲线看起来平了,你以为收

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 17:27:04

Mimics三维重建全流程:CT数据导入到STL导出避坑指南

简介&#xff1a;Mimics中文版教程&#xff08;持续更新版0812&#xff09;是一份面向医疗与生物工程领域三维建模初学者的实用PDF文档。教程依托Mimics医学图像处理软件&#xff0c;系统讲解从打开工程、窗口化、二值化、区域增长到建立3D表示并导出STL文件的完整操作流程&…

作者头像 李华
网站建设 2026/10/14 17:26:57

差动轮里程计设计与工程实现:从运动学推导到误差补偿

做移动机器人时间久了你会发现一个挺反直觉的现象&#xff1a;差动轮里程计&#xff08;odom&#xff09;几乎是所有定位方案里最容易上手、也最容易做错的东西。说它容易&#xff0c;是因为两轮差速的运动学模型几行公式就能写完&#xff1b;说它容易错&#xff0c;是因为从编…

作者头像 李华
网站建设 2026/10/14 17:26:47

SnackBar项目概览:Android Material Design风格提示条的完整前世今生

移动开发UI库/组件 【免费下载链接】SnackBar toast-like alert pattern for Android inspired by the Google Material Design Spec 项目地址&#xff1a; https://gitcode.com/gh_mirrors/sna/SnackBar 点击查看 免费下载 SnackBar 是一个经典的 Android 开源库&#xff0c;…

作者头像 李华