跳转至

04 - 探索与利用

学习时间: 2-3小时 重要性: ⭐⭐⭐⭐ 强化学习的核心权衡问题 前置知识: SARSA、Q-Learning


🎯 学习目标

完成本章后,你将能够: - 理解探索-利用权衡的本质 - 掌握ε-贪婪、UCB等探索策略 - 实现乐观初始化和梯度Bandit - 分析不同探索策略的优劣 - 在实际问题中选择合适的探索策略


1. 探索-利用权衡

1.1 问题定义

核心问题

智能体应该利用已知的最优动作,还是探索可能更好的未知动作?

类比: - 去餐厅:去最喜欢的餐厅(利用)vs 尝试新餐厅(探索) - 广告推荐:展示点击率最高的广告(利用)vs 测试新广告(探索)

1.2 形式化定义

遗憾(Regret)

\[R_T = T \cdot \mu^* - \sum_{t=1}^T \mu_{a_t}\]

其中: - \(\mu^*\):最优动作的期望奖励 - \(\mu_{a_t}\):第t步选择动作的期望奖励 - \(T\):总步数

目标:最小化累积遗憾


2. ε-贪婪策略

2.1 算法

思想:以ε概率随机探索,以1-ε概率选择当前最优动作。

Python
def epsilon_greedy(q_values, epsilon):
    """
    ε-贪婪策略

    参数:
        q_values: 各动作的Q值估计
        epsilon: 探索概率

    返回:
        选择的动作
    """
    if np.random.random() < epsilon:
        # 探索:随机选择
        return np.random.randint(len(q_values))
    else:
        # 利用:选择Q值最大的动作
        return np.argmax(q_values)

2.2 ε的衰减

固定ε的问题: - 太小:探索不足,可能陷入局部最优 - 太大:过度探索,收敛慢

解决方案:随时间衰减

Python
# 线性衰减
epsilon = max(0.01, 0.1 - episode * 0.0001)

# 指数衰减
epsilon = 0.1 * (0.995 ** episode)

# 反比衰减
epsilon = 1 / (episode + 1)

3. 乐观初始化

3.1 思想

核心:将所有动作的初始Q值设为很高,鼓励早期探索。

原理: - 高初始值 → 实际奖励 < 估计 → TD误差为负 → Q值下降 - 被选择过的动作Q值下降,未被选择的相对更高 - 自然鼓励探索未被选择的动作

3.2 代码实现

Python
def q_learning_optimistic(env, num_episodes=1000, alpha=0.1,
                          gamma=0.9, epsilon=0.1, init_value=10.0):
    """
    带乐观初始化的Q-Learning

    参数:
        init_value: 乐观初始值(通常设为可能的最大回报)
    """
    n_actions = env.get_action_space()

    # 乐观初始化
    Q = defaultdict(lambda: np.ones(n_actions) * init_value)  # defaultdict访问不存在的键时返回默认值

    for episode in range(num_episodes):
        state, _ = env.reset()
        done = False

        while not done:
            # 可以使用更小的epsilon,因为乐观初始化已经鼓励探索
            if np.random.random() < epsilon:
                action = np.random.randint(n_actions)
            else:
                action = np.argmax(Q[state])

            next_state, reward, terminated, truncated, _ = env.step(action)
            done = terminated or truncated

            # Q-Learning更新
            if not done:
                td_target = reward + gamma * np.max(Q[next_state])
            else:
                td_target = reward

            Q[state][action] += alpha * (td_target - Q[state][action])
            state = next_state

    return Q

4. 上置信界(UCB)

4.1 思想

核心:不仅考虑Q值,还考虑不确定性。

\[A_t = \arg\max_a \left[ Q_t(a) + c \sqrt{\frac{\ln t}{N_t(a)}} \right]\]

其中: - \(Q_t(a)\):动作a的Q值估计 - \(N_t(a)\):动作a被选择的次数 - \(c\):探索参数 - \(t\):总时间步

直观: - 选择次数少 → 第二项大 → 鼓励探索 - 选择次数多 → 第二项小 → 主要利用

4.2 代码实现

Python
def ucb_action(q_values, action_counts, total_count, c=2.0):
    """
    UCB动作选择

    参数:
        q_values: 各动作的Q值
        action_counts: 各动作被选择的次数
        total_count: 总选择次数
        c: 探索参数

    返回:
        选择的动作
    """
    n_actions = len(q_values)
    ucb_values = np.zeros(n_actions)

    for a in range(n_actions):
        if action_counts[a] == 0:
            # 从未选择的动作,赋予无穷大UCB值
            ucb_values[a] = float('inf')
        else:
            exploitation = q_values[a]
            exploration = c * np.sqrt(np.log(total_count) / action_counts[a])
            ucb_values[a] = exploitation + exploration

    return np.argmax(ucb_values)

def ucb_q_learning(env, num_episodes=1000, alpha=0.1, gamma=0.9, c=2.0):
    """使用UCB探索的Q-Learning"""
    n_actions = env.get_action_space()
    Q = defaultdict(lambda: np.zeros(n_actions))
    action_counts = defaultdict(lambda: np.zeros(n_actions))
    total_steps = 0

    for episode in range(num_episodes):
        state, _ = env.reset()
        done = False

        while not done:
            # UCB动作选择
            action = ucb_action(Q[state], action_counts[state], total_steps, c)
            action_counts[state][action] += 1
            total_steps += 1

            next_state, reward, terminated, truncated, _ = env.step(action)
            done = terminated or truncated

            # Q-Learning更新
            if not done:
                td_target = reward + gamma * np.max(Q[next_state])
            else:
                td_target = reward

            Q[state][action] += alpha * (td_target - Q[state][action])
            state = next_state

    return Q

5. 梯度Bandit

5.1 思想

核心:使用softmax分布选择动作,基于偏好(preference)而非Q值。

偏好更新

\[H_{t+1}(a) = H_t(a) + \alpha (R_t - \bar{R}_t)(\mathbb{1}_{a=A_t} - \pi_t(a))\]

其中: - \(H_t(a)\):动作a的偏好 - \(\bar{R}_t\):平均奖励(基线) - \(\pi_t(a)\):选择动作a的概率

5.2 Softmax策略

\[\pi_t(a) = \frac{e^{H_t(a)}}{\sum_b e^{H_t(b)}}\]

5.3 代码实现

Python
class GradientBandit:
    """梯度Bandit算法"""

    def __init__(self, n_actions, alpha=0.1):
        self.n_actions = n_actions
        self.alpha = alpha
        self.preferences = np.zeros(n_actions)
        self.avg_reward = 0
        self.count = 0

    def softmax(self):
        """计算softmax概率"""
        exp_prefs = np.exp(self.preferences - np.max(self.preferences))
        return exp_prefs / np.sum(exp_prefs)

    def select_action(self):
        """根据softmax选择动作"""
        probs = self.softmax()
        return np.random.choice(self.n_actions, p=probs)

    def update(self, action, reward):
        """更新偏好"""
        self.count += 1
        self.avg_reward += (reward - self.avg_reward) / self.count

        probs = self.softmax()

        # 更新所有动作的偏好
        for a in range(self.n_actions):
            if a == action:
                self.preferences[a] += self.alpha * (reward - self.avg_reward) * (1 - probs[a])
            else:
                self.preferences[a] -= self.alpha * (reward - self.avg_reward) * probs[a]

6. 探索策略对比

策略 优点 缺点 适用场景
ε-贪婪 简单直观 随机探索可能低效 通用场景
乐观初始化 无需调参 需要知道最大回报 早期探索
UCB 有理论保证 需要维护计数 有限动作空间
梯度Bandit 平滑探索 需要调学习率 连续动作偏好

7. 本章总结

核心概念

Text Only
探索-利用权衡:
├── ε-贪婪: 以ε概率随机探索
├── 乐观初始化: 高初始值鼓励探索
├── UCB: 基于不确定性探索
└── 梯度Bandit: 基于偏好softmax探索

选择建议:
├── 通用场景: ε-贪婪(衰减)
├── 需要理论保证: UCB
└── 大规模动作空间: 梯度方法

✅ 自测问题

  1. 为什么需要探索-利用权衡?纯利用或纯探索有什么问题?

  2. UCB中的c参数有什么作用?如何选择合适的c值?

  3. 乐观初始化和ε-贪婪相比有什么优势?

  4. 设计一个实验比较不同探索策略的性能。


→ 下一步:05-多步方法.md