2.1 探索和利用问题
第 1 章已经让 CartPole 智能体跑起来了。它在每一步根据小车位置、速度、杆子角度和角速度选择向左或向右推,随后环境给出奖励并进入下一个状态。这个例子已经包含完整的强化学习过程,但也把几个问题混在了一起:智能体要判断哪个动作更好,要处理动作带来的状态变化,还要关心当前动作对以后奖励的影响。
学习基本定义之前,我们先把问题缩到最小。假设环境没有状态变化,每次行动之后立刻得到奖励,下一轮又回到同样的选择场景。这样可以先单独看清一个核心矛盾:智能体既要利用已经看起来不错的动作,又要继续探索还不确定的动作。
这个最小问题就是多臂老虎机。
多臂老虎机问题
想象一台有 个摇臂的老虎机。每个摇臂 都对应一个未知奖励分布 ,拉动一次会返回一个随机奖励。智能体不知道哪个摇臂平均奖励最高,只能通过一次次尝试来估计。
形式化地说,第 轮智能体选择一个动作 ,然后观察奖励:
每个摇臂的真实期望奖励记为:
如果已经知道所有 ,最好的动作就是:
真正的问题在于,智能体一开始并不知道这些期望。它必须先试,才能获得数据;但每一次试错都会消耗一次行动机会。如果一直探索,智能体会把大量机会浪费在差动作上;如果过早利用,又可能因为早期随机结果误判,把真正好的动作排除掉。
多臂老虎机去掉了状态转移和长期回报,只保留动作选择和奖励反馈。这个简化让我们先看清强化学习里最早出现的难题:动作价值不是提前给出的,而是在交互中估计出来的。
从期望奖励到行动规则
智能体会为每个动作维护一个估计值 。如果动作 已经被选择过 次,得到过奖励 ,最直接的估计方式是样本均值:
这个公式回答的是“这个动作目前看起来平均能得多少分”。有了估计值,还需要一个行动规则,把这些估计变成下一步选择。最直接的规则是贪心策略:
贪心策略每一步都选择当前估计最高的动作。它看起来合理,却有一个明显问题:早期样本很少,估计值可能被偶然结果严重影响。一个真实期望很高的摇臂,如果第一次拉到低奖励,就可能长期得不到再次尝试的机会。
因此,强化学习中的策略不能只回答“当前哪个动作估计最高”,还要回答“哪些动作虽然现在估计不高,但仍然值得再试”。这就是探索和利用问题。
ε-贪心策略
ε-贪心策略在贪心动作之外保留一小部分随机探索。每一步先掷一次概率为 的硬币:
- 以 的概率选择当前估计最好的动作;
- 以 的概率随机选择一个动作。
写成公式就是:
这条规则的意义很直接:大部分时间相信当前经验,少部分时间给未知动作机会。它不会让智能体完全被早期几次随机结果锁死。
下面是一个最小实现:
import numpy as np
class EpsilonGreedy:
def __init__(self, n_arms, epsilon=0.1):
self.n_arms = n_arms
self.epsilon = epsilon
self.q = np.zeros(n_arms)
self.n = np.zeros(n_arms)
def select(self):
if np.random.random() < self.epsilon:
return np.random.randint(self.n_arms)
return np.argmax(self.q)
def update(self, arm, reward):
self.n[arm] += 1
self.q[arm] += (reward - self.q[arm]) / self.n[arm]self.q 保存每个动作的平均奖励估计,self.n 记录每个动作被尝试过多少次。更新式:
是样本均值的增量写法。它不需要保存全部历史奖励,只要用新奖励修正旧估计。
固定 的好处是稳定。无论当前估计多么确定,智能体都会保留探索机会。它的代价也很清楚:即使已经基本知道哪个动作最好,智能体仍然会按固定比例随机选择,长期看会损失一部分奖励。
让探索随时间减少
训练早期,智能体几乎没有经验,需要更多探索。训练后期,估计值已经比较稳定,继续大量随机尝试会降低收益。于是可以让 随时间下降:
class EpsilonDecaying:
def __init__(self, n_arms, epsilon_start=1.0, epsilon_end=0.01, decay=0.995):
self.n_arms = n_arms
self.epsilon = epsilon_start
self.epsilon_end = epsilon_end
self.decay = decay
self.q = np.zeros(n_arms)
self.n = np.zeros(n_arms)
def select(self):
if np.random.random() < self.epsilon:
arm = np.random.randint(self.n_arms)
else:
arm = np.argmax(self.q)
self.epsilon = max(self.epsilon_end, self.epsilon * self.decay)
return arm
def update(self, arm, reward):
self.n[arm] += 1
self.q[arm] += (reward - self.q[arm]) / self.n[arm]这个调度表达了一个常见训练节奏:早期多试,后期多信任已有经验。后面学习 DQN 时还会再次见到这个思想。DQN 在训练早期常用较大的 ε 收集多样经验,随后逐步降低 ε,让策略更多使用已经学到的动作价值。
更有针对性的探索
ε-贪心的探索方式很粗。只要进入探索分支,它会在所有动作中随机选择,包括那些已经明显很差的动作。更好的探索策略会继续追问:哪些动作仍然不确定,因而值得再试?
UCB(Upper Confidence Bound)的做法是给每个动作的估计奖励加上一个不确定性奖金:
这里 是当前平均奖励估计, 是动作 已经被尝试的次数。尝试次数越少,不确定性奖金越大;尝试次数越多,选择就越依赖真实估计值。
Thompson 采样换了一个角度。它为每个动作维护一个“这个动作真实平均奖励可能是多少”的分布,每一轮从各个分布中采样一次,然后选择采样值最高的动作。试得少的动作分布更宽,因此仍然有机会被选中;试得多的动作分布更窄,选择会逐渐稳定。
这些方法的共同目标不是盲目增加随机性,而是把探索机会分给仍有信息价值的动作。
从老虎机走向 MDP
多臂老虎机可以看作强化学习过程的最小版本:
- 动作集合已经存在;
- 奖励会在动作之后出现;
- 策略决定下一步选什么;
- 智能体根据经验更新对动作好坏的估计。
它缺少一个关键部分:状态不会因为动作而改变。CartPole 里向左推一下会改变小车位置和杆子角度,下一步可见的局面也随之变化。语言模型生成一个 token 后,后续上下文会改变,下一步可选动作的意义也会改变。
有了会变化的状态,问题就从“在同一个局面里反复选动作”扩展成“在一串连续变化的局面里做决策”。下一节引入马尔可夫决策过程,把状态、动作、转移、奖励和长期回报放进同一套定义里。