4、蒙特卡洛方法:蒙特卡洛预测、首次访问与每次访问、蒙特卡洛控制、探索性初始化
好,咱们今天来聊聊蒙特卡洛方法。说实话,我第一次接触这个概念时,觉得名字挺唬人的——蒙特卡洛,听起来像是某个赌场里的神秘算法。其实说白了,它就是「用大量随机采样来逼近真实值」的一套方法。
在强化学习里,蒙特卡洛方法特别适合解决那些「有明确终止状态」的问题。比如机器人走到目标点、游戏通关、或者棋局结束。它不需要知道环境的模型,只需要跟环境交互,收集完整的轨迹数据,然后从中学习。
我个人习惯把蒙特卡洛方法分成三个核心部分:预测、控制、以及探索策略。咱们一个一个来拆解。
4.1 蒙特卡洛预测:从经验中估算价值
蒙特卡洛预测,说白了就是「用实际回报的平均值来估计状态价值」。你想想看,如果我们让机器人从同一个状态出发,跑很多次完整的轨迹,每次都能得到一个总回报。把这些回报平均一下,不就逼近了真实的状态价值吗?
我在项目中遇到过这样一个场景:让机器人在一个迷宫里找出口。每次从起点出发,走到终点算一次完整的episode。记录下每个状态之后获得的总回报,然后取平均。这就是蒙特卡洛预测的核心思想。
公式其实很简单:
V(s) = (G1 + G2 + ... + Gn) / n
其中 G 是每次episode中从状态 s 开始的总回报。n 是访问次数。
核心要点:蒙特卡洛预测只适用于「有终止状态」的任务。它必须等到一个episode完全结束,才能开始更新价值函数。这一点跟后面要学的时序差分方法完全不同。
4.2 首次访问 vs 每次访问:两种不同的统计方式
这里有个细节,很多初学者会忽略。在一个episode中,同一个状态可能会出现多次。比如机器人在迷宫里绕圈,同一个路口走了好几遍。那我们在计算平均回报时,应该怎么处理这些重复访问?
嗯,这里有两种做法:
- 首次访问蒙特卡洛(First-Visit MC):只统计每个状态在episode中第一次被访问时的回报。后续再出现,直接忽略。
- 每次访问蒙特卡洛(Every-Visit MC):每次访问都记录回报,全部参与平均。
我曾经在调试一个路径规划算法时,发现两种方法的结果差异还挺大的。首次访问的方差更小,收敛更稳定。每次访问虽然利用了更多数据,但容易引入偏差——因为同一个episode中,后续访问的回报其实跟前面的决策有相关性。
| 方法 | 优点 | 缺点 |
|---|---|---|
| 首次访问 | 无偏估计,方差小 | 数据利用率低 |
| 每次访问 | 数据利用率高 | 有偏估计,方差大 |
我的建议:如果episode长度较短,或者状态空间不大,用首次访问更稳妥。如果数据稀缺,可以考虑每次访问,但要注意监控收敛情况。
4.3 蒙特卡洛控制:从预测到决策
预测只是第一步。我们真正想要的是——找到最优策略。蒙特卡洛控制,就是利用蒙特卡洛预测来改进策略的过程。
基本思路是:
- 用当前策略生成一些episode
- 用蒙特卡洛方法评估当前策略下的动作价值 Q(s, a)
- 根据 Q 值改进策略:在每个状态选择 Q 值最大的动作
- 重复以上步骤,直到策略收敛
这其实就是「广义策略迭代」在蒙特卡洛方法中的体现。评估和改进交替进行,最终收敛到最优策略。
我记得有一次做机器人抓取任务,用蒙特卡洛控制来学习抓取策略。刚开始机器人乱抓一气,但跑了上百个episode后,它学会了先调整姿态再抓取——这就是策略改进的效果。
4.4 探索性初始化:一个关键的假设
这里有个坑,我必须提醒你。蒙特卡洛控制有一个前提:每个状态-动作对都必须被访问足够多次。否则,你没法准确估计它的 Q 值。
但问题是,如果策略一开始就选择某个动作,其他动作可能永远没机会被尝试。这就陷入了「探索-利用困境」。
解决方案之一就是——探索性初始化。说白了,就是在每个episode开始时,随机选择一个状态和动作作为起点。这样能保证所有状态-动作对都有机会被访问到。
我曾经踩过的坑:有一次做路径规划,我忘了做探索性初始化。结果算法收敛到了一个次优策略——机器人只会走一条固定的路,完全不知道旁边还有更短的路径。后来加上探索性初始化,效果立竿见影。
不过要注意,探索性初始化只适用于那些「可以从任意状态开始」的任务。如果环境不允许随意设置初始状态,那就得用其他探索策略,比如 ε-贪婪策略。
4.5 一个小例子:用蒙特卡洛方法学走迷宫
咱们来看一个简单的代码示例,感受一下蒙特卡洛预测是怎么工作的:
# 伪代码:首次访问蒙特卡洛预测
def first_visit_mc_prediction(env, policy, num_episodes):
V = defaultdict(float) # 状态价值函数
returns = defaultdict(list) # 每个状态的回报列表
for _ in range(num_episodes):
episode = generate_episode(env, policy)
G = 0
visited = set()
# 从后往前遍历episode
for t in range(len(episode)-1, -1, -1):
state, action, reward = episode[t]
G = reward + gamma * G
# 首次访问检查
if state not in visited:
visited.add(state)
returns[state].append(G)
V[state] = mean(returns[state])
return V
这段代码的核心逻辑很简单:生成episode,从后往前累加回报,首次遇到的状态才更新。你想想看,为什么从后往前?因为这样能高效地计算每个时间步的回报。
关键点总结:
- 蒙特卡洛方法不需要环境模型,直接从经验中学习
- 首次访问 vs 每次访问:前者无偏但数据利用率低,后者相反
- 蒙特卡洛控制 = 蒙特卡洛预测 + 策略改进
- 探索性初始化是保证所有动作都被探索的关键手段
好了,蒙特卡洛方法就讲到这里。下一章咱们会聊时序差分学习,那是一种更高效、更灵活的方法。到时候你会发现,蒙特卡洛和时序差分各有各的用武之地。