探索与利用权衡:ε-greedy、UCB、Thompson Sampling

一句话概述

探索与利用(Exploration vs Exploitation)是强化学习的核心矛盾——利用是"做已知最好的事",探索是"尝试未知的事"。ε-greedy用简单的概率来平衡,UCB用不确定性来指导探索,Thompson Sampling用贝叶斯后验来采样。这个权衡决定了智能体是"保守的守成者"还是"冒险的探索者",直接影响学习效率和最终表现。

💡 核心要点:①探索-利用困境是多臂老虎机(Multi-armed Bandit)问题的核心,也是RL的基础挑战;②ε-greedy最简单——以概率ε随机探索,以概率1-ε选择最优;③UCB(置信上界)基于"乐观面对不确定性"原则,给探索少的动作一个"加分";④Thompson Sampling从贝叶斯后验分布中采样,天然平衡探索与利用。

教学与演示

一、多臂老虎机——探索与利用的经典问题

是什么(定义):多臂老虎机(Multi-armed Bandit)问题是一个简化的序列决策问题:有K个"手臂"(动作),每个手臂有一个未知的奖励分布。每次你拉一个手臂,获得一个随机奖励。目标是在T次拉动中最大化累积奖励。这个问题没有状态转移,只有动作选择,是探索-利用权衡的最纯粹形式。

大白话 想象你面前有K台老虎机,每台的中奖概率不同但你不知道。你的目标是玩100次后赢最多的钱。你是应该"专一"地玩看起来最好的那台(利用),还是"花心"地每台都试试(探索)?如果太早锁定一台,可能错过了更好的;如果一直试来试去,可能浪费了太多机会在烂机器上。

为什么(原理):多臂老虎机问题虽然简单,但它是理解探索-利用权衡的最佳模型。在RL中,智能体面临的每个状态都可以看作一个"情境老虎机"(Contextual Bandit)——在给定状态下,选择哪个动作类似于在给定情境下选择哪个手臂。因此,老虎机问题的探索策略(ε-greedy、UCB、Thompson Sampling)可以直接应用于RL。

怎么做(实现)

import numpy as np

# ==================== 多臂老虎机模拟 ====================
# 10台老虎机,每台有不同的中奖概率
np.random.seed(42)
n_arms = 10
true_probs = np.random.beta(2, 5, n_arms)  # 真实中奖概率(偏低的Beta分布)
print("=== 多臂老虎机 ===")
print(f"{n_arms}台老虎机的真实中奖概率:")
for i, p in enumerate(true_probs):
    print(f"  手臂{i}: {p*100:.1f}%")
best_arm = np.argmax(true_probs)
print(f"最优手臂是 #{best_arm},中奖概率 {true_probs[best_arm]*100:.1f}%")

# 模拟拉手臂
def pull_arm(arm):
    """拉一次手臂,返回奖励(0或1)"""
    return 1 if np.random.random() < true_probs[arm] else 0

# 仅利用策略:估计每个手臂的概率,然后只拉最好的
print("\n=== 仅利用(贪心)策略 ===")
estimates = np.zeros(n_arms)  # 估计概率
counts = np.zeros(n_arms)     # 拉过的次数

# 先每台拉1次作为初始探索
for arm in range(n_arms):
    r = pull_arm(arm)
    estimates[arm] = r
    counts[arm] = 1

total_reward = n_arms  # 初始探索的奖励
for step in range(n_arms, 100):
    best = np.argmax(estimates)
    r = pull_arm(best)
    total_reward += r
    counts[best] += 1
    estimates[best] += (r - estimates[best]) / counts[best]

print(f"仅利用策略: 100次拉动总奖励={total_reward}, 最优手臂#{best_arm}被拉{counts[best_arm]}次")
print(f"如果知道最优手臂,100次拉动期望奖励≈{true_probs[best_arm]*100+10:.0f}")
多臂老虎机的遗憾(Regret)\(\text{Regret}(T) = T \cdot \mu^* - \sum_{t=1}^{T} r_t, \quad \mu^* = \max_{a} \mu_a\)

什么用(应用):多臂老虎机问题直接应用于A/B测试、在线广告投放、推荐系统的冷启动。在RL中,探索策略(ε-greedy)是DQN、Q-learning等算法的标准组件。Contextual Bandit(上下文老虎机)是推荐系统和在线广告的核心模型。

哪些坑(缺点):老虎机问题假设没有状态转移,这与MDP不同;贪心策略可能永远锁定在次优手臂上(如果初始估计偏差);探索策略的选择直接影响学习效率和最终表现。

二、ε-greedy——最简单的探索策略

是什么(定义):ε-greedy策略以概率ε随机选择一个动作(探索),以概率1-ε选择当前估计价值最高的动作(利用)。ε通常从较大值(如1.0)开始,随着学习进行逐渐衰减(如到0.01),即"先广泛探索,再专注利用"。

大白话 ε-greedy就像"偶尔任性"——大部分时间(1-ε)你按照"经验"选最好的,但偶尔(ε)你随机选一个试试。ε从大到小就像"从孩子到大人"的过程——小时候什么都想试试,长大了越来越保守。

为什么(原理):ε-greedy的优点是极其简单,不需要任何额外的统计信息。但它有一个致命缺陷:即使某个动作已经被证明非常差,它仍然有ε/n的概率被选中——探索是"盲目的"。这导致ε-greedy的遗憾是O(T)(线性增长),在理论上不如UCB和Thompson Sampling。

怎么做(实现)

import numpy as np

# ==================== ε-greedy 策略的完整实现 ====================
np.random.seed(42)
n_arms = 10
true_probs = np.random.beta(2, 5, n_arms)

def pull_arm(arm):
    return 1 if np.random.random() < true_probs[arm] else 0

class EpsilonGreedy:
    """ε-greedy策略"""
    def __init__(self, n_arms, epsilon=0.1, decay=0.999, min_eps=0.01):
        self.n_arms = n_arms
        self.epsilon = epsilon       # 当前ε
        self.decay = decay           # 衰减系数
        self.min_eps = min_eps       # 最小ε
        self.estimates = np.zeros(n_arms)  # Q值估计
        self.counts = np.zeros(n_arms)     # 每个动作被选次数

    def select_action(self):
        """选择动作:ε概率随机,1-ε概率贪心"""
        if np.random.random() < self.epsilon:
            return np.random.randint(self.n_arms)  # 探索
        else:
            return np.argmax(self.estimates)       # 利用

    def update(self, action, reward):
        """更新估计值(增量式平均)"""
        self.counts[action] += 1
        # 增量更新:新估计 = 旧估计 + (奖励 - 旧估计) / 次数
        self.estimates[action] += (reward - self.estimates[action]) / self.counts[action]
        # 衰减ε
        self.epsilon = max(self.min_eps, self.epsilon * self.decay)

# 运行对比实验
def run_experiment(strategy, n_steps=1000):
    rewards = []
    optimal_actions = 0
    best_arm = np.argmax(true_probs)
    for step in range(n_steps):
        action = strategy.select_action()
        reward = pull_arm(action)
        strategy.update(action, reward)
        rewards.append(reward)
        if action == best_arm:
            optimal_actions += 1
    return rewards, optimal_actions

# 固定ε vs 衰减ε
eg_fixed = EpsilonGreedy(n_arms, epsilon=0.1, decay=1.0)  # ε不变
eg_decay = EpsilonGreedy(n_arms, epsilon=1.0, decay=0.995)  # ε衰减

rewards_fixed, opt_fixed = run_experiment(eg_fixed)
rewards_decay, opt_decay = run_experiment(eg_decay)

print("=== ε-greedy 策略对比 ===")
print(f"固定ε=0.1: 累积奖励={sum(rewards_fixed)}, 选最优动作{opt_fixed}次({opt_fixed/10:.1f}%)")
print(f"衰减ε=1.0→min: 累积奖励={sum(rewards_decay)}, 选最优动作{opt_decay}次({opt_decay/10:.1f}%)")
print(f"最优手臂真实概率: {true_probs[np.argmax(true_probs)]*100:.1f}%")
print(f"期望最优奖励≈{true_probs[np.argmax(true_probs)]*1000:.0f}")
print("\n衰减ε策略:前期大量探索,后期专注利用,总奖励更高")
ε-greedy 动作选择\(A_t = \begin{cases} \text{随机动作} & \text{以概率 } \varepsilon \\ \arg\max_a Q_t(a) & \text{以概率 } 1-\varepsilon \end{cases}\)

什么用(应用):ε-greedy是DQN、Q-learning等深度RL算法的默认探索策略。在Atari游戏DQN中,ε从1.0线性衰减到0.1(或0.01),智能体先在游戏里"乱玩"收集经验,然后逐渐利用学到的策略。在推荐系统中,ε-greedy用于平衡"推荐热门内容"和"探索用户新兴趣"。

哪些坑(缺点):探索是盲目的——即使某个动作已知很差,也有ε/|A|的概率被选中;ε衰减过快可能导致探索不足,过早锁定次优策略;ε衰减过慢则浪费太多时间在探索上;ε-greedy不考虑动作的不确定性,所有动作的探索概率相同。

三、UCB——基于不确定性的探索

是什么(定义):UCB(Upper Confidence Bound,置信上界)策略的核心思想是"乐观面对不确定性"——对于每个动作,我们不仅看它的估计价值,还加上一个"探索奖励"(bonus)。这个奖励与动作被选择的次数成反比:被选得越少的动作,bonus越大。动作选择规则:A_t = argmax_a [Q_t(a) + c·√(ln t / N_t(a))]。

大白话 UCB就像一个"乐观主义者"——对于没怎么试过的动作,它总是往好处想:"说不定这个动作特别好呢?"所以给它们一个"加分"。试得越多,加分越少。这样既不会错过好动作,也不会一直浪费在烂动作上。它比ε-greedy聪明的地方在于:探索是"有目的性的"——只探索"不确定性大"的动作。

为什么(原理):UCB的数学基础是Hoeffding不等式——它给出了样本均值偏离真实均值的概率上界。UCB的"探索奖励"就是Hoeffding置信区间的宽度。选择"估计值+置信上界"最大的动作,等价于"乐观地认为每个动作都在其置信区间的上界"。这种策略的遗憾是O(log T),理论上远优于ε-greedy的O(T)。

怎么做(实现)

import numpy as np

np.random.seed(42)
n_arms = 10
true_probs = np.random.beta(2, 5, n_arms)

def pull_arm(arm):
    return 1 if np.random.random() < true_probs[arm] else 0

class UCB:
    """UCB策略"""
    def __init__(self, n_arms, c=2.0):
        self.n_arms = n_arms
        self.c = c              # 探索系数
        self.estimates = np.zeros(n_arms)  # Q值估计
        self.counts = np.zeros(n_arms)     # 被选次数
        self.total_steps = 0               # 总步数

    def select_action(self):
        self.total_steps += 1
        # 先确保每个动作至少被选一次
        not_tried = np.where(self.counts == 0)[0]
        if len(not_tried) > 0:
            return not_tried[0]

        # UCB公式:argmax_a [Q(a) + c * sqrt(ln(t) / N(a))]
        ucb_values = self.estimates + self.c * np.sqrt(
            np.log(self.total_steps) / self.counts
        )
        return np.argmax(ucb_values)

    def update(self, action, reward):
        self.counts[action] += 1
        self.estimates[action] += (reward - self.estimates[action]) / self.counts[action]

# 运行UCB
ucb = UCB(n_arms, c=2.0)
rewards_ucb = []
best_arm = np.argmax(true_probs)
opt_count = 0

for step in range(1000):
    action = ucb.select_action()
    reward = pull_arm(action)
    ucb.update(action, reward)
    rewards_ucb.append(reward)
    if action == best_arm:
        opt_count += 1

print("=== UCB 策略 ===")
print(f"累积奖励: {sum(rewards_ucb)}")
print(f"选最优动作次数: {opt_count} ({opt_count/10:.1f}%)")
print(f"各动作估计值 vs 真实值:")
for i in range(n_arms):
    bonus = ucb.c * np.sqrt(np.log(ucb.total_steps) / max(ucb.counts[i], 1))
    print(f"  手臂{i}: 估计={ucb.estimates[i]:.4f}, 真实={true_probs[i]:.4f}, "
          f"次数={int(ucb.counts[i])}, UCB bonus={bonus:.4f}")

print(f"\nUCB的优势:探索奖励随次数递减,")
print(f"  被选次数少的动作有更大的bonus,被选次数多的动作bonus小")
UCB 动作选择\(A_t = \arg\max_{a} \left[ Q_t(a) + c \sqrt{\frac{\ln t}{N_t(a)}} \right]\)

什么用(应用):UCB在在线广告、推荐系统、A/B测试中广泛应用。在RL中,UCB的变体(如Bootstrapped DQN)用于深度探索。UCB的思想也启发了"好奇心驱动探索"——给"惊讶"的状态更多的探索奖励。在蒙特卡洛树搜索(MCTS)中,UCB1(UCT算法)用于选择搜索分支。

哪些坑(缺点):UCB假设奖励是有界的,对于无界奖励需要调整;探索系数c的选择需要调参;在非平稳环境(奖励分布随时间变化)中,UCB的"重选惩罚"机制不适用;对于连续动作空间,UCB难以直接应用。

四、Thompson Sampling——贝叶斯视角的探索

是什么(定义):Thompson Sampling(汤普森采样)是一种贝叶斯探索策略。它为每个动作维护一个奖励分布的后验分布(而非点估计),每次选择动作时,从每个动作的后验分布中采样一个值,然后选择采样值最大的动作。自然地在探索和利用之间取得平衡。

大白话 Thompson Sampling就像一个"赌徒"——它不直接说"这个动作好",而是说"这个动作有70%概率好,30%概率差"。每次决策时,它从这个概率分布中随机抽一个值,然后选抽到值最大的动作。如果某个动作的数据少,分布就宽,有时会抽到高值,自然就去探索了。

为什么(原理):Thompson Sampling的贝叶斯性质使得它能够自然地量化不确定性。每个动作被选中的概率等于"该动作是最优动作"的后验概率。这意味着:如果数据充分证明某个动作最优,它几乎总被选中;如果数据不足,最优动作不确定,其他动作也有机会被选中。Thompson Sampling在理论上和实践中都表现优异。

怎么做(实现)

import numpy as np

np.random.seed(42)
n_arms = 10
true_probs = np.random.beta(2, 5, n_arms)

def pull_arm(arm):
    return 1 if np.random.random() < true_probs[arm] else 0

class ThompsonSampling:
    """Thompson Sampling策略(Beta-Bernoulli模型)"""
    def __init__(self, n_arms):
        self.n_arms = n_arms
        # Beta分布的参数:α=成功次数+1, β=失败次数+1
        # 先验:Beta(1,1) = 均匀分布
        self.alphas = np.ones(n_arms)   # 成功次数 + 1
        self.betas = np.ones(n_arms)    # 失败次数 + 1

    def select_action(self):
        """从每个动作的后验分布中采样,选最大的"""
        samples = np.random.beta(self.alphas, self.betas)
        return np.argmax(samples)

    def update(self, action, reward):
        """更新后验分布"""
        if reward > 0:
            self.alphas[action] += 1  # 成功
        else:
            self.betas[action] += 1   # 失败

# 运行Thompson Sampling
ts = ThompsonSampling(n_arms)
rewards_ts = []
best_arm = np.argmax(true_probs)
opt_count = 0

for step in range(1000):
    action = ts.select_action()
    reward = pull_arm(action)
    ts.update(action, reward)
    rewards_ts.append(reward)
    if action == best_arm:
        opt_count += 1

print("=== Thompson Sampling 策略 ===")
print(f"累积奖励: {sum(rewards_ts)}")
print(f"选最优动作次数: {opt_count} ({opt_count/10:.1f}%)")
print(f"\n各动作的后验分布(Beta分布):")
for i in range(n_arms):
    mean = ts.alphas[i] / (ts.alphas[i] + ts.betas[i])
    std = np.sqrt(ts.alphas[i] * ts.betas[i] / 
                  ((ts.alphas[i] + ts.betas[i])**2 * (ts.alphas[i] + ts.betas[i] + 1)))
    print(f"  手臂{i}: Beta({ts.alphas[i]:.0f},{ts.betas[i]:.0f}), "
          f"均值={mean:.4f}, 标准差={std:.4f}, 真实={true_probs[i]:.4f}")

print(f"\nThompson Sampling的优势:")
print(f"  不确定性大的动作(数据少),Beta分布宽,有机会被采样到")
print(f"  确定性高的动作(数据多),Beta分布窄,稳定地采样到其均值附近")
Thompson Sampling 后验分布\(P(\mu_a \mid \mathcal{D}) \propto P(\mathcal{D} \mid \mu_a) \cdot P(\mu_a), \quad a_t = \arg\max_a \tilde{\mu}_a, \quad \tilde{\mu}_a \sim P(\mu_a \mid \mathcal{D})\)

什么用(应用):Thompson Sampling在在线广告、新闻推荐、A/B测试中广泛使用。在RL中,Bootstrapped DQN可以看作Thompson Sampling的深度版本——用多个Q网络头的分布来近似后验分布。在推荐系统中,Thompson Sampling用于平衡"探索用户新兴趣"和"推荐已知偏好"。

哪些坑(缺点):需要选择合适的先验分布和似然模型;对于复杂奖励分布,后验更新可能没有闭式解,需要近似推理(如MCMC);在深度RL中,维护完整的后验分布计算成本高;先验的选择会影响早期探索行为。

五、探索策略在RL中的应用——从Bandit到MDP

是什么(定义):在RL中,探索策略需要从Bandit问题推广到MDP。核心区别在于:MDP中的探索不仅影响即时奖励,还影响未来访问的状态分布。深度RL中的探索方法包括:ε-greedy(DQN)、熵正则化(SAC)、好奇心驱动探索(ICM)、参数噪声(Parameter Noise)等。

大白话 在Bandit中,探索就是"试试不同的动作"。在MDP中,探索更复杂——你需要探索的不仅是动作,还有"状态"本身。你可能需要故意走一些"弯路"来发现新的区域。这就是为什么在Atari游戏中,DQN一开始需要用ε-greedy"乱玩"来收集经验。

为什么(原理):MDP中的探索面临两个额外挑战:(1) 探索动作可能导致智能体进入之前从未访问过的状态,带来新的学习机会;(2) 探索的代价不仅仅是"少拿了一些奖励",还可能"掉入陷阱"导致episode提前结束。因此,深度RL中的探索策略通常需要更精巧的设计。

怎么做(实现)

import numpy as np

# ==================== RL中的探索策略对比 ====================
# 在网格世界上比较不同探索策略

n_states = 9
n_actions = 4
actions = [(-1, 0), (1, 0), (0, -1), (0, 1)]

def step(state, action):
    row, col = state // 3, state % 3
    dr, dc = actions[action]
    nr = max(0, min(2, row + dr))
    nc = max(0, min(2, col + dc))
    next_state = nr * 3 + nc
    if next_state == 8:   r = 10.0
    elif next_state == 7: r = -5.0
    else:                 r = -0.1
    return next_state, r

def q_learning_with_exploration(strategy_name, n_episodes=300, alpha=0.1, gamma=0.9):
    """Q-learning配合不同探索策略"""
    Q = np.zeros((n_states, n_actions))
    counts = np.zeros((n_states, n_actions))  # 用于UCB
    total_steps = 0
    episode_rewards = []

    for episode in range(n_episodes):
        state = 0
        ep_reward = 0
        while state != 8 and state != 7:
            total_steps += 1

            # 根据策略名称选择动作
            if strategy_name == "egreedy":
                epsilon = max(0.05, 1.0 / (episode + 1))
                if np.random.random() < epsilon:
                    action = np.random.randint(n_actions)
                else:
                    action = np.argmax(Q[state])

            elif strategy_name == "ucb":
                not_tried = np.where(counts[state] == 0)[0]
                if len(not_tried) > 0:
                    action = not_tried[0]
                else:
                    c = 2.0
                    ucb_vals = Q[state] + c * np.sqrt(np.log(total_steps+1) / counts[state])
                    action = np.argmax(ucb_vals)

            elif strategy_name == "greedy":
                action = np.argmax(Q[state])

            # 执行动作
            next_state, reward = step(state, action)
            counts[state][action] += 1

            # Q-learning更新
            best_next = np.max(Q[next_state])
            Q[state][action] += alpha * (reward + gamma * best_next - Q[state][action])

            ep_reward += reward
            state = next_state

        episode_rewards.append(ep_reward)

    return episode_rewards

np.random.seed(42)
rewards_eg = q_learning_with_exploration("egreedy")
rewards_ucb = q_learning_with_exploration("ucb")
rewards_greedy = q_learning_with_exploration("greedy")

print("=== Q-learning + 不同探索策略 ===")
print("前10个episode的奖励对比:")
print(f"{'Episode':<10} {'ε-greedy':<12} {'UCB':<12} {'Greedy':<12}")
for ep in range(10):
    print(f"{ep+1:<10} {rewards_eg[ep]:>8.1f}    {rewards_ucb[ep]:>8.1f}    {rewards_greedy[ep]:>8.1f}")

print(f"\n最后10个episode的平均奖励:")
print(f"  ε-greedy: {np.mean(rewards_eg[-10:]):.2f}")
print(f"  UCB:      {np.mean(rewards_ucb[-10:]):.2f}")
print(f"  Greedy:   {np.mean(rewards_greedy[-10:]):.2f}")
print(f"\n贪心策略没有探索,可能永远找不到最优路径")
print(f"ε-greedy和UCB通过探索找到了更好的策略")

什么用(应用):探索策略是RL算法的重要组成部分。在DQN中,ε-greedy配合经验回放实现稳定学习;在SAC中,最大熵框架自动鼓励探索;在ICM(Intrinsic Curiosity Module)中,好奇心驱动探索让智能体在没有外部奖励时也能学习;在AlphaGo中,MCTS内置的探索机制平衡了广度和深度。

哪些坑(缺点):ε-greedy在深度RL中探索效率低(需要大量随机交互);UCB难以扩展到高维动作空间;Thompson Sampling需要维护不确定性估计,在深度RL中计算成本高;探索策略的选择需要根据具体任务调参。

六、实战:三种探索策略的全面对比

是什么(定义):本节将三种经典探索策略(ε-greedy、UCB、Thompson Sampling)在同一个多臂老虎机问题上进行系统对比,从累积奖励、遗憾曲线、最优动作选择率等多个维度评估它们的表现。

大白话 我们来当一回"裁判",让三种探索策略在同场竞技,看看到底谁更聪明。通过对比实验,你会直观地感受到"盲目探索"和"智能探索"的巨大差距。

怎么做(实现)

import numpy as np

np.random.seed(42)
n_arms = 10
true_probs = np.random.beta(2, 5, n_arms)
best_arm = np.argmax(true_probs)

def pull_arm(arm):
    return 1 if np.random.random() < true_probs[arm] else 0

# 运行多次实验取平均
def run_multiple(strategy_class, n_runs=50, n_steps=1000):
    all_rewards = np.zeros((n_runs, n_steps))
    all_optimal = np.zeros((n_runs, n_steps))
    for run in range(n_runs):
        strategy = strategy_class(n_arms)
        for step in range(n_steps):
            action = strategy.select_action()
            reward = pull_arm(action)
            strategy.update(action, reward)
            all_rewards[run][step] = reward
            all_optimal[run][step] = 1 if action == best_arm else 0
    return all_rewards.mean(axis=0), all_optimal.mean(axis=0)

# 定义三种策略
class EpsilonGreedyWrapper:
    def __init__(self, n_arms):
        self.estimates = np.zeros(n_arms)
        self.counts = np.zeros(n_arms)
        self.epsilon = 0.1
    def select_action(self):
        if np.random.random() < self.epsilon:
            return np.random.randint(n_arms)
        return np.argmax(self.estimates)
    def update(self, a, r):
        self.counts[a] += 1
        self.estimates[a] += (r - self.estimates[a]) / self.counts[a]

class UCBWrapper:
    def __init__(self, n_arms):
        self.n_arms = n_arms
        self.estimates = np.zeros(n_arms)
        self.counts = np.zeros(n_arms)
        self.total = 0
        self.c = 2.0
    def select_action(self):
        self.total += 1
        untried = np.where(self.counts == 0)[0]
        if len(untried) > 0: return untried[0]
        ucb = self.estimates + self.c * np.sqrt(np.log(self.total) / self.counts)
        return np.argmax(ucb)
    def update(self, a, r):
        self.counts[a] += 1
        self.estimates[a] += (r - self.estimates[a]) / self.counts[a]

class TSWrapper:
    def __init__(self, n_arms):
        self.alphas = np.ones(n_arms)
        self.betas = np.ones(n_arms)
    def select_action(self):
        return np.argmax(np.random.beta(self.alphas, self.betas))
    def update(self, a, r):
        if r > 0: self.alphas[a] += 1
        else: self.betas[a] += 1

# 运行实验
rewards_eg, opt_eg = run_multiple(EpsilonGreedyWrapper)
rewards_ucb, opt_ucb = run_multiple(UCBWrapper)
rewards_ts, opt_ts = run_multiple(TSWrapper)

cum_eg = np.cumsum(rewards_eg)
cum_ucb = np.cumsum(rewards_ucb)
cum_ts = np.cumsum(rewards_ts)

# 理论最优
optimal_reward = true_probs[best_arm] * np.arange(1, 1001)

print("=== 三种探索策略全面对比(50次实验平均)===")
print(f"\n累积奖励(1000步):")
print(f"  ε-greedy:        {cum_eg[-1]:.0f}")
print(f"  UCB:             {cum_ucb[-1]:.0f}")
print(f"  Thompson Sampling: {cum_ts[-1]:.0f}")
print(f"  理论最优:         {optimal_reward[-1]:.0f}")

print(f"\n遗憾(Regret):")
print(f"  ε-greedy:        {optimal_reward[-1] - cum_eg[-1]:.0f}")
print(f"  UCB:             {optimal_reward[-1] - cum_ucb[-1]:.0f}")
print(f"  Thompson Sampling: {optimal_reward[-1] - cum_ts[-1]:.0f}")

print(f"\n最后100步最优动作选择率:")
print(f"  ε-greedy:        {opt_eg[-100:].mean()*100:.1f}%")
print(f"  UCB:             {opt_ucb[-100:].mean()*100:.1f}%")
print(f"  Thompson Sampling: {opt_ts[-100:].mean()*100:.1f}%")

print(f"\n结论:UCB和Thompson Sampling明显优于ε-greedy")
print(f"  因为它们进行的是"智能探索"而非"盲目探索"")

什么用(应用):理解不同探索策略的优劣,能帮助你在实际RL项目中做出正确的选择。对于简单任务,ε-greedy足够好;对于需要高效探索的任务(如稀疏奖励环境),UCB或好奇心驱动的方法更合适;对于不确定性量化的需求,Thompson Sampling或其深度版本是最佳选择。

哪些坑(缺点):探索策略的选择高度依赖任务特性;没有一种策略在所有场景下都最优;深度RL中的探索策略需要与函数近似配合,可能不稳定;探索过多可能导致训练时间过长,探索过少可能导致策略次优。

概念关系图谱

概念上位概念核心思想关键公式/方法应用场景
多臂老虎机序列决策K个动作,未知奖励分布,最大化累积奖励Regret = T·μ* - Σr_t探索-利用基础模型
ε-greedy探索策略以概率ε随机探索随机动作 vs argmax Q(a)简单RL任务
UCB探索策略乐观面对不确定性,加探索奖励Q(a) + c·√(ln t / N(a))高效探索
Thompson Sampling探索策略贝叶斯后验采样μ̃_a ~ Beta(α_a, β_a)不确定性量化
遗憾(Regret)评估指标与最优策略的累积奖励差距Regret = O(log T) / O(T)评估探索策略
衰减ε探索调度ε从大到小逐步衰减ε_t = max(ε_min, ε_0·d^t)平衡探索与利用
好奇心驱动深度探索用预测误差作为内在奖励ICM, RND稀疏奖励探索
熵正则化探索正则最大化策略熵鼓励探索SAC, H(π(·s))

重点答疑

Q1: 为什么ε-greedy的遗憾是O(T)而UCB是O(log T)?

ε-greedy即使已经确定了最优动作,仍然以概率ε随机探索(包括已知很差的动作),导致累积遗憾随T线性增长。而UCB的探索奖励c·√(ln t/N(a))随着动作被选次数增多而快速衰减,最终几乎只选最优动作,所以遗憾只随log T增长。

解答:ε-greedy是"永远在探索"(线性遗憾),UCB是"探索够了就停止"(对数遗憾)。这就是"盲目探索"和"智能探索"的本质区别。

Q2: Thompson Sampling和UCB哪个更好?

两者在理论上都是接近最优的(遗憾O(log T)),但各有优势:

  • Thompson Sampling在实际中通常表现更好,因为它自然地利用完整的后验分布,而不是仅用置信区间。
  • UCB计算更简单,不需要维护后验分布。
  • Thompson Sampling更容易扩展到复杂模型(如贝叶斯神经网络)。
  • 在深度RL中,Thompson Sampling的近似版本(如Bootstrapped DQN)表现优异。
解答:两者都很好,Thompson Sampling通常略优但计算成本更高。实际中选择取决于任务的计算约束和建模复杂度。

Q3: 在深度RL中,ε-greedy为什么还是最常用的探索策略?

尽管ε-greedy在理论上不如UCB和Thompson Sampling,但在深度RL中它仍然是最常用的,因为:

  1. 极其简单:只需一行代码,不需要修改网络结构。
  2. 与经验回放兼容:DQN的经验回放机制已经在一定程度上缓解了探索不足的问题。
  3. 深度RL中的主要挑战是函数近似和稳定性,探索策略的选择相对次要。
  4. 在密集奖励环境中,ε-greedy已经足够好。
解答:简单就是美。在深度RL中,稳定性比探索效率更重要。但随着RL应用越来越复杂(稀疏奖励),更智能的探索策略正在变得更重要。

Q4: 探索和利用的权衡是否可以完全自动化?

有一些方法尝试自动化这个权衡:

  • 贝叶斯方法(如Thompson Sampling)自然地从数据中学习探索程度。
  • 元学习(Meta-RL)可以学习"如何探索"的策略。
  • 内在动机(Intrinsic Motivation)让智能体自己决定何时探索。

但完全自动化仍然是一个开放问题。在大多数实际应用中,仍然需要人工设置探索参数(如ε的衰减速度、UCB的c值)。

解答:目前还没有完全自动化的解决方案,但贝叶斯方法和元学习正在朝这个方向努力。实践中,调参仍然不可避免。

Q5: Contextual Bandit和MDP中的探索有什么区别?

Contextual Bandit(上下文老虎机)介于Bandit和MDP之间:

  • Bandit:没有状态,只有动作选择。
  • Contextual Bandit:每次决策前观察到一个"上下文"(状态),但动作不影响下一个状态。
  • MDP:动作影响状态转移,探索影响未来能访问的状态。

Contextual Bandit中,探索只需要考虑"这个上下文下哪个动作好"。MDP中,探索还需要考虑"这个动作会把我带到哪里"——即探索的"策略性"(strategic exploration)。

解答:MDP中的探索比Bandit复杂得多,因为探索不仅影响即时奖励,还影响未来能访问的状态。这是深度RL中探索研究的核心挑战。

Q6: 在RL中,有什么方法可以替代ε-greedy进行更高效的探索?

除了ε-greedy,还有多种探索策略:

  1. 参数噪声:在策略网络的参数上加噪声,而非在输出上加噪声。比ε-greedy更结构化。
  2. 熵正则化:在损失函数中加入策略熵项,鼓励策略保持随机性(SAC)。
  3. 好奇心驱动:用状态预测误差作为内在奖励(ICM),鼓励探索"意外"的状态。
  4. 计数探索:给访问次数少的状态额外奖励(#Exploration)。
  5. Bootstrapped DQN:用多个Q网络头实现Thompson Sampling的近似。
解答:参数噪声对于连续控制任务效果好,好奇心驱动对于稀疏奖励环境好,熵正则化是SAC的标准做法。选择哪种取决于任务特性。

章节单词汇总

英文音标中文
exploration/ˌekspləˈreɪʃn/探索
exploitation/ˌeksplɔɪˈteɪʃn/利用
multi-armed bandit/ˈmʌlti ɑːrmd ˈbændɪt/多臂老虎机
epsilon-greedy/ˈepsɪlɒn ˈɡriːdi/ε-贪婪
upper confidence bound/ˈʌpər ˈkɑːnfɪdəns baʊnd/置信上界
Thompson Sampling/ˈtɑːmpsən ˈsæmplɪŋ/汤普森采样
regret/rɪˈɡret/遗憾
Hoeffding inequality/ˈhoʊfdɪŋ ˌɪnɪˈkwɑːləti/霍夫丁不等式
beta distribution/ˈbeɪtə ˌdɪstrɪˈbjuːʃn/Beta分布
posterior/pɑːˈstɪriər/后验
prior/ˈpraɪər/先验
Bayesian/ˈbeɪziən/贝叶斯的
curiosity-driven/ˌkjʊriˈɑːsəti ˈdrɪvn/好奇心驱动的
entropy regularization/ˈentrəpi ˌreɡjələrəˈzeɪʃn/熵正则化
parameter noise/pəˈræmɪtər nɔɪz/参数噪声
contextual bandit/kənˈtekstʃuəl ˈbændɪt/上下文老虎机
intrinsic motivation/ɪnˈtrɪnsɪk ˌmoʊtɪˈveɪʃn/内在动机

面试练习

Q1 [单选] ε-greedy策略中,ε=0.2的含义是什么?

  • A. 80%概率随机探索
  • B. 20%概率随机探索,80%概率选择最优动作
  • C. 每个动作有20%概率被选中
  • D. 学习率为0.2
解答:ε=0.2表示探索概率20%,利用概率80%。即20%的时间随机选择动作,80%的时间选择当前最优动作。

Q2 [单选] UCB策略中,探索奖励c·√(ln t/N(a))的作用是什么?

  • A. 惩罚被选次数多的动作
  • B. 给被选次数少的动作一个"加分",鼓励探索
  • C. 减小所有动作的估计值
  • D. 增加学习率
解答:探索奖励给被选次数少的动作一个加分,使得它们有机会被选中。当N(a)很小时,探索奖励大;当N(a)很大时,探索奖励接近0,策略退化为贪心。

Q3 [多选] 以下关于Thompson Sampling的说法,哪些是正确的?

  • A. 它从后验分布中采样,自然平衡探索与利用
  • B. 对于Bernoulli奖励,使用Beta分布作为共轭先验
  • C. 它总是选择估计值最大的动作
  • D. 不确定性大的动作有更大的概率被选中
解答:A正确,采样机制自然实现了探索。B正确,Beta-Bernoulli共轭对是Thompson Sampling最常见的实现。C错误,它选采样值最大的动作,而非估计值最大的。D正确,后验分布宽的动作,采样值范围大,有时会采到高值。

Q4 [单选] 多臂老虎机问题的"遗憾"(Regret)衡量的是什么?

  • A. 智能体的"后悔"情绪
  • B. 与已知最优策略相比,累积奖励的损失
  • C. 探索次数与利用次数的比例
  • D. 每个动作的方差
解答:遗憾 = T·μ* - Σ_{t=1}^T r_t,其中μ*是最优手臂的期望奖励。它衡量的是"因为不知道哪个手臂最好,而损失了多少奖励"。遗憾越小,探索策略越高效。

Q5 [多选] 以下哪些是MDP中探索比Bandit中探索更复杂的原因?

  • A. MDP中动作影响未来状态,Bandit中不影响
  • B. MDP需要探索状态空间,而不仅是动作空间
  • C. MDP中不需要探索
  • D. MDP中探索可能导致终止状态或危险状态
解答:A正确,MDP中动作影响状态转移,这是核心区别。B正确,MDP中需要探索"哪些状态值得访问"。C错误。D正确,MDP中探索可能带来代价(如掉入陷阱)。

Q6 [单选] UCB策略的遗憾上界是什么?

  • A. O(T)
  • B. O(log T)
  • C. O(1)
  • D. O(T²)
解答:UCB的遗憾上界是O(log T),即对数级别。这意味着随着T增大,平均遗憾趋于0。ε-greedy的遗憾是O(T),即线性级别。

Q7 [单选] 在UCB的计算中,如果某个动作的N(a)=0(从未被选过),通常如何处理?

  • A. 优先选择该动作
  • B. 跳过该动作
  • C. 给该动作赋予0值
  • D. 给该动作赋予无限大的值
解答:通常先确保每个动作至少被选一次(初始化阶段),或者在UCB计算中给未选过的动作赋予一个很大的值。这确保了探索的完整性。

Q8 [多选] 以下哪些是深度RL中常用的探索策略?

  • A. ε-greedy
  • B. 熵正则化(如SAC)
  • C. 参数噪声
  • D. 确定性策略
解答:A、B、C都是深度RL中常用的探索策略。D(确定性策略)本身不提供探索,需要配合其他探索机制(如Ornstein-Uhlenbeck噪声)。

Q9 [单选] Thompson Sampling中,如果某个动作被选了100次,成功80次,它的Beta后验分布是什么?

  • A. Beta(80, 20)
  • B. Beta(81, 21)
  • C. Beta(80, 100)
  • D. Beta(100, 80)
解答:Beta(α+success, β+failure) = Beta(1+80, 1+20) = Beta(81, 21)。Beta分布的先验参数(1,1)对应均匀分布,加上成功次数和失败次数得到后验参数。

Q10 [多选] 以下关于探索-利用权衡的说法,哪些是正确的?

  • A. 过度利用可能导致收敛到次优策略
  • B. 过度探索会降低短期绩效
  • C. 探索和利用是两个完全独立的问题
  • D. 好的探索策略应该在探索和利用之间找到平衡
解答:A正确,如果不探索,可能永远找不到更好的动作。B正确,探索期间可能选择次优动作,降低短期奖励。C错误,探索和利用是同一个问题的两面。D正确,平衡是关键。