Multi-Armed Bandit: UCB (Upper Bound Confidence)

转自:https://zhuanlan.zhihu.com/p/32356077

假设我们开了一家叫Surprise Me的饭馆

  • 客人来了不用点餐,由算法从N道菜中选择一道菜推荐给客人
  • 每道菜都有一定的失败概率:以1-p的概率不好吃,以p的概率做得好吃
  • 算法的目标是让满意的客人越多越好

解决方法:

[公式] 算法:

  • [公式] 的概率从N道菜中随机选择(概率为[公式] )一个让客人试吃
  • [公式] 的概率选择N道菜中选择好吃的概率最高的菜推荐给客

充分利用历史信息进行选择

  [公式]生硬的将选择过程分成探索阶段 (Exploration) 和 利用阶段(Exploitation),在探索时对所有物品进行以同样的概率 (概率为[公式] ) 进行探索,并不会利用任何历史信息,包括

(1)某道菜被探索的次数,

(2)某道菜获得好吃反馈的比例。

  让我们忘记探索阶段和利用阶段,仔细想想如何充分利用历史信息,找到最值得被推荐的菜:

 

观测 1: 如果一道菜已经推荐了k遍(获取了k次反馈),我们就可以算出菜做的好吃的概率:

[公式]

当k趋近正无穷时,[公式] 会趋近于真实的菜做的好吃的概率 [公式]

观测 2: 现实当中一道菜被试吃的次数k不可能无穷大,因此估计出的好吃的概率[公式] 和真实的好吃的概率 [公式] 总会存在一个差值 [公式] ,即 [公式]

基于上面两个观测,我们可以定义一个新的策略:每次推荐时,总是乐观地认为每道菜能够获得的回报是 [公式] ,这便是著名的Upper Confidence Bound (UCB) 算法,代码如下所示。

def UCB(t, N):
    upper_bound_probs = [avg_rewards[item] + calculate_delta(t, item) for item in range(N)]
    item = np.argmax(upper_bound_probs)
    reward = np.random.binomial(n=1, p=true_rewards[item])
    return item, reward

for t in range(1, T): # T个客人依次进入餐馆
   # 从N道菜中推荐一个,reward = 1 表示客人接受,reward = 0 表示客人拒绝并离开
   item, reward = UCB(t, N)
   total_reward += reward # 一共有多少客人接受了推荐

  真实的概率和估计的概率之间的差值 [公式]

最后只需解决一个问题,真实的概率和估计的概率之间的差值 [公式] 到底怎么计算呢?

在进入公式之前,让我们直观的理解影响 [公式] 的因素:

  • 对于被选中的菜,多获得一次反馈会使[公式]变小,最终会小于其他没有被选中的菜
  • 对于没被选中的菜,[公式] 会随着轮数的增大而增大,最终会大于其他被选中的菜

下面我们正式介绍如何计算 [公式] ,首先介绍Chernoff-Hoeffding Bound:

  [Chernoff-Hoeffding Bound] 假设 [公式] 是在[0, 1]之间取值的独立同分布

  随机变量,用 [公式] 表示样本均值,用 [公式] 表示分布的均值,那么有
   [公式]

[公式] 取值为 [公式] 时 (其中T表示有T个客人,n表示菜被吃过的次数),可以得到

[公式]

也就是说 [公式] 是以 [公式] 的概率成立的:

  • 当T=2时,成立的概率为0.875
  • 当T=3时,成立的概率为0.975
  • 当T=4时,成立的概率为0.992

可以看出 [公式] 是一个不错的选择。

最后,我们附上完整的代码,跟第一讲不一样的地方已经重点加粗标注。

import numpy as np

T = 1000 # T个客人
N = 10 # N道菜

true_rewards = np.random.uniform(low=0, high=1, size=N) # 每道菜好吃的概率
estimated_rewards = np.zeros(N) # 每道菜好吃的估计概率
chosen_count = np.zeros(N) #各个菜被选中的次数
total_reward = 0 

def calculate_delta(T, item):
    if chosen_count[item] == 0:
        return 1
    else:
        return np.sqrt(2 * np.log(T) / chosen_count[item])

def UCB(t, N):
    upper_bound_probs = [estimated_rewards[item] + calculate_delta(t, item) for item in range(N)]
    item = np.argmax(upper_bound_probs)
    reward = np.random.binomial(n=1, p=true_rewards[item])
    return item, reward

for t in range(1, T): # T个客人依次进入餐馆
   # 从N道菜中推荐一个,reward = 1 表示客人接受,reward = 0 表示客人拒绝并离开
   item, reward = UCB(t, N)
   total_reward += reward # 一共有多少客人接受了推荐
   
   # 更新菜的平均成功概率
   estimated_rewards[item] = ((t - 1) * estimated_rewards[item] + reward) / t
   chosen_count[item] += 1

 

 

 

posted @ 2020-05-29 19:42  ludiboke  阅读(623)  评论(0)    收藏  举报