【算法业务】基于Multi-Armed Bandits的个性化push文案自动优选算法实践

1. 背景介绍

该工作属于多年之前的用户增长算法业务项目。在个性化push中,文案扮演非常重要的角色,是用户与push的商品之间的桥梁,文案是用户最直接能感知的信息。应该说在push产品信息之外,最重要的就是文案,直接能够影响push曝光的打开率。好的文案能够诱导用户点击打开。

文案的表现会随着场景信息 ( 时间、地点等 ) 的变化而变化,编辑人工撰写的文案的质量有一定保证,利用 E&E 算法进行探索时不会严重地影响用户体验而导致用户流失。

文案示例:

  • 请收下这波安利 亲爱的,性价比超高的沃德百惠盘子都在这里,快去看看>>
  • 【优惠提醒】 你感兴趣的千芝雅防护口罩已降至59.9元,今天还可领20.0元优惠券,下单更优惠哦!点击领券 >>
  • 开抢提醒 你已订阅的商品已开抢:双枪银离子零胶水小麦材质防霉健康砧板
  • 猜你喜欢 猜你喜欢海南金煌芒5斤装 多规格任选,猛戳抢好货>>
  • 不能错过的爆款!! 大家都在抢的超级爆款原生亲肤卷纸,手慢无>>

push类型示例:

1元秒杀

奖励待领取(粉丝引导下首单奖励)

粉丝未下单提醒 (粉丝引导下首单奖励)

Vip升级提醒

今日爆款

优惠提醒(感兴趣)

降价提醒(浏览过,购物车)

今日必买(降价提醒)

签到红包(奖励金)

美妆好物领券

通知(100元待领取)

免费水果(果园)

不能错过的爆款(爆款)

新品上架提醒(邀请好友)

个人排名刺激

专属虚拟币待领取

奖励到账

店铺好友访问

果树在呼唤你(果园)

粉丝未下单(新人专享好货,下单5元)

优惠券到期提醒(到期、过期)

优惠券到账

购物车商品降价(降价信息)

免息用钱(免息10天)

(冬装清仓)降价信息

降价提醒(关注的产品秒杀中)

销量排名,刺激奖励(销售奖励)

商品降价啦(降价提醒)

20万现金待激活(新人红包)

购物金(权益)

订单未支付(未支付提醒)

收入增加提醒(新增收益0.01元)

宝贝已降价(降价信息)

在这里,我们将文案模板优化建模为多臂 Bandit 问题。每一条文案相当于老虎机的一条臂,当你选择一条臂,会带了一定的收益,收益可正可负。"正"表示使用了文案A给用户U push,用户U点击打开进入落地页,同理,负表示用户未打开直接忽视,这里对由于消息发送失败或者用户屏蔽导致未打开的情况暂时不做区分。显然有一些文案会更受用户青睐,而有部分则不太可能引起用户兴趣。假如在分发的过程中使用随机选取,就造成了等权问题,更理想的情况是对于收益高的文案进行利用,而受益低的可以减少利用,对于由于测试次数少暂时还不能准确判断受益的文案,进行探索。

2. MAB算法介绍及对比分析

文案上线了个性化推送功能后,加入了新的用户特征,并且随着测试次数的累增,各条文案的收益逐步分化,有些文案能够带来更高的点击,而有些偏低。在这样的背景,可以采用基于E&E的强化学习文案优选算法,这里做一些算法适用性讨论。

这里我们基于实际数据以及环境资源,进行了3种方案的对比选择:

2.1 UCB

UCB(Upper Confidence Bound - 置信上限)就是以收益(bonus)均值的置信区间上限代表对该arm未来收益的预估值:

其中是对arm i 期望收益的预估,n是总的选择次数, 是对arm i的尝试次数,可以看到尝试越多,其预估值与置信上限的差值就越小,也就是越有置信度。

代码示例:

python 复制代码
import numpy as np
k_arm_data = [[350573,6787,3097],[482356,7492,3783],[483053,7696,3479],[482243,8678,3802],[482160,9078,4096],[1,0,0]]
 
def sum_func(k_arm_data, index):
    total_num = 0
    for data in k_arm_data:
        total_num += data[0]
    return total_num
 
def UCB():
    t = float(sum_func(k_arm_data, 0))
    UCB = []
    for k in range(len(k_arm_data)):
        totals = k_arm_data[k][0] 
        successes = k_arm_data[k][1]
        estimated_means = successes*1.0/totals
        k_UCB = estimated_means + np.sqrt(2*np.log(t)/totals)
        UCB.append(k_UCB)
         
    return np.argmax(UCB)
 
for i in range(5):
    print(UCB())

结论:从实际数据测试下来,UCB的确定性过高,在我们的数据上很难起到探索的作用,只会选择各个场景下的唯一一条最好的文案,不符合预期。

2.2 Thompson Sampling

Thompson Sampling相对UCB,对反馈引入了随机性。对数据延迟反馈和批量数据反馈相比其他 Bandit 算法更加准确。Thompson Sampling 假设第 i 个臂拥有先验 Beta(1,1),Beta(1,1) 是 (0,1) 的均匀分布。采用 Batched Thompson Sampling,每个臂的 Beta 分布只在每批次的结尾更新。对于传统的 Bernoulli Bandits 的 Thompson Sampling 算法,如果观测到点击行为,被选择臂的 Beta(α,β) 更新为 Beta(α+1,β),否则更新为 Beta(α,β+1)。

对于 Batched Thompson Sampling,采用两种更新机制:求和更新与归一化更新【4】。

适用场景

  • 求和更新 更适合批次反馈量较为稳定的场景。
  • 归一化更新 更适合批次反馈量不均衡或者不同批次之间存在显著差异的情况。它能够平滑掉因批次反馈数量差异导致的参数波动,使得模型在面对不稳定的批次反馈数据时表现更为鲁棒。

由于TS依赖于beta函数,因此这里我们选用发送量和曝光pv,曝光uv的实际数据验证了beta分布,还是蛮符合我们的要求的。

python 复制代码
from scipy.stats import beta
 
def test_beta_distribution():
    a, b = 9078, 4096
    r = beta.rvs(a, b, size=10)
    print(r)
     
    a1, b1 = 5539, 2567
    r1 = beta.rvs(a1, b1, size=10)
    print(r1)
     
    a2, b2 = 1, 1
    r2 = beta.rvs(a2, b2, size=10)
    print(r2)
     
    a3, b3 = 2195, 1028
    r3 = beta.rvs(a3, b3, size=10)
    print(r3)
     
if __name__ == '__main__':
    test_beta_distribution()
 
[0.69575504 0.68626875 0.68873306 0.68593713 0.69575653 0.68754273
 0.68937758 0.68530153 0.68749733 0.68397584]
[0.6851433  0.67626678 0.68429746 0.67905056 0.68655858 0.69324291
 0.68437178 0.68259162 0.68661927 0.67916696]
[0.84763723 0.59356287 0.3363573  0.48248605 0.10986077 0.00211299
 0.15710948 0.22735167 0.46674019 0.9081156 ]
[0.67520224 0.67626631 0.68193351 0.68757709 0.67433103 0.67179957
 0.66383093 0.67666561 0.67411215 0.69284938]

结论: 从分布来看,TS符合我们的预期。

2.3 epsilon-greedy

-greedy算法倾向选择reward最大的文案,这种行为较为greedy。利用epsilon参数进行控制利用还是探索。利用是指选择目前为止平均reward最大的文案,这里的reward使用uv曝光率刻画。探索是指从文案空间中随机选择一个文案。虽然方法很简单,但在实际应用中,往往会取得优于UCB的效果。当然这里利用的机制,我们也可以采用softmax,选择平均收益较大的topK个文案的随机,而不是强制选择top1.

python 复制代码
import numpy as np
 
epsilon = 0.5
k_arm_data = [[350573,6787,3097],[482356,7492,3783],[483053,7696,3479],[482243,8678,3802],[482160,9078,4096],[1,0,0]]
num_arm = len(k_arm_data)
reward = []
for i in range(len(k_arm_data)):
    reward.append(k_arm_data[i][2]*1.0/k_arm_data[i][0])
     
def epison_greedy(epsilon):
    if np.random.random() > epsilon:
        index = np.argmax(reward)
    else:
        index = np.random.randint(0, num_arm)
    return index
 
count = [0 for i in range(6)]
for i in range(1000000):
    index = epison_greedy(epsilon)
    count[index] += 1   
 
print(count)
 
#[583665, 83015, 83491, 83035, 83266, 83528]

演进的方向:

上下文特征:利用上下文特征对Thompson Sampling进行优化,或者使用其他contexture bandit,能够利用用户文案的特征进行个性化。这个和推荐系统就非常的像。上下文特征就是我们拥有的用户信息,环境信息以及产品信息,基于这些特征我们可以对先验进行估计,然后结合Thompson Sampling进行实验。

3. MAB for Recommender分享材料

这个材料是当时在团队内部做的关于MAB for recommender的分享,这里也做一下记录:

动机

案例

The Multi-armed bandits定义

Bandits类别

累积后悔值定义

context-free算法

累积探索后悔值趋势对比

Contextual Bandits

动机

Contextual Bandits问题定义

Linear regression


Linear Upper Confidence Bounds(LinUCB)

Calculating Linear Upper Confidence Bounds

LinUCB

Yahoo! News Recommendation示例

Netflix Video Rec Application示例

4. 参考资料

【1】The Multi Armed Bandit Problem And Its Solutions

【2】Thompson Sampling for Contextual bandits | Guilherme's Blog

【3】GitHub - david-cortes/contextualbandits: Python implementations of contextual bandits algorithms

【4】Bandit算法在携程推荐系统中的应用与实践

【5】Stochastic Bandits and UCB Algorithm

【6】Regret Bounds for Thompson Sampling

【7】Adversarial Bandits and the Exp3 Algorithm

相关推荐
深度学习实战训练营7 小时前
基于AFM注意因子分解机的推荐算法
算法·机器学习·推荐算法
Kenneth風车10 小时前
【第十八章:Sentosa_DSML社区版-机器学习之协同过滤】
人工智能·低代码·机器学习·数据分析·推荐算法·协同过滤
B站计算机毕业设计超人3 天前
计算机毕业设计hadoop+spark知网文献论文推荐系统 知识图谱 知网爬虫 知网数据分析 知网大数据 知网可视化 预测系统 大数据毕业设计 机器学习
大数据·hadoop·爬虫·机器学习·spark·知识图谱·推荐算法
机器白学3 天前
【强化学习系列】Gym库使用——创建自己的强化学习环境3:矢量化环境+奖励函数设计
强化学习
kngines5 天前
【PLW004】基于Python网络爬虫与推荐算法的新闻推荐平台v1.0(Python+Django+NLP+Vue+MySQL前后端分离)
爬虫·python·nlp·推荐算法
Rivieres7 天前
算法入门-贪心1
java·算法·leetcode·推荐算法