ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

强化学习入门:蒙特卡洛方法原理与Python实现

强化学习入门:蒙特卡洛方法原理与Python实现 如果你正在学习强化学习大概率已经接触过“基于模型的动态规划”——状态转移概率已知价值函数可以用贝尔曼方程直接求解。但真实项目里这种理想条件几乎不存在。机器人的关节摩擦无法精确建模游戏环境的规则可能只对玩家隐藏一部分交易市场的状态转移更是无法用一张概率表描述。这时候你需要换一种思路不再试图推导环境的完整模型而是直接与环境交互、收集样本从样本中估计价值。蒙特卡洛方法Monte Carlo Method就是这种“无模型”model-free思路的第一块基石。它名字听起来很数学核心思想却非常朴素用大量随机采样的平均值来逼近真实期望。放到强化学习里就是让智能体完整地跑完若干局游戏记录每局从某个状态出发最终获得了多少回报然后把这些回报取平均作为该状态的价值估计。这篇笔记是“2026新版强化学习从入门到精通”系列的第17篇正式进入蒙特卡洛方法。文章会讲清楚三件事第一为什么动态规划之后必须学蒙特卡洛第二首次访问first-visit和每次访问every-visit两种预测模式有什么区别第三如何用 Python 从零实现蒙特卡洛预测和蒙特卡洛控制并在一个标准环境里跑出结果。读完之后你能动手写出第一个不依赖环境模型的强化学习算法也能理解后续时序差分TD方法到底改进了什么。1. 动态规划的最大局限环境模型从哪来在蒙特卡洛之前系列文章里讨论的强化学习问题主要是用动态规划DP求解的。动态规划的核心是贝尔曼方程它要求我们知道环境的完整模型也就是状态转移概率 (P(s|s,a)) 和奖励函数 (R(s,a,s))。有了这两样东西才能做策略评估policy evaluation和策略改进policy improvement的迭代。但这里有一个很实际的问题状态转移概率在绝大多数真实场景里是拿不到的。举几个例子推荐系统用户看到推荐内容后会不会点击这个概率不是一个固定表格它取决于用户偏好、当前时间、内容质量甚至网络加载速度机器人控制电机输出力矩后机械臂末端实际到达的位置受负载、磨损、温度影响很难写出精确的转移概率棋类游戏围棋的状态转移只取决于对手策略但对手策略是未知的无法预先写成静态概率分布。动态规划要求先有模型再求解现实往往是模型不可知只能通过与真实环境不断交互来获得经验。这就是“无模型强化学习”model-free reinforcement learning出现的原因。它的基本逻辑是不建模环境只用环境反馈的样本状态、动作、奖励来学习。蒙特卡洛方法正是无模型强化学习里最自然、也最容易理解的一种。它不要求任何先验知识只需要智能体能够在一个任务里完整走到结束并通过采样统计出真实的价值。许多初学者会在这一节产生一个误区认为蒙特卡洛只是“用随机数做模拟”的数学工具和强化学习没关系。实际上随机模拟只是手段在强化学习里它切换成了“与真实环境交互并收集样本”本质目标仍然是估计价值函数和寻找最优策略。2. 蒙特卡洛方法的核心思想与数学基础蒙特卡洛方法并不是某个具体算法的名字而是一大类“用随机采样解决确定性问题”的方法。它的名字来源于摩纳哥的蒙特卡洛赌场因为赌场里的轮盘、骰子天然就是随机过程而这种方法的核心就是随机数。2.1 大数定律平均值收敛于期望严格支撑蒙特卡洛方法的数学定理是大数定律Law of Large Numbers。它的直观含义是当独立同分布的随机样本数量足够多时样本均值会依概率收敛到随机变量的期望。用公式表示假设 (X_1, X_2, ..., X_n) 是从某一分布中独立采样得到的随机变量则[ \frac{1}{n}\sum_{i1}^n X_i \rightarrow \mathbb{E}[X] ]当 (n \rightarrow \infty) 时。这个性质在强化学习里意味着什么如果每次完整回合episode结束时我们都能拿到一批从某一状态出发的回报样本那么这批回报的平均值就无限接近该状态的真实价值。为了更直观地理解可以先做一个经典实验用蒙特卡洛方法估算圆周率 (\pi)。在正方形内随机撒点统计落在内切圆中的比例。当样本量从1000增加到100万时估算结果会越来越接近 3.14159。这个实验看起来和强化学习毫无关系但背后的思想完全一致用大量随机采样逼近真实值。2.2 强化学习中的蒙特卡洛从概率表到回报样本在强化学习语境下蒙特卡洛方法做的是这样一件事环境模型未知但我们能让智能体在环境中执行动作并推进到回合结束每个完整回合都能从某个初始状态开始产生一条轨迹 [ S_0, A_0, R_1, S_1, A_1, R_2, ..., S_T ]对回合中出现的每一个状态 (s)计算从这个状态之后累计的回报return [ G_t R_{t1} \gamma R_{t2} \gamma^2 R_{t3} ... ]用多个回合中同一状态的回报平均值来估计状态价值 [ V(s) \approx \frac{1}{N(s)} \sum_{i1}^{N(s)} G_i(s) ]这里面有一个非常重要的区别动态规划用的是“期望”需要模型蒙特卡洛用的是“样本均值”只需要采样。当样本量足够大时两者会收敛到同一个结果——这正好是蒙特卡洛方法不需要环境模型的数学基础。2.3 蒙特卡洛方法在强化学习里的基本条件不是所有MDP问题都能直接用蒙特卡洛方法求解它有两个适用条件必须是分幕式任务episodic task智能体与环境的交互能够分成一个个有终止状态回合例如游戏到终局、迷宫走到出口。对于没有明确终止状态的持续性任务需要先改造成分幕形式或者换用时序差分方法。必须能观察到完整回合因为计算回报 (G_t) 需要知道未来所有奖励所以必须等到回合结束才能更新更新是“回合级”的而不是“步级”的。第一个条件把蒙特卡洛和后续的时序差分方法清晰地区分开TD算法可以在每一步之后立即更新不必等回合结束蒙特卡洛必须等回合结束再更新。这种时间粒度的差异会直接影响到样本效率、方差和收敛速度。3. 强化学习中的蒙特卡洛两种访问模式与控制策略理解了核心思想后接下来要把蒙特卡洛落到强化学习的两个核心任务上预测prediction和控制control。预测解决“给定一个策略估计状态的价值”控制解决“如何找到最优策略”。3.1 首次访问蒙特卡洛与每次访问蒙特卡洛在同一个回合中某个状态可能被访问多次。例如在一个网格世界任务里智能体可能会反复经过同一格。此时计算回报有两种模式首次访问蒙特卡洛first-visit MC只使用该状态在回合中第一次被访问时后续的回报进行平均。也就是说一个回合最多给一个状态贡献一个回报样本。每次访问蒙特卡洛every-visit MC该状态在回合中每次被访问时都分别计算一次回报并参与平均。一个回合可能给同一个状态贡献多个回报样本。两种模式都能在大数定律下收敛到真实状态价值但性质不同对比维度首次访问 MC每次访问 MC样本利用率一个回合对同一状态只记一次同一状态每次访问都记录偏差无偏估计每笔样本间存在关联但同样收敛方差方差相对更大经验上方差略低实现复杂度需要标记回合内已访问状态实现更简单在入门阶段通常推荐先实现首次访问版本因为它理论更简洁、更容易证明收敛也是 Sutton 教材里默认的经典实现。每次访问版本在后续的批量更新和函数近似方法里更常见。3.2 蒙特卡洛控制如何从价值到策略预测解决的是“这个策略到底好不好”控制解决的是“如何找到最好的策略”。蒙特卡洛控制遵循的还是广义策略迭代GPI框架策略评估 策略改进不断交替。但这里有一个新问题如果我们永远只选择当前价值函数下的最优动作那么很多状态永远不会被访问到估计就会失真。比如某个状态明明可以通过走“探索路线”找到更高回报但因为贪心策略不选它算法永远不知道这个选项更好。为了让控制算法能够不断发现更好的动作需要给动作选择引入随机性。两种经典做法探索性初始化exploring starts强制每个回合的状态和动作都从不同的起点开始保证所有状态-动作对都有概率被访问到。这种方法理论简单但现实中很难做到——你无法控制真实环境的初始状态。epsilon-贪婪策略(\varepsilon)-greedy以较小的概率 (\varepsilon) 随机选择一个动作以 (1-\varepsilon) 的概率选择当前价值最高的动作。这一策略简单且实用是入门阶段最推荐的探索方式。在蒙特卡洛控制中评估的对象从状态价值 (V(s)) 扩展到了动作价值 (Q(s,a))。因为控制过程需要比较同一状态下不同动作的优劣只有估计出每个状态-动作对的价值 (Q(s,a))才能知道哪个动作更优。如果只看状态价值 (V(s))就无法在没有环境模型的情况下推导出最优动作。3.3 蒙特卡洛方法在强化学习算法体系里的位置从整个强化学习算法家族来看蒙特卡洛方法是一个重要的分水岭。在它之前动态规划是“有模型方法”在它之后时序差分TD、Q-learning、SARSA 等算法都是“无模型方法”。蒙特卡洛和 TD 都从样本中学习但蒙特卡洛必须等回合结束才能更新TD 则可以每步更新。理解蒙特卡洛的“回合级更新”逻辑能帮助你更好地理解为什么 TD 方法在样本效率上通常优于蒙特卡洛以及为什么蒙特卡洛的梯度估计方差较大。4. 环境准备与实验设计从这一节开始进入实操。为了让蒙特卡洛方法有直观的落地场景我们需要一个环境。在强化学习入门里Blackjack21点是最经典的蒙特卡洛实验环境之一原因有三它是分幕式任务每局结束很快符合蒙特卡洛的更新要求状态空间是离散的且维度不高可视化容易它天然适合用首次访问蒙特卡洛Sutton 的经典教材也用它作为主示例。4.1 安装环境本文使用 Python 和 GymnasiumGym 的维护版本。Gymnasium 提供了现成的 Blackjack 环境不需要自己实现游戏逻辑。pip install gymnasium pip install matplotlib版本方面Gymnasium 目前是持续维护的库建议直接安装最新的稳定版本。Python 建议使用 3.9 及以上版本。安装完成后可以用下面的代码验证环境是否可用import gymnasium as gym env gym.make(Blackjack-v1, naturalTrue, sabFalse) obs, info env.reset() print(初始状态:, obs)初始状态是一个三元组分别代表玩家当前点数、庄家明牌点数、玩家是否有可用Ausable ace。如果输出正常说明环境已经就绪。4.2 理解 Blackjack 环境的状态与动作状态((\text{玩家点数}, \text{庄家明牌点数}, \text{是否有可用A}))。动作0 表示“要牌”hit1 表示“停牌”stick。奖励如果玩家点数超过21则立即结束奖励为 -1双方停牌后比较点数玩家赢为 1输为 -1平局为 0。自然21点如果玩家前两张牌恰好是21点且庄家不是自然21点直接获胜奖励为 1。这里“可用A”是指手中有可当作11点计算的A。如果A被当作1点了就是不可用A。这个细节对状态表示很重要需要保留否则状态不完整价值估计会失真。5. 从零实现蒙特卡洛预测5.1 实现一个简单的固定策略预测的第一步是给定一个策略。为了演示可以先实现一个非常简单的固定策略玩家点数小于 18 就要牌大于等于 18 就停牌。这个策略不一定是好策略但足够用来测试预测算法的正确性。import random import gymnasium as gym import numpy as np def simple_policy(state): 简单的固定策略点数小于 18 要牌否则停牌 player_sum, dealer_card, usable_ace state return 0 if player_sum 18 else 1 # 0 hit, 1 stick5.2 首次访问蒙特卡洛预测的完整实现接下来实现核心算法。我们需要维护两个字典一个记录每个状态累积的总回报一个记录每个状态被首次访问的次数。对每个回合先执行完整的轨迹然后反向遍历每一步计算回报并更新状态价值。def generate_episode(env, policy): 执行一个回合返回 [(state, action, reward)] 列表 episode [] obs, _ env.reset() done False while not done: action policy(obs) next_obs, reward, terminated, truncated, _ env.step(action) episode.append((obs, action, reward)) obs next_obs done terminated or truncated return episode def first_visit_mc_prediction(env, policy, num_episodes, gamma1.0): 首次访问蒙特卡洛策略评估 returns_sum {} returns_count {} V {} for _ in range(num_episodes): episode generate_episode(env, policy) visited_states set() G 0 # 从回合末尾反向计算回报 for t in reversed(range(len(episode))): state, action, reward episode[t] G reward gamma * G if state not in visited_states: visited_states.add(state) returns_sum[state] returns_sum.get(state, 0) G returns_count[state] returns_count.get(state, 0) 1 V[state] returns_sum[state] / returns_count[state] return V这段代码里有一个容易忽略的细节为什么要从回合末尾反向遍历因为正向遍历时我们无法预知未来的累计回报反向遍历时可以逐步把后面的奖励累加进来保证每一步的 (G) 都是完整的回报。对首次访问模式visited_states集合保证了每个状态在这个回合里只被统计一次。5.3 为什么使用回报的反向累积假设一个回合的轨迹是 ((s_0, a_0, r_1), (s_1, a_1, r_2), (s_2, a_2, r_3))从末尾开始第 2 步(G_2 r_3)第 1 步(G_1 r_2 \gamma r_3)第 0 步(G_0 r_1 \gamma r_2 \gamma^2 r_3)这样每一步的回报都包含了未来的所有奖励和回报定义完全一致。如果你从前往后计算就需要额外维护一个队列来记录未来的奖励代码会复杂很多。反向遍历是解决这一问题的标准做法。6. 从零实现蒙特卡洛控制预测做完之后进入控制阶段。控制的最终目标是找到最优策略而不是评估固定策略。6.1 epsilon-贪婪策略的蒙特卡洛控制这里实现的是带 (\varepsilon)-贪婪探索的首次访问蒙特卡洛控制。它会同时维护动作价值 (Q(s,a))并在每个回合结束后根据当前 (Q) 值更新策略。def make_epsilon_greedy_policy(Q, epsilon, num_actions2): 根据 Q 值表生成 epsilon-greedy 策略函数 def policy(state): if random.random() epsilon: return random.randint(0, num_actions - 1) else: q_values [Q.get((state, a), 0.0) for a in range(num_actions)] max_q max(q_values) # 如果有多个动作拥有相同的最大值随机选一个 best_actions [a for a, q in enumerate(q_values) if q max_q] return random.choice(best_actions) return policy def mc_control_epsilon_greedy(env, num_episodes, gamma1.0, epsilon0.1): 首次访问蒙特卡洛控制 Q {} returns_sum {} returns_count {} for _ in range(num_episodes): policy make_epsilon_greedy_policy(Q, epsilon) episode generate_episode(env, policy) visited_state_actions set() G 0 for t in reversed(range(len(episode))): state, action, reward episode[t] G reward gamma * G sa_pair (state, action) if sa_pair not in visited_state_actions: visited_state_actions.add(sa_pair) returns_sum[sa_pair] returns_sum.get(sa_pair, 0) G returns_count[sa_pair] returns_count.get(sa_pair, 0) 1 Q[sa_pair] returns_sum[sa_pair] / returns_count[sa_pair] return Q注意一个关键点policy是在每个回合开始时重新生成的。这意味着当前回合的探索策略会基于此前所有回合积累的 (Q) 值既能利用已学到的知识又能保留一定的随机性继续探索。如果把policy放在循环外面策略就会一直是初始随机策略无法完成策略迭代。6.2 从 Q 表推导最终策略控制完成后从中提取确定性策略对每个状态选择 (Q(s,a)) 最大的动作。def derive_policy_from_Q(Q): policy {} state_keys set(s for s, _ in Q.keys()) for state in state_keys: q_vals [Q.get((state, a), 0.0) for a in range(2)] policy[state] int(np.argmax(q_vals)) return policy这个策略就是算法最终学到的最优策略。在 Blackjack 环境中可以用它来模拟新的对局验证胜率。7. 运行结果与效果验证7.1 运行预测并画图为了观察预测结果是否正确可以把每个状态的价值函数绘制成三维曲面图。这里固定“没有可用A”的情况绘制玩家点数与庄家明牌对应的价值。import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D env gym.make(Blackjack-v1, naturalTrue, sabFalse) V first_visit_mc_prediction(env, simple_policy, num_episodes50000, gamma1.0) # 提取无可用A的状态价值 states_plot [] values_plot [] for (player, dealer, usable_ace), v in V.items(): if not usable_ace and 11 player 21: states_plot.append((player, dealer)) values_plot.append(v) player_scores [p for p, d in states_plot] dealer_cards [d for p, d in states_plot] fig plt.figure(figsize(10, 6)) ax fig.add_subplot(111, projection3d) ax.scatter(player_scores, dealer_cards, values_plot, cvalues_plot, cmapviridis, s30) ax.set_xlabel(Player Sum) ax.set_ylabel(Dealer Card) ax.set_zlabel(Value) plt.title(First-Visit MC Prediction: No Usable Ace) plt.show()运行后你会看到价值曲面呈现出合理的规律玩家点数低时价值为负点数接近 20 或 21 时价值为正这和 21 点游戏的直觉完全吻合。如果出现大面积乱数值优先检查回报计算和状态标记逻辑。7.2 运行控制并统计收益控制算法的验证可以看两个指标平均每一回合的累计奖励是否在训练过程中上升以及最终策略是否合理。env gym.make(Blackjack-v1, naturalTrue, sabFalse) total_rewards [] for _ in range(100): Q mc_control_epsilon_greedy(env, num_episodes10000, gamma1.0, epsilon0.1) policy derive_policy_from_Q(Q) # 用最终策略独立测试 1000 局 wins 0 total 1000 for _ in range(total): obs, _ env.reset() done False while not done: action policy.get(obs, 0) # 默认要牌 obs, reward, terminated, truncated, _ env.step(action) done terminated or truncated if reward 0: wins 1 total_rewards.append(wins / total) print(平均胜率:, np.mean(total_rewards))如果算法实现正确收益率会在 0.38 到 0.42 附近波动Blackjack 扣除庄家优势后最优策略的胜率大致在这个范围。如果你的胜率只有 0.2 左右通常说明策略没有学到最优常见原因是探索参数设置不当或回报计算错误。7.3 成功与否的判断标准预测阶段价值曲面应呈现单调合理的趋势而不是随机噪声控制阶段独立测试胜率应显著高于纯随机策略纯随机胜率大概在 0.28 左右训练曲线如果记录每批回合的平均回报应有整体上升趋势后期趋于平稳。如果运行结果不理想第一步检查反向遍历计算回报的逻辑第二步检查visited_state_actions标记的位置第三步检查策略生成函数是否在每个回合都根据最新 (Q) 值重新生成。8. 常见问题与排查思路蒙特卡洛方法的代码量不大但新手容易在几个隐蔽细节上出错。这里整理出最常见的五类问题。问题现象可能原因排查方式解决方案价值函数长时间不收敛Gamma 参数设置不当或回报计算使用了未经过折扣的累计奖励打印若干状态的回报样本人工核算几步确认 G 的更新公式G reward gamma * G同一状态出现多个不同价值使用了每次访问模式但没有意识到状态在一个回合内可能多次出现在回合内打印状态序列确认是否采用首次访问标记若用每次访问则接受其收敛性质控制算法学到固定动作、无探索策略函数在循环外生成或 epsilon 设置过小检查策略生成位置打印每个回合的动作分布确保每个回合都基于最新 Q 重新生成 epsilon-greedy 策略独立测试胜率很低训练回合数不足或 epsilon 过大导致策略始终接近随机统计每个状态-动作对的访问频次增大回合数动态降低 epsilonBlackjack 环境初始状态为 None使用的 API 与 Gymnasium 版本不一致打印 env.reset() 返回值使用 obs, info env.reset() 并确认环境名排查时有一个通用的思路把问题拆成“环境”和“算法”两部分。先用一个最简单的策略比如固定动作跑通环境确认环境交互正常再用较大训练轮数专门验证算法避免两个因素混在一起更难定位。9. 最佳实践与工程建议9.1 关于探索策略如果使用固定 epsilon一般设置在 0.1 到 0.2 之间太小会过早收敛到次优策略太大会导致策略接近随机。更推荐的做法是让 epsilon 随训练进度衰减开始阶段大探索后期小探索。例如设定初始 epsilon0.5每 1000 回合乘以 0.95最终保持在 0.01。探索性初始化在 Blackjack 这类环境里很容易实现但真实项目中往往不可行所以建议直接以 epsilon-greedy 作为默认探索方案它更接近实际约束。9.2 关于训练稳定性蒙特卡洛方法的方差比较大如果训练曲线剧烈波动不必立刻怀疑代码错误增加样本量通常是第一选择。如果资源有限可以采用“批量平均”每跑完 1000 个回合计算一次平均回报而不是每个回合都打点。对状态空间较大的问题最好不要用字典存储 (Q(s,a))应该改用二维数组或更紧凑的数据结构避免哈希查找开销。9.3 关于学习过程的观察指标建议在训练过程中记录三类数据每个回合的累计奖励用于观察策略是否变好状态-动作对的访问覆盖率用于判断探索是否充分代表性状态的价值变化曲线例如 Blackjack 中“玩家20点 vs 庄家6点”的价值应接近正值且稳定。这些比单纯看最终胜率更能帮助你诊断问题出现在哪个环节。9.4 关于环境 API 的兼容性Gymnasium 沿用了较新的 reset/step 返回格式reset()返回(obs, info)step()返回(obs, reward, terminated, truncated, info)。如果你之前用过老版本 Gym会发现返回值数量不同。在写通用代码时建议统一按较新的 API 格式处理并在代码开头做环境边界检查。10. 总结与下一步这篇文章从“动态规划需要环境模型”的现实局限出发引出了蒙特卡洛方法。它用随机采样的均值替代期望用完整回合的回报估计状态价值虽然等待回合结束才能更新却因此摆脱了对状态转移概率的依赖成为无模型强化学习的起点。文中通过 Blackjack 环境完整实现了首次访问蒙特卡洛预测和基于 epsilon-greedy 的蒙特卡洛控制并给出了验证标准。掌握这套代码你已经具备了在未知环境中训练一个简单智能体的能力。如果对这个主题继续深挖建议按以下顺序推进尝试把 epsilon 衰减策略加进控制算法观察胜率变化在同一个 Blackjack 环境上手动计算一两个状态的理论价值与蒙特卡洛结果对比换成每次访问蒙特卡洛实现一遍对比两者在训练曲线上的差异再往后学习时序差分TD和 Q-learning你会明显感受到“每步更新”和“回合结束更新”的效率差异。蒙特卡洛方法最值得记住的一句话是当环境模型不可知时经验本身就是最好的模型。把所有采样到的回报取平均就是对这个未知世界最诚实的估计。下一篇文章会进一步讨论蒙特卡洛方法的方差缩减、重要性采样和离策略学习这些是连接蒙特卡洛与更现代强化学习算法的关键桥梁。
返回列表