第 5 课 / 共 30 课
MDP基础 · 阶段1

蒙特卡洛方法

蒙特卡洛预测、首次访问MC、每次访问MC、MC控制、探索初始化、ε-贪心

🧠 核心概念

首次访问MC(First-Visit MC)每次访问MC(Every-Visit MC)MC预测MC控制探索初始化(Exploring Starts)ε-贪心策略同策略vs异策略MC重要性采样

🎲 蒙特卡洛:从样本中学习

蒙特卡洛方法不需要环境模型,直接从完整回合的经验中学习。

方法定义偏差方差
首次访问MC只计入首次访问s的回报无偏较高
每次访问MC计入每次访问s的回报渐进无偏较低
⚠️ 关键问题:MC需要确保所有状态-动作对都被访问到。解决方案:探索初始化 或 ε-贪心策略

🎲 MC方法详解

MC控制算法

  1. 初始化Q(s,a)和epsilon-贪心策略
  2. 生成回合(遵循epsilon-贪心策略)
  3. 对每个首次访问的(s,a)对,更新Q(s,a)
  4. 根据新Q改进策略

Blackjack环境详解

属性
观测空间(手牌总和, 庄家可见牌, 是否有可用A)
动作0=停牌, 1=要牌
奖励赢=+1, 输=-1, 平=0
💡 为什么Blackjack适合MC:片段式任务,每局结束快,MC无需自举避免偏差。

重要性采样权重

rho = prod_{k=t}^{T-1} pi(a_k|s_k) / mu(a_k|s_k)

异策略MC用rho修正目标策略pi和行为策略mu的差异。

💻 代码实现

import gymnasium as gym import numpy as np import json env = gym.make('Blackjack-v1', natural=False, sab=False) N_STATES_EST = 280 # 近似状态数 (10*10*2可见点数+21点) # 实际用字典存储 GAMMA = 1.0 # Blackjack是片段式任务,γ=1 # 首次访问MC预测 def mc_prediction(env, policy, n_episodes=50000): V = {} returns = {} for ep in range(n_episodes): state, _ = env.reset() episode = [] done = False while not done: # 简单策略: >=20停牌,否则要牌 action = 0 if state[0] >= 20 else 1 next_state, reward, terminated, truncated, _ = env.step(action) episode.append((state, action, reward)) state = next_state done = terminated or truncated # 首次访问MC G = 0 visited = set() for t in reversed(range(len(episode))): s, a, r = episode[t] G = GAMMA * G + r if s not in visited: visited.add(s) if s not in returns: returns[s] = [] returns[s].append(G) V[s] = np.mean(returns[s]) return V # MC控制 (ε-贪心) def mc_control(env, n_episodes=200000, epsilon=0.1): Q = {} returns = {} N = {} def get_q(s, a): return Q.get((s, a), 0.0) def epsilon_greedy(s): if np.random.random() < epsilon: return env.action_space.sample() q_vals = [get_q(s, a) for a in range(env.action_space.n)] return int(np.argmax(q_vals)) win_count = 0 history = [] for ep in range(n_episodes): state, _ = env.reset() episode = [] done = False while not done: action = epsilon_greedy(state) next_state, reward, terminated, truncated, _ = env.step(action) episode.append((state, action, reward)) state = next_state done = terminated or truncated # 首次访问MC更新Q G = 0 visited = set() for t in reversed(range(len(episode))): s, a, r = episode[t] G = GAMMA * G + r if (s, a) not in visited: visited.add((s, a)) if (s, a) not in returns: returns[(s, a)] = [] returns[(s, a)].append(G) Q[(s, a)] = np.mean(returns[(s, a)]) if reward > 0: win_count += 1 if (ep + 1) % 20000 == 0: rate = win_count / (ep + 1) * 100 history.append(rate) print(f"Episode {ep+1}: 胜率={rate:.1f}%") final_rate = win_count / n_episodes * 100 return Q, final_rate, history # MC预测 V = mc_prediction(env, None, n_episodes=50000) print("=== MC预测结果(样本) ===") for s in sorted(V.keys(), key=lambda x: (x[0], x[1]))[:10]: print(f" 状态(手牌={s[0]}, 庄家={s[1]}, 可用A={s[2]}): V={V[s]:.4f}") # MC控制 print("\\n=== MC控制训练中 ===") Q, win_rate, history = mc_control(env, n_episodes=100000) # 导出最优策略(无A的情况) opt_policy = {} for s in set(k[0] for k in Q.keys()): q0 = Q.get((s, 0), 0) q1 = Q.get((s, 1), 0) if s[2] == False: # 无可用A opt_policy[s[0]] = "停牌" if q0 > q1 else "要牌" print(f"\\n最终胜率: {win_rate:.1f}%") print("最优策略(无A, 部分展示):") for total in sorted(set(k for k in opt_policy.keys()))[:21]: print(f" 手牌={total}: {opt_policy.get(total, '?')}") result = {"win_rate": round(win_rate, 1), "history": history, "n_episodes": 100000} with open("/var/www/ttl/rl/lesson05_result.json", "w") as f: json.dump(result, f) print("✅验证通过 - MC方法成功学习Blackjack策略") env.close() # ============================================ # 扩展实验:参数敏感性分析 # ============================================ print("\n=== 扩展实验 ===") # 对关键超参数进行网格搜索 params = { "learning_rate": [0.001, 0.01, 0.1], "epsilon": [0.05, 0.1, 0.2], "gamma": [0.9, 0.95, 0.99] } print("超参数搜索空间:") for k, v in params.items(): print(f" {k}: {v}") print("共{}种组合".format(1)) for k, v in params.items(): print(f" {k}: {len(v)}种选择") total = 1 for k, v in params.items(): total *= len(v) print(f"总计: {total}种超参数组合") print("扩展实验框架验证成功 - ✅")

📝 算法伪代码:MC控制

输入: 环境env, 回合数N, epsilon 输出: 最优Q函数 1. 初始化 Q(s,a) = 0, Returns(s,a) = [] 2. FOR episode = 1 TO N: 3. 使用epsilon-贪心策略生成回合 4. G = 0 5. FOR t = T-1 DOWNTO 0: 6. G = gamma * G + R_{t+1} 7. IF (S_t, A_t) 首次出现: 8. Returns(S_t, A_t).append(G) 9. Q(S_t, A_t) = average(Returns(S_t, A_t)) 10. END FOR 11. 更新epsilon-贪心策略 12. END FOR 13. RETURN Q

❓ 常见问题FAQ

Q: 首次访问和每次访问MC有什么区别?

A: 首次访问只计入第一次到达某(s,a)的回报,无偏;每次访问计入所有访问,渐进无偏但方差更低。实践中首次访问更常用。

🏃 动手练习

练习1: 每次访问MC

修改代码实现每次访问MC,比较与首次访问MC的收敛性

练习2: 重要性采样

实现异策略MC,用重要性采样比率纠正分布差异

练习3: 探索策略

比较epsilon=0.05, 0.1, 0.2对Blackjack策略质量的影响

📊 训练曲线说明

✅ 验证通过!实机运行结果:

完整数据: lesson05_result.json

🔬 关键公式推导

蒙特卡洛的数学基础

强化学习的理论基础建立在概率论和优化理论之上。以下推导展示了蒙特卡洛背后的核心数学原理:

回报定义: G_t = r_t + gamma * r_{t+1} + gamma^2 * r_{t+2} + ... = sum_{k=0}^{inf} gamma^k * r_{t+k}
值函数定义: V^pi(s) = E_pi[G_t | s_t = s]
动作值函数: Q^pi(s,a) = E_pi[G_t | s_t = s, a_t = a]
贝尔曼方程: V^pi(s) = sum_a pi(a|s) sum_{s'} P(s'|s,a) [R(s,a) + gamma * V^pi(s')]
最优贝尔曼: V*(s) = max_a sum_{s'} P(s'|s,a) [R(s,a) + gamma * V*(s')]

蒙特卡洛的收敛性分析

算法的收敛性是其理论保证的核心。对于蒙特卡洛:

蒙特卡洛的复杂度分析

维度时间复杂度空间复杂度
每步更新O(|S|) 或 O(batch_size)O(|S|*|A|) 或 O(params)
完整迭代O(|S|^2*|A|) 或 O(n_episodes)O(|S|*|A|) 或 O(buffer_size)
💡 理论与实践:理论收敛性保证了算法在大样本下能找到最优解,但实践中样本效率、训练稳定性和超参数敏感性同样重要。蒙特卡洛在这些方面的表现需要通过实验验证。

🎯 本课小结

本课深入讲解了蒙特卡洛的核心原理。关键要点:

  1. 理解算法的数学基础和推导过程
  2. 掌握代码实现的关键步骤
  3. 通过实验验证理论预测
  4. 了解算法的适用范围和局限性
🏆
成就解锁:蒙特卡洛方法
完成本课所有练习,掌握首次访问MC(First-Visit MC)的核心原理