很多人学到“强化学习(二)”这一步,往往是从各种花里胡哨的演示视频转回到实际动手的分界点。上一讲基本把马尔可夫决策过程、回报、策略、价值函数这些“骨架”搭明白了,这一讲要解决的核心问题就一个字:怎么把“值”算出来,以及怎么根据这个值改进策略。这里涉及三块内容——动态规划、蒙特卡洛方法、时序差分学习,它们是整个表格型强化学习里最核心的三种求解思路,后面无论接触到深度强化学习还是策略梯度,底层逻辑都绕不开这三套东西。
这一讲适合所有想真正把强化学习跑起来的初学者,尤其是那些已经被数学符号劝退过、但还想再试一次的人。我会抛开教科书式的推导,用一套简单的格子世界例子贯穿全文,把每次迭代到底在算啥、为什么这么算、实际调试时哪里容易翻车,一次说清楚。
1. 动态规划:先把“已知环境”这碗饭端稳
1.1 为什么第二讲要先讲动态规划
强化学习问题本质上是一个序列决策问题:智能体在每个状态下选择一个动作,环境返回下一个状态和奖励,然后不断循环。如果这个环境的动力学模型——也就是状态转移概率 (P(s'|s,a)) 和奖励函数 (R(s,a,s'))——完全已知,这个问题就退化成传统的动态规划问题,不需要“试错”,只需要“规划”。
很多人觉得动态规划是过时内容,反正现实中很难拿到精确环境模型。但我不这么看。动态规划是理解所有后续方法的基础,因为它把“贝尔曼方程可以迭代求解”这件事完完整整地演示了一遍,而且它引入了强化学习里一个极重要的抽象概念:广义策略迭代(Generalized Policy Iteration, GPI)。蒙特卡洛和时序差分方法,本质上都是用不同方式去执行GPI里的“策略评估”那一步。动态规划里的很多直觉,比如“先评估、再改进、循环反复”,可以平滑迁移到无模型方法,只是评估用的数据来源从“查模型”变成了“采样”。
打个比方:动态规划像你有一张完整的等高线地图,标好了每一处海拔。你要找山顶,顺着梯度走就行;蒙特卡洛方法则是蒙着眼睛走,每走一段摸一下地面感受高度变化;时序差分方法比蒙特卡洛更抠门,走一步就立刻停下来掂量一下这个方向对不对。三种方式各有各的适用场景,但都得先学会看“梯度”。
1.2 策略评估:把每个状态的“身价”算明白
动态规划的第一步是策略评估。给定一个策略 (\pi),我们希望算出每个状态的价值 (v_\pi(s))。这里用到的就是贝尔曼期望方程:
[ v_\pi(s) = \sum_{a} \pi(a|s) \sum_{s', r} p(s', r | s, a) \left[ r + \gamma v_\pi(s') \right] ]
简单说:当前状态的价值等于“立即能拿到的奖励 + 折扣后的下一状态价值”的加权平均。问题是这是一个包含未知数 (v_\pi(s')) 的方程组,状态多的时候解起来麻烦。动态规划给出的解法很朴素:先给每个状态一个初始价值(比如全0),然后用上式逐步迭代更新,直到收敛。
我用一个4×4的网格世界来说明。假设有一个5×5的网格,起点在左上角,终点在右下角。智能体每一步能执行上下左右四个动作,如果动作导致撞墙,就停在原地并且获得-1的奖励;成功到达终点奖励为0且过程终止;其他所有普通移动奖励也是-1。折扣因子 (\gamma=1),策略采用均匀随机策略,每个动作概率都是0.25。
第一次迭代的时候,所有状态的价值都是0。更新公式展开后,你会发现所有状态的新价值都等于它周围状态奖励的平均值,也就是-1。经过足够多次迭代后,靠近终点的状态价值会明显高于远离终点的状态价值,因为从靠近终点的地方出发,撞墙挨打的次数少、走几步就结束了。
一个关键细节是迭代公式里的同步更新。实际写代码时,我会准备两个数组,一个保存旧价值,一个保存新价值。如果你用原地更新,那么同一轮迭代里,前面状态的新价值会被后面状态当作旧价值使用,虽然结果通常也能收敛,但收敛速率和最终行为会和你以为的公式不一致,调试时会觉得很诡异。建议所有DP实现都保持“双缓冲”的更新方式,少踩一种坑,多一分确定性。
策略评估的收敛判定,我一般不看价值差值的绝对值,而是看最大变化量(max delta)小于某个阈值,比如 (10^{-4})。差值小说明当前策略的价值估算已经稳定,这时再进入策略改进阶段才有意义。
1.3 策略改进与策略迭代:从“估值选手”到“决策选手”
算出了价值函数,接下来的问题是怎么改进策略。拿均匀随机策略来说,你在网格世界里按照上下左右四个方向等概率乱走,每个状态的价值都很低,因为效率太差。改进的思路非常直观:既然价值函数已经告诉我们“从某个状态出发,按照当前策略能拿多少分”,那我们在那个状态下干脆选择一个能让“立即奖励 + 下一状态价值”最大的动作。这个“只看一步”的贪心改进,实际上就是策略改进定理的应用。
把策略评估和策略改进交替进行:先用当前策略算价值,算稳了之后贪心选动作形成新策略,然后再对新策略重新算价值,再改进。这个循环就是策略迭代。每次改进之后策略会严格变好,最终收敛到最优策略。
这里有一个特别值得说的洞察:策略评估真的需要完全收敛吗?不需要。你只跑一轮价值迭代,价值估计虽然不精确,但已经有了正确的大致方向,此时立即做一次贪心策略改进,效果往往也不错。这就是价值迭代的来源。它是一种特殊形式的GPI,策略评估被压缩成“只更新一步”,然后立刻进行策略改进,简化后的更新式就是贝尔曼最优方程:
[ v_{k+1}(s) = \max_a \sum_{s', r} p(s', r | s, a) \left[ r + \gamma v_k(s') \right] ]
直接取最大而不是求平均,是价值迭代和策略评估的唯一区别,但这个区别带来收敛速度的显著提升。实际项目里,我很少做完全的策略迭代,因为完整的策略评估实在是太耗时了,价值迭代是性价比最高的选择。
需要注意的坑是,价值迭代的中间价值函数并不对应任何真实策略下的价值函数。它只对应“这个状态下,假如未来每一步都按贪心走,能得到的价值”这个近似值。所以不要拿价值迭代跑到一半的价值函数去解释策略好坏,要看策略本身的变化。
2. 蒙特卡洛方法:没有环境模型,就靠真实“下注”来估
2.1 无模型问题的困境与蒙特卡洛的出路
动态规划最大的限制是必须知道环境模型。可是很多真实场景里,你根本拿不到精确的转移概率和奖励函数。比如一个机器人打扫房间,你很难提前建一张表,写明“从柜子旁边走到沙发旁边,有0.3的概率滑倒,掉1分”这种概率。你能做的,就是让机器人真的去走、真的去撞、真的去试,然后看结果。
蒙特卡洛方法的核心就是用大量真实轨迹(episode)的累积回报来估计价值函数。既然价值函数的定义是“从状态s出发,按照策略\pi行动,能获得的期望折扣回报”,那我们就把从状态s出发的每一次完整轨迹都记录下实际回报,然后求平均。样本量足够大时,这个平均回报必然趋近于期望值。
相比动态规划,蒙特卡洛方法有两个显著优势:第一,它不需要环境模型,只需要能够产生轨迹;第二,它估计某个状态的价值时,完全不依赖其他状态的估计值,是“独立”估计。这就避免了动态规划里可能出现的误差传播问题。代价是方差很大,需要跑很多轮才能把均值拉稳。
2.2 首次访问与每次访问:蒙特卡洛的一处细节坑
按“从状态s出发的实际回报取平均”这个朴素思路实现时,很快会遇到一个问题:一条轨迹可能多次经过同一个状态。比如网格世界里的随机策略,智能体很可能绕一圈,又回到刚刚走过的格子。那么这条轨迹里从该状态出发的“回报”到底算哪一个?
标准做法分成两类。首次访问蒙特卡洛:一条轨迹里,状态s第一次被访问之后直到轨迹结束的累计回报才被记录计入平均值。每次访问蒙特卡洛:一条轨迹里,状态s每一次被访问之后直到轨迹结束的累计回报都被记录并参与平均。
理论上两种方法都能收敛,但行为不同。首次访问的估计是“首访回报”的无偏估计,方差更大但更稳定;每次访问利用的数据更多,方差相对小,但引入了相邻回报之间的相关性,收敛性证明也更麻烦。我在实际写Demo时一般用首次访问,因为它线性、清晰、不容易在某些状态下产生震荡。
还有一个特别常见的bug:蒙特卡洛增量更新公式。很多人会把更新写成:
[ V(S_t) \leftarrow V(S_t) + \alpha (G_t - V(S_t)) ]
其中 (G_t) 是从 (S_t) 到轨迹结束的实际回报。但这里的 (G_t) 是一整条轨迹结束后才拿到的完整回报,也就是说你必须先把一条轨迹走完,才能回过头来更新轨迹里访问过的每一个状态。有些初学者想模仿在线学习方法,边走边更新,但这时 (G_t) 还没完整算出,更新用的值是残缺的,最终结果就是价值估计整体偏离。这个是蒙特卡洛方法的标志性节奏:完整玩一局,再下结论。
2.3 蒙特卡洛策略控制:探索还不够,得确保能“看到”全局
有了蒙特卡洛策略评估还不够,我们同样要用贪心改进策略。但这里藏着一个无模型方法的经典难题:如果当前策略已经偏向某种行为,那某些状态-动作对可能永远不会被探索到。比如网格世界里的智能体,如果某次碰巧发现走右边到达终点比较快,它就可能越来越多地选择向右的动作,而那之后“向左走但偶尔也有意外收获”的可能性就再没机会验证了。
解决探索问题最常见的手段是 (\varepsilon)-贪心策略。以较小概率 (\varepsilon) 随机选择动作,以 (1-\varepsilon) 的概率选择当前价值最高的动作。这样既能利用已有经验,又保持了对未知动作的尝试。蒙特卡洛控制里,我会在每次策略改进时重新发起一批轨迹,并且逐步降低 (\varepsilon),让算法前期大胆探索、后期认真利用。
这里我必须再提醒一次:蒙特卡洛方法因为要等一个episode结束才能更新,所以它的学习速度其实很慢。尤其任务特别长、奖励特别稀疏的时候,完整回报里包含的大量随机噪声会让方差爆表,导致算法需要极多轨迹才能稳定。这种情况下,下一节的时序差分方法就是更优解,因为它是真正的“边走边学”。
3. 时序差分学习:走一步、看一眼、立刻调
3.1 TD(0):让预测学会“半场复盘”
时序差分学习,也就是TD方法,是强化学习里最让我觉得“巧妙”的一套思路。它不像蒙特卡洛那样等整个episode结束,也不像动态规划那样直接动用环境模型。它只用到一步实测数据:当前状态 (S_t),执行动作 (A_t),得到奖励 (R_{t+1}),到达下一个状态 (S_{t+1}),然后立刻更新:
[ V(S_t) \leftarrow V(S_t) + \alpha \left[ R_{t+1} + \gamma V(S_{t+1}) - V(S_t) \right] ]
方括号里的差叫TD误差。它表示“新观察到的一步,带给我对当前价值的修正量”。蒙特卡洛方法用整个轨迹的真实回报做分母,TD(0)只用现有估计值 (V(S_{t+1})) 来替代剩余部分的期望。这等于用估计去引导估计,这种做法有个术语叫“自举(bootstrapping)”。
TD方法的优势是立竿见影的:它每一步都能更新,不需要等完整episode,方差也比蒙特卡洛低得多。代价是引入系统性偏差,因为 (V(S_{t+1})) 本身可能不准。用一句很白话的话总结:MC方差大、无偏差,TD偏差有、方差小。
实际项目里这个取舍导向非常明显。绝大多数现代强化学习算法都采用某种形式的TD,因为它适合长周期任务,甚至适合永不结束的连续任务。比如股票交易模拟,没有一个清晰“回合”的概念,MC根本没法用,TD却是天然适配。我们只需要每走一步就更新一次即可。
3.2 SARSA:控制策略与评估策略绑定的在线学习者
从价值预测推广到控制,TD方法有一个最直接的代表算法,叫SARSA。名字来自于它更新时用到的一串变量:当前状态 (S_t)、当前动作 (A_t)、奖励 (R_{t+1})、下一状态 (S_{t+1})、下一动作 (A_{t+1}),合起来就是SARSA。
更新公式写成:
[ Q(S_t, A_t) \leftarrow Q(S_t, A_t) + \alpha \left[ R_{t+1} + \gamma Q(S_{t+1}, A_{t+1}) - Q(S_t, A_t) \right] ]
注意这里用的是 (Q(S_{t+1}, A_{t+1})),也就是下一步实际执行的那个动作的Q值。这意味着SARSA是“边走边学”的在线算法,它评估的是当前正在执行的策略本身。你用 (\varepsilon)-贪心选择动作,更新目标里也包含这个 (\varepsilon)-贪心动作的价值。所以SARSA学到的是“带探索的贪心策略”对应的价值。
这一点直接决定了SARSA的一个很有名的行为:它会学习“避开危险”。在一个悬崖环境里,如果太接近悬崖边,探索机制很容易导致一步踏空,造成很差的回报。SARSA会把悬崖边缘的Q值压得很低,宁可绕远路也不愿意冒险。而下一节要讲的Q-Learning,行为就完全不一样。
如果你在做的是“评估既定策略”或者“智能体风险偏好比较低”的业务场景,比如让机器人送货时优先求稳,SARSA就很合适。它训练出来的策略对探索噪声更“敏感”,行为更保守。
3.3 Q-Learning:离线学习的代表,也是无数现代算法的起点
Q-Learning是很多人接触强化学习时记住的第一个算法。它的更新式是:
[ Q(S_t, A_t) \leftarrow Q(S_t, A_t) + \alpha \left[ R_{t+1} + \gamma \max_a Q(S_{t+1}, a) - Q(S_t, A_t) \right] ]
和SARSA唯一的区别就是把 (A_{t+1}) 换成了 (\max_a Q(S_{t+1}, a))。也就是说,更新目标不依赖实际选择的动作,而是下一状态里最优的动作。这种“目标策略和实际行为策略分离”的思路,就是离线策略学习(off-policy learning)的核心。
Q-Learning学到的最终策略是“完全贪心的最优策略”,而不是“带探索的 (\varepsilon)-贪心策略”。所以它学出来的Q值偏乐观——它假设自己以后每一步都会选择最优动作,哪怕训练时实际在到处探索。这个乐观偏差在悬崖环境里表现得非常典型:Q-Learning敢于贴着悬崖边走,因为它认为只要自己不乱探索,就不会掉下去,而最终学出的最优路径确实就是那条最短的悬崖边路径。
从工程实现角度,实现Q-Learning通常比SARSA更简单,因为你不需要存储 (A_{t+1}) 用于更新,只需要从状态转移结果里取出下一状态的最大Q值就行。也正因为Q-Learning在更新时能利用“未来最优动作”的信息,它广泛出现在各种深度强化学习算法里,比如DQN就是Q-Learning的思想加上了神经网络和经验回放。
3.4 期望SARSA、双Q学习:那些值得了解的小进阶
SARSA和Q-Learning之间其实还有一段中间地带。把SARSA更新里的下一动作 (A_{t+1}) 对应的Q值,换成“下一状态下所有动作Q值的期望”,就得到了期望SARSA:
[ Q(S_t, A_t) \leftarrow Q(S_t, A_t) + \alpha \left[ R_{t+1} + \gamma \sum_a \pi(a|S_{t+1}) Q(S_{t+1}, a) - Q(S_t, A_t) \right] ]
期望SARSA的方差比SARSA更低,因为不再依赖某个随机采样到的动作值,而是用了所有动作值的概率加权平均。同时它依然可以做离线学习:只要把那个 (\pi) 替换成你希望的目标策略,然后数据用别的方式采样也行。它是个很实用的折中方案,但不知道什么原因,在许多教程里被一笔带过。我自己做工程时,经常拿期望SARSA作为Q-Learning的“降方差替代品”,效果相当不错。
双Q学习解决的是另一个问题:Q-Learning乐观偏差在某些场景下会过大,导致选出一个过度高估的次优动作。双Q学习的解法是维护两套独立的Q表 (Q_1)、(Q_2),更新时用其中一套选择动作,用另一套给出价值,打破高估偏差。细节上它比Q-Learning复杂一些,但如果你发现算法在收敛后频繁选择“看起来高分、实际稀疏奖励”的路径,就可以考虑用双Q学习了。
4. n步方法与三种方法的统一
4.1 从TD目标到n步回报
蒙特卡洛走完整轨迹再更新,TD(0)走一步就更新,这两者之间是不是还有中间地带?有的,就是n步方法。n步TD用从当前状态出发的n步真实奖励,加上折扣后的第n步状态价值估计,作为更新目标:
[ G_t^{(n)} = R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + \cdots + \gamma^{n-1} R_{t+n} + \gamma^n V(S_{t+n}) ]
当 (n=1) 时它就是TD(0),当 (n) 趋于无穷时就退化成蒙特卡洛。n的取值控制了偏差和方差之间的平衡。有一点要提前提醒:n步TD在实现时比TD(0)麻烦不少,常见做法是要维护一个长度为n的队列,里面保存最近n步的奖励和状态记录,等n步之后才能触发一次更新。当年我实现n步SARSA时,在“队列什么时候清空”这个逻辑上卡了很久。
4.2 表格型方法的完整谱系
把三种方法放在一起看,其实它们可以统一成一个框架。动态规划是“直接看环境模型,下一状态的价值也在公式里显式求期望”;MONTE-CARLO是“不做期望,采样一整条轨迹,用真实回报当目标”;TD(0)是“不做完整采样,只做一个真实步骤,用现有价值估计当目标”。三者共享同一个GPI循环,只是策略评估的工具不一样。
学习中这个统一视角特别重要。它解释了为什么后续的深度强化学习算法总在提“bootstrap”和“回放”这两个概念:经验回放本质上是把TD的单步样本变成一个小型的“离线数据集”,而自举则始终绕不开“使用自己的估计来修正自己的估计”这个思路。理解了这张谱系图,你看到任何新算法,基本都能迅速判断出它处在哪种方法论光谱上。
5. 踩坑实录:表格型强化学习的六大经典问题
5.1 奖励没有归一化,Q值满天飞
表格型方法里最普遍的翻车现场是奖励设计太随意。我的一个模拟项目里,某次把正常移动的奖励设成 (R=2),撞墙设置成 (R=-50),终点设置成 (R=100)。Q值初始为0的时候,所有状态都会被负奖励逼成负值,然后Q函数在负数区间里振荡半天才能被终点的大正值拉回来。因为TD更新中学习率 (\alpha) 和折扣因子 (\gamma) 都是按之前的量级调的,奖励量级一变,参数全部失效。更好的做法是把奖励保持在一个有限的窄区间内,比如所有奖励都在 ([-1, +1]) 之内,或者做完奖励归一化再训练。
5.2 折扣因子太小,长视野策略出不来
(\gamma) 控制的是智能体看多远。(\gamma=0.9) 时,未来第10步的奖励对当前价值的影响已经缩水到 (0.9^{10} \approx 0.35),而 (\gamma=0.99) 时第10步奖励仍有约0.9的权重。有些任务里“前期忍受小惩罚、后期获得大回报”是唯一解,但如果你把 (\gamma) 设到0.85以下,算法就几乎学不出这种“先苦后甜”的策略。排查这种问题时,我一般先检查 (\gamma),再检查奖励设计,顺序不能反。
5.3 学习率过大,Q值震荡发散
TD方法依赖步长 (\alpha) 控制收敛。(\alpha) 设定过高时,单步TD误差会对Q值造成过大修正,整体Q值会在更新中反复横跳,甚至发散。一个直观检验方式是盯住TD误差的绝对值:如果它不但没有变小,反而越来越大,那多半是学习率太大,或者奖励设计带来的方差过大。表格型方法里我一般从 (\alpha=0.1) 起步,任务稳定之后再把 (\alpha) 衰减到0.01甚至更低,用“低学习率先跑稳定”代替“高学习率求速度”,是性价比最高的调试策略。
5.4 探索率衰减太快,策略固化在次优值
(\varepsilon)-贪心策略里,探索率从1慢慢降到0.1是一个经典设置,但如果你把衰减速度调得太快,智能体还没充分观察环境各个角落的机会,就已经固化成某个局部的策略了。我在一个 4×4 网格实验里把 (\varepsilon) 从0.9衰减到0.01仅用100个episode,结果学到的最优路径是“向右撞墙两次再绕路”,明显是探索不足导致的次优解。后来把衰减周期拉长到5000个episode,同样的设置下策略变得干净利落。探索和利用的平衡没有银子弹,但“探索率下降不要过快”是一个基本底线。
5.5 Q表初始值不当:从乐观还是从悲观开始
Q表的初始化也是一个容易被忽略的坑。Q-Learning配 (\varepsilon)-贪心有一个读起来反直觉的性质:如果把Q表初始值设置成较大的正数,会鼓励智能体在一开始把每个动作都试一遍,因为每个没试过的动作看起来都很“值钱”。反之,如果初始值为0而真实奖励大多是负值,智能体就会倾向于少动、待着不动。按我经验,表格型方法里把Q表的初始值设成一个比预期平均回报稍高的正数,往往能让探索更彻底;但如果你希望智能体从保守策略开始,那就把初始值压低。这个选择没有绝对对错,但至少要在心里明确你是故意这么设的,而不是因为忘记初始化。
5.6 中段更新的同步问题
最后再提一个实现层面的坑。表格型TD方法如果不加经验回放,按顺序在线更新时,Q表更新的顺序其实影响很大。如果你在一个episode里从前往后处理状态,然后又立即用更新后的Q值计算TD误差,这就变成了一种在线策略性的“蹭热度”,结果取决于遍历状态的方式。有些固定顺序的遍历会让智能体系统性地偏爱后遍历的状态。解决方案是不要在一轮内部反复横跳,剪枝式地把单轮更新顺序固定在“逆序”或“随机”上,并且每一轮结束后再统一评估。
这些坑说起来都很小,但每一个都足以让你的强化学习项目多一些“神秘的无法收敛”时光。实际项目里真正花时间的通常不是推导公式,而是这些实现细节:奖励怎么设、参数怎么调、表怎么初始、更新顺序怎么排。多留几个日志,每次训练时把Q值的分布、TD误差的均值、episode回报的曲线都打出来,很快就能定位问题出在哪一环。
我个人最近做格子世界实验时,最大的体会是不要贪快。强化学习本身就是个方差极大的过程,有时候你盯着训练曲线看半天都没有变化,突然某个episode之后曲线猛然上升,然后又开始波动。这种波动不是bug,是策略在被反复修正过程中的正常表现。只要总趋势在上升、TD误差在下降,就说明算法在干活。要坚持住,别一看到震荡就立刻换参数。