资讯动态

MC、TD与Q-learning:强化学习价值学习核心算法解析与实践

发布时间:2026/10/23 3:22:48 来源:尧图企业网站定制
1. 价值学习要解决的核心问题——从一个网格世界说起先说结论强化学习里所谓的“价值学习”本质上是在回答一个问题——在某个状态下我将来大概能拿多少回报。这个问题想清楚了策略优化就水到渠成。而MC、TD和Q-learning正是估算这个“将来回报”的三条经典技术路线。我见过不少刚接触强化学习的朋友一上来就抱着深度强化学习跑PyTorch结果连最基础的表格型Q-learning都没写过训练曲线一团乱麻也不知道问题出在哪。我个人的建议是先把MC、TD和Q-learning这三兄弟彻底吃透找一个最简单的网格世界Grid World把算法亲手写一遍再考虑神经网络那套东西。为了不空谈理论我们约定一个贯穿全文的例子。假设一个4x4的网格世界有16个格子左上角是起点右下角是终点也就是目标位置。智能体每一步可以选择上、下、左、右四个动作之一每走一步获得-1的即时奖励到达终点获得0的奖励并结束回合。这个设置虽然简单但包含了价值学习的全部要素状态、动作、奖励、回合结束。写代码之前先把几个关键符号理清楚策略 (\pi(a|s))在状态 (s) 下选择动作 (a) 的概率。回报 (G_t)从时刻 (t) 开始往后的折扣累计奖励即 (G_t R_{t1} \gamma R_{t2} \gamma^2 R_{t3} \dots)。状态价值 (V(s))从状态 (s) 出发遵循策略 (\pi) 所能获得的期望回报。动作价值 (Q(s, a))在状态 (s) 下先执行动作 (a)之后遵循策略 (\pi) 的期望回报。价值学习的目标就是估计 (V(s)) 或者 (Q(s, a))。有了 (Q) 函数策略就可以直接贪心在当前状态选 (Q) 值最大的动作。问题在于期望怎么算。如果环境模型已知可以直接用动态规划迭代求解但真实场景下我们往往只有采样样本没有环境模型。这时候MC和TD就该登场了。注意本文讨论的是无模型model-free场景下的价值估计。也就是说我们不知道状态转移概率 (P(s|s,a))也不知道奖励函数 (R(s,a))只能通过与环境交互获得经验样本。这一点贯穿全文理解不了这个前提后面看代码就容易迷糊。2. 蒙特卡洛方法用完整样本估计长期回报2.1 核心思想平均完整回合的回报蒙特卡洛方法Monte Carlo简称MC的思路很直接想算期望就多采样几次取平均。一个回合episode走完之后我们拿到了完整的轨迹 ((S_0, A_0, R_1, S_1, A_1, R_2, \dots, S_T))于是可以计算每个时间步对应的回报 (G_t)。如果有很多个回合那每个状态 (s) 的 (V(s)) 就可以用“所有经过状态 (s) 的回报”求平均值来近似。以我们的4x4网格世界为例。假设在第1个回合中智能体从起点出发走了7步到达终点中间经过了第3号格子。那么在 (t3) 这个时刻如果折扣因子 (\gamma1)回报 (G_3) 就是从该时刻起所有剩余奖励之和。因为每步是-1、最后是0假设还剩4步那么 (G_3 -4)。这个数就作为第3号格子价值的一次观测样本。如果第2个回合再次经过第3号格子又得到一个 (G_t) 值比如-5那么两个回合平均下来(V(3) \approx (-4 (-5)) / 2 -4.5)。2.2 首次访问型与每次访问型你写代码时一定会碰到两个术语first-visit MC和every-visit MC。它们的区别在于首次访问型每个回合中只取状态 (s) 第一次出现时的回报用于平均。每次访问型每个回合中状态 (s) 每次出现时的回报都用于平均。理论上这两个版本在样本量足够大时都会收敛到真实价值但实践中首次访问型更常用因为它的方差特性更容易分析而且计算开销更小。对于网格世界这种状态数少、回合短的场景两者差距几乎可以忽略但如果你做的是探索性强的连续任务建议直接用首次访问型。实现MC需要特别注意一个前提所有回合必须能完整结束。如果环境没有终止状态而是一个无限持续的交互过程MC就无法直接使用。这是它最典型的局限性。2.3 MC的优缺点和代码骨架MC的优点非常突出无偏性回报 (G_t) 是真实回报的采样理论上只要样本够多估计值就会逼近真实价值不存在系统性偏差。简单直观不需要额外维护任何递推结构跟环境交互攒一批回合就能算。缺点也很明显方差大每个回合的回报受动作随机性影响很大尤其当 (\gamma) 接近1时长期累积的随机性会让单个样本的波动非常大。学习慢必须等到回合结束才能更新无法利用中途的部分信息。我把MC的核心更新伪代码放在这里方便你一会儿对照着写# 伪代码风格实际误差可直接用增量式更新 def mc_first_visit(episodes, gamma): returns {s: [] for s in states} for _ in range(episodes): trajectory generate_episode(policy) G 0 visited set() for t in reversed(range(len(trajectory))): state, action, reward trajectory[t] G gamma * G reward if state not in visited: visited.add(state) returns[state].append(G) V {s: np.mean(returns[s]) for s in states} return V看到这段代码你可能已经察觉到一个问题要收集的样本量很不确定。状态太多、回报波动太大的时候MC的收敛速度会很感人有。所以我的建议是MC适合作为理解强化学习的第一块基石但生产环境里很少单独用它做价值估计除非确实拿不到单步模型只能跑完整回合。实操提示MC的更新是“离线批次式”的——先攒一批回合再统一平均。如果你在动态环境里实时交互这套思路会显得笨重。这时候就该看下面的TD方法。3. 时序差分方法单步也能学习未来3.1 用贝尔曼方程打破“回合完整”的限制时序差分方法Temporal Difference简称TD最核心的洞察是不需要等到回合结束走完一步就能利用下一步的估计值来更新当前状态的价值。它依托的是贝尔曼方程[ V(s) \mathbb{E}[R_{t1} \gamma V(S_{t1}) | S_t s] ]这个等式说明当前状态的价值可以拆成“即时奖励 下一状态价值的折扣和”。如果我们用当前对 (V(S_{t1})) 的估计来代替真实期望那么就得到了一个递归更新公式[ V(S_t) \leftarrow V(S_t) \alpha \left[ R_{t1} \gamma V(S_{t1}) - V(S_t) \right] ]方括号里的部分被称为TD误差TD error记作 (\delta_t)。它表示“实际得到的奖励加上下一状态估计价值”与“当前估计价值”之间的差距。更新时我们用学习率 (\alpha) 把一小部分TD误差“吸收”进当前估计值。这有点像一个渐进式的判断过程你原本以为某个状态值-2结果实际走了一步后、用下一步估计一算发现应该是-2.3于是你把估计值往-2.3的方向调一点。这样每走一步都在调整对过去的认知而不必等结局揭晓。3.2 TD(0) 和 MC的联系TD(0)特指只向前看一步就更新的情况。它处于“两把火”之间比MC更新快因为“样本”丰富——每个时间步都可以做一次更新。比动态规划适用广因为不依赖环境模型只需要实采样的 (R) 和 (S)。但TD有一个重要的性质需要正视有偏性。因为 (V(S_{t1})) 本身是估计值用它做更新目标相当于用“估计值的估计值”去修正当前估计。在还没有收敛时这个偏差是实实在在存在的。或者用我常跟人开的玩笑——TD的每一步都在“对着草稿打草稿”误差没法一下子就消失需要多步迭代才能慢慢逼近真相。不过TD的方差比MC小因为每条样本只用了一步随机性而不是累积整个回合的随机性。所以在总训练步数相同的情况下TD通常学得比MC稳得多。3.3 MC与TD的学习速度对比实验体会我曾经在网格世界做过一组简单的对比实验分别用MC和TD(0)估计同一个随机策略下的 (V(s))记录每一步更新后的价值误差用真实价值来算误差。结论和理论基本一致TD前面几百步误差下降特别快几分钟就能勾勒出大概的价值轮廓。MC前一段表现平平但随着回合数增加它的估计越来越准最终收敛精度反而更好。一旦 (\gamma) 选得比较大比如0.99MC的方差会明显增大误差曲线“毛刺”非常多而TD相对平滑。所以你如果只想要一个快速可用的价值估计TD是首选如果你需要极高精度的无偏估计并且能接受较长的训练时间MC才值得考虑。3.4 TD的代码骨架# 假设已经初始化了V表 for episode in range(num_episodes): state env.reset() while True: action epsilon_greedy(state, Q, epsilon) next_state, reward, done env.step(action) td_target reward gamma * V[next_state] * (not done) V[state] alpha * (td_target - V[state]) state next_state if done: break注意上面有个小细节当done为真时下一状态的价值应该为0。因为回合已经结束没有“未来”了。很多新手在这里忘记乘(not done)导致最后一步的TD目标多算了一个估计值收敛结果偏得离谱。这个问题我在后文“常见问题”里还会重点提。4. Q-learning不依赖当前策略的价值学习4.1 从状态价值到动作价值前面讲的价值函数 (V(s)) 只回答“当前状态值多少钱”但没有告诉我们“在状态 (s) 下做哪个动作更好”。要输出策略更直接的思路是学习动作价值函数 (Q(s, a))。Q-learning是一种**离策略off-policy**的时序差分控制算法。它更新的是 (Q) 值表更新公式长这样[ Q(S_t, A_t) \leftarrow Q(S_t, A_t) \alpha \left[ R_{t1} \gamma \max_{a} Q(S_{t1}, a) - Q(S_t, A_t) \right] ]核心区别在于目标值里的下一状态价值不是按当前行为策略来选择动作而是直接取Q表中下一状态下所有动作的最大值。意思是——我在更新时默认下一步会采取“最优”动作哪怕行为策略实际上在探索比如用了贪心策略随机乱走。这带来了一个非常优雅的性质Q-learning不依赖行为策略的具体形式。无论当前用什么样的策略去收集经验完全随机、贪心还是带噪声只要保证每个动作被探索到的概率大于零Q表都能最终收敛到最优的 (Q^*)。我打个比方MC和TD像是“记录实际走路的路线”走歪了路线就歪而Q-learning像是在“抄最佳规划图”虽然眼下在乱逛但每次都拿“如果下一步最优”来打底所以最终学到的是全局最优路线。4.2 Q-learning的探索与利用Q-learning本身并不规定行为策略。通常我们会用 (\epsilon)-贪心策略来生成动作以概率 (1-\epsilon) 选择当前Q值最大的动作以概率 (\epsilon) 随机选择动作。这个 (\epsilon) 一般开始时较高比如0.3~1.0随着训练逐步衰减最终趋近于很小的值。为什么要探索因为如果总选“看起来最优”的动作那些被低估但实际更好的动作就永远没机会被验证。尤其是价值初始化全部为0时贪心策略只会傻乎乎地沿着一条平庸路线走容易陷入次优。合理地让 (\epsilon) 随训练轮数递减是Q-learning实操中最重要的超参调整手段之一。4.3 Q-learning的收敛条件和适用范围Q-learning能在表格型场景下保证收敛但要满足几个条件状态和动作空间是离散且有限的。所有状态-动作对 ((s, a)) 都被无限次地访问。学习率 (\alpha) 满足特定的衰减条件比如随着迭代递减到0且保证无限次更新需求。网格世界正好满足这些条件所以它是学习Q-learning的首选“训练场”。但如果状态空间极大比如连续状态表格存放不下就得靠深度神经网络来近似 (Q) 函数了也就是深度Q网络DQN那套路。理解了表格型Q-learning再去接触DQN你会觉得思路非常顺。4.4 Q-learning代码骨架下面这段是Q-learning训练循环的典型实现我强烈建议你亲手写一遍import numpy as np def q_learning(env, num_episodes1000, alpha0.1, gamma0.99, epsilon0.3): Q np.zeros((env.n_states, env.n_actions)) for episode in range(num_episodes): state env.reset() while True: if np.random.rand() epsilon: action env.sample_action() else: action np.argmax(Q[state]) next_state, reward, done env.step(action) best_next np.max(Q[next_state]) if not done else 0 td_target reward gamma * best_next Q[state, action] alpha * (td_target - Q[state, action]) state next_state if done: break return Q重要这里的best_next一定要在done时置为0。这个不是技巧而是定义。如果懒得处理你会在所有终点状态的动作价值上平白多算出一截误报的“未来奖励”给最终收敛带来灾难。5. 三个算法放在一起对比怎么选、怎么用5.1 核心差异一更新时机算法更新时机所需样本能否中途学习MC回合结束完整轨迹否TD(0)每一步单步转移是Q-learning每一步单步转移目标贪心是MC天然适合“回合制、结局清晰”的任务比如棋类对局、单局游戏。TD和Q-learning则适合“临场反应、结局遥远”的任务比如机器人控制、自动驾驶因为不可能每次决策都要等到整个试错结束才学习。5.2 核心差异二策略相关 vs 策略无关MC和TD(0)都是**在策略on-policy**的方法——它们估计的是“当前正在执行的行为策略”的价值。如果你训练过程中改了策略之前攒下的价值估计就“过期”了。Q-learning则不同它利用 (\max) 操作直接瞄准“最优策略”的价值和行为策略是什么无关。这也是为什么Q-learning在实际工程里更受欢迎你可以用任意方式收集经验然后用这些经验不断优化价值表数据利用率和灵活性都比较高。5.3 偏差与方差的权衡再帮大家把这层窗户纸捅破MC无偏但方差高。需要很多回合才能压平噪声。TD有偏但方差低。单步更新随机性小收敛快但目标值里带“估计的估计”。Q-learning有偏它继承来了TD的偏差再加上 (\max) 带来的正向选择偏差方差低实践中通常学得最快。(\max) 带来的正向偏差值得一提。因为Q值的初始估计普遍不精准取最大值时容易高估“放大镜效应”会让被高估的动作被反复选中导致Q函数整体偏高。在DQN系列算法里这个问题催生了Double DQN等改进方案。表格型Q-learning中只要 (\epsilon) 保持一定探索这种高估一般不太致命但你要心里有数。5.4 算法选择的经验法则按照我个人的经验可以这样选任务回合短、结局明确、想要高精度优先MC。任务回合长、需要快速反馈、环境动态变化优先TD或Q-learning。任务有清晰的离散动作空间想要简单可靠的控制策略直接上Q-learning。任务状态空间大了表格装不下从DQN开始但先回头把Q-learning彻底弄懂。6. 完整实操网格世界里的MC、TD与Q-learning6.1 环境的定义与参数选择我们来定义一个最简单的4x4网格环境。状态编号从0到15动作0上、1下、2左、3右。落在终点状态15时回合结束奖励为0其余每一步奖励-1。下面的代码用字典表示转移方便复现class GridWorld: def __init__(self): self.n_states 16 self.n_actions 4 self.start 0 self.goal 15 self.state self.start def reset(self): self.state self.start return self.state def step(self, action): row, col divmod(self.state, 4) if action 0: # 上 row max(row - 1, 0) elif action 1: # 下 row min(row 1, 3) elif action 2: # 左 col max(col - 1, 0) else: # 右 col min(col 1, 3) next_state row * 4 col reward 0 if next_state self.goal else -1 done (next_state self.goal) self.state next_state return next_state, reward, done def sample_action(self): return np.random.randint(self.n_actions)参数上我推荐从保守值开始学习率 (\alpha0.1)折扣因子 (\gamma0.99)探索率初始 (\epsilon0.3)之后每个回合衰减 (\epsilon \times 0.999)。这样既保证前期有足够的探索后期又能稳住策略。6.2 MC的完整实现MC需要完整的回合所以环境必须支持generate_episode。我们直接用行为策略随机来生成多个回合再对首次访问状态累加回报求平均def generate_episode(env, policy): state env.reset() episode [] while True: action policy(state) next_state, reward, done env.step(action) episode.append((state, action, reward)) if done: break state next_state return episode def mc_first_visit(env, num_episodes, gamma): returns {s: [] for s in range(env.n_states)} for _ in range(num_episodes): episode generate_episode(env, lambda s: np.random.randint(env.n_actions)) G 0 visited set() for t in reversed(range(len(episode))): state, action, reward episode[t] G gamma * G reward if state not in visited: visited.add(state) returns[state].append(G) V {s: np.mean(returns[s]) if returns[s] else 0.0 for s in range(env.n_states)} return V注意我用了倒序累积G这样从回合末尾往前扫一遍就能算出每个时刻的回报时间复杂度只是 (O(T))。如果正序每次从头算会白白多花 (O(T^2)) 的时间网格世界感觉不明显但换成长回合任务会非常痛。6.3 TD(0)的完整实现TD(0)估计同一个随机策略的价值def td_zero(env, num_episodes, alpha, gamma): V np.zeros(env.n_states) for _ in range(num_episodes): state env.reset() while True: action np.random.randint(env.n_actions) next_state, reward, done env.step(action) td_target reward gamma * V[next_state] * (1 - done) V[state] alpha * (td_target - V[state]) state next_state if done: break return V建议你做这样的可视化训练过程中每隔50个回合打印一次V表你会看到价值从0开始被“刷”成负值越靠近终点的格子负得越轻越远的格子负得越重。这个过程能帮你在脑子里建立“价值距离终点的远近”的直觉。6.4 Q-learning的完整实现与策略提取Q-learning实现前面给过骨架这里再补上策略提取和指标输出def learn_and_evaluate(env, episodes2000): Q np.zeros((env.n_states, env.n_actions)) alpha, gamma, epsilon 0.1, 0.99, 0.3 for ep in range(episodes): state env.reset() epsilon max(0.01, epsilon * 0.999) while True: if np.random.rand() epsilon: action env.sample_action() else: action np.argmax(Q[state]) next_state, reward, done env.step(action) best_next np.max(Q[next_state]) if not done else 0 Q[state, action] alpha * (reward gamma * best_next - Q[state, action]) state next_state if done: break policy {s: np.argmax(Q[s]) for s in range(env.n_states)} return Q, policy跑完之后把格子对应的最优动作打印出来你会发现一个很有意思的现象靠近右下角终点的格子会指向终点或绕行方向靠近墙边的格子会自动学会避开边界。这看起来像智能体“懂得规则”了其实只是Q表在反复试错中把最优路线的数值刷了出来。6.5 参数选择的计算过程参考Q-learning里的 (\alpha) 和 (\gamma) 值得一提。如果你把 (\alpha) 设成0.5TD目标比例一半当前估计保留一半初期会学得很快但容易震荡。设成0.01则学到极慢但非常稳。我的经验是回合短任务(\alpha) 可以大一点0.2~0.5。回合长任务(\alpha) 小一点0.05~0.1不然一段学习的噪声会被反复放大。(\gamma) 在小网格世界可以设0.99如果换成非常大的地图(\gamma) 不宜过大不然远处的奖励经过多次折扣还是会对路径选择产生强影响策略会倾向于绕远路学习难度陡增。6.6 评估收敛程度的参考指标一个常见的坑只看每个回合的总奖励下降就觉得算法“学好了”。这不是充分条件。我建议至少看两个指标每一回合的步数或总奖励检验控制表现。Q表/价值表变化的量级检验是否收敛例如最后500回合的平均TD误差是否降到了一个很小的区间。具体做法是每个回合记录下 (\max_{s,a} |Q_{new}(s,a) - Q_{old}(s,a)|)。如果这个值持续缩小到一个稳定小量说明Q表基本稳定了。这个方法比单纯看奖励曲线更可靠因为奖励曲线虽然接近最优但Q表可能还在缓慢漂移。7. 常见问题与排查心得7.1 价值表格不收敛越训练越跳怎么办通常有三个原因(\alpha) 太大导致更新步子迈太大在最优值附近来回震荡。解决调低 (\alpha)并配合指数衰减。(\epsilon) 衰减太快导致算法长期不探索死守一个次优路线。解决放慢 (\epsilon) 衰减速率。(\gamma) 过大且环境随机性强远期回报噪声太大价值目标本身波动剧烈。解决适度降低 (\gamma)比如从0.99降到0.9或0.95。建议先调 (\alpha)再调衰减策略最后才动 (\gamma)。别一上来就把所有参数都改了那样根本定位不到问题根源。7.2 终点状态的价值被高估Q-learning里最容易出这种问题。原因就是前面说的——更新时没有对done状态做特殊处理导致终点状态的价值被加上了新一轮估计值。典型的静态症状是输出策略时智能体在终点某个相邻格子会反复横跳因为“终点之后”的价值被错误地当作正收益在累积。排查方法很简单追踪训练过程打印终点状态的Q值如果是正数几乎可以肯定就是终止处理出了问题。7.3 MC的回报方差爆炸如果你在长回合任务里用MC而且回报波动特别大一个实用办法是引入基线baseline比如用当前价值函数做中心化[ \text{更新量} (G_t - V(s)) \cdot \nabla \log \pi(a|s) ]这是策略梯度里的常见技巧。换成价值学习的视角本质是让“超出预期的部分”去指导更新而不是让绝对回报值去指导。如果只是做价值估计不想引入策略梯度也可以考虑把 (\gamma) 调小些或者改用TD方法。7.4 从表格到神经网络要注意什么表格型Q-learning理解之后跳到DQN衔接点主要在下面几个地方经验回放Replay Buffer表格时代样本用一次就丢DQN里你需要把转移样本存储起来随机采样做小批量更新打破时间相关性。目标网络Target NetworkQ-learning更新目标 (\max_a Q(s,a)) 每次都取自当前Q表导致目标和参数互相追逐容易震荡。DQN把“计算目标”和“被更新”的Q网络分开定期同步。奖励标准化表格时代无所谓量纲但神经网络对奖励尺度非常敏感。建议把即时奖励做均值方差归一化或至少缩放到稳定区间。我在实际开发某强化学习Demo时的体会是凡事从表格版开始调试再换神经网络版。你只有知道表格版的正确行为是什么才能判断神经网络版的loss和曲线是不是出了问题。白手起家就跑DQN你会连“该长成什么样子”的基准都没有调试难度会陡增。7.5 建议的学习路线参考最后给一条实在的学习顺序适合大多数零基础的朋友手动推导网格世界里的 (V(s)) 和 (Q(s,a))不必推太深至少知道公式里每一项对应哪个变量。用Python实现TD(0)和Q-learning调通上面的示例代码打印每个状态的Q表。给环境加点随机性比如风、滑动、起点不固定观察MC、TD、Q-learning的表现差异。再去看策略梯度、Actor-Critic、DQN动手写DQN测试CartPole或类似任务。这套路看起来慢但能省掉后面大量的弯路。我自己带过不少人学习强化学习凡是先啃完表格型算法再看深度强化学习的普遍比直接跑大模型Demo的人理解扎实得多。这个主题往下延伸还可以做多步TD比如TD((\lambda))、Double Q-learning、优先级经验回放等。从初学者角度建议先把今天讲的三个算法练到“闭着眼能默写”的程度再谈扩展。后面有机会我可以再写一篇从Q-learning到DQN的过渡实战把经验回放和目标网络的部分拆细了聊。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑