Week 4: 蒙特卡洛方法 - 从完整经验中学习
回顾:MDP 与 最优决策
前三周我们建立了强化学习的基础框架:
- MDP (\(S\), \(A\), \(P\), \(R\), \(\gamma\)): 描述序贯决策问题。
- 策略 \(\pi\): 智能体的行为方式。
- 价值函数 (\(V_\pi\), \(Q_\pi\)): 评估策略的好坏。
- 最优价值函数 (\(V^*\), \(Q^*\)): 描述最优行为能达到的价值上限。
- Bellman 期望方程: 用于策略评估。
- Bellman 最优方程: 描述最优价值函数的性质,理论上解出它就能得到最优策略。
问题: Bellman 方程(无论是期望还是最优)都依赖于我们知道环境的模型 (Model),即状态转移概率 \(P(s' | s, a)\) 和奖励函数 \(R(s, a, s')\)。
现实挑战: 在许多实际商业问题中,我们无法精确知道环境模型 \(P\) 和 \(R\)。
- 市场如何对价格变动做出反应?(\(P\) 未知)
- 客户点击广告的具体概率是多少?(\(P\) 未知)
- 供应链中断的概率和影响?(\(P\), \(R\) 未知)
因此,我们需要无模型 (Model-Free) 的强化学习方法。这类方法不依赖于环境模型,而是直接从智能体与环境交互产生的经验 (Experience) 中学习。
本周我们将学习第一类重要的无模型方法:蒙特卡洛 (Monte Carlo, MC) 方法。
在有模型的情况下,我们可以直接使用 Bellman 期望方程来计算价值函数。以下是一个具体的可计算例子:
问题描述: 考虑一个简单的库存管理问题,状态空间为 {0,1,2}(库存量),动作空间为 {0,1}(不补货/补货)。已知状态转移概率和奖励函数如下:
- 状态转移概率 \(P(s'|s,a)\):
- 当 \(a=0\)(不补货)时:
- \(P(s'=\max(s-1,0)|s,a=0) = 0.8\)
- \(P(s'=\max(s-2,0)|s,a=0) = 0.2\)
- 当 \(a=1\)(补货)时:
- \(P(s'=\min(s+1,2)|s,a=1) = 0.7\)
- \(P(s'=\min(s+2,2)|s,a=1) = 0.3\)
- 当 \(a=0\)(不补货)时:
- 奖励函数 \(R(s,a,s')\):
- 持有成本:\(-0.1 * s'\)
- 补货成本:\(-0.5 * a\)
- 销售收益:\(+1.0 * (s - s')\)
- 总奖励:\(R(s,a,s') = 1.0*(s-s') - 0.1*s' - 0.5*a\)
- 折扣因子 \(\gamma = 0.9\)
针对库存管理例子的 Bellman 期望方程计算
def inventory_bellman_expectation():
"""
计算库存管理例子的价值函数
"""
# 定义状态和动作空间
= [0, 1, 2]
states = [0, 1]
actions
# 定义状态转移概率
= {
P 0: {
0: {0: 1.0, 1: 0.0, 2: 0.0}, # 不补货, s'=0 with prob 1.0, s'=1,2 with prob 0.0
1: {0: 0.0, 1: 0.7, 2: 0.3} # 补货, s'=1 with prob 0.7, s'=2 with prob 0.3, s'=0 with prob 0.0
},1: {
0: {0: 1.0, 1: 0.0, 2: 0.0}, # 不补货, s'=0 with prob 1.0, s'=1,2 with prob 0.0
1: {0: 0.0, 1: 0.0, 2: 1.0} # 补货, s'=2 with prob 1.0, s'=0,1 with prob 0.0
},2: {
0: {0: 0.2, 1: 0.8, 2: 0.0}, # 不补货, s'=0 with prob 0.2, s'=1 with prob 0.8, s'=2 with prob 0.0
1: {0: 0.0, 1: 0.0, 2: 1.0} # 补货, s'=2 with prob 1.0, s'=0,1 with prob 0.0
}
}
# 定义奖励函数
def R(s, a, s_prime):
return 1.0 * (s - s_prime) - 0.1 * s_prime - 0.5 * a
# 定义策略(均匀随机策略)
= {
policy 0: {0: 0.5, 1: 0.5},
1: {0: 0.5, 1: 0.5},
2: {0: 0.5, 1: 0.5}
}
# 初始化价值函数
= {s: 0 for s in states}
V = 0.9
gamma = 1e-6
theta
# 迭代更新价值函数
while True:
= 0
delta for s in states:
= V[s]
v # 计算新的价值函数
= sum(policy[s][a] * sum(P[s][a][s_prime] *
V[s] + gamma * V[s_prime])
(R(s, a, s_prime) for s_prime in states)
for a in actions)
# 更新最大变化量
= max(delta, abs(v - V[s]))
delta # 检查是否收敛
if delta < theta:
break
return V
# 示例调用
inventory_bellman_expectation()
这个例子展示了在有完整环境模型的情况下,如何具体应用 Bellman 期望方程进行价值函数的计算。
优点: 1. 精确计算: 直接使用 Bellman 期望方程可以得到精确的价值函数值,不需要通过采样来估计。 2. 收敛速度快: 通过迭代更新,通常可以在较少的迭代次数内收敛到真实值。 3. 理论保证: 在满足条件的情况下,可以保证收敛到唯一解。
缺点: 1. 需要完整环境模型: 必须知道状态转移概率 P 和奖励函数 R,这在很多实际问题中难以获得。 2. 计算复杂度高: 对于大规模状态空间,计算所有状态的价值函数会非常耗时。 3. 内存需求大: 需要存储整个状态空间的价值函数,对于高维状态空间可能不现实。 4. 无法处理连续状态空间: 仅适用于离散且有限的状态空间。
无模型预测:蒙特卡洛评估
目标: 在不知道环境模型 \(P\) 和 \(R\) 的情况下,给定一个策略 \(\pi\),如何估计其价值函数 \(V_\pi(s)\) 或 \(Q_\pi(s, a)\)?
核心思想: 利用大数定律 (Law of Large Numbers)。通过多次模拟(采样),用样本回报的平均值来估计期望回报(即价值函数)。
蒙特卡洛方法的基本流程:
- 遵循策略 \(\pi\) 与环境交互,生成大量的完整回合 (Episodes)。
- 一个回合是从某个起始状态开始,直到达到终止状态为止的一系列 \((S_0, A_0, R_1, S_1, A_1, R_2, ..., S_T)\)。
- 对于回合中出现的每个状态 \(s\) (或状态-动作对 \((s, a)\)),计算其在该回合中的实际回报 \(G_t\)。
- \(G_t = R_{t+1} + γ R_{t+2} + ... + γ^{T-t-1} R_T\)
- 将所有回合中状态 \(s\) (或状态-动作对 \((s, a)\)) 的回报收集起来。
- 用这些回报的平均值作为 \(V_{\pi}(s)\) (或 \(Q_{\pi}(s, a)\)) 的估计值。
\[ \begin{aligned} V_{\pi}(s) &\approx \text{多次回合中} G_t | S_t = s \text{的平均值} \\ Q_{\pi}(s, a) &\approx \text{多次回合中} G_t | S_t = s, A_t = a \text{的平均值} \end{aligned} \]
首次访问 (First-Visit) MC vs. 每次访问 (Every-Visit) MC
在计算状态 \(s\) (或状态-动作对 \((s, a)\)) 的平均回报时,对于一个回合中 \(s\) (或 \((s, a)\)) 可能出现多次的情况,有两种处理方式:
- 首次访问 MC (First-Visit MC):
- 对于每个回合,只计算状态 \(s\) 第一次出现时的回报 \(G_t\),并将其计入状态 \(s\) 的回报列表。
- 忽略该回合后续再次访问状态 \(s\) 时的回报。
- \(V_{\pi}(s) \approx \text{多次回合中} G_t | S_t = s \text{的平均值}\)
- 每次访问 MC (Every-Visit MC):
- 对于每个回合,状态 \(s\) 每一次出现时的回报 \(G_t\) 都被计入状态 \(s\) 的回报列表。
- \(V_{\pi}(s) \approx \text{多次回合中} G_t | S_t = s \text{的平均值}\) (与首次访问MC公式相同,区别在于计算时是否包含重复访问的回报)
- 两者在理论上都能收敛到真实的 \(V_{\pi}(s)\) (随着回合数趋于无穷)。
- 首次访问 MC 在理论分析上更常用。
- 每次访问 MC 更容易实现,并且在某些情况下可能更高效(利用了更多数据点)。
- 在实践中,两者的差异通常不大。
MC 评估 \(V_{\pi}\) 算法伪代码 (首次访问)
初始化:
$\pi$ ← 要评估的策略
$V(s)$ ← 任意状态价值函数(例如,对所有 $s \in S$,$V(s)=0$)
$Returns(s)$ ← 空列表,对所有 $s \in S$
无限循环(对每个回合):
使用 $\pi$ 生成一个回合: $S_0, A_0, R_1, S_1, A_1, R_2, ..., S_{T-1}, A_{T-1}, R_T$
$G$ ← 0 # 初始化本回合的回报
Visited_States_In_Episode ← 空集合 # 记录本回合已访问的状态
对回合的每个时间步循环,$t = T-1, T-2, ..., 0$:
$G$ ← $R_{t+1} + \gamma * G$ # 计算从时间步t开始的回报
如果 $S_t$ 不在 Visited_States_In_Episode 中:
将 $G$ 添加到 $Returns(S_t)$
$V(S_t)$ ← $Returns(S_t)$ 的平均值
将 $S_t$ 添加到 $Visited_States_In_Episode$
解释:
- 初始化 \(V(s)\) 和用于存储回报的列表 \(Returns(s)\)。
- 无限循环生成回合。
- 对每个生成的回合,从后往前计算每个时间步 \(t\) 的回报 \(G\)。
- 对于每个时间步 \(t\) 的状态 \(S_t\),检查它是否是本回合首次访问。
- 如果是首次访问,将计算得到的回报 \(G\) 添加到该状态的回报列表 \(Returns(S_t)\) 中。
- 更新 \(V(S_t)\) 为 \(Returns(S_t)\) 中所有回报的平均值。
- 标记 \(S_t\) 在本回合已访问。
每次访问 MC 的修改: 只需去掉 Visited_States_In_Episode
的检查和记录即可。
MC 评估 \(Q_{\pi}\) 算法伪代码 (首次访问)
评估 \(Q_{\pi}(s, a)\) 的过程类似,只是我们需要记录和平均状态-动作对 \((s, a)\) 的回报。
初始化:
π ← 要评估的策略
Q(s, a) ← 任意的动作价值函数(例如,对所有 s ∈ S, a ∈ A,Q(s,a)=0)
Returns(s, a) ← 空列表,对所有 s ∈ S, a ∈ A
无限循环(对每个回合):
使用 π 生成一个回合: S_0, A_0, R_1, S_1, A_1, R_2, ..., S_{T-1}, A_{T-1}, R_T
G ← 0
Visited_StateActions_In_Episode ← 空集合 # 用于首次访问MC
对回合的每个时间步循环,t = T-1, T-2, ..., 0:
G ← R_{t+1} + γ * G
StateAction_Pair = (S_t, A_t)
如果 StateAction_Pair 不在 Visited_StateActions_In_Episode 中: # 用于首次访问MC
将 G 添加到 Returns(S_t, A_t)
Q(S_t, A_t) ← Returns(S_t, A_t) 的平均值
将 StateAction_Pair 添加到 Visited_StateActions_In_Episode # 用于首次访问MC
为了确保 \(Q(s, a)\) 对所有的状态-动作对都有估计值,我们需要保证在足够多的回合中,所有的 \((s, a)\) 对都被访问到。一种方法是采用探索性开端 (Exploring Starts):每个回合的起始状态 \(S_0\) 和起始动作 \(A_0\) 是随机选择的,覆盖所有可能的 \((s, a)\) 对。这在模拟中可行,但在真实环境中通常不现实。后续的控制算法会使用其他探索机制(如 \(\epsilon\)-greedy)。
Lab 2: MC 预测实践
目标
- 在一个简单的环境中(如 Gridworld 或 Blackjack)实现或运行 MC 预测算法。
- 可视化学习到的价值函数。
- 理解 MC 方法的优缺点。
环境选择
- Gridworld (网格世界):
- 一个经典的 RL 测试平台。智能体在一个二维网格中移动(上、下、左、右)。
- 某些格子是目标(正奖励),某些是陷阱(负奖励),撞墙保持原地。
- 状态是离散的(格子坐标),动作是离散的。
- 通常是回合制任务(到达目标或陷阱结束)。
- Gymnasium 没有内置的标准 Gridworld,但很容易自己实现或找到第三方实现。
- Blackjack (二十一点):
- Gymnasium 内置环境 (
gym.make("Blackjack-v1")
)。 - 目标:通过要牌 (hit) 或停牌 (stick) 使得总点数接近 21 点且不超过 21 点,并大于庄家。
- 状态: (玩家当前总点数, 庄家明牌点数, 玩家是否有可用的 Ace [值为 1 或 11]) (离散)。
- 动作: 0 (停牌 stick), 1 (要牌 hit) (离散)。
- 奖励: +1 (赢), -1 (输), 0 (平局)。
- 回合制任务。
- Gymnasium 内置环境 (
我们将以 Blackjack 为例进行说明,因为它更标准且易于运行。
示例代码框架 (Blackjack - Vπ 评估)
假设我们要评估一个简单的固定策略 π:只要玩家点数小于 20 就一直要牌 (hit),否则停牌 (stick)。
import gymnasium as gym
import numpy as np
from collections import defaultdict
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D # 用于 3D 绘图
# 创建 Blackjack 环境
= gym.make("Blackjack-v1", sab=True) # sab=True 表示状态包含玩家是否有可用 Ace
env
# 1. 定义要评估的策略 π
def simple_policy(observation):
"""
只要玩家点数小于 20 就一直要牌 (hit),否则停牌 (stick)。
observation: (player_sum, dealer_showing, usable_ace)
"""
= observation
player_sum, _, _ return 1 if player_sum < 20 else 0 # 1: hit, 0: stick
# 2. 初始化
= defaultdict(float) # 状态值函数 V(s),用 defaultdict 初始化为 0
V = defaultdict(list) # 存储每个状态的回报列表
Returns = defaultdict(int) # (可选) 记录每个状态被访问的次数,用于增量更新 V
N
= 500000 # 模拟的回合数
num_episodes
# 3. MC 预测主循环
for i in range(num_episodes):
if (i + 1) % 50000 == 0:
print(f"Episode {i+1}/{num_episodes}")
# 生成一个回合
= []
episode = env.reset()
observation, info = False
terminated = False
truncated while not (terminated or truncated):
= simple_policy(observation) # 根据策略选择动作
action = env.step(action)
next_observation, reward, terminated, truncated, info # 记录 (状态, 动作, 奖励)
episode.append((observation, action, reward)) = next_observation
observation
# 处理回合数据 (首次访问 MC)
= 0.0
G = set()
visited_states # 从后往前遍历回合
for t in range(len(episode) - 1, -1, -1):
= episode[t]
state, action, reward = reward + 1 * G # Blackjack 环境 gamma 默认为 1
G
# 如果是本回合首次访问该状态
if state not in visited_states:
Returns[state].append(G)# 更新 V(state) 为平均回报
= np.mean(Returns[state])
V[state] # --- 或者使用增量更新 (更高效) ---
# N[state] += 1
# V[state] = V[state] + (1/N[state]) * (G - V[state])
# ---------------------------------
visited_states.add(state)
# 4. 可视化价值函数 (以 V 为例)
# Blackjack 状态: (player_sum, dealer_showing, usable_ace)
# 我们需要将 3D 状态映射到 2D 图上,通常分别绘制 usable_ace=True 和 usable_ace=False 的情况
def plot_blackjack_value_function(V, title="Value Function"):
= min(k[0] for k in V.keys()) if V else 12 # Handle empty V
min_player_sum = max(k[0] for k in V.keys()) if V else 21
max_player_sum = min(k[1] for k in V.keys()) if V else 1
min_dealer_show = max(k[1] for k in V.keys()) if V else 10
max_dealer_show
= np.arange(min_player_sum, max_player_sum + 1)
player_range = np.arange(min_dealer_show, max_dealer_show + 1)
dealer_range = np.meshgrid(dealer_range, player_range) # 注意顺序
X, Y
# 分别绘制有可用 Ace 和无可用 Ace 的情况
= np.apply_along_axis(lambda idx: V.get((idx[1], idx[0], False), 0), 2, np.dstack([X, Y]))
Z_no_ace = np.apply_along_axis(lambda idx: V.get((idx[1], idx[0], True), 0), 2, np.dstack([X, Y]))
Z_ace
= plt.figure(figsize=(12, 5))
fig
= fig.add_subplot(121, projection='3d')
ax1 ='viridis')
ax1.plot_surface(X, Y, Z_no_ace, cmap'Dealer Showing')
ax1.set_xlabel('Player Sum')
ax1.set_ylabel('Value')
ax1.set_zlabel(f"{title} (No Usable Ace)")
ax1.set_title(# Set viewing angle for better visibility if needed
# ax1.view_init(elev=30, azim=-135)
= fig.add_subplot(122, projection='3d')
ax2 ='viridis')
ax2.plot_surface(X, Y, Z_ace, cmap'Dealer Showing')
ax2.set_xlabel('Player Sum')
ax2.set_ylabel('Value')
ax2.set_zlabel(f"{title} (Usable Ace)")
ax2.set_title(# Set viewing angle
# ax2.view_init(elev=30, azim=-135)
plt.tight_layout()
plt.show()
# Check if V is populated before plotting
if V:
="MC Estimated Value Function (Simple Policy)")
plot_blackjack_value_function(V, titleelse:
print("Value function V is empty. No plot generated.")
env.close()
任务与思考
- 运行代码: 运行上述 Blackjack MC 预测代码。观察生成的价值函数图像。它是否符合你对这个简单策略的直觉?(例如,点数高时价值是否更高?庄家明牌点数低时价值是否更高?)
- 修改策略: 尝试修改
simple_policy
,例如改成点数小于 18 就 hit。重新运行 MC 预测,观察价值函数的变化。 - (可选) 实现 \(Q_{\pi}\) 评估: 修改代码,计算并可视化 \(Q_{\pi}(s, a)\) 而不是 \(V_{\pi}(s)\)。\(Q\) 函数的可视化稍微复杂,可能需要为每个动作 (hit/stick) 单独绘制价值曲面。
- (可选) Gridworld: 如果你找到了或自己实现了 Gridworld 环境,尝试在 Gridworld 中运行 MC 预测。可视化价值函数(可以用热力图表示每个格子的价值)。
MC 方法的优缺点
- 优点:
- 无模型: 不需要知道环境的 \(P\) 和 \(R\)。
- 简单直观: 基于大数定律,易于理解和实现。
- 无偏估计 (Unbiased): 只要回合能完整生成,MC 估计是 \(V_{\pi}(s)\) 或 \(Q_{\pi}(s, a)\) 的无偏估计。
- 适用于非马尔可夫环境: 即使环境不完全满足马尔可夫性质,MC 仍然可以应用(尽管理论保证可能减弱)。
- 缺点:
- 需要完整回合: 必须等到一个回合结束后才能更新价值函数。对于回合非常长的任务(如某些商业模拟可能持续很久)或者持续性任务,效率低下或无法应用。
- 高方差 (High Variance): 回报 \(G_t\) 依赖于一个回合中所有的随机转移和奖励,其方差可能很大,导致价值估计收敛慢,需要大量回合才能得到较准确的结果。
- 学习效率相对较低: 相比于后面要学的 TD 方法,MC 没有利用状态之间的关联信息(Bellman 方程隐含的关系),学习效率可能较低。
- 只适用于回合制任务 (Episodic Tasks): 基本的 MC 方法不适用于没有明确终点的持续性任务。
MC 方法必须等待回合结束才能学习,这在很多实时决策或长周期商业场景中是不可接受的。
下周预告: 时序差分学习 (Temporal-Difference Learning, TD) - 从不完整经验中学习,克服 MC 的部分缺点。