从多臂老虎机开始理解强化学习:探索与利用

一直想要系统学习RL的相关知识,脑子里想着能不能完全依靠 Codex 进行学习,于是就有了下面的教程。

完整内容:https://github.com/lanheixingkong/rl-course

强化学习听起来经常和复杂游戏、机器人控制、PPO、DQN 这些词绑在一起。但真正进入 RL,最适合的第一步不是神经网络,而是一个非常小的问题:多臂老虎机。

这个问题小到只有“选择哪个动作”,但它已经包含 RL 的核心矛盾:你是应该选择当前看起来最好的动作,还是继续尝试那些不确定的动作?

1. 问题:你要反复做选择,但看不到所有答案

想象你在做推荐系统实验,有 5 种推荐策略:


   
   
    
   
   动作 0: 平均收益 0.2
动作 1: 平均收益 0.0
动作 2: 平均收益 1.4
动作 3: 平均收益 0.7
动作 4: 平均收益 1.0

真实最优动作是动作 2。但 agent 一开始不知道这些真实均值。它每次只能选择一个动作,然后看到这次选择的奖励。

这很像真实问题:

  • • 推荐系统只能知道用户对已推荐内容的反馈;
  • • 广告投放只能知道被展示广告的点击或转化;
  • • A/B 实验只能知道被分配方案的结果;
  • • 交易或调度策略只能知道自己实际执行后的收益。

关键限制是:你不能同时知道“如果刚才选另一个动作会怎样”。

2. 运行实验

在项目根目录运行:


   
   
    
   
   python lessons/01_bandit/bandit.py

这个脚本会比较三种策略:

  • • greedy:永远选择当前估计收益最高的动作。
  • • epsilon_greedy:大多数时候选当前最好,少数时候随机探索。
  • • ucb:优先尝试“不确定但可能很好”的动作。

默认实验会做 100 次独立运行,每次运行 2000 步,然后看平均表现。

这里要先分清两个数字:


   
   
    
   
   5 个动作 = 每一步可选的 5 个选项
2000 步 = 在这 5 个选项里重复选择 2000 次

不是有 2000 个不同动作,而是同样 5 个动作被反复选择。可以把它想成 5 台老虎机:第 1 步从 5 台里选 1 台,第 2 步再从同样 5 台里选 1 台,一直重复到第 2000 步。

3. 先看结果,不急着看公式

默认输出的核心结果是:

策略
平均总收益
平均 regret
最优动作选择率
greedy
1991.53
808.47
35.95%
epsilon-greedy, epsilon=0.1
2604.36
195.64
86.82%
UCB
2683.85
116.15
90.52%

这几个指标可以先这样理解:

指标
中文含义
怎么看
平均总收益
100 次实验里平均拿到多少奖励
越高越好
平均 regret
相比“每次都选真正最优动作”少拿了多少
越低越好
最优动作选择率
有多少比例选中了真实最好的动作 2
越高越好

如果你看到程序里的英文输出,对应关系是:


   
   
    
   
   avg total reward     = 平均总收益
avg regret           = 平均 regret / 平均损失
avg best action rate = 最优动作选择率

这里有一个很重要的反直觉点:greedy 看起来最“聪明”,因为它总是选当前看起来最好的动作。但它的平均表现最差。

原因是:早期样本很少,奖励又有随机噪声。某个普通动作可能刚开始运气好,拿到几次高奖励;真正最优的动作可能刚开始运气差。greedy 一旦被早期噪声误导,就会一直重复错误选择,因为它不愿意再探索。

这就是强化学习里探索的价值:探索不是“瞎试”,而是为减少长期决策错误付出的信息成本。

程序还会输出两行看起来更细的内容:


   
   
    
   
   last-run estimates
last-run counts

last-run estimates 表示最后一次实验里,agent 对每个动作平均收益的估计。比如:


   
   
    
   
   [0.17, -0.14, 1.43, 0.61, 1.06]

这表示 agent 估计动作 0 的平均收益大约是 0.17,动作 1 大约是 -0.14,动作 2 大约是 1.43,依此类推。

last-run counts 表示最后一次实验里,每个动作被选了多少次。比如:


   
   
    
   
   [48, 23, 1752, 47, 130]

这 5 个数字加起来是 2000,意思是在 2000 次选择中,动作 2 被选了 1752 次,动作 4 被选了 130 次。它能帮你看出 agent 把大部分机会给了哪些动作。

4. 三种策略分别在做什么

greedy:只利用

greedy 的规则很简单:


   
   
    
   
   选择当前 Q(a) 最大的动作

Q(a) 是动作 a 的平均历史收益估计。

它的问题不是不会利用,而是太早利用。刚开始每个动作的信息都很少,当前最优估计不一定可靠。

epsilon-greedy:固定比例探索

epsilon-greedy 的规则:


   
   
    
   
   以 epsilon 的概率随机探索
以 1 - epsilon 的概率选择当前 Q(a) 最大的动作

默认 epsilon=0.1,意思是 10% 的时间随机试,90% 的时间利用当前最好动作。

它很常用,因为简单、稳定、容易作为 baseline。很多复杂 RL 算法里仍然能看到它的影子。

UCB:按不确定性探索

UCB 的思路是:一个动作如果平均收益高,应该被选择;一个动作如果尝试次数少、不确定性大,也值得再试。

代码里的 UCB 分数是:


   
   
    
   
   score(a) = Q(a) + c * sqrt(log(t) / N(a))

含义:

  • • Q(a):当前估计收益,代表利用。
  • • N(a):动作被尝试的次数,越少越不确定。
  • • sqrt(log(t) / N(a)):不确定性奖励,代表探索。
  • • c:探索强度。

所以 UCB 不是随机探索,而是更系统地探索“不确定但可能有价值”的动作。

5. 动作价值是怎么学出来的

每个动作都有一个真实但未知的期望收益:


   
   
    
   
   q*(a)

agent 看不到 q*(a),只能维护估计:


   
   
    
   
   Q(a)

当动作 a 被选择并得到奖励 r 后,用增量平均更新:


   
   
    
   
   Q(a) <- Q(a) + (r - Q(a)) / N(a)

这句话可以这样读:

  • • r - Q(a) 是这次奖励和旧估计之间的差距;
  • • N(a) 越大,新样本对平均值的影响越小;
  • • 试得越多,估计通常越稳定。

这个公式不是所有 RL 都固定使用的唯一更新公式。它是“样本平均”的增量写法,适合当前 bandit 例子,因为我们要估计某个动作到目前为止的平均奖励。

更通用的结构是:


   
   
    
   
   新估计 = 旧估计 + 步长 * 误差

在本课里:


   
   
    
   
   误差 = r - Q(a)
步长 = 1 / N(a)

所以得到:


   
   
    
   
   Q(a) <- Q(a) + (r - Q(a)) / N(a)

这也是后面 Q-learning 的伏笔。以后我们会看到类似形式:


   
   
    
   
   新估计 <- 旧估计 + 学习率 * 误差

Q-learning 里的步长通常写成学习率 alpha,不一定等于 1 / N(a);误差也会变成 TD error,包含下一状态的长期价值。RL 里很多更新公式,本质上都是这种“根据误差修正估计”的结构。

6. regret:为什么要关心错过了多少

在这个实验里,我们知道真实最优动作是动作 2,平均收益是 1.4。如果每一步都选它,2000 步的理论最优收益是:


   
   
    
   
   2000 * 1.4 = 2800

regret 定义为:


   
   
    
   
   regret = 理论最优总收益 - 实际总收益

默认实验里:

  • • greedy 的 regret 是 808.47;
  • • epsilon-greedy 的 regret 是 195.64;
  • • UCB 的 regret 是 116.15。

regret 越低,说明策略越接近长期最优。

在实际业务里,你通常不知道理论最优是多少,所以不能总是直接计算 regret。但这个概念很重要,因为它提醒你:探索的代价和错误利用的代价都应该算进长期效果里。

7. 改 epsilon:探索太少和太多都不理想

接下来改参数不是为了随便试,而是每次验证一个问题。

第一个参数是 epsilon。它只影响 epsilon_greedy 策略,表示随机探索的比例。


   
   
    
   
   epsilon = 0.1

意思是:


   
   
    
   
   90% 的时候选当前看起来最好的动作;
10% 的时候随机选一个动作。

运行:


   
   
    
   
   python lessons/01_bandit/bandit.py --epsilon 0.01
python lessons/01_bandit/bandit.py --epsilon 0.3

这两个命令分别在问:


   
   
    
   
   epsilon = 0.01: 如果只有 1% 的机会探索,会不会太保守?
epsilon = 0.3 : 如果有 30% 的机会随机探索,会不会浪费太多机会?

关键结果:

epsilon
平均总收益
平均 regret
最优动作选择率
0.01
2420.52
379.48
61.49%
0.10
2604.36
195.64
86.82%
0.30
2340.51
459.49
74.42%

epsilon=0.01 探索太少,容易早早卡在错误动作上。

epsilon=0.3 探索太多,即使已经知道动作 2 更好,也会频繁随机选择差动作。

所以探索率不是越大越好,也不是越小越好。它是在“获取信息”和“利用已有信息”之间做权衡。

8. 改步数:短期最优和长期最优可能不同

第二个参数是 steps。它表示一轮实验里,agent 要连续做多少次选择。


   
   
    
   
   steps = 2000

意思是:在同样 5 个动作里,重复选择 2000 次。

运行:


   
   
    
   
   python lessons/01_bandit/bandit.py --steps 200
python lessons/01_bandit/bandit.py --steps 10000

这两个命令分别在问:


   
   
    
   
   steps = 200  : 如果任务很短,探索还有没有足够时间回本?
steps = 10000: 如果任务很长,前期探索找到好动作后,后面能不能长期受益?

200 步时:

策略
平均总收益
平均 regret
最优动作选择率
greedy
196.99
83.01
35.55%
epsilon-greedy
235.65
44.35
62.03%
UCB
232.92
47.08
65.21%

10000 步时:

策略
平均总收益
平均 regret
最优动作选择率
greedy
9963.03
4036.97
35.99%
epsilon-greedy
13213.48
786.52
91.01%
UCB
13826.56
173.44
97.14%

步数越长,早期探索带来的价值越明显。因为你前面花一点成本找到好动作,后面可以反复受益。

这也是很多 RL 问题的本质:当前动作不只是影响当前奖励,也影响未来你知道什么、能做什么。

9. 改噪声:反馈越不稳定,越不能轻信早期结果

第三个参数是 reward-stdstd 是 standard deviation,也就是标准差。这里它表示奖励的随机波动大小。

默认:


   
   
    
   
   reward_std = 1.0

如果改成:


   
   
    
   
   reward_std = 2.0

意思是:同一个动作每次给出的奖励更不稳定,有时偏高,有时偏低。

运行:


   
   
    
   
   python lessons/01_bandit/bandit.py --reward-std 2.0

这个命令是在问:


   
   
    
   
   如果反馈更随机,agent 是否更容易被少量早期样本误导?

关键结果:

策略
平均总收益
平均 regret
最优动作选择率
greedy
2026.77
773.23
39.84%
epsilon-greedy
2475.66
324.34
73.75%
UCB
2661.89
138.11
87.72%

奖励噪声变大后,单次反馈更不可靠。你更需要多试几次,才能区分“这个动作真的好”和“只是这次运气好”。

10. 改 UCB 探索强度:探索奖励也不能太大

第四个参数是 ucb-c。它只影响 UCB 策略。

UCB 给每个动作算分时,大致是:


   
   
    
   
   分数 = 当前估计收益 + 探索奖励

代码里是:


   
   
    
   
   estimate + c * sqrt(log(step + 1) / count)

这里的 c 就是 ucb-c,它控制探索奖励的强弱。

为什么公式长这样?先不要从数学证明看,而是从目标看。

UCB 想做的是乐观决策:


   
   
    
   
   UCB 分数 = 当前估计收益 + 不确定性奖励

estimate 是当前估计收益。count 是这个动作被试过多少次。动作试得越少,count 越小,不确定性奖励越大;动作试得越多,不确定性奖励越小。

log(step + 1) 的作用是让探索压力随着总步数慢慢增长,但增长得很慢。它提醒 agent:如果时间已经过去很久,某些动作仍然很少被试过,就应该给它们一点机会。

sqrt(...) 让不确定性奖励平滑变化。当前阶段不需要推导这个形式,只要理解它来自经典 UCB1 算法背后的概率上界,用来表达“试得越少越不确定,越值得乐观一点”。

UCB 不是只有这一种公式。它是一类方法,核心都是:


   
   
    
   
   选择“估计收益 + 不确定性奖励”最高的动作

常见变体包括 UCB1、Gaussian UCB、Bayesian UCB、Linear UCB。当前代码使用的是适合入门的 UCB1 风格:


   
   
    
   
   estimate + c * sqrt(log(step + 1) / count)

运行:


   
   
    
   
   python lessons/01_bandit/bandit.py --ucb-c 0.5
python lessons/01_bandit/bandit.py --ucb-c 4.0

在当前固定案例里,一组参考结果是:

ucb-c
UCB 平均总收益
UCB 平均 regret
UCB 最优动作选择率
0.5
2704.82
95.18
89.59%
2.0
2683.85
116.15
90.52%
4.0
2478.10
321.90
75.44%

ucb-c=4.0 时探索奖励太强,agent 会花更多次数尝试不确定动作,即使它已经有足够证据知道动作 2 很好。于是长期收益下降,regret 变大。

ucb-c=0.5 在这个小例子里表现更好,但这不代表它永远更好。不同任务里,动作差距、奖励噪声、总步数不同,合适的探索强度也会变。

这和 epsilon 的结论类似:探索不是越多越好,也不是越少越好。关键是控制探索成本和长期收益之间的平衡。

11. 为什么 Bandit 是 RL 入门问题

如果你学过传统机器学习和深度神经网络,很自然会问:这个问题不就是估计每个动作的平均收益吗?为什么要叫 RL?

答案是:Bandit 不是因为算法复杂才属于 RL,而是因为它具备 RL 的核心数据生成方式。

在监督学习里,数据通常先给定:


   
   
    
   
   x -> y

模型要做的是从已有样本里学习映射。比如给你一批广告展示记录、用户特征、广告特征、是否点击,然后训练一个点击率预测模型。

但 Bandit 里的关键不是“如何拟合已有数据”,而是:


   
   
    
   
   我下一步该选择哪个动作,才能一边赚钱,一边收集更有价值的数据?

数据不是静态给你的。数据是你的策略主动选择动作之后产生的。你选择动作 0,就只能看到动作 0 的奖励;你没有选择动作 2,就拿不到动作 2 在这一步的反馈。

这带来三个监督学习通常不会直接处理的问题:

第一,反馈是部分可见的。每一步只看到被选动作的结果,看不到所有动作的标签。

第二,采样分布由策略决定。你越常选择某个动作,数据里这个动作的样本就越多;你不选择某个动作,就几乎没有它的数据。

第三,学习和决策同时发生。模型不是在训练结束后才影响世界,而是一边决策、一边产生训练数据。

这三个点就是 Bandit 适合作为 RL 入门的原因:它去掉了复杂状态转移,只保留“主动决策如何影响数据和收益”这个核心。

12. 能不能用传统机器学习或神经网络解决

可以,但要看你说的是哪一种问题设置。

情况 A:你已经有完整、无偏、覆盖充分的历史数据

如果你已经知道每个动作在大量样本上的收益,或者历史数据对每个动作都有充分随机试验,那么可以直接做统计估计:


   
   
    
   
   每个动作的平均收益 = 这个动作历史奖励的平均值

如果还有用户特征、上下文特征,也可以训练监督学习模型:


   
   
    
   
   输入: 用户特征 + 动作特征
输出: 预测奖励或点击率

神经网络当然也能做这件事。它可以预测:


   
   
    
   
   reward = f(context, action)

然后每次选择预测 reward 最高的动作。

但这只解决了“利用已有数据做预测”,没有自动解决探索问题。

情况 B:历史数据是由旧策略产生的

真实业务里更常见的是:历史数据不是随机来的,而是旧系统选择后的结果。

比如旧推荐系统更常推荐动作 4,所以你有很多动作 4 的样本;动作 2 很少被推荐,所以样本很少。此时直接训练监督模型会继承旧策略的偏见:


   
   
    
   
   数据多的动作更容易被学好;
数据少的动作即使真实很好,也可能被低估;
模型上线后继续少选它,于是永远收不到足够反馈。

这叫选择偏差或反馈循环。Bandit/RL 方法要处理的正是这个问题:策略必须主动给不确定动作一些机会,否则系统可能永远发现不了更好的选择。

情况 C:你在线上连续做决策,并且试错成本可控

这时 Bandit 就是合适的选择。因为你关心的不只是预测准不准,而是长期总收益:


   
   
    
   
   短期少赚一点,用来探索;
如果找到更好的动作,长期赚更多。

这就是为什么在广告、推荐、A/B 实验、定价、实验设计里,Bandit 经常比“训练一个预测模型然后贪心选择”更合适。

13. 那为什么不用一个神经网络直接学最优动作

神经网络是函数近似器,不是问题定义本身。它可以成为 Bandit 或 RL 系统的一部分,但不能替你消除探索问题。

如果动作很少、没有上下文,当前课程里的表格平均值就够了:


   
   
    
   
   Q(a)

如果动作很多,或者每次决策有上下文,例如用户年龄、最近行为、时间、设备、内容特征,就可以用模型近似:


   
   
    
   
   Q(context, action)

这个模型可以是线性模型、树模型,也可以是神经网络。

但即使用了神经网络,你仍然要回答:

  • • 新动作没有数据,怎么让它被尝试?
  • • 模型对某些动作不确定,怎么把不确定性纳入决策?
  • • 是永远选预测最高的动作,还是保留探索?
  • • 上线后新数据由当前策略产生,如何避免反馈循环?

所以准确说:


   
   
    
   
   Bandit/RL 解决的是决策和探索框架;
传统 ML/神经网络可以用来估计奖励、价值或策略。

二者不是互斥关系。很多实际系统是“Bandit 框架 + 监督模型/神经网络预测奖励”。

14. 什么时候不该用 RL

如果你只是有一批固定数据,要做分类、回归、排序,而且模型决策不会影响未来数据收集,那么先用传统监督学习。

如果你可以离线拿到每个动作的真实标签,比如每个商品对每个用户是否会点击都已知,那么这不是 Bandit 的典型设置。

如果试错成本极高,又没有模拟器或安全约束,也不要贸然用在线 RL。

更准确地说,判断标准不只是“有没有数据”,而是数据是否足够支持你要做的决策。

如果数据完整、覆盖充分、偏差可控,并且你的模型上线后不会明显改变未来数据分布,优先使用传统 ML:


   
   
    
   
   已有可靠数据 -> 训练预测模型 -> 用模型做决策

如果数据不完整,尤其是只能看到旧策略曾经选择过的动作反馈,未选择动作没有反馈,那么就不能把它当成普通监督学习问题直接处理:


   
   
    
   
   旧策略选择了什么 -> 你只看到这些选择的结果 -> 数据本身带有选择偏差

这时 Bandit/RL 的价值在于:它把“如何产生新数据”也纳入了算法设计。agent 不只是学习已有数据,还要决定下一步该探索什么、利用什么。

判断是否需要 Bandit/RL,可以问三个问题:


   
   
    
   
   1. 我的动作会不会影响我未来能看到的数据?
2. 我是不是只能看到被选择动作的反馈,而看不到未选择动作的反馈?
3. 我是否需要在探索新选择和利用当前好选择之间做权衡?

如果这三个问题的答案都是“是”,就进入了 Bandit/RL 的地盘。

15. 这和完整 RL 有什么关系

Bandit 是最小 RL 问题。它没有复杂状态,也没有长期状态转移。但它已经包含了完整 RL 的几个核心词:

Bandit 里的概念
完整 RL 里的对应概念
动作 a
action
奖励 r
reward
动作价值 Q(a)
action-value function
选择动作的规则
policy
探索 vs 利用
exploration vs exploitation
regret
长期决策损失

下一课进入 GridWorld 后,会多出状态 state 和状态转移 transition。到那时问题会变成:


   
   
    
   
   不只是哪个动作平均收益最高,
而是在某个状态下,哪个动作能带来更好的长期结果。

这就是从 bandit 走向真正强化学习的关键一步。

16. 本课你应该带走什么

第一,RL 不是一上来就训练神经网络。先问:agent 在反复做什么选择?它能看到什么反馈?它看不到什么反事实结果?

第二,探索不是随机犯错,而是在信息不足时主动收集信息。

第三,短期看起来最好的选择,可能因为早期噪声导致长期很差。

第四,读 RL 代码时,先找这些东西:

  • • action 是什么;
  • • reward 是什么;
  • • policy 如何选择 action;
  • • value estimate 如何更新;
  • • exploration 如何发生;
  • • 最终用什么指标评价。

只要能把这些问题问清楚,你就已经开始用 RL 的方式理解问题了。

如果觉得内容不错,欢迎你点一下「在看」,或是将文章分享给其他有需要的人^^

相关好文推荐:

每次看见有人说能够识别出一段文字是不是AI生成的,我都忍不住想笑

飞书会取代微信吗?

AI 时代的软件与软件公司应该长什么样?

0条留言

留言