Q-learning vs SARSA: On-Policy与Off-Policy核心区别详解
2025-04-22 22:02:57
Q-learning 和 SARSA:不只是 Max 的区别
刚接触强化学习,看到 Q-learning 和 SARSA 的更新公式,是不是有点懵?它们长得太像了:
SARSA:
Q(s_t, a_t) = Q(s_t, a_t) + α * (r_t + γ * Q(s_{t+1}, a_{t+1}) - Q(s_t, a_t))
Q-learning:
Q(s_t, a_t) = Q(s_t, a_t) + α * (r_t + γ * max_a Q(s_{t+1}, a) - Q(s_t, a_t))
这俩公式就差一个 ‘max’,但背后代表的意义和学习方式却差了不少。咱们这就来彻底捋捋清楚。
关键分歧:跟着规则走 vs. 看着最优走 (On-policy vs. Off-policy)
要理解 Q-learning 和 SARSA 的不同,得先搞明白「On-policy」(策略学习)和「Off-policy」(离策略学习)是啥意思。这俩词听着挺玄乎,其实说白了就是智能体(Agent)学习更新的方式不同。
- On-policy (比如 SARSA) :智能体学习的策略,就是它当前正在执行的策略。它像个循规蹈矩的学生,自己怎么做,就学怎么做的结果好坏。它更新自己的 Q 表(动作价值函数)时,会用到自己下一步 实际 要走的动作
a_{t+1}
。它基于当前的策略(比如 ε-greedy 策略,有一定概率随机探索,一定概率选择当前认为最好的动作)来产生行为,也基于这个行为产生的数据来学习。 - Off-policy (比如 Q-learning) :智能体学习的策略,可以和它当前执行的策略不一样。它像个会“旁观”的学霸,就算自己因为探索偶尔走了“臭棋”,它学习的时候还是会看着“最优”的路来更新自己的认知。它更新 Q 表时,并不会看自己下一步 实际 会走哪个动作
a_{t+1}
,而是直接去看在下一个状态s_{t+1}
时,所有可能 的动作里,哪个动作能带来最大的 Q 值(也就是max_a Q(s_{t+1}, a)
)。
这个核心区别直接导致了它们更新公式的不同。
更新规则拆解:一个‘瞻前顾后’,一个‘大胆假设’
让我们再仔细看看这两个公式,把它们和 On/Off-policy 联系起来。两个公式都在尝试估计 Q(s_t, a_t) 的值,也就是在状态 s_t
执行动作 a_t
能获得的长期回报。这个估计基于当前的估计值 Q(s_t, a_t)
和一个“目标值”(Target Value)。不同之处就在于这个目标值的计算方式。
目标值通常由两部分组成:
- 立即奖励
r_t
:执行动作a_t
后立刻得到的奖励。 - 未来回报的估计:对后续状态价值的折现估计
γ * (未来价值)
。
差别就在这个「未来价值」怎么算。
SARSA:步步为营,用实际行动更新
Q(s
t, at) = Q(st, at) + α * (rt+ **γ * Q(st+1, at+1) ** - Q(st, at))
看加粗的部分 γ * Q(s_{t+1}, a_{t+1})
。这里的 a_{t+1}
是关键。它是智能体在状态 s_{t+1}
时,根据当前遵循的策略(例如 ε-greedy)实际选择的下一个动作。
原理和作用:
SARSA 的更新,是基于一个完整的交互样本 (s_t, a_t, r_t, s_{t+1}, a_{t+1})
。这也是它名字(State-Action-Reward-State-Action)的由来。它用智能体实际要走的下一步 a_{t+1}
的 Q 值来估算未来的回报。这意味着 SARSA 的学习过程完全依赖于当前策略产生的行为。如果当前策略是探索性的(比如经常随机选择动作),那么它的学习目标也会包含这些探索性动作带来的(可能非最优的)价值。它评估的是“在当前策略下,执行这个动作有多好”。
简化版 SARSA 流程(单步更新):
- 初始化 Q 表(比如全零)。
- 对于每一轮(episode):
a. 获取初始状态s
。
b. 根据当前策略 (比如 ε-greedy),基于Q(s, :)
选择动作a
。
c. 循环直到s
是终止状态:
i. 执行动作a
,得到奖励r
和下一个状态s'
。
ii. 根据当前策略 (比如 ε-greedy),基于Q(s', :)
选择下一个动作a'
。
iii. 更新 Q 值:
Q(s, a) = Q(s, a) + α * (r + γ * Q(s', a') - Q(s, a))
iv. 将状态和动作更新到下一步:s = s'
,a = a'
。
伪代码示例(Python 风格):
def sarsa_update(q_table, state, action, reward, next_state, next_action, alpha, gamma):
""" 使用 SARSA 更新 Q 表 """
current_q = q_table.get((state, action), 0.0) # 获取当前 Q 值,没有则为 0
next_q = q_table.get((next_state, next_action), 0.0) # 获取下一个状态-实际动作的 Q 值
target_value = reward + gamma * next_q
new_q = current_q + alpha * (target_value - current_q)
q_table[(state, action)] = new_q
return q_table
# 在主循环中:
# ...
# state = current_state
# action = choose_action(q_table, state, epsilon) # 根据 ε-greedy 等策略选择当前动作
# next_state, reward = environment.step(action)
# next_action = choose_action(q_table, next_state, epsilon) # 根据 ε-greedy 等策略选择下一个动作
# q_table = sarsa_update(q_table, state, action, reward, next_state, next_action, alpha, gamma)
# state = next_state
# action = next_action # 注意:SARSA 需要将选择的 next_action 用于下一轮的 action
# ...
安全建议/适用场景:
由于 SARSA 评估的是实际执行策略的表现,它通常更“保守”。如果智能体因为探索策略而选择了一个危险(低回报)的动作 a_{t+1}
,这个低价值会直接反映在 Q(s_t, a_t)
的更新中。这使得 SARSA 在需要规避风险或评估当前策略实际效果的场景下很有用,比如需要确保机器人不会在学习过程中频繁撞墙。
Q-learning:着眼未来,选最优路径更新
Q(s
t, at) = Q(st, at) + α * (rt+ γ * maxaQ(st+1, a) - Q(st, at))
再看 Q-learning 加粗的部分 γ * max_a Q(s_{t+1}, a)
。这里的 max_a Q(s_{t+1}, a)
表示在下一个状态 s_{t+1}
时,选取 所有可能的动作 a
中,能使 Q(s_{t+1}, a)
达到最大的那个 Q 值。
原理和作用:
Q-learning 的更新,只需要 (s_t, a_t, r_t, s_{t+1})
这部分信息就够了,它不需要知道下一步实际执行了什么动作 a_{t+1}
。它在计算目标值时,总是假设在未来会采取最优的行动(即使当前策略因为探索可能不会这么做)。这使得 Q-learning 直接学习最优策略(Greedy Policy),不管实际执行的是什么探索策略(Behavior Policy)。它评估的是“如果未来都按最优方式走,当前这个动作有多好”。
简化版 Q-learning 流程(单步更新):
- 初始化 Q 表(比如全零)。
- 对于每一轮(episode):
a. 获取初始状态s
。
b. 循环直到s
是终止状态:
i. 根据当前策略 (比如 ε-greedy),基于Q(s, :)
选择动作a
。
ii. 执行动作a
,得到奖励r
和下一个状态s'
。
iii. 计算下一个状态的最大 Q 值:
max_q_prime = max(Q(s', a') for a' in all_possible_actions(s'))
(如果s'
是终止状态,则为 0)。
iv. 更新 Q 值:
Q(s, a) = Q(s, a) + α * (r + γ * max_q_prime - Q(s, a))
v. 更新状态:s = s'
。
伪代码示例(Python 风格):
import numpy as np
def q_learning_update(q_table, state, action, reward, next_state, all_possible_actions_func, alpha, gamma):
""" 使用 Q-learning 更新 Q 表 """
current_q = q_table.get((state, action), 0.0) # 获取当前 Q 值
# 找到下一个状态所有可能动作的最大 Q 值
next_possible_actions = all_possible_actions_func(next_state)
max_next_q = 0.0
if next_possible_actions: # 如果不是终止状态
max_next_q = max(q_table.get((next_state, next_a), 0.0) for next_a in next_possible_actions)
target_value = reward + gamma * max_next_q
new_q = current_q + alpha * (target_value - current_q)
q_table[(state, action)] = new_q
return q_table
# 假设有一个函数能返回某个状态下所有合法的动作
def get_possible_actions(state):
# ... 实现细节依赖具体环境 ...
# 示例: return [0, 1, 2, 3] # 上下左右
pass
# 在主循环中:
# ...
# state = current_state
# action = choose_action(q_table, state, epsilon) # 根据 ε-greedy 等策略选择当前动作
# next_state, reward = environment.step(action)
# q_table = q_learning_update(q_table, state, action, reward, next_state, get_possible_actions, alpha, gamma)
# state = next_state
# ...
安全建议/适用场景:
Q-learning 目标是学习最优策略,即使当前的探索策略比较“浪”,它也能朝着最优的方向去更新 Q 值。这让它在很多任务中学习速度可能更快,因为它不受次优探索行为的直接“拖累”。但是,这种“乐观”的态度也意味着它可能低估执行探索动作的负面影响。如果智能体探索时选择的动作有严重后果,Q-learning 不会直接把这个后果计入 Q 值更新(除非这个差动作恰好是当前认为“最优”的)。它更适合目标是找到绝对最优策略,且允许智能体在环境中进行充分探索的场景。
走迷宫的例子:SARSA 小心翼翼,Q-learning 眼观六路
想象一个机器人在走迷宫,目标是找到出口拿到奖励,同时路上有一些坑(负奖励)。
- 使用 SARSA 的机器人: 它会按照某种探索策略(比如大部分时间走当前认为最好的路,小部分时间随机走)来移动。当它更新路径价值时,会看自己 实际 准备走的下一步。如果因为随机探索,它下一步准备踏入一个已知的坑附近(假设坑附近的格子 Q 值较低),那当前这一步的价值评估就会受到影响,变得更低。它学到的是“按照我现在的走路方式,走这条路大概会怎么样”。如果它一直很小心翼翼(探索率低),可能会找到一条安全的、但不一定最短的路。
- 使用 Q-learning 的机器人: 它也可能因为探索策略而实际走向坑的方向。但是,在更新路径价值时,它会抬头看看下一步所有可能的方向,找到那个通往“光明”(最高 Q 值)的方向,并用那个方向的价值来更新当前这一步。它不关心自己下一步实际上会不会踩坑,只关心理论上的最优走法。所以,即使它偶尔因为探索走了弯路或靠近了坑,它内心对“最优路径”的估计仍然会朝着全局最优(最短路径)快速更新。
选哪个?看你的目标和环境
没有绝对的哪个更好,选择取决于具体任务:
-
什么时候可能用 SARSA?
- 安全性要求高: 当智能体的动作有潜在危险,不希望它在学习过程中过于冒险时。SARSA 的 On-policy 特性使得 Q 值的评估更贴近实际执行策略的表现,能更快地学到规避危险动作。
- 需要评估当前策略: 如果你想知道当前采用的这个探索策略本身的效果怎么样,SARSA 更合适,因为它直接基于这个策略产生的数据进行学习。
- 环境模型未知且在线学习: SARSA 非常适合在线学习场景,步步为营地根据实际反馈调整。
-
什么时候可能用 Q-learning?
- 目标是找到最优策略: 如果最终目标就是找到理论上的最优解决方案,不管智能体在学习过程中走了多少弯路,Q-learning 通常更直接。
- 可以进行充分探索: 当环境允许智能体安全地进行大量探索尝试时。
- 经验回放(Experience Replay): Q-learning 的 Off-policy 特性让它能很好地结合经验回放(把过去的
(s, a, r, s')
存起来,反复学习)。SARSA 直接用经验回放会有点问题,因为存储的样本可能来自旧的策略,直接用 SARSA 的更新规则(需要a'
)就不对了(虽然也有改进版的 SARSA(λ) 可以利用历史数据)。
一些容易搞混的点 (以及记号的小坑)
-
行动选择 vs. 价值更新: 关键在于区分“智能体实际如何选择动作去与环境交互”(通常用 ε-greedy 等探索策略)和“用哪个值来更新 Q 表的目标值”。
- 两个算法在选择 实际执行 的动作
a_t
时,通常都用同一个行为策略(比如 ε-greedy)。 - 区别在于计算 更新目标 时:SARSA 用的是基于 同一个行为策略 在
s_{t+1}
选择的 实际 下一个动作a_{t+1}
的 Q 值;Q-learning 则是直接取s_{t+1}
所有可能动作中 理论上 Q 值最大的那个,与行为策略无关。
- 两个算法在选择 实际执行 的动作
-
r_t
vsr_{t+1}
记号问题:
就像问题中提到的,一些资料(包括 Sutton & Barto 的书或 Wikipedia 的早期版本)在公式记号上可能有点模糊。公式里的r
指的是执行动作a_t
后、到达状态s_{t+1}
之前 收到的那个即时奖励 (immediate reward) 。虽然下标有时会写成r_{t+1}
,逻辑上它是紧跟在a_t
之后的结果,与s_{t+1}
和 (如果是 SARSA 的话)a_{t+1}
处于同一“时间片”的开端。更清晰的理解是:在时间步t
,处于状态s_t
,执行动作a_t
,获得奖励r_t
,然后转移到状态s_{t+1}
。所以更新Q(s_t, a_t)
时用的奖励就是r_t
。理解这一点很重要,别被下标弄晕了。
进阶:Q-learning 的‘过于乐观’与 Double Q-learning
Q-learning 因为总是选取 max_a Q(s_{t+1}, a)
,在 Q 值存在估计误差时(这在学习初期很常见),可能会系统性地高估 Q 值。这就是所谓的「最大化偏差」(Maximization Bias)。想象一下,如果 s_{t+1}
有多个动作,它们的真实 Q 值都差不多,但因为噪声或估计不足,某个动作的 Q 值被偶然高估了,max
操作就会选中它,导致整体的目标值偏高。
如何缓解?Double Q-learning
一个常用的改进方法是 Double Q-learning。它维护两套独立的 Q 值估计表(比如叫 QA 和 QB)。
- 在选择下一步最优动作时,用其中一个表,比如 QA:
a* = argmax_a QA(s_{t+1}, a)
。 - 在计算目标值时,用另一个表,比如 QB,来评估这个选定动作的价值:
Target = r_t + γ * QB(s_{t+1}, a*)
。
然后用这个 Target 去更新 QA。反之亦然(用 QB 选动作,用 QA 评估值来更新 QB)。这样就把“选择最优动作”和“评估该动作价值”这两步分开了,减少了因为同一个表中的偶然高估而被选中的概率,从而缓解最大化偏差。
希望这次能把 Q-learning 和 SARSA 的区别讲明白了。记住核心:一个看实际下一步怎么走,一个看理论上最优的下一步在哪儿。