第9章:On-policy 与 Off-policy 方法
一、导读
1.1 本章学习目标
第9章深入探讨 on-policy 和 off-policy 方法的区别,以及如何通过重要性采样(importance sampling)将 off-policy 数据用于学习。
本章核心学习目标:
- 理解 on-policy 与 off-policy 的本质区别
- 掌握重要性采样原理:用一种策略的数据来估计另一种策略的价值
- 理解普通重要性采样和加权重要性采样的区别
- 了解 off-policy 学习的方差问题及解决方案
1.2 为什么本章如此重要
Off-policy 学习在实际应用中极其重要。它允许智能体从历史数据、人类演示或其他策略的数据中学习,极大地提高了样本效率。现代强化学习中的很多技术(如经验回放、行为克隆)都建立在 off-policy 学习的基础上。
1.3 核心问题引导
为什么 Q-learning 是 off-policy 的而 Sarsa 是 on-policy 的?
重要性采样如何解决 off-policy 学习的偏差问题?
普通重要性采样和加权重要性采样各有什么优缺点?
Off-policy 学习的方差为什么高?如何降低?
二、核心概念详解
9.1 On-policy vs Off-policy
- On-policy:评估和改进的是实际用于决策的策略(行为策略 = 目标策略)
- Off-policy:评估和改进的策略(目标策略)与实际用于决策的策略(行为策略)不同
import numpy as np
from collections import defaultdict
def demonstrate_on_off_policy():
class SimpleMDP:
def __init__(self):
self.states = [0, 1, 2]
self.n_actions = 2
self.gamma = 0.9
def step(self, state, action):
if state == 2:
return 2, 0, True
if state == 0:
if action == 0:
return 1, 0, False
else:
return 2, 1, True
if state == 1:
if action == 0:
return 2, 2, True
else:
return 0, 0, False
env = SimpleMDP()
print("On-policy (Sarsa): evaluates the behavior policy")
print("Off-policy (Q-learning): evaluates the greedy policy")
print("Key difference: what policy are we learning about?")
demonstrate_on_off_policy()9.2 重要性采样(Importance Sampling)
重要性采样是一种从不同分布的样本中估计目标分布期望的技术。
要估计 $\mathbb{E}_{\pi}[G_t]$,但只有从行为策略 $b$ 采样的数据,可以使用:
$$\mathbb{E}_{\pi}[G_t] = \mathbb{E}_b\left[\frac{\pi(A_t|S_t)}{b(A_t|S_t)} G_t\right]$$
对于整个回合的重要性采样比率:
$$\rho_{t:T-1} = \prod_{k=t}^{T-1} \frac{\pi(A_k|S_k)}{b(A_k|S_k)}$$
def importance_sampling_demo():
np.random.seed(42)
n_samples = 10000
target_mean = 5.0
behavior_mean = 3.0
samples_from_b = np.random.normal(behavior_mean, 1, n_samples)
weights = np.exp(-0.5 * ((samples_from_b - target_mean)**2 - (samples_from_b - behavior_mean)**2))
ordinary_is = np.mean(weights * samples_from_b)
naive_mean = np.mean(samples_from_b)
print(f"True mean (target): {target_mean}")
print(f"Naive mean (from behavior): {naive_mean:.3f}")
print(f"Importance sampling estimate: {ordinary_is:.3f}")
importance_sampling_demo()9.3 普通重要性采样 vs 加权重要性采样
普通重要性采样(Ordinary IS):
$$V(s) = \frac{\sum \rho_{t:T-1} G_t}{|\text{episodes visiting } s|}$$
无偏但方差可能极大。
加权重要性采样(Weighted IS):
$$V(s) = \frac{\sum \rho_{t:T-1} G_t}{\sum \rho_{t:T-1}}$$
有偏(渐近无偏)但方差显著更小。实践中通常使用加权重要性采样。
def off_policy_mc_evaluation(n_episodes=1000):
class GridEnv:
def __init__(self, size=4):
self.size = size
self.goal = (size-1, size-1)
self.actions = [(-1,0),(1,0),(0,-1),(0,1)]
def reset(self):
self.state = (0, 0)
return self.state
def step(self, action):
dr, dc = self.actions[action]
r, c = self.state
nr, nc = max(0, min(self.size-1, r+dr)), max(0, min(self.size-1, c+dc))
self.state = (nr, nc)
reward = 1.0 if self.state == self.goal else 0.0
done = self.state == self.goal
return self.state, reward, done
env = GridEnv(size=4)
target_policy = defaultdict(lambda: 0)
for s in [(0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1)]:
target_policy[s] = 3 if s[0] < 2 else 1
behavior_policy_probs = np.ones((4, 4, 4)) / 4
V_ordinary = defaultdict(float)
V_weighted = defaultdict(float)
C_ordinary = defaultdict(int)
C_weighted = defaultdict(float)
returns_ordinary = defaultdict(list)
returns_weighted_num = defaultdict(float)
returns_weighted_den = defaultdict(float)
for ep in range(n_episodes):
state = env.reset()
episode = []
done = False
while not done:
action = np.random.randint(4)
next_state, reward, done = env.step(action)
episode.append((state, action, reward))
state = next_state
G = 0
rho = 1.0
for t in range(len(episode) - 1, -1, -1):
s, a, r = episode[t]
G += r
if s == env.goal:
continue
pi_a = 1.0 if a == target_policy[s] else 0.0
b_a = behavior_policy_probs[s[0]][s[1]][a]
rho *= pi_a / b_a
if rho == 0:
break
returns_ordinary[s].append(rho * G)
V_ordinary[s] = np.mean(returns_ordinary[s])
returns_weighted_num[s] += rho * G
returns_weighted_den[s] += rho
if returns_weighted_den[s] > 0:
V_weighted[s] = returns_weighted_num[s] / returns_weighted_den[s]
print("Off-policy MC Evaluation:")
for s in [(0,0), (1,1), (2,0)]:
print(f" V_ordinary({s}) = {V_ordinary.get(s, 0):.3f}")
print(f" V_weighted({s}) = {V_weighted.get(s, 0):.3f}")
off_policy_mc_evaluation()9.4 Off-policy 的方差问题
重要性采样的比率 $\rho$ 是多个概率比的乘积。当回合很长时,$\rho$ 可能非常大或非常小,导致估计的方差极大。
解决方案:
折扣感知重要性采样:考虑折扣因子对比率的影响
截断重要性采样:限制比率的最大值
使用 per-decision 重要性采样:逐步更新而非整回合
Off-policy TD 方法:如 Q-learning 天然避免了长回合的比率问题
三、实战案例
3.1 案例:On-policy vs Off-policy 在 Cliff Walking 上的对比
def compare_on_off_policy():
class CliffWalking:
def __init__(self):
self.width = 12
self.height = 4
self.start = (3, 0)
self.goal = (3, 11)
self.cliff = [(3, c) for c in range(1, 11)]
def reset(self):
self.state = self.start
return self.state
def step(self, action):
moves = [(-1,0),(1,0),(0,-1),(0,1)]
dr, dc = moves[action]
r, c = self.state
nr, nc = max(0,min(3,r+dr)), max(0,min(11,c+dc))
if (nr,nc) in self.cliff:
self.state = self.start
return self.state, -100, False
self.state = (nr, nc)
reward = -1
done = self.state == self.goal
return self.state, reward, done
env = CliffWalking()
n_actions = 4
Q_sarsa = defaultdict(lambda: np.zeros(n_actions))
Q_ql = defaultdict(lambda: np.zeros(n_actions))
sarsa_rewards = []
ql_rewards = []
for ep in range(500):
state = env.reset()
action_s = np.argmax(Q_sarsa[state]) if np.random.random() > 0.1 else np.random.randint(4)
done = False
rew_s, rew_q = 0, 0
while not done:
next_state, reward, done = env.step(action_s)
rew_s += reward
rew_q += reward
next_action_s = np.argmax(Q_sarsa[next_state]) if np.random.random() > 0.1 else np.random.randint(4)
target_s = reward if done else reward + 0.99 * Q_sarsa[next_state][next_action_s]
Q_sarsa[state][action_s] += 0.5 * (target_s - Q_sarsa[state][action_s])
target_q = reward if done else reward + 0.99 * np.max(Q_ql[next_state])
Q_ql[state][action_s] += 0.5 * (target_q - Q_ql[state][action_s])
state = next_state
action_s = next_action_s
sarsa_rewards.append(rew_s)
ql_rewards.append(rew_q)
print("Sarsa (on-policy) avg reward (last 50):", np.mean(sarsa_rewards[-50:]))
print("Q-learning (off-policy) avg reward (last 50):", np.mean(ql_rewards[-50:]))
compare_on_off_policy()四、本章小结
On-policy:行为策略 = 目标策略(如 Sarsa)
Off-policy:行为策略 ≠ 目标策略(如 Q-learning)
重要性采样:用行为策略的数据估计目标策略的价值
普通 vs 加权 IS:无偏高方差 vs 有偏低方差
方差问题:长回合的重要性采样比率方差极大
五、思考与练习
思考题
为什么 Q-learning 不需要重要性采样就能实现 off-policy 学习?
加权重要性采样的偏差随着样本量增加如何变化?
在什么场景下 off-policy 学习特别有用?
编程练习
实现带重要性采样的 off-policy MC 控制
比较普通和加权重要性采样的方差
在 Cliff Walking 上画出 Sarsa 和 Q-learning 的学习曲线