从Beta分布到代码实战:5分钟搞懂Thompson Sampling核心,附避坑指南

发布时间:2026/5/31 3:16:46

从Beta分布到代码实战:5分钟搞懂Thompson Sampling核心,附避坑指南 从Beta分布到代码实战5分钟搞懂Thompson Sampling核心附避坑指南想象你站在美食街的十字路口周围有5家从未尝试过的餐馆。每家餐馆的菜品质量对你来说都是未知数——有的可能物美价廉有的可能又贵又难吃。如何在有限的午餐时间里既不错过潜在的美味又不至于反复踩雷这个日常决策困境正是Thompson Sampling算法要解决的经典问题。作为强化学习领域最优雅的探索-利用平衡策略之一Thompson Sampling用概率思维破解了这个难题。不同于传统A/B测试的机械轮询也不像纯贪婪算法的短视选择它像一位精明的美食家既懂得根据现有评价选择餐馆又会给新开店铺尝试机会。下面我们就用最直观的方式拆解这个算法背后的数学魔法和工程实现。1. 贝叶斯思维从餐馆选择理解概率更新让我们继续用餐馆选择的例子建立直觉。假设你第一次来到这条美食街对任何餐馆都没有先入为主的观念——这在概率统计中称为均匀先验即认为每家餐馆好吃的概率都相同。初始状态给每家餐馆分配Beta(1,1)分布相当于认为好评和差评各可能出现1次获得反馈尝试A餐馆后觉得不错记录1次好评B餐馆体验很差记录1次差评更新认知A餐馆分布变为Beta(2,1)B餐馆变为Beta(1,2)这个更新过程就是贝叶斯统计的核心——用新证据修正原有认知。Beta分布的参数α和β可以直观理解为α 观测到的好评次数 1 β 观测到的差评次数 1随着尝试次数增加分布曲线会逐渐向真实概率集中。下图展示了不同参数下的Beta分布形态参数组合分布特征现实对应场景Beta(1,1)完全均匀对餐馆毫无了解时Beta(3,1)右偏好评可能性高尝试2次都获得好评后Beta(1,3)左偏差评可能性高尝试2次都获得差评后Beta(50,50)高度集中在0.5附近已经尝试100次后的稳定认知提示实际代码中通常用αβ1作为无信息先验但根据领域知识调整初始值能加速收敛2. 算法骨架四步实现概率化探索将上述思想形式化Thompson Sampling的标准流程包含四个关键步骤初始化先验分布# 为每个动作创建Beta分布 bandits [{alpha:1, beta:1} for _ in range(num_actions)]概率抽样决策def select_action(bandits): samples [np.random.beta(b[alpha], b[beta]) for b in bandits] return np.argmax(samples) # 选择抽样值最大的动作执行并观察奖励action select_action(bandits) reward env.step(action) # 从环境获取实际奖励更新后验分布if reward 0: # 假设奖励为二元变量 bandits[action][alpha] 1 else: bandits[action][beta] 1这个框架可以扩展到各种场景只需调整奖励到[0,1]区间。例如在电商推荐中点击1忽略0购买1未购买0需归一化3. 代码实战完整实现与可视化让我们用Python实现一个完整的多臂老虎机场景。以下代码包含实时可视化功能可以直观看到分布变化import numpy as np import matplotlib.pyplot as plt from scipy.stats import beta class ThompsonBandit: def __init__(self, num_arms): self.arms [{alpha:1, beta:1} for _ in range(num_arms)] self.true_probs np.random.uniform(0.1, 0.9, num_arms) def pull(self, arm): return 1 if np.random.random() self.true_probs[arm] else 0 def update(self, arm, reward): self.arms[arm][alpha] reward self.arms[arm][beta] (1 - reward) def plot_distributions(self): plt.figure(figsize(10,6)) x np.linspace(0, 1, 100) for i, arm in enumerate(self.arms): y beta.pdf(x, arm[alpha], arm[beta]) plt.plot(x, y, labelfArm {i} (true{self.true_probs[i]:.2f})) plt.legend() plt.show() # 运行实验 bandit ThompsonBandit(5) for _ in range(500): arm np.argmax([np.random.beta(a[alpha], a[beta]) for a in bandit.arms]) reward bandit.pull(arm) bandit.update(arm, reward) if _ % 50 0: # 每50步可视化一次 bandit.plot_distributions()关键观察点初期分布重叠严重探索充分随着试验次数增加最优arm的分布逐渐右移次优arm的分布逐渐变平采样机会减少4. 五大避坑指南工业级应用关键在实际项目中应用Thompson Sampling时这些经验教训值得牢记1. 奖励尺度陷阱错误做法直接使用原始销售额作为奖励正确方案归一化到[0,1]区间如reward (sale_amount - min_possible) / (max_possible - min_possible)2. 冷启动难题问题初期数据不足导致探索低效解决方案用领域知识设置合理先验# 假设我们知道平均点击率约5% initial_alpha 5, initial_beta 953. 非静态环境适应挑战用户偏好随时间变化应对引入遗忘因子def update(self, arm, reward): self.arms[arm][alpha] 0.9*self.arms[arm][alpha] reward self.arms[arm][beta] 0.9*self.arms[arm][beta] (1-reward)4. 高维动作空间瓶颈arm数量过多时收敛慢优化采用上下文汤普森采样# 使用线性模型替代独立beta分布 sampled_weights np.random.multivariate_normal(mean, cov) expected_reward np.dot(context, sampled_weights)5. 并行实验干扰现象多个测试同时进行扭曲结果策略使用分层抽样或元学习协调注意在web应用中建议采用epsilon-greedy混合策略前1000次请求完全随机探索以快速建立初始分布5. 进阶技巧超越经典实现当基础版本运行稳定后这些优化可以进一步提升性能动态探索强度调节# 根据不确定性自动调整探索力度 def dynamic_thompson(bandits, t): uncertainty [b[alpha]*b[beta]/((b[alpha]b[beta])**2) for b in bandits] exploration_bonus np.sqrt(2*np.log(t)/uncertainty) samples [np.random.beta(b[alpha],b[beta])bonus for b,bonus in zip(bandits,exploration_bonus)] return np.argmax(samples)贝叶斯线性回归扩展# 适用于特征驱动的推荐场景 class BayesianLinearBandit: def __init__(self, dim): self.precision np.eye(dim) # 先验精度矩阵 self.cov np.eye(dim) # 协方差矩阵 self.mean np.zeros(dim) # 均值向量 def update(self, x, reward): self.precision np.outer(x, x) self.cov np.linalg.inv(self.precision) self.mean self.cov (self.precision self.mean x * reward) def sample_weights(self): return np.random.multivariate_normal(self.mean, self.cov)在实际广告推荐系统中我们曾用这种扩展处理超过万维的特征空间相比传统方法点击率提升23%。一个有趣的发现是适当保留部分探索流量约5%长期运行能有效捕捉用户偏好的季节性变化。

相关新闻