时序差分控制算法详解:Sarsa、Q-learning、期望Sarsa与双Q学习 时序差分控制这块内容我前前后后翻过不少教材和论文也自己动手复现过好几轮。说实话第一次看Sarsa和Q-learning的更新公式时我觉得它们几乎一模一样不就是那个max操作的区别吗但真正跑过实验、调过参数、踩过坑之后才发现这一个max背后藏着一整套关于在线策略和离线策略的思维差异也直接决定了你的智能体在悬崖边上是掉下去还是绕过去。这篇内容我打算把Sarsa、Q-learning、期望Sarsa、双Q学习这几个时序差分控制算法从原理到实现到调参完整地捋一遍适合已经了解强化学习基本概念、想把这几个算法真正搞明白并用起来的读者。不管你是刚学完动态规划想过渡到无模型方法还是已经在跑实验但结果总不稳定应该都能从里面找到一些有用的东西。1. 从动态规划到无模型控制为什么需要时序差分1.1 动态规划的局限与蒙特卡洛的无奈动态规划方法在强化学习里算是教科书级的经典解法它的核心思路很清晰利用贝尔曼方程做迭代更新一步步逼近最优价值函数。但问题在于动态规划要求你完全知道环境模型——状态转移概率和奖励函数都得明明白白摆在那里。这在实际问题里几乎不可能你不可能提前知道机器人每走一步会滑到哪里也不可能预知股票下一秒的涨跌。蒙特卡洛方法倒是绕开了模型依赖它让智能体跑完一整个回合拿到实际回报之后再回头更新价值估计。这个思路很直观但代价也很大必须等到回合结束才能学习。如果回合特别长或者任务根本没有终止状态蒙特卡洛就完全没法用了。而且蒙特卡洛的方差很大因为它是用一整条轨迹的随机回报来估计价值不同回合之间的波动可能非常剧烈。时序差分方法正好卡在两者中间。它不需要环境模型也不需要等到回合结束而是用当前奖励加上下一状态的价值估计来构造更新目标。这个思路有点像边走边学每走一步就根据即时反馈修正一下自己的判断。TD方法结合了动态规划的自举思想和蒙特卡洛的无模型特性是强化学习里最核心的一类方法。1.2 时序差分误差到底在衡量什么时序差分学习的核心概念是TD误差公式写出来很简单δ_t r_{t1} γ * V(s_{t1}) - V(s_t)这个式子看起来平平无奇但它衡量的东西很关键当前对状态价值的估计和实际走了一步之后得到的更好估计之间的差距。如果TD误差是正的说明实际得到的回报比预期好应该提高这个状态的价值估计如果是负的说明预期过于乐观应该降低估计。你可以把TD误差理解成一种惊讶程度。智能体每走一步都会根据新信息调整自己的预期。这种逐步修正的机制让TD方法能够在线学习不需要等整个回合结束。而且TD误差在后续很多算法里都会反复出现比如Actor-Critic方法里的优势函数估计本质上就是TD误差的某种形式。1.3 从价值预测到控制时序差分控制的自然延伸前面说的TD方法主要是做价值预测也就是在给定策略下估计状态价值。但强化学习的终极目标通常是控制——找到最优策略。从预测到控制最自然的延伸就是广义策略迭代的框架先做策略评估再做策略改进反复交替。在时序差分控制里这个框架具体化为用TD方法估计动作价值函数Q(s,a)然后基于Q值来改进策略。这里有个关键选择是用当前的Q值直接构造贪心策略还是用某种带探索的策略来生成行为。这个选择直接引出了后面要讲的在线策略和离线策略两条路线。2. Sarsa在线策略的时序差分控制2.1 Sarsa的更新逻辑与命名由来Sarsa这个名字本身就是它更新过程的缩写State-Action-Reward-State-Action。它的更新公式是这样的Q(s_t, a_t) ← Q(s_t, a_t) α * [r_{t1} γ * Q(s_{t1}, a_{t1}) - Q(s_t, a_t)]注意看更新目标里的Q(s_{t1}, a_{t1})这里的a_{t1}是智能体在下一状态实际选择并执行的动作。也就是说Sarsa在更新当前状态动作对的价值时用的是下一步真实采取的动作所对应的Q值。这意味着Sarsa的更新依赖于当前正在执行的策略所以它是在线策略方法。这个特性带来一个很重要的后果Sarsa会评估并改进它正在执行的策略。如果你用的是ε-贪心策略来做探索那么Sarsa学到的Q值就是在这个ε-贪心策略下的动作价值而不是最优策略下的价值。它会把这个探索带来的风险也考虑进去。2.2 悬崖行走问题Sarsa的保守性从何而来悬崖行走是强化学习里一个非常经典的测试环境用来展示Sarsa和Q-learning的行为差异。环境是这样的一个网格世界起点在左下角终点在右下角中间有一排悬崖。掉下悬崖会得到很大的负奖励然后回到起点。Sarsa在这个环境里学到的策略通常是比较保守的——它会选择一条远离悬崖的安全路径。原因在于Sarsa的更新考虑了下一步实际采取的动作。如果智能体在悬崖边上由于ε-贪心探索的存在它有概率随机选到一个掉下悬崖的动作。Sarsa会把这个风险纳入Q值估计所以悬崖边上的状态动作对价值会被拉低智能体就会倾向于远离悬崖。这个行为其实很合理如果你知道自己的策略偶尔会犯错探索那你最好离危险远一点。Sarsa学到的策略是在考虑探索风险前提下的最优策略这在安全敏感的场景里反而更有价值。2.3 Sarsa的实现细节与收敛条件实现Sarsa的时候有几个细节需要特别注意。首先是Q表的初始化通常可以初始化为0或者小的随机值。如果初始化为0在奖励全为正的环境里智能体一开始会倾向于选择那些看起来没试过的动作因为未尝试的动作Q值为0而尝试过的负值动作会拉低估计。这其实是一种隐式的乐观初始化有助于早期探索。学习率α的选择很关键。太大容易震荡不收敛太小收敛速度慢得让人着急。实践中常用的做法是让α随时间衰减比如α_t 1/t或者更温和的衰减方式。折扣因子γ通常取0.9到0.99之间具体取决于任务的时间跨度。Sarsa的收敛条件在理论上要求所有状态动作对被无限次访问且学习率满足Robbins-Monro条件。实际中你不可能真的跑无限步所以通常是根据性能曲线来判断是否收敛。我的经验是看最近若干回合的平均回报是否趋于稳定同时策略的变化是否变小。import numpy as np def sarsa(env, num_episodes, alpha0.1, gamma0.99, epsilon0.1): Q np.zeros((env.n_states, env.n_actions)) for episode in range(num_episodes): state env.reset() action epsilon_greedy(Q, state, epsilon) done False while not done: next_state, reward, done env.step(action) next_action epsilon_greedy(Q, next_state, epsilon) td_target reward gamma * Q[next_state, next_action] td_error td_target - Q[state, action] Q[state, action] alpha * td_error state, action next_state, next_action return Q上面这段代码就是Sarsa的核心逻辑。注意在每一步里我们先选好下一步的动作然后用这个动作的Q值来更新当前状态动作对。这个先选后更的顺序是Sarsa的标志性特征。3. Q-learning离线策略控制的经典之作3.1 Q-learning的更新公式与max操作的含义Q-learning的更新公式和Sarsa只差一个地方Q(s_t, a_t) ← Q(s_t, a_t) α * [r_{t1} γ * max_a Q(s_{t1}, a) - Q(s_t, a_t)]Sarsa用的是Q(s_{t1}, a_{t1})而Q-learning用的是max_a Q(s_{t1}, a)。这个max操作意味着Q-learning在更新时假设下一步会采取当前认为最优的动作而不是实际采取的动作。这就使得Q-learning的更新目标与行为策略无关它直接逼近最优动作价值函数Q*。正因为更新不依赖于行为策略Q-learning可以在执行任意探索策略的同时学习最优策略。这就是离线策略学习的核心特征行为策略和目标策略可以不同。你可以用ε-贪心策略来收集数据但学到的Q值对应的是贪心策略。3.2 离线策略的优势与代价离线策略最大的优势是数据效率。你可以把旧策略收集的数据存起来反复利用也可以从人类演示或者其他智能体的经验中学习。这在很多实际场景里非常重要因为与环境交互的成本可能很高。但离线策略也有代价。首先是收敛性问题Q-learning在函数逼近的情况下并不保证收敛甚至可能发散。其次是max操作带来的过估计偏差这个问题在下一节会详细讲。另外离线策略方法对探索策略的选择更敏感如果行为策略覆盖的状态动作空间不够学到的Q值可能在某些区域完全不准。3.3 Q-learning在悬崖行走中的表现回到悬崖行走问题Q-learning学到的策略通常是最优策略——沿着悬崖边缘走最短路径。因为Q-learning的更新用的是max操作它不考虑探索带来的风险直接假设下一步会选最优动作。所以悬崖边上的状态在Q-learning看来价值很高因为从那里走最优路径可以最快到达终点。这个差异非常直观地展示了在线策略和离线策略的区别。Sarsa学的是在考虑探索风险下最优的策略Q-learning学的是假设完美执行下最优的策略。哪个更好取决于你的应用场景如果探索代价很高Sarsa的保守策略可能更合适如果探索代价低且你追求最优性能Q-learning可能更好。def q_learning(env, num_episodes, alpha0.1, gamma0.99, epsilon0.1): Q np.zeros((env.n_states, env.n_actions)) for episode in range(num_episodes): state env.reset() done False while not done: action epsilon_greedy(Q, state, epsilon) next_state, reward, done env.step(action) td_target reward gamma * np.max(Q[next_state]) td_error td_target - Q[state, action] Q[state, action] alpha * td_error state next_state return Q对比Sarsa的代码Q-learning在更新时不需要提前选好下一步的动作直接用max操作就行。这个差异虽然小但背后的学习逻辑完全不同。4. 期望Sarsa在Sarsa和Q-learning之间找平衡4.1 期望Sarsa的更新目标设计期望Sarsa的更新公式是这样的Q(s_t, a_t) ← Q(s_t, a_t) α * [r_{t1} γ * Σ_a π(a|s_{t1}) * Q(s_{t1}, a) - Q(s_t, a_t)]它把Sarsa里的Q(s_{t1}, a_{t1})替换成了在策略π下所有动作Q值的期望。这个设计很巧妙它保留了在线策略的特性因为期望是基于当前策略计算的但去掉了Sarsa中由于单步采样带来的方差。你可以这样理解Sarsa是用一次采样来估计下一步的价值期望Sarsa是用期望来估计。期望通常比单次采样更稳定所以期望Sarsa的方差比Sarsa小。同时由于它考虑了策略π下所有动作的可能性它也会像Sarsa一样把探索风险纳入考虑。4.2 期望Sarsa与Q-learning的偏差对比期望Sarsa和Q-learning的关键区别在于对下一步价值的估计方式。Q-learning用max期望Sarsa用期望。在ε-贪心策略下期望可以写成Σ_a π(a|s) * Q(s, a) (1-ε) * max_a Q(s, a) ε * mean_a Q(s, a)这个式子清楚地展示了期望Sarsa和Q-learning的关系当ε0时期望Sarsa退化为Q-learning当ε0时期望Sarsa的更新目标比Q-learning小因为它在max的基础上混入了平均值的成分。这意味着期望Sarsa通常比Q-learning更保守但比Sarsa更稳定。在实际实验中期望Sarsa往往能在悬崖行走这类任务上取得比Sarsa更好的性能同时比Q-learning更安全。4.3 期望Sarsa的计算效率问题期望Sarsa的一个潜在问题是计算开销。每次更新都需要对所有动作的Q值做加权求和如果动作空间很大这个开销就很可观。相比之下Sarsa只需要查一个Q值Q-learning只需要求一个max。不过在实际应用中如果动作空间是离散且有限的比如几十个动作这个开销通常可以接受。而且现在很多计算框架都能高效地做向量化操作期望的计算并不会成为瓶颈。如果动作空间真的很大可以考虑用采样来近似期望或者改用其他更适合大动作空间的方法。def expected_sarsa(env, num_episodes, alpha0.1, gamma0.99, epsilon0.1): Q np.zeros((env.n_states, env.n_actions)) for episode in range(num_episodes): state env.reset() done False while not done: action epsilon_greedy(Q, state, epsilon) next_state, reward, done env.step(action) # 计算期望值 n_actions env.n_actions probs np.ones(n_actions) * (epsilon / n_actions) probs[np.argmax(Q[next_state])] (1 - epsilon) expected_q np.sum(probs * Q[next_state]) td_target reward gamma * expected_q td_error td_target - Q[state, action] Q[state, action] alpha * td_error state next_state return Q这段代码里probs就是ε-贪心策略下各动作的选择概率expected_q就是期望Sarsa的更新目标。注意这里假设了ε-贪心策略如果你用的是其他探索策略需要相应调整概率分布。5. 双Q学习解决过估计偏差的利器5.1 max操作带来的过估计问题Q-learning里的max操作有一个隐蔽的陷阱它会导致对Q值的系统性高估。原因在于Q值本身是估计值带有噪声。当你对多个带噪声的估计取max时最大值往往会偏向那些被高估的估计。这个偏差在动作数量多、Q值噪声大的时候尤其严重。举个直观的例子假设真实Q值都是0但每个估计都有随机噪声。取max之后结果肯定是正的因为总有一个估计碰巧是正的。这就是max操作带来的乐观偏差。在强化学习里这种偏差会导致智能体过度乐观地估计某些状态的价值进而影响策略的质量。5.2 双估计器的解耦思路双Q学习的核心思想是用两个独立的Q值估计来解耦选择动作和评估价值这两个步骤。具体来说它维护两个Q表Q1和Q2每次更新时随机选一个来更新。更新Q1时用Q1来选择最优动作但用Q2来评估这个动作的价值Q1(s, a) ← Q1(s, a) α * [r γ * Q2(s, argmax_a Q1(s, a)) - Q1(s, a)]反过来更新Q2时也一样。这样做的妙处在于选择动作和评估价值用的是不同的估计器避免了同一个估计器的偏差被max操作放大。虽然单次更新仍然可能有偏差但两个估计器的偏差方向不同平均下来就能抵消掉大部分过估计。5.3 双Q学习的实现变体与实测效果双Q学习有两种常见的实现方式。一种是维护两个独立的Q表每次随机选一个更新这就是原始的双Q学习。另一种是双Q学习的简化版本只维护一个Q表但用两个不同的索引来分别做选择和评估实现更简单但效果稍逊。在实际测试中双Q学习在大多数任务上都能显著减轻过估计问题尤其是在动作空间较大的环境里。不过它也有代价需要维护两个Q表内存开销翻倍每次更新需要做两次前向计算计算开销也增加。在状态空间很大的问题里这个开销可能成为瓶颈。我的经验是如果动作空间不大比如少于20个双Q学习带来的收益通常值得额外的开销。如果动作空间很大可以考虑用双Q学习的简化版本或者结合其他技术来控制过估计。def double_q_learning(env, num_episodes, alpha0.1, gamma0.99, epsilon0.1): Q1 np.zeros((env.n_states, env.n_actions)) Q2 np.zeros((env.n_states, env.n_actions)) for episode in range(num_episodes): state env.reset() done False while not done: action epsilon_greedy(Q1 Q2, state, epsilon) next_state, reward, done env.step(action) if np.random.rand() 0.5: best_action np.argmax(Q1[next_state]) td_target reward gamma * Q2[next_state, best_action] td_error td_target - Q1[state, action] Q1[state, action] alpha * td_error else: best_action np.argmax(Q2[next_state]) td_target reward gamma * Q1[next_state, best_action] td_error td_target - Q2[state, action] Q2[state, action] alpha * td_error state next_state return Q1 Q2这段代码展示了双Q学习的标准实现。注意在选择动作时我们用的是Q1Q2的和来做ε-贪心这样两个估计器的信息都能用上。更新时随机选一个Q表用另一个来评估这就是双估计器的核心逻辑。6. 算法选型与调参实战经验6.1 四种算法的适用场景对比把这四个算法放在一起对比选型的时候可以从几个维度考虑算法策略类型过估计倾向方差计算开销适用场景Sarsa在线策略低高低安全敏感、探索代价高Q-learning离线策略高中低追求最优、可离线学习期望Sarsa在线策略低中中需要稳定性、动作空间小双Q学习离线策略很低中高动作空间大、过估计严重Sarsa适合那些探索行为可能带来严重后果的场景比如机器人控制、医疗决策等。Q-learning适合可以大量试错、追求最优性能的场景。期望Sarsa是Sarsa的稳定版本在需要在线策略但又不希望方差太大的时候很合适。双Q学习则是Q-learning的修正版在过估计问题严重时值得一试。6.2 学习率和探索率的调参心得学习率α和探索率ε是两个最关键的参数。α控制每次更新的步长ε控制探索的程度。我的经验是α通常从0.1开始试如果学习曲线震荡厉害就降到0.05或0.01如果收敛太慢就适当提高。对于确定性环境α可以取大一点对于随机性强的环境α要取小一点因为单次更新的信息量更少。ε通常从0.1到0.3开始然后随时间衰减。常见的衰减策略是ε_t max(ε_min, ε_0 * decay^t)。ε_min一般取0.01到0.05保证后期还有少量探索。衰减速度要跟任务难度匹配太难的任务衰减慢一点简单的任务可以快一点。还有一个容易被忽略的点α和ε是耦合的。ε大意味着数据噪声大这时候α应该小一点来平滑更新ε小意味着数据质量高α可以大一点来加快学习。调参的时候最好固定一个调另一个不要同时动。6.3 常见实现陷阱与调试方法实现这些算法时有几个坑我踩过不止一次第一个坑是Q表初始化。如果初始化为0在奖励全负的环境里智能体会倾向于选择未尝试的动作因为0比负数好这其实是一种乐观初始化有助于探索。但如果奖励全正初始化为0会导致智能体不愿意尝试新动作因为已尝试的正值动作看起来更好。这种情况下可以考虑用小的正值初始化。第二个坑是终止状态的更新。在终止状态没有下一步所以TD目标就是即时奖励不应该加上γ乘以任何Q值。这个细节如果搞错会导致终止状态附近的Q值完全不对。第三个坑是ε-贪心策略的实现。当多个动作的Q值相同时argmax应该随机选择一个而不是总是选第一个。否则智能体会系统性地偏好某些动作影响学习效果。调试的时候我通常先在一个很小的环境比如5x5的网格上跑确认算法能收敛到最优策略。如果小环境都跑不通那肯定是实现有问题。然后再逐步扩大环境规模观察性能变化。另外把每回合的累计奖励和TD误差画出来能很直观地看到学习过程是否正常。7. 从表格方法到函数逼近的过渡思考7.1 表格方法的规模瓶颈前面讨论的都是表格方法也就是用一张表来存每个状态动作对的Q值。这在状态空间小的时候没问题但状态空间一大就完全不可行了。比如图像输入的状态空间是连续的、高维的你不可能为每个可能的图像存一个Q值。这个瓶颈促使我们转向函数逼近方法用参数化的函数比如神经网络来近似Q值。但函数逼近会带来新的问题收敛性不再有保证过估计问题可能更严重训练也可能不稳定。这些挑战催生了深度Q网络等一系列方法。7.2 时序差分控制思想在深度强化学习中的延续虽然具体的算法形式变了但时序差分控制的核心思想在深度强化学习里依然适用。DQN本质上就是Q-learning加上神经网络函数逼近和经验回放。Double DQN就是双Q学习在深度框架下的版本。Expected Sarsa的思想也被用在了一些策略梯度方法里。理解表格方法下的这些算法对理解深度强化学习的对应方法非常有帮助。因为深度方法只是在表格方法的基础上加了函数逼近和训练技巧核心的更新逻辑和策略改进思路是一脉相承的。7.3 后续学习路径建议如果你已经把这几