news 2026/10/22 4:09:02

时序差分方法详解:从TD到Q-learning与资格迹

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
时序差分方法详解:从TD到Q-learning与资格迹

这一篇我们啃一下强化学习里最核心的一块硬骨头——时序差分方法(Temporal Difference,TD)。如果你已经看过前面的MDP、动态规划、蒙特卡洛,就会发现TD刚好站在“需要完整模型”和“需要跑完一条轨迹”中间,既不需要环境转移概率,也不用等回合结束就能更新价值估计。很多入门的朋友在这儿容易卡住,因为TD的更新公式看起来只是“把MC的G_t换掉”,但底层的思想转变非常大。我尽量用送外卖的例子、手推的公式、能跑的Python代码,把TD方法、Sarsa、Q-learning、TD(λ)一次性讲透,适合已经会写MDP和策略迭代、但还没搞懂TD为什么工作的人。

1. 时序差分方法到底解决了什么问题

1.1 前两代方法的短板

我们先把视野拉回强化学习的核心任务:估计一个策略的价值函数。动态规划(DP)的做法是查表式的自举(bootstrap):用当前的价值估计去更新另一个状态的价值估计。它最大的问题是,更新的时候需要知道环境的转移概率P(s'|s,a)和期望奖励,这在很多真实的场景里根本拿不到——你不可能让一个机器人先在仿真器里把所有状态转移概率全部算一遍再行动。蒙特卡洛(MC)换了思路:我不需要模型,直接让智能体跟环境互动,跑到回合结束,拿到真实回报G_t,然后一次性更新整个轨迹上的状态价值。这个方法虽然摆脱了模型依赖,但代价是方差很大,而且必须等到回合结束才能更新,碰到“一盘棋要下几个小时”的任务就特别痛苦。

这两代方法就像两种学习方式。DP像考前先背标准答案,但你必须有一本完整的答案书;MC像做完一整张模拟卷再对答案,反馈慢,而且每份卷子答案波动大。

TD方法做的事就是“边做题边对答案”。它既像MC一样通过与真实环境的交互获取采样数据,又像DP一样通过已有的价值估计来构造更新目标。TD只往前走一步:用当前状态S_t、采取动作后拿到的奖励R_{t+1},以及下一个状态的估计价值V(S_{t+1}),来更新V(S_t)。不完整的一步奖励带来的是低方差,自举带来的是高效率。

我做个生活化类比。你在估算外卖送餐时间,一开始听到订单预估30分钟。骑手出门后,你发现它过了5分钟才到取餐点,这时候你会立刻修正“这单估计要超时”,而不是非要等餐送到客户手里才反思。TD也是这种思路——每走一步,就根据最新的局部信息调整价值判断。这个“局部信息”就是下一步的真实奖励R_{t+1}加上对下一状态的已有估计。

1.2 TD怎么平衡偏差和方差

很多教程喜欢画一张表,把DP、MC、TD放在一起对比,但它们之间最本质的区别不是公式长得不一样,而是更新目标里的“随机变量”有多少。DP的更新目标是期望形式: V(S_t) ← Σ_a π(a|S_t) Σ_{s',r} P(s',r|S_t,a) [r + γV(s')] 它里面没有随机变量,是一个纯期望计算,所以无偏但依赖模型。

MC的更新目标是G_t,这是从S_t开始一直到回合结束的折扣总回报。G_t包含了后面很多步的真实奖励,所以它是V(S_t)的无偏估计,但是因为路径很长,累积了许多随机性,方差特别大。

TD的更新目标是R_{t+1} + γV(S_{t+1})。这里面R_{t+1}是真实的下一步奖励,但V(S_{t+1})是估计值。如果初始时候V估计得不准,那么TD目标就会偏离真实值,这就是偏差的来源;但因为只引入了一个随机变量R_{t+1},方差就比MC低得多。

打个比方,MC像用一整年的花销记录来估算你每个月的平均开销,数字准确但记录过程很累,且某个月特殊收入会带来很大的波动;TD像用“这个月实际花销+你对下个月的存量估计”来调整预算,灵活但一开始可能偏差很大,随着估计不断修正会越来越准。理解了这个偏差-方差权衡,后面看Sarsa和Q-learning的差异就会顺很多。

2. 手推TD(0)更新公式:从一个网格例子的完整计算说起

2.1 从一步更新到TD误差

先看标准TD(0)的价值更新: V(S_t) ← V(S_t) + α [R_{t+1} + γV(S_{t+1}) − V(S_t)]

方括号里的东西叫TD误差,记作δ_t。它表示“我实际得到的一步信息”和“我之前对这个状态的价值估计”之间的差异。如果δ_t为正,说明这一步的反馈比预期好,应该上调V(S_t);为负则下调。

很多人会问:为什么不是直接令V(S_t)=R_{t+1}+γV(S_{t+1}),而是加一个α只移动一小步?因为R_{t+1} + γV(S_{t+1})本身只是带噪声的估计,如果一次性跳过去,方差会很大。步子迈小一点,相当于做了平滑,兼顾了更新速度和稳定性。

我用手推一个小例子。假设一个只有4个状态的简单环境,状态编号1到4,状态4是终止态。策略固定,每一步都从当前状态推进到下一个状态。γ=0.9,α=0.1。初始时所有V都是0。假设从状态1出发,观察到转移是1→2,且R=1。

此时δ_0 = R + γV(2) − V(1) = 1 + 0.9×0 − 0 = 1。 V(1)更新为:0 + 0.1×1 = 0.1。

继续走,状态2→3,R=−1。 V(2) = 0 + 0.1×(−1 + 0.9×0 − 0) = −0.1。

状态3→4,R=5,状态4是终止态,V(4)=0。 V(3) = 0 + 0.1×(5 + 0 − 0) = 0.5。

这个例子虽然简单,但暴露了TD的一个关键性质:V(1)的更新只依赖V(2),V(2)又依赖V(3),误差会一步步向后传播。正是因为这种向前推进式的更新,TD可以做到“回合未结束也能学习”,而不是像MC那样必须等G_t算完。

2.2 为什么这个简单公式能收敛到真实价值

如果只做一次完整回合,TD(0)并没有直接看到未来所有奖励,为什么它能逼近真实值?答案是持续的学习过程。TD(0)的收敛性依赖一个有趣的性质:它最终学到的价值函数满足不动点方程,而这个不动点正是策略的真实价值函数。每访问一个状态,我们都在做“用一步真实反馈修正当前估计”这件事。随着访问次数增加,估计会越来越接近真实值。若满足标准随机近似条件(学习率α满足Robbins-Monro条件,即α_t衰减、且Σα_t=∞、Σα_t²<∞),TD(0)几乎必然收敛到真实价值函数。

与其去看严谨的数学证明,不如关注背后的直觉:TD把MC的“完成一条轨迹后回顾总结”改成了“每走一步就修正一次”。一条轨迹里可能访问了100个状态,MC只能更新这100个状态的估计一次;TD可以走一步就更新一次,同一个回合内反复利用信息。数据效率上TD天然更优,这也是它在工程中被广泛使用的原因。

实操里我建议新手自己写一次这个小网格的随机游走,把TD(0)和MC的更新轨迹打出来对比。你会发现TD(0)的学习曲线更平滑,但初期会有一点偏差;MC则跳得厉害,特别是奖励稀疏的时候,初始阶段几乎像噪声。

3. Sarsa、Q-learning、期望Sarsa:TD如何从状态价值扩展到动作价值

3.1 同策略与离策略

光学会状态价值的TD(0),其实还解决不了控制问题。因为控制需要确定在每个状态选哪个动作,而动作选择依赖Q值(动作价值)。把TD思想从V扩展到Q并不难,关键在于更新Q(S,A)的时候,目标里该用哪个“下一个动作的Q值”。

Sarsa的核心更新公式: Q(S_t, A_t) ← Q(S_t, A_t) + α [R_{t+1} + γ Q(S_{t+1}, A_{t+1}) − Q(S_t, A_t)]

注意这里必须知道A_{t+1},也就是下一个时刻实际采取的动作。这意味着Sarsa用“当前策略真正做出的选择”来更新自己,属于同策略(on-policy)方法。举个例子,如果你用ε-greedy策略选动作,那Sarsa在更新Q(S_t,A_t)的目标里也“默认”下一步还会继续按ε-greedy来选A_{t+1}。这带来的好处是理论上的收敛性很干净,坏处是学习到的最优策略会受到探索程度的影响——ε越大,最终收敛的Q值会越“保守”,因为目标里掺入了探索带来的次优行为。

Q-learning则是离策略(off-policy)的典型。更新公式: Q(S_t, A_t) ← Q(S_t, A_t) + α [R_{t+1} + γ max_a Q(S_{t+1}, a) − Q(S_t, A_t)]

它不管下一步策略实际选了哪个动作,更新目标里一律取下一个状态的最大Q值。这意味着Q-learning学习的潜在策略是“始终贪心”的目标策略,即使当前行为策略还在做探索,它仍然能逐步逼近最优策略。离策略的优点很明显,解耦了探索策略与目标策略;缺点也很微妙,max操作会带来过估计问题。

特别提醒一下,很多人觉得“Q-learning离线评估,Sarsa在线评估”,这个说法不准确。Q-learning是离策略,不是说它不与环境交互,而是指它更新目标里用的是贪心动作,不是真实采集到的动作。我在实际项目里见过不少新手因为这个误解,觉得Q-learning只能离线用,其实完全不是这样。

3.2 一个四行代码级别的实现对比

把三种TD控制算法放到同一个网格世界对比一下更有说服力。我们用经典的4×4网格,起点在左下角,终点在右上角,每走一步奖励−0.1,到达终点奖励10。动作为上下左右,碰到墙壁原地不动。我用Python写一个简版。

import numpy as np class GridWorld: def __init__(self, size=4): self.size = size self.start = (0, 0) self.goal = (size - 1, size - 1) self.actions = [(0, 1), (0, -1), (1, 0), (-1, 0)] self.cur = self.start def reset(self): self.cur = self.start return self.cur def step(self, a): x, y = self.cur dx, dy = self.actions[a] nx, ny = x + dx, y + dy if 0 <= nx < self.size and 0 <= ny < self.size: self.cur = (nx, ny) else: self.cur = (x, y) done = (self.cur == self.goal) reward = 10 if done else -0.1 return self.cur, reward, done def td_control(update_type='q', episodes=500, alpha=0.1, gamma=0.99, eps=0.1): env = GridWorld() Q = np.zeros((env.size, env.size, 4)) for ep in range(episodes): s = env.reset() a = np.random.choice(4, p=[eps, (1-eps)/3, (1-eps)/3, (1-eps)/3]) # 简化 done = False while not done: s2, r, done = env.step(a) if update_type == 'q': a2 = np.argmax(Q[s2[0], s2[1]]) target = r + gamma * Q[s2[0], s2[1], a2] elif update_type == 'sarsa': if np.random.rand() < eps: a2 = np.random.choice(4) else: a2 = np.argmax(Q[s2[0], s2[1]]) target = r + gamma * Q[s2[0], s2[1], a2] elif update_type == 'expected_sarsa': # 期望Sarsa用当前策略的期望值 target = r + gamma * (eps * Q[s2[0], s2[1], :].mean() + (1 - eps) * Q[s2[0], s2[1], :].max()) Q[s[0], s[1], a] += alpha * (target - Q[s[0], s[1], a]) s = s2 if update_type != 'q': a = a2 return Q

这段代码里我特意用了简化的探索策略,实际写的时候建议把ε-greedy封装成独立函数。从实验效果看,Q-learning在这个小网格里收敛最快,Sarsa的最终策略会更保守(因为目标里包含探索噪声),期望Sarsa则介于两者之间,但方差更小。期望Sarsa的更新: Q(S_t, A_t) ← Q(S_t, A_t) + α [R_{t+1} + γ Σ_a π(a|S_{t+1}) Q(S_{t+1}, a) − Q(S_t, A_t)]

它跟Sarsa最大的区别是,更新目标不用采样A_{t+1},而是对下一步所有动作的Q值按策略概率求期望。这相当于去掉了Sarsa目标里的随机性,方差更低。

如果你刚开始学这几个算法,我建议不要死记公式,而是从“更新目标里有没有随机性”这个角度去区分:Q-learning目标里的max是确定性的,期望Sarsa目标里的期望是确定性的,Sarsa目标里的Q(S_{t+1}, A_{t+1})是随机的(因为A_{t+1}是采样的)。哪个公式的随机性低,哪个方差就小。

4. 前向视角与后向视角:TD(λ)和资格迹

4.1 λ到底在调和什么

TD(0)只往前走一步,MC要走完整回合,很多人自然想到:能不能往前走两步、三步,然后取一个组合?这就是TD(λ)的前向视角。定义n步回报: G_t^{(n)} = R_{t+1} + γR_{t+2} + γ²R_{t+3} + ... + γ^{n−1}R_{t+n} + γ^nV(S_{t+n})

当n=1时就是TD目标,当n→∞时就是MC的G_t。TD(λ)把所有这些n步回报用几何权重组合起来: G_t^λ = (1−λ) Σ_{n=1}^∞ λ^{n−1} G_t^{(n)}

λ的取值范围是[0,1]。λ=0时只剩一步目标,退化成TD(0);λ越大,越看重更多步的真实奖励,越接近MC。λ的引入让你能连续地在“低方差高偏差”和“高方差低偏差”之间滑动。

用实际场景体会:假设你在走迷宫,每个岔路口给一个中间奖励。TD(0)只看到一步奖励,所以如果奖励藏在很深的岔路后面,它学得很慢;MC虽然能看到终点奖励,但方差大,一条路径如果偶尔绕远路,会误导后续更新;TD(λ)给了中间奖励和远期奖励一个折中的比重,既让远期奖励逐步渗透进来,又不至于被单次路径的随机波动带偏。

4.2 资格迹的后向实现

前向视角公式看着优雅,但工程上很难实现。因为G_t^λ依赖未来很多步的奖励,在实际线学习里不可能每走一步就知道未来的G_t^{(n)}。聪明的做法是引入资格迹(eligibility trace),用后向视角实现相同的效果。

资格迹的核心思想是:每个状态维护一个“痕迹值”e_t(s),它记录了“这个状态最近被访问了多少次,以及离现在有多近”。每走一步,所有状态的痕迹先按γλ衰减,同时当前状态的痕迹加1。更新的时候,所有状态的价值都按各自痕迹值成比例调整: V(s) ← V(s) + α δ_t e_t(s)

其中δ_t = R_{t+1} + γV(S_{t+1}) − V(S_t)是当前TD误差。

为什么这个简单的机制能实现λ加权组合?我试着说人话:资格迹是一种“记性”机制。如果某个状态A在5步之前被访问过,现在状态B产生了一个TD误差δ,A虽然不能直接受益于δ,但因为它留下了痕迹(还没衰减到0),它也会被稍微更新一下。这样就相当于把“从A到B这一段收到的奖励”通过痕迹扩散回A的价值估计里。λ控制的就是这个“记忆”的衰减速度:λ大,痕迹存得久,过去很多步的状态都会因为当前误差而更新,更像MC;λ小,痕迹很快就消失了,只有最近几步的状态会被更新,更像TD(0)。

工程实现上要注意一个细节:资格迹对每个状态都要维护一个计数器,所以状态空间很大时内存开销会翻倍。另一点是,如果你在同一个训练回合里做多个更新,痕迹不会被清空,但回合结束后必须把e_t(s)清零,否则下一个回合的痕迹会叠加到错误的路径上。我在第一次写这个的时候忘了清空,结果训练曲线歪得离谱,排查了半天才发现是痕迹没有重置。

很多新学者不明白为什么学TD还要搞资格迹,我的看法是:如果你只做小型表格型任务,TD(0)已经完全够用,资格迹的收益不明显;但如果你做的是机器人控制、游戏AI这类奖励稀疏的长程任务,一个合适的λ能把学习速度提升一大截,同时还不用承受MC的方差。这就是工程上为什么很多实际系统喜欢λ取0.9到0.99之间的原因。

5. 实际训练中的坑与调参心得

5.1 发散陷阱:三大要素齐聚的后果

TD类方法看起来简单,实际上匹配问题相当常见。最常见的坑是“死性三元组”导致的发散:函数近似(比如用神经网络拟合Q值)、离策略更新(比如Q-learning用max取目标)、自举(TD目标里用了V(S_{t+1})),三个要素只要同时出现,训练就容易不稳定甚至发散。倒不是说这三个条件单独出现就一定会发散,但它们凑齐时理论上的收敛保证就很脆弱。如果你用DQN训练时发现loss爆炸,八成是这个问题。

在这个问题上的应对经验是三选一:要么把自举去掉(改为MC目标,但这样方差大),要么让策略更接近在线(降低探索率),要么用更平滑的目标网络。DQN里的目标网络本质上就是为了缓解离策略和自举带来的不稳定性。表格型的Q-learning在简单环境里不会轻易发散,但这不代表你可以忽视目标网络的价值。

5.2 学习率α和折扣因子γ应该如何配合调整

很多人把α固定成0.1就开始训练,运气好可能没问题,但任务一复杂就会出问题。α过大,更新目标中的噪声会被放大,价值函数可能出现周期性的振荡;α过小,学习速度太慢。我的经验是:如果TD误差的绝对值在某几个量级之间来回跳动,通常说明α偏大;如果曲线非常平滑但收敛很慢,试着把α乘10倍,往往能明显加速。

γ的选择也有讲究。γ越接近1,智能体越“高瞻远瞩”,但对远期奖励的估计方差也越大。γ接近0,智能体只顾眼前,很可能学不到需要多步迂回才能拿到的奖励。一个常见调参顺序是:先把γ定在0.95或0.99,去调α,让时序差分误差曲线稳定下降;然后再回头微调γ,看策略是否变得更“有远见”。

奖励的尺度也很重要。我见过有人把奖励设计成1000分制,导致TD误差动不动就是几十甚至几百,学习率只能用极小的值,训练极慢。把奖励做归一化到−1到1之间,或者按数量级把α调小,能让学习过程平滑很多。

5.3 我自己写TD方法时踩过的三个反复出现的坑

第一个坑:Sarsa的目标里用错了A_{t+1}。这里必须强调的是,Sarsa的A_{t+1}应该是在状态S_{t+1}时按同一条策略重新采样出来的动作,不是直接沿用之前的动作A_t。我见过有人把“下一步的动作”当成了“当前动作”,结果训练曲线变成一条毫无意义的横线。

第二个坑:Q-learning更新的时候,目标里的max取的是下一个状态的所有动作,但很多误以为要对当前状态的所有动作取max。这会导致所谓的“次优解陷阱”:智能体在状态S_t选择了动作A_t,却用当前状态的最优Q值去替换Q(S_t,A_t),完全绕过了对真实动作价值的修正。

第三个坑:网格世界里“碰到墙壁原地不动”这个规则,如果实现错了,会导致状态的访问频率严重偏斜,进而让价值估计失真。我建议在step函数里打印出每次转移的四元组(S, A, R, S'),肉眼检查前几十条,比直接看最终曲线靠谱得多。

5.4 一个实用的经验:怎么判断TD训练结果是否真的正常

最直观的验证方式不是看累计奖励曲线,而是把学出来的Q函数可视化。对于表格型环境,直接把Q表打印成热力图,观察“哪些状态的价值高、哪些低”,是否符合你对环境的直觉。对于连续状态空间,可以固定某个状态,画出不同动作的Q值随时间的变化,看它是否趋于稳定。

还有一个容易被忽略的点:如果你用TD方法只做价值评估,不接控制,那可以取策略π下的Q值去算TD误差的平均值和标准差。平均误差趋近0说明估计基本收敛,标准差大说明某些状态还没被充分探索。这个指标很轻量,适合训练中定期打印监控。

我实际调试过程中发现,很多“看起来训练失败”的案例,其实只是学习率太大加奖励尺度不匹配的结合体。先固定随机种子,把环境奖励统一缩放到小范围,再用一个小网格把TD(0)跑通,再逐步换到复杂环境,这样的渐进式调试思路会省掉很多头皮发麻的排查时间。

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

ARM架构AI芯片如何破局算力困局:此芯科技异构计算路径解析

1. 算力格局的另一条路&#xff1a;为什么值得关注聊到AI算力&#xff0c;大多数人第一反应就是那家做GPU的巨头。训练大模型要用它的卡&#xff0c;推理部署也要用它的卡&#xff0c;仿佛整个AI基础设施只有一条路可走。但实际情况是&#xff0c;算力需求正在剧烈分化——不是…

作者头像 李华
网站建设 2026/10/22 4:07:48

微信小程序+SSM+MySQL设备报修系统:从部署到改造的毕设全解析

简介&#xff1a;这套设备故障报修小程序毕设资源&#xff0c;面向计算机专业毕业生与微信小程序开发者&#xff0c;解决设备报修管理系统的设计与实现。项目基于微信小程序SSMMySql开发&#xff0c;设管理员、用户、维修员三个角色&#xff0c;覆盖报修提交、维修报告、经验分…

作者头像 李华
网站建设 2026/10/22 4:06:57

基于Python与CNN卷积神经网络的会飞昆虫识别

1. 毕业设计选题&#xff1a;从需求到可行性每年到毕设季&#xff0c;总有同学在选题上纠结半天。有的题目太宽泛&#xff0c;做起来没有边界&#xff1b;有的题目太偏门&#xff0c;查资料都费劲。这次分享的这个案例——基于Python与CNN卷积神经网络的会飞的昆虫识别&#xf…

作者头像 李华
网站建设 2026/10/22 4:06:55

微信小程序+SSM+MySQL设备报修系统:毕业设计部署与答辩全攻略

简介&#xff1a;一套基于微信小程序与SSM架构的设备故障报修毕业设计项目&#xff0c;面向计算机相关专业毕业生或需要快速搭建完整管理系统的开发者。项目包含用户、维修员、管理员三个角色&#xff0c;覆盖实验室管理、报修提交、维修报告、经验分享等核心流程&#xff0c;后…

作者头像 李华
网站建设 2026/10/22 4:06:05

基于SpringBoot+Vue的大学新生报到系统设计与实践

1. 项目定位与技术选型解析大学新生报到&#xff0c;听起来就是一个“填表、缴费、领钥匙”的流程&#xff0c;但真正做过这个系统的人才知道&#xff0c;它背后牵扯的业务远比你想象的多。报到环节涉及学生信息核验、缴费状态确认、宿舍分配、入学资格审核、军训物资领取、绿色…

作者头像 李华
网站建设 2026/10/22 4:06:05

SpringBoot+Vue房屋交易系统:前后端分离毕设全栈实现与避坑指南

拿到这套 SpringBootVue 的房屋交易系统完整源码时&#xff0c;我第一反应不是急着部署&#xff0c;而是先花半小时把目录结构和核心数据表捋了一遍。这类项目在Java Web毕设里太常见了&#xff0c;但“常见”不代表“简单”&#xff0c;真正把它从能跑通的Demo做成一个经得起答…

作者头像 李华