Python demo
# !/usr/bin/python
# -*- coding=utf-8 -*-
# Name: UCB
# Description:
# Author: liu
# Date: 2021/9/14
# Mail:
from __future__ import absolute_import
from __future__ import print_function
from __future__ import division
import numpy as np
N = 3 # 物品总数
T = 100 # 试验次数/轮数
epsilon = 0.1 # 贪婪系数
P = [0.5, 0.6, 0.55] # 每个物品的真实被转化率
Round = [0, 0, 0] # 每个物品被选中的次数
Reward = [0, 0, 0] # 每个物品被转化的次数
def pull(N, epsilon, P):
"""通过epsilon-greedy来选择物品
Args:
N(int) :- 物品总数
epsilon(float) :- 贪婪系数
P(iterables) :- 每个物品被转化率
Returns:
本次选择的物品
"""
# 通过一致分布的随机数来确定是搜索还是利用
exploration_flag = True if np.random.uniform() <= epsilon else False
# 如果选择探索
if exploration_flag:
i = int(min(N - 1, np.floor(N * np.random.uniform())))
# 如果选择利用
else:
i = np.argmax(P)
return i
def calculate_delta(Round, i):
"""利用所有物品被选中的次数和i物品被选中的次数来计算delta
Args:
Round(iterables) :- 每个物品被选择的次数
i(int) :- 物品序号
Returns:
使得置信度为1-2/ sum(Round) ** 2的delta
"""
round_i = Round[i]
if round_i == 0:
return 1
else:
return np.sqrt(np.log(sum(Round)) / round_i)
def calculate_empirical(Round, Reward, i):
"""利用所有物品被选中和获得奖励的次数来计算实证参数
Args:
Round(iterables) :- 每个物品被选择的次数
Reward(iterables) :- 每个物品获得奖励的次数
i(int) :- 物品序号
Returns:
i物品的实证参数
"""
round_i = Round[i]
if round_i == 0:
return 1
else:
return Reward[i] / round_i
def trial_ucb(Reward, Round, rounds=T):
"""做rounds轮试验
Args:
Reward(iterables) :- 每个物品的被转化次数
Round(iterables) :- 每个物品被选择的次数
rounds(int) :- 一共试验的次数
Returns:
一共的转化数
rewards来记录从头到位的奖励数
"""
rewards = 0
for t in range(rounds):
P_ucb = [calculate_empirical(Round, Reward, i) + calculate_delta(Round, i) for i in range(len(Round))]
i = pull(N, epsilon, P_ucb)
Round[i] += 1
reward = np.random.binomial(1, P[i])
Reward[i] += reward
rewards += reward
return rewards
if __name__ == '__main__':
trial_ucb(Reward, Round, rounds=T)
Beta分布
与Bernoulli分布
和二项式分布
共轭先验,是(0, 1)上的随机变量的一种分布,它有两个大于0的参数α,β
,概率密度函数为:
f B e ( θ ; α , β ) = θ α − 1 ( 1 − θ ) β − 1 B ( α , β ) f_{Be}(\theta ;\alpha,\beta )=\frac{\theta^{\alpha -1 } (1-\theta ) ^{\beta -1}}{B(\alpha,\beta)} fBe(θ;α,β)=B(α,β)θα−1(1−θ)β−1其中: B ( α , β ) : = ∫ 0 1 θ α − 1 ( 1 − θ ) β − 1 d θ = Γ ( α ) Γ ( β ) Γ ( α + β ) B(\alpha,\beta):=\int_{0}^{1} \theta ^{\alpha -1}(1-\theta )^{\beta -1}\mathrm{d}\theta =\frac{\Gamma (\alpha) \Gamma (\beta)}{\Gamma (\alpha + \beta)} B(α,β):=∫01θα−1(1−θ)β−1dθ=Γ(α+β)Γ(α)Γ(β)
B(α,β)
是B函数
,Γ(x)
是伽马函数,B函数
可以理解为归一化因子(Normalizer),其存在是为了使得概率的积分为1
beta
分布的支撑集: θ ∈ ( 0 , 1 ) \theta \in(0, 1) θ∈(0,1) 意义为二元随机变量X
其中一种结果x0
的概率,所以Beta
分布是一种“概率的概率分布”
,其具体形式是为了满足贝叶斯推断过程中其内涵的一致性确定出来的。一般被用于建模伯努利试验事件成功的概率的概率分布。B函数
在计算机求解中一般用Γ函数(分布)
换算(如上面公式),计算机的内部实现就由积分
变成阶乘
。Beta
分布直观理解(可视化)1> Beta分布
与二项分布
的关系 (解释建模问题)
进行n
次伯努利试验,其出现试验成功的概率p
服从一个先验概率密度分布: B e t a ( α , β ) Beta(\alpha,\beta) Beta(α,β),试验结果出现k
次试验成功,则试验成功的概率p
的后验概率密度分布为: B e t a ( α + k , β + n − k ) Beta(\alpha +k ,\beta + n -k) Beta(α+k,β+n−k),解释为什么beta分布
用建模伯努利实验
。
2> 共轭分布与共轭先验 (解释参数更新问题)
a、 {先验概率分布}*{实验数据/似然函数/似然概率} ∝ {后验概率分布} ,∝表示等比于。通过贝叶斯推理,可发现先验概率与后验概率具有同样的数学形式
,所以也称它们为共轭分布
(Conjugate Distribution),称先验概率
为似然函数
的共轭先验
(Conjugate Prior)
b、在MAB问题中,Bernoulli分布正好有Beta分布作为共轭先验
,有了共轭先验,就容易做贝叶斯更新。为什么?因为beta 分布的更新只要考虑两个参数即可。
c、所以 Thompson sampling,允许我们利用每个物品被转化率
的先验知识
来设定每个beta
分布的参数。
d、在业务中的应用:α
表示为点击的次数
,β
表示为曝光
了但没有点击的次数,所以α+β
表示曝光数
。
Python:np.random.beta(α, β) # 返回值[0,1] 概率大小,其中α > 0,β > 0
C++:boost::random::beta_distribution<>(alpha, beta) // alpha > 0,beta > 0
Beta分布
的参数,可通过历史数据计算出来点击曝光
数据的Beta分布中随机采样
获得对应的被转化率得分
被转化则α加 1
,否则 β 加 1
。(这个过程可以实时计算或者小时粒度进行在线计算)# !/usr/bin/python
# -*- coding=utf-8 -*-
# Name: demo
# Description:
# Author: liu
# Date: 2021/9/14
# Mail:
from __future__ import absolute_import
from __future__ import print_function
from __future__ import division
import numpy as np
N = 3 # 物品总数
T = 100 # 试验次数/轮数
epsilon = 0.1 # 贪婪系数
P = [0.5, 0.6, 0.55] # 每个物品的真实被转化率
Round = [0, 0, 0] # 每个物品被选中的次数
Alpha = [25, 50, 75] # 每个物品被转化率的Beta先验参数
Beta = [75, 50, 25]
# 在代码中加入了 epsilon-greedy 策略,这个过程不是必要的
def pull(N, epsilon, P):
"""通过epsilon-greedy来选择物品
Args:
N(int) :- 物品总数
epsilon(float) :- 贪婪系数
P(iterables) :- 每个物品被转化率
Returns:
本次选择的物品
"""
# 通过一致分布的随机数来确定是搜索还是利用
exploration_flag = True if np.random.uniform() <= epsilon else False
# 如果选择探索
if exploration_flag:
i = int(min(N-1, np.floor(N*np.random.uniform())))
# 如果选择利用
else:
i = np.argmax(P)
return i
def trial_thompson(Alpha, Beta, rounds=T):
"""做rounds轮试验
Args:
Alpha, Beta(iterables) :- 每个物品被转化率的Beta分布的参数
rounds(int) :- 一共试验的次数
Returns:
一共的转化数
rewards来记录从头到位的奖励数
"""
rewards = 0
for t in range(rounds):
P_thompson = [np.random.beta(Alpha[i], Beta[i]) for i in range(len(Round))]
i = pull(N, epsilon, P_thompson)
Round[i] += 1
reward = np.random.binomial(1, P[i])
Alpha[i] += reward
Beta[i] += 1 - reward
rewards += reward
return rewards
if __name__ == '__main__':
trial_thompson(Alpha, Beta, rounds=T)
BTS算法特点
:Beta 分布
只在每批次
的结束时进行更新,例如一个小时一次等
Beta分布
主要有α
和 β
两个参数,这两个参数决定了分布的形状,从上图及其均值和方差的公式可以看出α/(α+β)
也就是均值,其越大,概率密度分布的中心位置越靠近1
,依据此概率分布产生的随机数也多说都靠近1,反之则都靠近0
α+β
越大,则分布越窄,也就是集中度越高,这样产生的随机数更接近中心位置,从方差公式上也能看出来α+β (曝光)越大
,它的分布就会变窄,换言之,这个候选项的收益已经非常确定了,不管分布中心靠近0
,还是靠近1
,都几乎比较确定。用它产生的随机数,基本上就在中心位置附近,接近平均收益。α+β
很大,即分布很窄,而且α/(α+β) 也很大(点击率很高)
,靠近1
,那就确定这是个好的候选项,平均收益很好,每次选择很占优势,就进入利用阶段(Exploitation)
。反之则有可能平均分布比较靠近0
,几乎再无出头之日
。α+β
很小(曝光很少
),分布很宽,也就是没有被选择太多次,说明这个候选项是好是坏还不确定,那么分布就是跳跃
的,这次可能好,下次就可能坏,也就是还有机会被选中,没有完全抛弃。那么用它产生随机数就有可能得到一个较大的随机数,就在排序时被优先输出,这就是所说的探索作用(Exploration)
。c
一般是固定的,同一个粒度下一般是相同
的霍夫丁不等式
推导出来的[1] 《 Bandit算法在携程推荐系统中的应用与实践》
[2] 《汤普森采样(Thompson sampling) 》
[3] 《多臂老虎机之Thompson Sampling》
[4] 《Bate分布公式推导》
[5] 《Beta分布,共轭先验与贝叶斯推断》
[6] 《Multi-armed bandit and Thompson Sampling in C++ and Python》
声明: 总结学习,有问题或不当之处,可以批评指正哦,谢谢。