从多臂老虎机开始理解强化学习:探索与利用
一直想要系统学习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. 先看结果,不急着看公式
默认输出的核心结果是:
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
这几个指标可以先这样理解:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
如果你看到程序里的英文输出,对应关系是:
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=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 步时:
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10000 步时:
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
步数越长,早期探索带来的价值越明显。因为你前面花一点成本找到好动作,后面可以反复受益。
这也是很多 RL 问题的本质:当前动作不只是影响当前奖励,也影响未来你知道什么、能做什么。
9. 改噪声:反馈越不稳定,越不能轻信早期结果
第三个参数是 reward-std。std 是 standard deviation,也就是标准差。这里它表示奖励的随机波动大小。
默认:
reward_std = 1.0
如果改成:
reward_std = 2.0
意思是:同一个动作每次给出的奖励更不稳定,有时偏高,有时偏低。
运行:
python lessons/01_bandit/bandit.py --reward-std 2.0
这个命令是在问:
如果反馈更随机,agent 是否更容易被少量早期样本误导?
关键结果:
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
奖励噪声变大后,单次反馈更不可靠。你更需要多试几次,才能区分“这个动作真的好”和“只是这次运气好”。
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=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 的几个核心词:
|
|
|
|---|---|
a
|
|
r
|
|
Q(a)
|
|
|
|
|
|
|
|
|
|
|
下一课进入 GridWorld 后,会多出状态 state 和状态转移 transition。到那时问题会变成:
不只是哪个动作平均收益最高,
而是在某个状态下,哪个动作能带来更好的长期结果。
这就是从 bandit 走向真正强化学习的关键一步。
16. 本课你应该带走什么
第一,RL 不是一上来就训练神经网络。先问:agent 在反复做什么选择?它能看到什么反馈?它看不到什么反事实结果?
第二,探索不是随机犯错,而是在信息不足时主动收集信息。
第三,短期看起来最好的选择,可能因为早期噪声导致长期很差。
第四,读 RL 代码时,先找这些东西:
-
• action 是什么; -
• reward 是什么; -
• policy 如何选择 action; -
• value estimate 如何更新; -
• exploration 如何发生; -
• 最终用什么指标评价。
只要能把这些问题问清楚,你就已经开始用 RL 的方式理解问题了。
如果觉得内容不错,欢迎你点一下「在看」,或是将文章分享给其他有需要的人^^
相关好文推荐:

0条留言