尧图精选

Q-learning强化学习入门:从Q表、贝尔曼方程到DQN实战

🕒 发布时间:2026/10/2 5:20:42 📁 来源:尧图网络
1. QLearning到底在解决什么样的问题如果你刚开始接触强化学习大概率第一个撞上的算法就是QLearning。它足够简单简单到用一个二维表格就能跑起来又足够深刻深刻到几乎所有后续的深度强化学习算法都能看到它的影子。我在带新人做项目时通常会把QLearning当作强化学习的Hello World原因很直接——它把智能体怎么在试错中学会做决策这件事压缩成了一个可以手算、可以打印出来看的小例子。先说清楚它解决的核心问题一个智能体Agent在一个环境Environment里不断采取动作Action环境会返回奖励Reward和新的状态State智能体需要学会一套策略使得长期累积的奖励最大。这件事听起来抽象但放到具体场景里就很实在机械臂抓取时每一步关节角度怎么调、自适应PID控制器的参数怎么在线调整、游戏AI下一步该走哪、推荐系统下一屏该推什么本质上都是同一个数学结构。QLearning给出的答案非常暴力却又优雅为每一个状态-动作组合维护一个数值叫做Q值代表在状态s下做动作a之后一直按最优方式走下去能拿到的总回报期望。训练的过程就是不断用实际经历去修正这张表。为什么很多人学强化学习卡在入门这一步因为教材往往先甩出马尔可夫决策过程MDP、贝尔曼方程、策略迭代、值迭代一大堆概念符号多到让人放弃。而QLearning的好处在于它天然与MDP框架对齐但又不需要你一开始就理解所有理论细节。你完全可以先把它当成一个查表更新的工程问题跑通再回头补原理。我在实际带项目时发现先跑通再理解的路径比先啃公式再动手的效率高得多尤其是对已经有编程基础但没接触过RL的工程师。这篇文章适合三类人一是刚入门强化学习、想找一个能真正跑起来的最小闭环的初学者二是做控制、机器人、调度类项目想把RL落到具体问题的工程师三是已经用过现成库、但想搞清楚更新公式里每一项到底在干什么的实践者。我会从直觉、公式、代码、调参、踩坑五个层面把QLearning讲透尽量做到你读完之后能自己写出一个不用任何RL库、纯NumPy就能训练的版本。1.1 从一个生活场景理解试错学习想象你第一次去一家很大的商场找洗手间。你对布局一无所知只能随便走。走到某个位置发现指示牌你记住了这个位置往左走能到下次再来就直接往左。走错方向浪费了时间这个浪费时间就是负奖励找到洗手间是正奖励。你脑子里逐渐形成了一张在商场哪个位置该往哪走的经验表这就是Q表的现实版本。QLearning做的事情和这个过程几乎一模一样只不过它用数字精确记录在位置A往左走的得分是8.5往右走的得分是-2.1然后每次只选得分高的那个方向。关键在于这个得分不是当前这一步的即时收益而是从现在开始一直到终点能拿到的总收益。这一点是初学者最容易忽略的地方也是QLearning区别于贪心算法的本质。贪心算法只看眼前一步QLearning看的是长远回报所以它能学会先绕路、后抄近道这种需要延迟满足的策略。我在做自适应PID参数调节的项目时深有体会如果只用即时误差作为奖励控制器会变得非常短视参数抖得厉害而用累积回报作为优化目标控制器反而愿意在前期忍受一点误差换取后面长期的平稳。这就是折扣因子存在的意义后面会详细讲。1.2 Q表把经验压缩成一张可查询的索引QLearning的核心数据结构就是Q表。它是一个二维数组行是状态列是动作。假设你的问题有100个离散状态、4个可选动作那Q表就是100×4的矩阵一共400个数。每个数代表在这个状态下做这个动作的长期价值。训练开始前这张表全是零或者随机小数代表智能体一无所知。训练过程中智能体每走一步就用实际得到的奖励去更新表里对应的那个格子。这里有个非常重要的设计前提状态和动作必须是离散且有限的。这是表格型QLearning的硬约束。状态连续的问题比如机械臂关节角度是连续值不能直接查表必须先离散化或者改用函数逼近也就是DQN那一类。我在项目里见过不少人一上来就想用QLearning处理连续控制结果卡在状态怎么存进表里这一步。正确的做法是先做状态分箱或者干脆换算法。这个边界一定要在项目初期就想清楚否则后面会返工。Q表的大小直接决定内存和收敛速度。状态数一多表就爆炸这就是所谓的维度灾难。比如你有10个状态变量每个分10档那就是10的10次方个状态任何机器都存不下。所以在实际工程中控制状态维度、合理分箱是QLearning能否落地的关键。我个人经验是状态维度控制在3到5个、每维分箱不超过20档表格型QLearning还能跑得动再往上就该考虑DQN了。1.3 QLearning在强化学习家族里的定位强化学习算法可以粗分成两大类基于值Value-based和基于策略Policy-based。QLearning属于典型的基于值的方法它不直接学该做什么动作而是学每个动作值多少分然后选分最高的。与之相对的策略梯度方法如REINFORCE、PPO直接学一个策略函数。还有一类是Actor-Critic两者结合。QLearning还有一个特殊身份它是**离线策略Off-policy**算法。意思是它学习时用的数据可以不是当前正在执行的策略产生的。这一点非常实用意味着你可以用旧数据、别人探索的数据、甚至随机策略产生的数据来训练只要更新公式对就行。这个特性直接催生了后来的DQN经验回放机制也是离线强化学习如IQL能成立的理论基础之一。所以你看热搜词里iql离线强化学习基于模型强化学习这些追根溯源都能扯到QLearning的off-policy性质上。理解这个定位很重要因为它决定了你什么时候该选QLearning。如果你的环境是离散、状态不多、需要快速验证想法QLearning是最省事的选择如果你要做连续控制、高维感知直接上DQN或PPO会更合适。2. 核心原理拆解Q值、贝尔曼方程与更新规则很多人看QLearning的更新公式第一反应是这一堆符号是什么。其实拆开看每一项都有非常直白的含义。这个公式是整个算法的灵魂理解了它QLearning就掌握了一大半。我下面会用最笨的办法一项一项拆给你看。2.1 Q值的物理含义不是眼前分是长远账先明确Q值的定义Q(s, a) 表示当前在状态s先执行动作a然后一直按照当前最优策略行动能获得的累积折扣回报的期望。注意三个关键词累积、折扣、期望。累积意味着它把从现在到未来的所有奖励加起来。折扣意味着未来的奖励要打折越远的奖励权重越低折扣因子γgamma通常取0.9到0.99。期望意味着因为环境可能有随机性同一个动作每次结果不一样所以要取平均。为什么要打折两个原因。第一数学上如果无限步奖励累加而不打折总和可能发散没法比较。第二现实上未来的收益确实不如眼前的确定打折符合直觉。γ越接近1智能体越有远见γ越接近0越短视。这个参数的选取有讲究我后面会专门说。我在调自适应PID那个项目时一开始γ设成0.5结果控制器只顾眼前误差参数震荡得很厉害。后来调到0.95控制器明显愿意先稳住、再优化整体表现好了很多。所以γ不是一个随便填的数它直接决定智能体的性格。2.2 贝尔曼最优方程的直觉版本贝尔曼方程听起来吓人说白了就是一句话当前状态的价值等于眼前的奖励加上下一步最优价值的折扣。用公式写就是Q(s, a) r γ · max[Q(s, a)]这里的 r 是执行动作a后拿到的即时奖励s 是执行后的新状态max[Q(s, a)] 是在新状态下所有动作里最大的Q值。为什么取max因为我们假设之后会走最优路线所以要挑最好的那个后续价值。这个方程的美妙之处在于它把一个无限步的优化问题变成了一步的递推关系。你不需要一次性算出整条路径的总回报只需要保证每一步都满足这个关系整体就是最优的。这正是动态规划的核心思想。但要注意这是一个等式是理想情况下的真值关系。实际训练时我们并不知道真实的Q值所以用当前估计的Q值去逼近它。这就引出了下面的更新规则。2.3 更新公式逐项拆解QLearning的实际更新公式是Q(s, a) ← Q(s, a) α · [r γ · max Q(s, a) - Q(s, a)]我把它拆成五块项含义取值建议Q(s, a)当前估计值初始化为0或小随机数α学习率0.1 ~ 0.5太大不收敛太小跑太慢r即时奖励由环境定义设计好坏决定成败γ折扣因子0.9 ~ 0.99max Q(s, a)下一状态最大Q值查表得到方括号整体TD误差衡量预测和实际差多少这个公式的直觉是把当前估计往更准确的目标方向拉一点。那个方括号叫TD误差时序差分误差它等于实际拿到的奖励对未来的新估计减去原来的老估计。如果误差是正的说明这个动作比想象中好就调高Q值如果是负的就调低。α决定每次调多少相当于学习步长。为什么用max而不是实际下一步选的动作的Q值这正是QLearning的off-policy特性所在它假设下一步会走最优动作而不一定是你实际会走的动作。所以哪怕你用随机策略探索学的仍然是最优策略的价值。这是它和SARSAon-policy的核心区别。SARSA用实际下一步动作的Q值QLearning用最大值。这个差异在小问题上影响不大但在有风险的环境里比如悬崖行走QLearning会更激进SARSA更保守。2.4 探索与利用epsilon-greedy策略光有更新公式还不够智能体得决定怎么选动作。如果永远选当前Q值最大的动作前期表全是零等于没探索很容易卡在一个次优解上。所以需要探索和利用的平衡。最常用的方法是ε-greedy以概率ε随机选一个动作探索以概率1-ε选当前Q值最大的动作利用。ε通常从一个较大的值如1.0逐渐衰减到一个较小的值如0.01前期多探索后期多利用。ε的衰减策略有讲究。线性衰减简单但可能后期探索不足指数衰减前期掉得快。我一般用指数衰减ε ε_min (ε_max - ε_min) · exp(-step/decay_rate)。decay_rate控制衰减速度通常设成总训练步数的几分之一。实测下来这个策略在大部分小规模问题上比线性衰减稳。注意ε不要一直保持很大否则永远在乱走也不要一开始就很小否则学不到全局最优。前期探索、后期收敛是铁律。3. 从零手写QLearning完整实操流程理论讲完直接上手。我下面用一个经典环境来演示FrozenLake冰湖。为什么选它因为状态离散、动作只有4个、奖励稀疏非常适合展示QLearning的全部要点也足够小你可以在几分钟内跑完看到效果。我用纯NumPy实现不依赖任何RL库方便你看清楚每一行在干什么。3.1 环境选择与问题定义FrozenLake是一个4×4的网格共16个格子。起点在左上角终点在右下角中间有些格子是冰洞踩上去就结束奖励0。目标是走到终点奖励1。动作是上下左右四个方向。状态就是16个格子的编号0到15。这个环境的难点在于奖励稀疏只有走到终点才有奖励中间过程全是0。这对QLearning是个考验因为它意味着大部分时间智能体拿不到正向反馈Q表更新很慢。这也是为什么很多人跑FrozenLake发现智能体学不会——不是算法错了是奖励太稀疏探索不够。解决稀疏奖励的常见做法一是增加训练轮数二是设计中间奖励reward shaping三是用更聪明的探索策略。在FrozenLake里最有效的就是多跑几千轮加上ε从1.0慢慢衰减。我实测一般20000轮左右能稳定收敛。问题定义阶段有几件事必须想清楚状态空间所有可能的状态集合必须有限且可枚举动作空间所有可选动作同样有限奖励函数什么情况给正奖励什么情况给负奖励终止条件什么状态算结束到终点或掉冰洞这四件事定清楚QLearning就能跑。定义模糊的话后面调参会很痛苦。3.2 状态空间与动作空间设计FrozenLake已经把状态和动作设计好了但我们用更一般的思路来看。假设你自己的项目有一个连续状态比如温度控制在20到30度之间你需要把它离散化。常见做法是把范围均分成N档比如每1度一档就变成10个离散状态。档数太少精度不够档数太多Q表爆炸。我在做PID参数自适应时把误差和误差变化率两个连续量各分了11档状态数就是121个。动作是调大Kp、调小Kp、不变三个。这样Q表是121×3非常小训练很快。这个分箱策略是表格型QLearning能否落地的关键工程技巧。动作空间的设计同样重要。动作不能太多否则每个动作的样本变少学习变慢。我的经验是动作数控制在2到10之间比较合适。如果大一统的动作太多考虑分层或者用连续动作的算法。下面这张表总结了设计状态和动作时的关键考量设计项考量点常见坑状态分箱档数够用即可分太细导致样本稀疏状态维度控制在3-5维维度高直接爆炸动作数量2-10个太多导致学习慢奖励设计与目标强相关奖励和真正目标脱节3.3 训练循环代码实现下面是一个完整的FrozenLake版QLearning实现纯NumPy你可以直接复制运行import numpy as np import gymnasium as gym # 1. 初始化环境和Q表 env gym.make(FrozenLake-v1, is_slipperyFalse) n_states env.observation_space.n # 16 n_actions env.action_space.n # 4 q_table np.zeros((n_states, n_actions)) # 2. 超参数 alpha 0.1 # 学习率 gamma 0.99 # 折扣因子 epsilon 1.0 # 初始探索率 epsilon_min 0.01 epsilon_decay 0.9995 n_episodes 20000 max_steps 100 # 3. 训练循环 for episode in range(n_episodes): state, _ env.reset() done False for step in range(max_steps): # ε-greedy 选动作 if np.random.rand() epsilon: action env.action_space.sample() else: action np.argmax(q_table[state]) # 执行动作 next_state, reward, terminated, truncated, _ env.step(action) done terminated or truncated # QLearning更新 best_next np.max(q_table[next_state]) td_target reward gamma * best_next * (not done) q_table[state, action] alpha * (td_target - q_table[state, action]) state next_state if done: break # ε衰减 epsilon max(epsilon_min, epsilon * epsilon_decay) # 4. 查看学到的策略 print(学到的Q表) print(np.round(q_table, 2))这段代码有几个细节值得说。第一td_target里的not done很重要如果这一局结束了未来没有奖励了所以不加折扣项否则会错误地估计。第二epsilon_decay用0.9995配合20000轮能让ε从1.0衰减到大约0.0005再被截断到0.01探索和利用的过渡比较平滑。第三max_steps防止智能体在冰洞里无限循环。跑完之后你会看到Q表里某些格子有非零值那些就是智能体学到这里该往哪走的地方。把这个Q表转成方向就能画出最优路径。3.4 参数调优学习率、折扣因子、epsilon衰减代码跑通不难难的是让它稳定收敛。参数调优是QLearning最耗时的部分。我把关键参数的影响整理成下面这张表都是我反复试出来的经验值参数太小的问题太大的问题推荐范围α 学习率收敛极慢Q值震荡不收敛0.1 ~ 0.3γ 折扣因子短视学不会延迟策略方差大训练不稳0.9 ~ 0.99ε 初始值探索不足易卡局部最优前期效率低1.0ε 最小值后期不再探索永远不收敛0.01 ~ 0.05ε 衰减率探索太久探索不足0.999 ~ 0.9999有个技巧分享一下α不要设成固定值可以随训练步数递减前期大、后期小。这样既能快速学习又能在后期稳定。公式类似 α α_min (α_max - α_min) · exp(-step/decay)。我在PID项目里用这个策略比固定α收敛得更稳。另外一个常见问题是奖励尺度。如果奖励太大比如奖励1但每一步有-100的惩罚Q值会爆炸。解决办法是奖励归一化把奖励缩放到[-1, 1]区间。这一步看起来不起眼但能避免很多数值问题。4. 常见问题与排查技巧实录QLearning看着简单实际跑起来坑不少。我把自己和团队踩过的坑整理出来做成速查表你遇到问题可以直接对照。4.1 典型问题速查表现象可能原因排查方向解决办法Q表几乎全零奖励太稀疏检查是否有正奖励加shaping或增轮数智能体原地打转探索不足看ε是否衰减太快提高ε最小值Q值越来越大折扣不当检查γ和奖励尺度归一化奖励降γ训练不收敛学习率过大打印Q值变化降α加衰减策略忽好忽坏环境随机性强看每轮回报方差多跑平均降α只走一条路过早收敛看是否总选同一动作增大探索这张表是我实际项目里高频问题的浓缩。比如Q值越来越大很多时候是因为奖励没有归一化加上γ接近1导致累积回报持续膨胀最后溢出。这类问题在工程上很常见但教材里往往不提。4.2 奖励设计与shaping的坑奖励设计是QLearning成败的第一因素。教科书常用到终点给1其他给0但实际项目里这样往往学不动。FrozenLake就是典型稀疏奖励让智能体像无头苍蝇。常见的reward shaping做法是给中间步骤一点引导。比如机器人导航除了到目标给大奖励离目标越近给小奖励撞墙给惩罚。这样学习快很多。但shaping有风险如果设计不好智能体会学会刷奖励比如故意在目标附近兜圈子拿小奖励。这叫奖励黑客reward hacking是必须警惕的。注意reward shaping时确保绕圈子刷分的总收益低于真正完成任务的收益否则智能体会走向作弊策略。我一般的做法是主奖励给足完成任务辅助奖励给得克制且随距离单调。同时训练后一定要看智能体的实际行为不能只看回报曲线。4.3 收敛性判断与调试手段怎么知道训练收敛了不能只看回报曲线因为它波动大。我一般看三个指标Q表变化量相邻两轮Q表的平均变化小于阈值如1e-3说明稳定了平均回报的滑动窗口最近100轮的均值趋于平稳策略稳定性连续多轮的贪心策略一致调试时最有用的一招是打印Q表的局部看几个关键状态的值是否合理。比如FrozenLake里终点旁边那格的Q值应该最高。如果不符合直觉说明奖励或更新有问题。还有一个容易被忽略的点随机种子。RL训练随机性大同一个参数不同种子结果可能差很多。我一般固定种子复现问题再用多个种子验证方案是否稳健。这个习惯能避免你被偶然成功误导。5. 工程化落地与进阶方向跑通FrozenLake只是开始真正有价值的是把它用到实际问题。但表格型QLearning的适用范围有限你得知道它的边界以及怎么平滑地过渡到更高级的方法。5.1 表格型QLearning的规模瓶颈先说清楚天花板。表格型QLearning的复杂度随状态数和动作数线性增长。如果你的状态是离散的、总数在几千以内它完全够用。我见过用QLearning做库存调度的项目状态就是库存量分档几百个状态跑得很好。但一旦状态上万表就大了训练样本需求也爆炸因为每个状态都要被访问足够多次才能学好。还有一个隐性问题是泛化能力差。表格型学到的经验不能迁移到没见过的状态。如果状态空间有轻微变化得重学。而函数逼近神经网络能泛化这是DQN的核心优势。所以判断标准很明确状态离散、数量可控、不需要泛化用表格QLearning否则上DQN。这个判断在实际项目里非常关键选错方向会浪费很多时间。5.2 从QLearning到DQN的自然过渡DQN本质上就是用神经网络代替Q表的QLearning。它的更新目标还是那个贝尔曼方程只不过Q(s,a)不再查表而是网络输出的预测。为了让网络训练稳定DQN加了两个工程技巧经验回放Experience Replay和目标网络Target Network。经验回放把过去的转移(s, a, r, s)存进一个缓冲区训练时随机采样。这样做的好处是打破样本的时间相关性让训练更稳。目标网络则是用一个参数更新较慢的网络计算target避免自己追自己导致的震荡。这两个技巧都直击QLearning的痛点也是深度学习能用到RL上的关键。理解了QLearning再看DQN就不会觉得突兀——它只是把查表换成了预测把直接更新换成了训练网络。热搜词里的基于模型强化学习图强化学习与深度强化学习本质上都是在这条路上继续扩展。5.3 真实应用场景与选型建议QLearning在实际工程里的应用比很多人想的多。举几个我了解的方向自适应PID控制把误差和误差变化率离散化动作是PID参数调整量QLearning在线学习控制策略。这类应用在AUV自主水下航行器等场景有研究本质是把传统控制器的参数整定变成RL问题。机械臂简易抓取在低维、离散化动作的情况下可以用QLearning做初步验证再上连续控制算法。资源调度服务器任务分配、库存管理等状态可离散化QLearning能给出不错的策略。游戏AI小规模棋盘类游戏QLearning是经典解法。选型建议如果你的问题能用有限的离散状态和动作描述清楚且对泛化要求不高QLearning是性价比最高的选择。先用它快速验证想法再根据瓶颈决定是否升级。最后分享一个我踩过的坑我曾在一个状态空间约5000的项目里直接用手写QLearning结果训练了十几个小时还没收敛。后来把状态重新分箱压到800个状态训练时间一下就降下来了效果还更好。状态空间的精简往往比算法技巧带来的收益更大。在动手写代码之前花半天时间想清楚状态怎么定义比后面调参调一天都值。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →