9强化学习:导论(第2版)

On-policy与Off-policy方法

策略差异

on-policyoff-policy重要性采样

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第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:评估和改进的策略(目标策略)与实际用于决策的策略(行为策略)不同
python
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)}$$

python
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}}$$

有偏(渐近无偏)但方差显著更小。实践中通常使用加权重要性采样。

python
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 上的对比

python
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 的学习曲线

9/11On-p
0%