资讯动态

Q-Learning为何是离线策略?max操作与行为策略解耦深度解析

发布时间:2026/10/20 9:44:58 来源:尧图企业网站定制
强化学习系列更新到第 54 篇前半程我们已经把动态规划、蒙特卡洛、时序差分TD这些基础方法都过了一遍代码也写了十几套。这一讲我不想急着推新算法而是想把一个最容易被绕晕、也最影响后续理解 DQN 的概念彻底讲清楚Q-Learning 为什么是离线策略off-policy它到底“离线”在哪里。很多读者学到 Q-Learning 时都会提出一个非常合理的疑问我们用 ε-greedy 去和环境交互收集到一条条轨迹然后照着这些轨迹更新 Q 值——这不是明摆着在用“当前策略”产生的数据吗怎么 SARSA 就被叫在线策略Q-Learning 就成了离线策略差别只在更新公式里一个不起眼的位置却决定了两种算法的本质。本文会从定义、公式、代码实验和常见坑位几个角度把这个关键分水岭讲透。1. 先从“在线策略”和“离线策略”的区别说起1.1 一个容易卡住的问题很多初学者看到“离线策略”四个字第一反应是“离线 不和环境交互直接拿历史数据训练”这个理解不能说全错但放在强化学习语境里并不准确。Q-Learning 仍然是需要不断和环境交互的它每走一步都要拿到真实的奖励和下一个状态。它真正“离线”的地方不在于数据的来源而在于更新目标所依赖的策略。要解释清楚这件事必须先区分两个概念行为策略Behavior Policy也叫采样策略是 agent 真实用来和环境交互、产生轨迹数据的策略。在 Q-Learning 里这一步一般就是 ε-greedy。目标策略Target Policy是 agent 真正想学到的策略也就是更新公式里不断优化的那个策略。如果“用来产生数据的策略”和“正在学习的策略”是同一个算法就叫在线策略on-policy如果两者不是同一个算法就叫离线策略off-policy。SARSA 之所以是在线策略是因为它的更新公式里下一步动作 a 是用“当前正在学习的策略”选出来的。Q-Learning 之所以是离线策略是因为它的更新公式里根本不关心行为策略在下一个状态选了哪个动作它直接取下一个状态中所有动作 Q 值的最大值。1.2 为什么必须区分这两个策略区分在线和离线不是学术洁癖它会直接影响三个工程问题样本利用率在线策略每更新一次旧样本就“作废”了因为策略已经变了离线策略却能反复利用历史样本这就是经验回放Experience Replay能工作的前提。收敛性质在线策略的更新目标和采样分布是一致的理论上更好分析离线策略的更新目标和采样分布不一致容易出现不稳定的情况尤其在引入神经网络之后。安全性与保守性在线策略学到的价值函数天然包含了“训练时探索带来的风险”离线策略学到的价值函数则直接逼近理论最优不考虑探索带来的破坏。等我们后面讲到 DQN 时这三个区别会全部体现出来。DQN 之所以能用经验回放能把以前收集的数据翻来覆去地训练本质上就是继承了 Q-Learning 的离线策略特性。1.3 Q-Learning 到底“离线”在哪里一句话概括Q-Learning 的 TD 目标里有一个 max 操作这个 max 让它不依赖行为策略在下一个状态的选择因此它可以学习一个和当前行为策略完全不同的目标策略。行为策略负责探索、负责踩坑、负责收集数据目标策略负责给出“如果这一步走对了未来能拿多少回报”的期望。Q-Learning 在更新时相当于一边用行为策略去外面冒险一边在心里假设“下一步我一定会走最优动作”。正是这种“冒险归冒险学习归学习”的分离构成了离线策略的本质。2. 环境准备与版本说明2.1 运行环境本文的代码不依赖第三方强化学习库直接用 NumPy 实现一个经典的“悬崖行走”Cliff Walking环境便于观察 Q-Learning 和 SARSA 在同一个环境下的策略差异。你只需要准备Python 3.8 或以上版本NumPy 库建议 1.20 以上稳定版本即可一个能运行 Python 脚本的环境Jupyter Notebook 或命令行都行。如果你的机器还没有 NumPy执行下面命令安装pip install numpy版本不需要和我完全一致示例逻辑非常基础只要 NumPy 能正常读取和更新多维数组就能跑。2.2 示例项目结构建议按下面的目录结构组织代码后续加入 DQN 时也方便扩展rl_offpolicy/ ├── cliff_walking.py # 悬崖行走环境定义 ├── q_learning.py # Q-Learning 算法 ├── sarsa.py # SARSA 算法作对比 └── main.py # 训练入口与策略可视化下面每段代码我都会标注文件路径你可以直接复制到对应文件里运行。3. Q-Learning 离线策略原理拆解3.1 从贝尔曼最优方程说起先回到最基础的定义。一个策略 π 的状态-动作价值函数写作Q_π(s, a) E_π[ r_t γ·r_{t1} γ²·r_{t2} ... | s_t s, a_t a ]它表示从状态 s 执行动作 a 之后一直按照策略 π 走下去能获得的期望折扣回报。最优策略对应的价值函数是 Q*它满足贝尔曼最优方程Q*(s, a) E[ r γ · max_{a} Q*(s, a) | s, a ]这个方程的关键在于当前状态的价值是用“下一个状态里所有动作中最大的 Q 值”来递归定义的。也就是说最优价值函数的定义本身就要求下一步走最优动作而不是按照某个固定策略的分布去抽样。Q-Learning 的更新公式正是对这个最优方程的采样逼近。3.2 更新公式逐项拆解Q-Learning 的标准更新式如下Q(s, a) ← Q(s, a) α · [ r γ · max_{a} Q(s, a) − Q(s, a) ]我们把这一行拆成三部分来看Q(s, a)旧估计值也就是更新前 agent 对“状态 s 执行动作 a”能拿多少回报的估计。r γ · max_{a} Q(s, a)时序差分目标TD Target它由真实奖励 r 和下一状态的最优价值估计共同构成。Q(s, a) − TD Target整体乘以学习率 α这就是每次更新时修正的方向和幅度。初学者容易把目光放在 α 上头几遍看公式会觉得“这不就是普通的 TD 更新吗”但真正的关键在于 TD Target 里的max_{a}。它不是跟着行为策略在 s 实际选择的动作走而是假设自己在 s 一定执行最好的动作。如果行为策略因为探索在 s 选择了一个很差的动作SARSA 会把这次“踩坑”记到价值估计里Q-Learning 则会直接忽略它把目光盯在最优动作上。也就是说Q-Learning 的更新目标里根本没有出现行为策略。3.3 max 操作与行为策略解耦为什么一个 max 就能实现离线策略因为 TD Target 不依赖行为策略的分布。SARSA 的更新目标是r γ · Q(s, a)其中 a 是按当前策略从 Q 值分布里采样得到的。要计算这个目标你必须知道行为策略下一步会选什么动作。行为策略变了这个目标就变了所以它是在线策略。Q-Learning 的更新目标是r γ · max_{a} Q(s, a)它对所有动作取最大行为策略在 s 选哪个、不选哪个、选择的概率是多少都和这个目标无关。只要你的轨迹里有(s, a, r, s)这样的四元组Q-Learning 就可以把它当作“来自最优策略的其中一条经验”来更新。这就是“离线”的真正含义学习过程中不需要知道当前采样用的策略到底怎么走只需要状态、动作、奖励、下一状态这些转移数据。这也是为什么经验回放能成立。经验回放池里的数据可能是老策略收集的也可能是新策略刚产生的Q-Learning 并不关心它来自哪个策略只要覆盖了足够多的状态-动作对就能从中恢复出最优价值函数。3.4 收敛条件与理论基础讲到这里有读者会问离线策略听着很玄凭什么它还能收敛到最优解Watkins 和 Dayan 在 1992 年证明了表格型 Q-Learning 的收敛性。要保证收敛需要满足几个条件所有状态-动作对(s, a)被无限次访问即行为策略必须保证足够的探索学习率 α 满足 Robbins-Monro 条件∑α_t ∞且∑α_t² ∞奖励有界Q 值存放在表格中不依赖函数逼近。只要探索足够充分Q-Learning 的 Q 表会以概率 1 收敛到 Q*。注意这里的“探索足够充分”是行为策略的职责。行为策略负责保证每个状态-动作对都能被访问到目标策略负责把价值估计推向最优两者各司其职。这也解释了为什么 ε-greedy 的 ε 不能过早降到 0一旦失去探索某些状态-动作对永远不被访问离线策略的自然收敛条件就破坏了。4. 完整实战案例悬崖行走对比 Q-Learning 与 SARSA4.1 环境定义与动作空间为了直观展示离线策略和在线策略的差异我们用 Sutton 教材中最经典的“悬崖行走”环境。这个环境改编自教材例 6.6不依赖第三方库方便你观察两种算法的策略形态。环境是一个 4 行 12 列的网格起点在左下角(3, 0)终点在右下角(3, 11)最后一行的第二列到倒数第二列是悬崖落入悬崖奖励 −100并且回到起点继续其他每一步普通移动奖励 −1动作空间为 4 个动作上、下、左、右到达终点后该回合结束。最优路径是向上一步、沿第二行横穿、再向下一步到达终点一共 13 步理论最优回报是 −13。4.2 编写环境代码# 文件路径rl_offpolicy/cliff_walking.py import numpy as np class CliffWalkingEnv: 悬崖行走环境4 x 12 网格起点左下角终点右下角最后一行为悬崖。 def __init__(self, rows4, cols12): self.rows rows self.cols cols self.start (rows - 1, 0) # 左下角 self.goal (rows - 1, cols - 1) # 右下角 self.cliff_cells [(rows - 1, c) for c in range(1, cols - 1)] self.reset() def reset(self): 回到起点返回初始状态。 self.state self.start return self.state def step(self, action): 执行动作返回 (下一个状态, 奖励, 是否结束)。 row, col self.state # 动作空间0上1下2左3右 if action 0: row max(row - 1, 0) elif action 1: row min(row 1, self.rows - 1) elif action 2: col max(col - 1, 0) elif action 3: col min(col 1, self.cols - 1) next_state (row, col) if next_state in self.cliff_cells: reward -100 next_state self.start done False elif next_state self.goal: reward -1 done True else: reward -1 done False self.state next_state return next_state, reward, done这里有一个细节需要说明落入悬崖后我们设置done False让 agent 回到起点继续这个回合。这个设定与教材原版保持一致。如果你用的是 Gymnasium 里的 CliffWalking 环境可能落崖后会直接终止回合返回的数值会有差异但 Q-Learning 和 SARSA 的策略形态结论不会变。4.3 实现 Q-LearningQ-Learning 的实现重点是找出 TD Target 里的max。代码里我会把那关键一行单独注释出来。# 文件路径rl_offpolicy/q_learning.py import numpy as np def q_learning(env, episodes500, alpha0.5, gamma0.9, epsilon0.1): 表格型 Q-Learning 算法。 参数 env: 悬崖行走环境实例 episodes: 训练回合数 alpha: 学习率 gamma: 折扣因子 epsilon: 探索概率 q_table np.zeros((env.rows, env.cols, 4)) episode_rewards [] for _ in range(episodes): state env.reset() total_reward 0 done False while not done: # 行为策略epsilon-greedy if np.random.random() epsilon: action np.random.randint(4) # 探索 else: action int(np.argmax(q_table[state[0], state[1]])) # 利用 next_state, reward, done env.step(action) # 核心TD Target 使用 max不依赖行为策略在 next_state 选的什么动作 best_next_value np.max(q_table[next_state[0], next_state[1]]) td_target reward gamma * best_next_value td_error td_target - q_table[state[0], state[1], action] q_table[state[0], state[1], action] alpha * td_error state next_state total_reward reward # 防御性跳出避免极端情况下陷入死循环 if total_reward -1000: break episode_rewards.append(total_reward) return q_table, episode_rewards注意这段代码里我们在next_state上计算best_next_value时并没有用到行为策略在next_state实际选择的动作。即使行为策略因为探索走到了悬崖边Q-Learning 也只会把“从 next_state 出发的最优价值”当作学习目标这就是离线策略在代码里最直接的表现。4.4 实现 SARSA 作对比为了对照“在线策略”和“离线策略”的差别我们几乎不改代码只改一处更新目标里不再取 max而是使用行为策略在下一步真实选择的动作价值。# 文件路径rl_offpolicy/sarsa.py import numpy as np def sarsa(env, episodes500, alpha0.5, gamma0.9, epsilon0.1): 表格型 SARSA 算法。 q_table np.zeros((env.rows, env.cols, 4)) episode_rewards [] for _ in range(episodes): state env.reset() # 行为策略选第一个动作 if np.random.random() epsilon: action np.random.randint(4) else: action int(np.argmax(q_table[state[0], state[1]])) total_reward 0 done False while not done: next_state, reward, done env.step(action) # 关键区别next_action 仍然按同样的 epsilon-greedy 策略选 if np.random.random() epsilon: next_action np.random.randint(4) else: next_action int(np.argmax(q_table[next_state[0], next_state[1]])) td_target reward gamma * q_table[next_state[0], next_state[1], next_action] td_error td_target - q_table[state[0], state[1], action] q_table[state[0], state[1], action] alpha * td_error state next_state action next_action total_reward reward if total_reward -1000: break episode_rewards.append(total_reward) return q_table, episode_rewards对比两份代码唯一的本质区别就是 TD TargetQ-Learning: reward gamma * max(Q(s, :)) ← 离线 SARSA: reward gamma * Q(s, next_action) ← 在线这一字之差决定了 SARSA 学习的是“当前行为策略本身的期望回报”而 Q-Learning 学习的是“下一步一定走最优动作时的期望回报”。4.5 运行与结果解读最后写一个入口脚本把两个算法训练 500 个回合并打印学习到的策略。# 文件路径rl_offpolicy/main.py import numpy as np from cliff_walking import CliffWalkingEnv from q_learning import q_learning from sarsa import sarsa def extract_policy(q_table): 把 Q 表转换成可视化的策略网格。 rows, cols q_table.shape[0], q_table.shape[1] policy [] for r in range(rows): line [] for c in range(cols): if (r, c) (rows - 1, cols - 1): line.append(G) # 终点 elif (r, c) in [(rows - 1, i) for i in range(1, cols - 1)]: line.append(C) # 悬崖 else: best_action int(np.argmax(q_table[r, c])) line.append([↑, ↓, ←, →][best_action]) policy.append(line) return policy if __name__ __main__: np.random.seed(0) # 固定随机种子方便复现对比 env CliffWalkingEnv() q_table_q, rewards_q q_learning(env, episodes500, alpha0.5, gamma0.9, epsilon0.1) q_table_s, rewards_s sarsa(env, episodes500, alpha0.5, gamma0.9, epsilon0.1) print(Q-Learning 学习到的策略) for line in extract_policy(q_table_q): print( .join(line)) print(\nSARSA 学习到的策略) for line in extract_policy(q_table_s): print( .join(line)) print(\n最近 50 个回合平均回报) print(Q-Learning:, round(float(np.mean(rewards_q[-50:])), 2)) print(SARSA :, round(float(np.mean(rewards_s[-50:])), 2))命令行运行cd rl_offpolicy python main.py由于训练过程有随机性单次运行结果会有波动但正常情况下你会观察到两种典型的策略形态Q-Learning 学到的策略基本贴着悬崖上沿走到了终点附近再下切。这是因为它的更新目标假设“下一步一定走最优动作”所以学到的是理论最优路径但这条路径一旦结合训练时的探索就很容易掉进悬崖。SARSA 学到的策略明显更靠上宁可绕一点路也要和悬崖保持距离。这是因为它在更新目标里显式包含了 ε-greedy 探索带来的动作选择相当于把“训练时可能踩坑”这件事提前算进了价值估计里。回合回报曲线也很有特点Q-Learning 在前几十回合经常出现 −100 这种大负值后期逐渐收敛到接近 −13 附近SARSA 的回报曲线更平稳但最终平均回报通常低于 Q-Learning因为它绕了远路。这个实验是理解在线与离线策略最直观的案例Q-Learning 更“激进”SARSA 更“保守”各有各的适用场景。5. 常见问题与排查思路5.1 Q-Learning 学到了贴着悬崖的路线这是不是 bug不是 bug而是离线策略的必然结果。Q-Learning 的目标策略是贪心策略它在更新时假设自己永远不会因为探索而走错路所以它认为贴着悬崖走是最优的。但在测试或者训练过程中只要还有 ε 概率的探索走这条路就可能掉下去。如果你希望 agent 在实际交互中更安全有几种可选方案改用 SARSA 这类在线策略让价值估计包含探索风险在 Q-Learning 训练结束后的评估阶段把 ε 置为 0 再评估重新设计奖励给悬崖边增加惩罚让“贴着悬崖”不再是理论最优。这个问题的本质是理论最优不等于工程安全。离线策略擅长寻找极限最优解在线策略更擅长给出稳健解。5.2 Q 值一直震荡训练后期反而变差怎么办表格型 Q-Learning 出现震荡常见原因有三个。第一max操作天然带来最大化偏差maximization bias。Q 值是带噪声的估计值对所有动作取 max 会把噪声中偏高的部分放大导致 Q 值虚高。处理办法是使用 Double Q-Learning用两个 Q 表互相作参照Q^A(s, a) ← Q^A(s, a) α · [ r γ · Q^B(s, argmax_{a} Q^A(s, a)) − Q^A(s, a) ]argmax用 Q^A 选动作估值用 Q^B 来算两边不对称就能缓解偏差。第二学习率 α 太大更新步子过猛Q 值容易来回摆动。可以把 α 从 0.5 调小到 0.1 试试或者使用递减学习率。第三ε 没有衰减行为策略始终有很高的探索率导致已经收敛的 Q 值被不断破坏。实践中可以让 ε 随回合数从 0.5 线性衰减到 0.01保证前期充分探索、后期稳定收敛。问题现象常见原因解决思路Q 值收敛到偏高水平max 操作放大噪声改用 Double Q-Learning训练后期回报变差ε 过高、探索过多设置 ε 衰减策略收敛速度慢α 太小适当调大 α 或使用自适应学习率结果不稳定随机种子不同固定种子、多次运行取平均5.3 表格型 Q-Learning 能处理多大的状态空间表格型 Q-Learning 需要为每个(s, a)对维护一个独立的数值。悬崖行走只有 48 个状态每状态 4 个动作表格非常小。但真实任务中状态可能是图像、传感器数据、连续坐标状态空间一旦变大表格会迅速爆炸。比如一个 1000 × 1000 的离散网格状态数就是一百万个每个状态还对应多个动作表格存储和访问成本已经很高。连续状态空间更不可能用表格枚举。解决办法是引入函数逼近用神经网络代替 Q 表这就是 DQN。DQN 在 Q-Learning 的基础上做了两个关键改动经验回放把(s, a, r, s)存进缓冲区随机采样小批量更新打破数据相关性提高样本利用率目标网络固定一份 Q 网络参数生成 TD Target每隔若干步再更新一次减少自举带来的训练震荡。DQN 能在 Atari 游戏上接近甚至超过人类水平靠的正是“Q-Learning 离线策略 深度神经网络 经验回放”的组合拳。离线策略在这里的价值是经验回放池里的数据可以反复用很多遍而不需要每收集一条新数据就立刻原地更新。如果你准备进入 DQN这一讲的离线策略概念就是最重要的前置知识。5.4 离线策略 函数逼近会不会出问题会。表格型 Q-Learning 有严格收敛保证但当“离线策略”遇上“函数逼近”和“自举”三个因素凑在一起可能产生不稳定甚至发散这就是强化学习里常说的“致命三要素”deadly triad离线策略更新目标和数据分布不一致函数逼近Q 值存储在可泛化的参数网络中而不是独立表格自举用当前估计值本身去更新估计值TD Target 里包含了网络自己的预测。三要素单独出现都没问题组合起来就可能让 Q 值越涨越高、策略越来越差。DQN 之所以能稳定工作正是因为目标网络限制了自举的传播经验回放又在一定程度上缓解了数据分布不一致的问题。后面对 DQN 的改进算法比如 Double DQN、Dueling DQN、Rainbow很多都是在调节三要素之间的平衡。6. 最佳实践与工程建议6.1 探索与利用的平衡是离线策略的命门Q-Learning 的收敛性成立前提是“所有状态-动作对被无限次访问”。因此行为策略的探索设计不是可有可无的细节而是离线策略成立的硬条件。工程上常用的做法是训练初期使用较高 ε比如 0.5充分探索训练中后期让 ε 线性或指数衰减到 0.01减少随机动作对 Q 值的破坏如果状态访问次数可以统计可以用 UCB、count-based exploration 这类更智能的探索策略而不是纯随机尝试。另外要注意在评估阶段应该把 ε 设为 0用贪心策略跑测试否则评估结果里混入了探索噪声无法反映算法真实水平。6.2 什么时候选离线策略什么时候选在线策略这取决于业务目标。如果交互成本高、数据珍贵比如自动驾驶、机器人操作通常会用离线策略算法配合经验回放让每一条轨迹都发挥最大价值。如果任务对安全性要求极高训练过程中的一次失误可能造成严重后果在线策略的价值就在于它学到的策略会把探索风险考虑进去行为更保守。SARSA、PPO 这类方法往往更适合这种场景。如果只是做算法研究或标准 benchmarkQ-Learning、DQN 这类离线策略算法更常作为基线因为它们样本利用率高、收敛后最优性更强。6.3 调参与复现建议表格型 Q-Learning 虽然简单但要复现结果依然要小心几个细节。第一固定随机种子。强化学习代码里至少有环境随机性和策略随机性两层随机源不确定种子时两个算法之间的微小差异会被噪声淹没。第二多次运行取平均。建议至少跑 5 个不同种子把每条回报曲线画出来再画均值曲线。单次运行的结果只能看个大概不能说明算法优劣。第三学习率参数要合理。表格型方法 α 常用 0.1 到 0.5折扣因子 γ 需要根据任务的最长步数确定。悬崖行走这类短程任务用 0.9 或 1.0 都可以长程任务打折太狠会导致远期奖励几乎被忽略。第四训练循环要有防御性跳出。比如设定total_reward -1000就跳出当前回合防止环境配置出错导致无限循环。这类保护代码在真实项目中很重要不要删掉。6.4 从表格方法到深度方法的迁移建议当你从表格型 Q-Learning 迁移到 DQN 时最大的变化不是网络结构而是价值更新的假设变了表格方法里每个状态是独立的更新一个状态不会影响其他状态深度网络里状态通过特征共享参数更新会泛化到相近状态这是优点也可能造成干扰。建议先在一个离散小环境上跑通 Q-Learning把 Q 表可视化理解策略是怎么从无到有形成的再换到 DQN 解决连续状态问题。如果直接拿 DQN 跑业务环境往往会因为目标网络更新频率、经验回放容量、奖励缩放等超参数太多而不知道从哪里排查。7. 总结与下一步学习路线7.1 这一讲的关键结论回到这一讲标题提出的问题为什么 Q-Learning 能实现离线策略根本原因是它的 TD Target 里有一个max_{a} Q(s, a)。这个 max 让更新目标不依赖行为策略在下一个状态的动作选择行为策略可以专心探索目标策略可以专心求最优两者完全解耦。SARSA 因为用Q(s, a)作为目标把行为策略的动作选择写进了价值估计里所以只能是在线策略。同时要记住离线策略不是免费的午餐。它必须靠充分探索来满足收敛条件会引入最大化偏差在神经网络加持下还可能和函数逼近、自举产生不稳定三角。理解这些边界条件才算真正理解了 Q-Learning。7.2 动手做三件小事读完这一讲不一定要急着学新算法先把下面三件事做了收益会非常大。第一把悬崖行走代码跑通固定随机种子观察 Q-Learning 和 SARSA 的策略差异再把 ε 衰减逻辑加进去看策略形态会不会改变。第二实现一个 Double Q-Learning对比普通 Q-Learning 在随机噪声下的 Q 值虚高程度。这个实验会让你对max操作的理解上升一个台阶。第三把 DQN 的原始论文找出来对照这一讲的离线策略概念理解 DQN 为什么需要经验回放、为什么需要目标网络。这三件事做完你就可以放心进入强化学习后续章节接触 DDPG、SAC 这些 Actor-Critic 家族的离线策略算法时思路会顺畅很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑