强化学习中的蒙特卡洛方法:从采样到策略优化详解

引言

在强化学习的世界里,蒙特卡洛(Monte Carlo, MC)方法就像一位"实践派"探险家——它不依赖环境模型,而是直接从完整的经验轨迹中学习。与动态规划(DP)的"全知全能"不同,MC方法仅凭采样数据就能估计价值函数、优化策略,奠定了无模型强化学习的基石。

本文将深入剖析MC方法的核心思想、算法实现及其在RL中的独特地位。


目录


一、核心思想:从模型驱动到数据驱动

1.1 蒙特卡洛的基本思想

核心公式 G t = R t + 1 + γ R t + 2 + γ 2 R t + 3 + ⋯ G_t = R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + \cdots Gt=Rt+1+γRt+2+γ2Rt+3+

MC方法的核心很简单:用实际观测到的回报(Return)来估计价值函数。关键特点:

  • 无模型:不需要状态转移概率 P ( s ′ ∣ s , a ) P(s'|s,a) P(ss,a)
  • 基于 episodes:必须等到 episode 结束才能计算
  • 经验平均:通过多次采样取平均来逼近期望

1.2 与动态规划的本质区别

对比维度 动态规划(DP) 蒙特卡洛(MC)
知识要求 需要完整MDP模型 仅需采样数据
更新方式 自举(Bootstrapping) 采样回报
计算对象 期望 经验平均
适用范围 小规模、已知模型 大规模、未知模型

二、蒙特卡洛预测:估计价值函数

2.1 首次访问(First-Visit) vs 每次访问(Every-Visit)

首次访问MC:在episode中只使用状态s的第一次出现

V ( s ) = 1 N ( s ) ∑ i = 1 N ( s ) G t ( i ) V(s) = \frac{1}{N(s)} \sum_{i=1}^{N(s)} G_t^{(i)} V(s)=N(s)1i=1N(s)Gt(i)

每次访问MC:使用状态s的所有出现

V ( s ) = 1 N ( s ) ∑ i = 1 N ( s ) ∑ t : S t = s G t ( i ) V(s) = \frac{1}{N(s)} \sum_{i=1}^{N(s)} \sum_{t: S_t=s} G_t^{(i)} V(s)=N(s)1i=1N(s)t:St=sGt(i)

2.2 增量式更新

为了避免存储所有回报,使用增量更新:

N ( s ) ← N ( s ) + 1 V ( s ) ← V ( s ) + 1 N ( s ) ( G − V ( s ) ) \begin{align} N(s) &\leftarrow N(s) + 1 \\ V(s) &\leftarrow V(s) + \frac{1}{N(s)}(G - V(s)) \end{align} N(s)V(s)N(s)+1V(s)+N(s)1(GV(s))

指数平均版本(使用学习率 α \alpha α):

V ( s ) ← V ( s ) + α ( G − V ( s ) ) V(s) \leftarrow V(s) + \alpha(G - V(s)) V(s)V(s)+α(GV(s))

# 首次访问MC策略评估
def mc_policy_evaluation(env, policy, num_episodes=10000, gamma=0.9):
    V = defaultdict(float)
    N = defaultdict(int)
    
    for _ in range(num_episodes):
        episode = generate_episode(env, policy)
        states, actions, rewards = zip(*episode)
        
        # 计算每个时间步的回报
        G = 0
        visited_states = set()
        
        for t in reversed(range(len(episode))):
            state = states[t]
            G = gamma * G + rewards[t]
            
            # 首次访问检查
            if state not in visited_states:
                visited_states.add(state)
                N[state] += 1
                # 增量更新
                V[state] += (G - V[state]) / N[state]
    
    return V

三、蒙特卡洛控制:寻找最优策略

3.1 通用策略迭代框架

MC控制遵循"评估-改进"循环,但有两个关键挑战:

  1. 探索问题:需要保证所有状态-动作对都被访问
  2. 预测问题:策略改进需要准确的Q值

3.2 ε-贪婪策略

π ( a ∣ s ) = { 1 − ϵ + ϵ ∣ A ( s ) ∣ if  a = arg ⁡ max ⁡ a ′ Q ( s , a ′ ) ϵ ∣ A ( s ) ∣ otherwise \pi(a|s) = \begin{cases} 1 - \epsilon + \frac{\epsilon}{|A(s)|} & \text{if } a = \arg\max_{a'} Q(s,a') \\ \frac{\epsilon}{|A(s)|} & \text{otherwise} \end{cases} π(as)={1ϵ+A(s)ϵA(s)ϵif a=argmaxaQ(s,a)otherwise

3.3 GLIE条件

Greedy in the Limit with Infinite Exploration:

  • 所有状态-动作对被无限次访问
  • 策略最终收敛到贪婪策略
# MC控制(on-policy)
def monte_carlo_control(env, num_episodes=500000, gamma=0.9):
    Q = defaultdict(lambda: np.zeros(env.action_space.n))
    N = defaultdict(lambda: np.zeros(env.action_space.n))
    
    # ε-贪婪策略
    def epsilon_greedy_policy(state, epsilon=0.1):
        if random.random() < epsilon:
            return env.action_space.sample()
        else:
            return np.argmax(Q[state])
    
    epsilon = 1.0
    for episode_num in range(1, num_episodes + 1):
        # GLIE:ε逐渐衰减
        epsilon = 1.0 / episode_num
        
        # 生成episode
        episode = []
        state = env.reset()
        done = False
        
        while not done:
            action = epsilon_greedy_policy(state, epsilon)
            next_state, reward, done, _ = env.step(action)
            episode.append((state, action, reward))
            state = next_state
        
        # 首次访问MC更新
        G = 0
        visited_state_action_pairs = set()
        
        for t in reversed(range(len(episode))):
            state, action, reward = episode[t]
            G = gamma * G + reward
            sa_pair = (state, action)
            
            if sa_pair not in visited_state_action_pairs:
                visited_state_action_pairs.add(sa_pair)
                N[state][action] += 1
                Q[state][action] += (G - Q[state][action]) / N[state]
    
    # 提取最终策略
    policy = {s: np.argmax(a_values) for s, a_values in Q.items()}
    return policy, Q

四、On-policy vs Off-policy

4.1 重要性采样

Off-policy MC使用行为策略(behavior policy)生成的数据来评估目标策略(target policy)。

重要性采样比

ρ t : T − 1 = ∏ k = t T − 1 π ( A k ∣ S k ) b ( A k ∣ S k ) \rho_{t:T-1} = \prod_{k=t}^{T-1} \frac{\pi(A_k|S_k)}{b(A_k|S_k)} ρt:T1=k=tT1b(AkSk)π(AkSk)

目标策略价值估计

V π ( s ) ≈ ∑ i = 1 N ( s ) ρ t : T − 1 ( i ) G t ( i ) N ( s ) V_\pi(s) \approx \frac{\sum_{i=1}^{N(s)} \rho_{t:T-1}^{(i)} G_t^{(i)}}{N(s)} Vπ(s)N(s)i=1N(s)ρt:T1(i)Gt(i)

4.2 加权重要性采样

解决普通重要性采样方差大的问题:

V ( s ) ← V ( s ) + W C ( s ) ( G − V ( s ) ) V(s) \leftarrow V(s) + \frac{W}{C(s)}(G - V(s)) V(s)V(s)+C(s)W(GV(s))

# Off-policy MC控制(基于重要性采样)
def off_policy_mc_control(env, num_episodes=100000, gamma=0.9):
    Q = defaultdict(lambda: np.zeros(env.action_space.n))
    C = defaultdict(lambda: np.zeros(env.action_space.n))
    
    # 目标策略(贪婪)
    target_policy = defaultdict(lambda: 0)
    
    # 行为策略(均匀随机)
    behavior_policy = lambda s: env.action_space.sample()
    
    for _ in range(num_episodes):
        episode = generate_episode_with_policy(env, behavior_policy)
        states, actions, rewards = zip(*episode)
        
        G = 0
        W = 1.0
        
        for t in reversed(range(len(episode))):
            state, action = states[t], actions[t]
            G = gamma * G + rewards[t]
            
            # 更新累积重要性权重
            C[state][action] += W
            
            # 加权重要性采样更新
            Q[state][action] += (W / C[state][action]) * (G - Q[state][action])
            
            # 更新目标策略
            target_policy[state] = np.argmax(Q[state])
            
            # 如果行为策略不等于目标策略,则停止
            if action != target_policy[state]:
                break
            
            # 更新重要性权重
            W *= 1.0 / (1.0 / env.action_space.n)  # 均匀随机策略的概率
            
            if W == 0:
                break
    
    return target_policy, Q

五、完整代码实现:21点游戏案例

import gym
from collections import defaultdict
import numpy as np

class BlackjackMC:
    def __init__(self, env_name='Blackjack-v1'):
        self.env = gym.make(env_name)
        self.gamma = 1.0  # 无折扣
        
    def train(self, num_episodes=500000):
        Q = defaultdict(lambda: np.zeros(self.env.action_space.n))
        N = defaultdict(lambda: np.zeros(self.env.action_space.n))
        
        for episode_num in range(1, num_episodes + 1):
            if episode_num % 10000 == 0:
                print(f"Episode {episode_num}/{num_episodes}")
            
            # 生成episode(使用ε-贪婪策略)
            episode = self._generate_episode(Q, episode_num)
            
            # 首次访问MC更新
            self._update_q_values(episode, Q, N)
        
        # 提取最优策略
        policy = {state: np.argmax(actions) for state, actions in Q.items()}
        return policy, Q
    
    def _generate_episode(self, Q, episode_num):
        """生成一个完整的episode"""
        episode = []
        state = self.env.reset()
        epsilon = 1.0 / episode_num
        
        while True:
            # ε-贪婪选择
            if random.random() < epsilon:
                action = self.env.action_space.sample()
            else:
                action = np.argmax(Q[state])
            
            next_state, reward, done, _ = self.env.step(action)
            episode.append((state, action, reward))
            
            if done:
                break
            state = next_state
        
        return episode
    
    def _update_q_values(self, episode, Q, N):
        """首次访问MC更新Q值"""
        G = 0
        visited_sa = set()
        
        for t in reversed(range(len(episode))):
            state, action, reward = episode[t]
            G = self.gamma * G + reward
            sa_pair = (state, action)
            
            if sa_pair not in visited_sa:
                visited_sa.add(sa_pair)
                N[state][action] += 1
                # 增量更新
                Q[state][action] += (G - Q[state][action]) / N[state]
    
    def evaluate(self, policy, num_episodes=1000):
        """评估策略性能"""
        wins = 0
        for _ in range(num_episodes):
            state = self.env.reset()
            while True:
                action = policy[state]
                state, reward, done, _ = self.env.step(action)
                if done:
                    wins += 1 if reward > 0 else 0
                    break
        return wins / num_episodes

# 使用示例
if __name__ == "__main__":
    import random
    
    # 训练
    blackjack = BlackjackMC()
    optimal_policy, Q_values = blackjack.train(num_episodes=500000)
    
    # 评估
    win_rate = blackjack.evaluate(optimal_policy, num_episodes=10000)
    print(f"\n训练后胜率: {win_rate:.2%}")
    
    # 查看特定状态的价值
    print("\n玩家(18, dealer:5, usable_ace:False)时的Q值:")
    state = (18, 5, False)
    if state in Q_values:
        print(f"  停牌: {Q_values[state][0]:.3f}")
        print(f"  要牌: {Q_values[state][1]:.3f}")

六、优缺点与适用场景

✅ 优点

  1. 无模型:无需环境动态知识
  2. 无偏差:回报率是无偏估计
  3. 易于理解:基于简单平均
  4. 适用于 episodes 任务:如游戏、对话系统

❌ 缺点

  1. 高方差:回报G的方差较大
  2. 需要完整 episodes:不能用于连续任务
  3. 效率低:必须等待episode结束才能更新
  4. 探索困难:需要精心设计探索策略

🎯 典型应用场景

  • 棋盘游戏:围棋、象棋终局评估
  • 卡牌游戏:21点、扑克策略优化
  • 机器人路径规划:已知目标位置的导航
  • 推荐系统:完整用户会话分析

七、MC vs DP vs TD:三大方法对比

特性 蒙特卡洛(MC) 动态规划(DP) 时序差分(TD)
模型需求 无模型 需完整模型 无模型
更新目标 完整回报G 贝尔曼期望 单步回报+估计
更新时机 Episode结束 每个step 每个step
偏差/方差 无偏差/高方差 有偏差/低方差 可调节
收敛速度 较慢 较快 最快
适用性 Episodic 任意MDP 任意MDP

记忆口诀

  • MC:慢但准,像考试后总结
  • DP:快但需模型,像开挂玩家
  • TD:中庸之道,像边玩边学


参考文献

  1. Sutton, R. S., & Barto, A. G. (2018). Reinforcement Learning: An Introduction. MIT Press.
  2. Silver, D. (2015). Reinforcement Learning Course (UCL)
  3. OpenAI Gym Documentation

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐