1. 先聊个直觉:不背地图的人怎么找路
如果你跟着"强化学习简史"这个系列一路读到这里,应该还记得动态规划那一套:只要给出完整的转移概率和奖励函数,就能用贝尔曼方程一层层迭代出最优策略。问题在于,绝大多数真实场景根本不会把"地图"交到你手上。机器人不知道自己关节的摩擦系数,棋手不知道对手脑子里的算路,交易系统拿不到市场的完整状态转移。你手里只有一段接一段的观测序列:状态、动作、奖励、下一个状态。这就是"不依赖模型"(model-free)的由来——不尝试拟合环境的物理规则,直接拿经验数据说话。
在强化学习的历史里,最早站出来的两个 model-free 方法就是 MC(蒙特卡洛)和 TD(时序差分)。它们都做同一件事——从经验里估计状态值函数——但思路几乎是两个极端。如果把每个回合(episode)比作一段人生,MC 活一种"回头看"的人生:把一辈子过完,拿到最终结果,再回头给过程中的每个时刻打分;TD 活一种"当下修"的人生:每迈一步,都根据这一步的实际结果和脑中已有的预期,立刻修正自己的判断。
这两种人生态度贯穿了强化学习之后几十年的发展。今天你看到的深度强化学习(DQN 那一脉)核心技巧,本质上全是 TD 的逻辑;而策略梯度那一脉,又大量借用 MC 式的完整回报。所以我一直觉得,MC 和 TD 这道选择题,是所有想搞懂强化学习的人必须迈过去的第一道坎。这篇不是教科书,我想用比较直白的方式把这两个方法拆开讲清楚:它们各自怎么算、为什么这么算、坑在哪、为什么最后 TD 成了主流而 MC 也没有退休。
1.1 为什么"没有模型"反而成了常态
很多人一开始会困惑:既然有模型就能用动态规划精算,为什么非要搞 model-free?答案是,建模本身就是极难的事,而且经常是白费功夫。围棋有明确规则,但分支因子大到根本没法用动态规划直接展开;机器人抓取时,接触力学模型比真实做实验还难写;更别提现实环境往往非平稳,模型刚建好,世界已经变了。
而且,有时候你根本不需要模型。想象你天天走同一条路上班,你已经形成"见到那个红绿灯要提早变道"的经验,但你并不知道路口车流的完整概率分布。你只需要知道:这么做,然后通常会有好结果。MC 和 TD 就是把这层逻辑数学化。它们共同的假设是:经验足够多,统计规律会自动浮现。
1.2 两者的共同点与分歧点
相同点很清楚:都用样本回报来更新价值估计,都不依赖环境转移概率。分歧点更清楚:一个要等回合结束才知道答案,另一个每一步都在猜测答案,而且用猜测去更新猜测。这个差异看起来只是"更新时机"问题,但后面你会看到,它引出了偏差、方差、收敛性、批次更新等一系列连锁反应,直接决定了现代强化学习算法的走向。
2. MC:跑完整局才肯打分的"爆发式人生"
2.1 MC 的记账逻辑:只看真实回报
MC 的想法简单得近乎朴素:你想知道一个状态好不好,那就从该状态出发跑一整条轨迹,记录实际收到的总回报,多跑几条取平均。这就是大数定律在强化学习里的直接搬用。更新公式长这样:
G_t = R_{t+1} + γR_{t+2} + γ²R_{t+3} + ... + γ^{T-1}R_T
V(s) ← V(s) + α[G_t - V(s)]
这里G_t是"这一整段人生最终的清算单",α是学习率。值得强调的是,G_t是真实发生的、不加任何猜测的回报,所以 MC 对价值函数的估计是无偏的——给它无穷多条完整轨迹,它能收敛到真值,而且不会因为别的什么估计误差污染结果。
2.2 首次访问与每次访问的两个变体
MC 有两个经典变体需要注意。首次访问 MC:一条轨迹里同一个状态出现多次,只用第一次遇到的回报做更新。每次访问 MC:每次出现都参与平均。理论上两者最终都收敛到真实值,但首次访问的方差性质更好,分析起来也更干净,大部分教材和工程实现默认用首次访问。
原因很直观:同一条轨迹里的回报高度相关,如果同一个状态的每一次出现都拿去平均,相当于把强相关的样本重复用了几遍,样本的有效性是被虚增的。这就像你在一家餐厅连吃三道菜,把三次感受当成三个独立顾客的评价来统计,数据量看着上去了,信息量并没有。
2.3 MC 评估策略的代码骨架
写一段最朴素的 MC 策略评估,帮助建立直觉。这里的关键是:先攒完整条轨迹,再倒着算出每一步的回报,最后做平均。
from collections import defaultdict def mc_policy_evaluation(env, policy, episodes=5000, gamma=0.95): # 记录每个状态累计回报和出现次数 returns_sum = defaultdict(float) returns_count = defaultdict(float) for _ in range(episodes): trajectory = [] state = env.reset() done = False while not done: action = policy[state] next_state, reward, done = env.step(action) trajectory.append((state, reward)) state = next_state # 倒着算完整回报 G,首次访问才更新 G = 0 visited = set() for state, reward in reversed(trajectory): G = reward + gamma * G if state not in visited: visited.add(state) returns_sum[state] += G returns_count[state] += 1 return {s: returns_sum[s] / returns_count[s] for s in returns_sum}注意那个reversed,它是 MC 的命根子:只有倒着走才能用递推式G = reward + gamma * G把整段回报算出来。visited集合则是首次访问的开关。
2.4 MC 的代价:必须等打完才知道结果
MC 最大的局限是:只能在有终止回合的任务里用。连续任务(比如恒温器控制、股票持仓管理,根本没有"结局"的那一天)里,G_t连定义都没有,MC 直接瞎了。
另一个问题是慢且脆。每更新一次价值估计,要先等一整条轨迹跑完。评价基金经理至少要等一个完整牛熊周期——MC 就是这个理念。但现实中往往等不起,而且单条轨迹的回报方差很大,一次偶然的坏动作可能把整段回报带偏,让这次更新变成一次"无效教育"。
提示:如果你只是在做小规模的 grid world 或 CartPole 实验,MC 完全够用,代码也最容易调试。它的坑要到连续控制、探索奖励稀疏这类场景里才真正暴露。
3. TD:边走边改的"渐进式人生"
3.1 TD 的核心:拿估计去更新估计
TD 只改了一处:不等回报全部到账,走一步就更新。公式:
V(s) ← V(s) + α[r + γV(s') - V(s)]
这种操作叫 bootstrap(自助),因为我们在用"自己对下一个状态的估计"去修正"对当前状态的估计"。新信息其实只有两个:这一步实际拿到的奖励r,以及下一步落在哪个状态s'。如果r + γV(s')比原来对V(s)的预期高,说明这个状态比想象中好,就往上调;反之往下调。
这就是"当下修"的人生:不等到盖棺定论,每一小步都即时反馈、即时纠偏。它不需要知道环境模型,也不需要等回合结束,因此天然适用于连续任务。代价是,V(s')本身是个估计值,用它做基准,等于在流沙上盖房子——每一步都有点"自作聪明"的嫌疑,但你架不住它快。
3.2 TD(0) 的代码实现
对比上面 MC 的代码,TD 的实现简洁得多,也不需要reversed,不需要visited,走一步更新一步:
from collections import defaultdict def td0_policy_evaluation(env, policy, episodes=5000, gamma=0.95, alpha=0.1): V = defaultdict(float) for _ in range(episodes): state = env.reset() done = False while not done: action = policy[state] next_state, reward, done = env.step(action) # TD 目标:一步真实奖励 + 对下一步的已有估计 td_target = reward + gamma * V[next_state] td_error = td_target - V[state] V[state] += alpha * td_error state = next_state return Vtd_error这个名字值得记住,它叫时序差分误差,是整个 TD 家族的核心信号。你后面看 Q-learning、DQN,看到的都是它的变形。
3.3 与动物心理学、多巴胺的暗合
这里有个值得聊的历史故事。Sutton 在 1980 年代做 TD 的动机之一,是心理学里的 Rescorla-Wagner 模型——上世纪 70 年代提出的动物学习理论。这个模型认为,动物对某个刺激的反应强度更新,正比于"实际结果与预期之差"。你看td_error = r + γV(s') - V(s),本质上就是这个"预期与现实的落差"。
这条线后来又和神经科学接上了。研究发现,大脑多巴胺神经元的活动,大约就编码这个预测误差:被生活的意外惊喜砸中,多巴胺就分泌多一点;一切如预期,就没什么波澜。TD 不是凭空发明的数学技巧,它本身就是对人类和动物学习机制的粗糙建模。这也是为什么当时做动物学习研究的人,反而比做 AI 的人更容易接受 TD——听过"巴甫洛夫的狗"实验的人,对"预测误差驱动学习"这个观念并不陌生。
4. 两者交锋:偏差、方差与那道经典的选择题
4.1 偏差和方差的互换
把这俩放一起,最核心的对比就是偏差与方差的对调。MC 的G_t是真实回报,无偏,但方差大——一条轨迹的偶然性太强,终点一哆嗦,整段分数都跟着抖。TD 的r + γV(s')只用了一步随机性,方差小得多,但V(s')本身带着估计误差,所以有偏。
这就像评价一家餐厅。MC 只看"整顿饭吃完的总体感受",一次被一道菜毁了,整体评分就崩了;TD 是"每上一道菜记录一次印象",单道菜的失误影响小,但你对"这家店应该是什么水准"的先入为主会一直带偏后续判断。实践里这个权衡直接决定收敛速度:数据少、噪声大的环境,TD 通常快得多;回合极长、终端奖励稀疏的场景,MC 的真信号又有不可替代的价值。
4.2 随机游走实验:TD 为什么收敛得更快
Sutton 和 Barto 的书里有个著名的随机游走例子。一维线上排着几个状态,最左边是终止并给 0 分,最右边是终止并给 1 分,中间状态按均匀随机策略左右移动。两种方法做策略评估,把均方误差画出来,TD(0) 的曲线肉眼可见地压在 MC 下面,尤其是前几十次迭代,差距大得惊人。
原因在于信号传播的方式不同。TD 每一步都在利用相邻状态的关联,把终点附近的信号逐步"蹭"回起点,信息是连续扩散的;MC 则必须等一条轨迹走完,信号才从终点一次性倒灌。用传播的比喻:TD 像口口相传的邻里消息,每步扩散一点;MC 像等最终审计报告出来,再给全公司发群邮。后者信息准,但就是慢。
4.3 批量训练下的"分道扬镳"
更精彩的例子在批量(batch)更新下,两者算出来的答案会直接不一样。假设我们采集到 6 条片段:第一条是A - 0 - B - 0 终止,后五条都是B - 1 终止。
用 MC 做批量统计:A 只出现了一次,那条轨迹的回报是 0,所以V(A) = 0;B 的平均回报是(0 + 1 + 1 + 1 + 1 + 1) / 6 ≈ 0.83。用 TD:先算出V(B) ≈ 0.83,再利用"A 一定转移到 B"这个结构关系,得到V(A) = 0 + V(B) ≈ 0.83。
谁更合理?从数据蕴含的结构看,TD 的答案更对:A 必然通向 B,那么 A 的价值就理应继承 B 的价值。这正是 TD 被称为"确定性等价"更新的原因——它在默认经验中隐含的结构关系。MC 是纯经验的囚徒,只认自己亲眼看过的数字,反而被单条样本的偶然性困住了。这个例子我建议每个学强化学习的人都亲手跑一遍,它比任何长篇大论都更能说明问题。
5. 为什么 TD 最终胜出:从 Q-learning 到 DQN 的血缘
5.1 一条清晰的传承线
把时间线拉直,你会发现现代深度强化学习的血脉就是从 TD 这条线下来的:
- 1988 年,Sutton 发表 TD(0) 的理论论文,解决了收敛性证明。
- 1989 年,Watkins 提出 Q-learning,把 TD 逻辑从"评估给定策略"推进到"直接学最优动作值函数"。它的更新是
Q(s,a) ← Q(s,a) + α[r + γ·max_{a'} Q(s',a') - Q(s,a)],这是 off-policy 的:行为策略随便怎么走,学的都是最优策略。 - 1994 年前后,SARSA 作为 on-policy 的对应物出现,行为策略和更新目标绑在一起。
- 2015 年,Mnih 等人的 DQN 发表在 Nature 上,把神经网络当成 Q 函数的近似器,配合经验回放和独立的目标网络,在 Atari 游戏上打出超人类水平。
DQN 的更新公式就是上面那行 Q-learning 公式,最多加上了目标网络和冻结参数。可以说,你手上任何一个深度强化学习项目,只要用了 Q 值更新,骨子里都是 TD 的"走一步修一步"逻辑。
5.2 MC 并未消失:策略梯度里的回归
但 MC 没有死。REINFORCE 算法的更新里,用的就是从当前时刻到回合结束的完整回报作为"这次动作好坏"的度量。在稀疏奖励、结局决定一切的场景里,这种无偏性格外珍贵。
后来 GAE(广义优势估计)出现,本质上是把 MC 式的完整回报和 TD 式的单步差分按权重 λ 融合在一起。λ 取 0,退化成纯 TD;λ 取 1,趋近 MC。这已经不是二选一,而是找到了一个连续的光谱来调节"多相信真实回报"还是"多相信即时修正"。
5.3 TD(λ):两者之间的完整连续谱
顺带提一下 TD(λ)。它用资格迹(eligibility trace)记录"哪些状态最近被访问过",然后把 1 步、2 步、3 步……直到完整回报全部按 λ 折现加权。λ=0 退化成 TD(0),λ=1 趋近 MC,中间是各种 n 步回报的混合。这套机制在当年解决了"MC 方差大、TD 有偏"的僵局——你想要什么,就调 λ。今天你看 PPO、TRPO 这些现代算法里的优势估计,依然藏着这个谱系的思想。
提示:如果只看工程落地,λ 往往取 0.95 左右就能获得不错的折中;新手纠结 λ 之前,建议先把 TD(0) 和完整回报各自的极限行为摸清楚。
6. 一些实操体会与踩坑记录
6.1 初学者应该怎么看待这两种"人生"
我的建议是别急着二选一。先把"更新 = 目标 − 当前估计"这个通用框架记住:MC 的目标是整段真实回报G_t,TD 的目标是"一步奖励 + 下一步的估计值"r + γV(s')。两者的差别只在"目标里面有没有掺入估计值"。
然后去看 TD 的 bootstrap 如何把方差压下来、如何牺牲一点偏差,再看 MC 的无偏性如何守住底线。最后去理解 λ 这个旋钮。等你亲手在同一个环境里跑出两条不同的误差曲线,你会突然发现,它们其实是同一枚硬币的两面,争论谁优谁劣没有意义,搞清楚你的任务吃哪一套才重要。
6.2 我踩过的坑与常见误区
这几条都是我在实际实验里摔过跟头的地方,列出来供参考:
- 折扣因子 γ 设成 1 还浑然不觉。在连续任务里 γ=1 会让 TD 的误差永不衰减,
V(s')的影响被无限放大,很容易发散。小任务里 0.9 到 0.99 之间通常比较稳。 - MC 硬套连续任务。没有终止,
G_t无定义,代码跑起来看着有输出,实际语义已经错了。 - 学习率 α 不看轨迹长度。回合越长,MC 回报的方差越大,α 要相应调小;TD 的 α 可以稍大,但太大照样震荡。我习惯先用随机游走环境把 α 扫一遍再上正式实验。
- 首次访问和每次访问混用。新手图省事直接用每次访问,结果同一个状态在同一条轨迹里反复更新,方差被低估,收敛曲线看着比实际漂亮。
- TD 家族的"高估病"。Q-learning 里的
max操作会系统性高估价值,DQN 用目标网络缓解,但它治标不治本——只要你拿着估计去更新估计,误差就会累积。做实验时看到 Q 值一路膨胀但策略没变好,先怀疑这里。
6.3 个人体会
说点我自己的感受。我最早看这两套方法时,总觉得 MC 更"诚恳"——人家是真刀真枪跑完一局才下结论,不像 TD 处处带着猜测。但做着做着就发现,现实中大部分任务根本不允许你等那么久,而且你千辛万苦等来的"结论",往往就建立在几次偶然的轨迹上。
TD 那种"边走边改"的策略,虽然每一步都有点自作聪明,但在复杂环境里反而更稳健。这个教训放到生活里也成立:先接受不完美的反馈持续修正,比攒一个大样本再一次性判断,往往更接近真实世界的生存法则。今天再回头看 MC 和 TD,它们不该被当成两种对立算法,而应该被理解成"采样效率"和"估计偏差"之间的两种摆法。理解了这一点,后面看 Q-learning、DQN、PPO 都会顺畅很多。这个系列下一期打算接着聊时序差分的误差传播,或者直接进入 Q-learning 的细节,到时候再会。