item得分的计算通常用于召回并且配合用户兴趣画像一同使用。item得分计算的方式可以归为三类:
本文重点针对“千人一面”的item得分计算方式来浅谈一下贝叶斯平滑在CTR上的实践。
以内容分发为例,在计算item得分的时候,可以有很多维度,比如item的互动热度、变化率、创建时间、CTR、负反馈等。若取CTR作为得分依据来对item进行召回,则我们希望可以召回点击率较高且数据相对置信的一批item。这时候可能就会有以下两个问题:
最简单的思考,我们可以人为的定两个值,a和b,将CTR改造成:
但是这样的改造只能解决问题1,对于问题2的解决效果并不是很好。因此我们思考这里可不可以做的不那么“暴力”?简单一点的改造是取历史日志的平均点击、曝光,但是还是不够优雅,并且若给的a、b值太大会导致所有item的打分向平均值集中。
我们知道,一个item是否被点击是符合伯努利分布的,而伯努利分布中只有一个参数P,我们可以通过取一些日志算出P,继而得到后验,接着后验又可以作为下一次迭代的先验,但是这样不好计算。
因此我们希望先验和后验同属于一个分布,只是其中的参数不一样,这样我们在实现的时候只用更新里面的参数,不需要通过大量的计算。而伯努利分布的共轭分布是beta分布,因此可以利用beta分布做贝叶斯平滑,并将CTR得分改造成:
其中α和β通过矩估计来获得,具体的:
其中μ为均值,σ为方差。
Beta分布的横轴和纵轴分别表示随机变量的取值和概率密度函数(Probability Density Function,PDF)的取值。横轴表示Beta分布中随机变量的取值,取值范围为[0,1],可以理解为某个事件发生的概率,比如CTR。在Beta分布中,横轴的取值范围是由Beta分布的参数α和β决定的。纵轴表示在Beta分布中某个随机变量取某个特定值的概率密度,取值范围为[0,∞),表示在横轴某一点处的概率密度。用python画图:
import numpy as np
from scipy.stats import beta
import matplotlib.pyplot as plt
ab_pairs = [(2.81,21.92), (14.19,123.57)]
x = np.linspace(0, 1, 1002)[1:-1]
for a, b in ab_pairs:
print(a, b)
dist = beta(a, b)
y = dist.pdf(x)
plt.plot(x, y, label=r'$\alpha=%.1f,\ \beta=%.1f$' % (a, b))
# 设置标题
plt.title(u'Beta Distribution')
# 设置 x,y 轴取值范围
plt.xlim(0, 1)
plt.ylim(0, 25)
plt.legend()
plt.savefig("./beta.svg", format="svg")
接下来我们就要来计算α和β了,到这里我们根据粒度大脑袋一拍可以想到三种方案:
很显然,方法3根本就是错的,无论是从实现还是贝叶斯平滑的角度出发都不正确。
【我们希望的是】:能用一批“有代表性”的item求出其beta分布,来做所有item的平滑。同时,若一个item曝光点击数据较小,我们认为它数据不够置信,那么我的先验(α和β)应该起到主导作用;若一个item曝光点击数据足够多,我们认为它已经足够置信,则先验(α和β)作用几乎没用。
因此可以采用的是方法1和方法2。针对这两个方法下面也会展开讨论。
对于所有的item只算一套α和β作为平滑参数(粗粒度),具体在实践中,通常取一个周期(比如7天),然后在每天,按uid、itemid、traceid进行去重,接着分别对每一个item计算CTR,然后根据这些CTR求出当天的方差和均值,并根据方差和均值计算出每天的α和β,再求7天的平均α和β。用SparkSQL来实现,如下代码所示:
SELECT AVG(alpha) AS avg_alpha
, AVG(beta) AS avg_beta
FROM(
SELECT ftime
, mean
, variance
, mean*(mean*(1-mean)/variance-1) AS alpha
, (1-mean)*(mean*(1-mean)/variance-1) AS beta
FROM(
SELECT ftime
, AVG(ctr) AS mean
, VARIANCE(ctr) AS variance
FROM (
SELECT ftime
, item_id
, imp
, clk
, ctr
FROM mid_tb
WHERE imp>500
)
GROUP BY ftime
)
WHERE NOT isnan(variance)
)
WHERE alpha IS NOT NULL AND beta IS NOT NULL
这里需要注意的是,在计算方差和均值的时候可以卡一个曝光阈值剔除长尾数据,比如我这里是WHERE imp>500
。如果不剔除长尾数据,那么CTR的方差会过大,导致矩估计计算得出的平滑参数过小,进而导致平滑效果失效。下图反应的是卡阈值之前的beta分布,以及卡阈值之后的beta分布:
上图可以看出黄色曲线没卡阈值,有很多长尾数据,比如曝光3次点击2次,CTR=2/3。导致计算出的α=0.1,β=2.3,基本上就没有平滑的作用了,其原因是长尾数据的CTR不置信增大了方差。但是其实不难发现,方法1即使是卡了曝光阈值,计算出的α和β依然没有很大,平滑力度还是有限。
对每个类别分桶下分别计算一套α和β作为每一个类别的平滑参数(折中)。方法1实现起来较为简单,线上工程部署也比较方便,因为只需要求得一组α和β就可以了。但是其也拥有一定的局限性:不同品类(category)的item的CTR分布天然就会有差异,这个差异和产品形态有关系。例:一个主打交友的产品,那“交友”类的帖子CTR可能就会普遍高于“体育”类的帖子。下图展示的是一个产品中不同品类的CTR差异,横轴为品类,纵轴为CTR:
基于上面的情况,若直接用方法1,计算出的方差仍然较大,导致平滑粒度不够。针对这个问题一个简单的方法是人工放大平滑参数,增强平滑力度,但是会改变其原始分布。比如针对方法1中的α=1.7,β=35.9,人为放大4倍,变成α=6.8,β=143.6。
更优雅的方法则是:由于每个品类的CTR差异较大,因此可以对每一个品类下分别计算α和β。一方面可以减少方差,增加平滑力度,另一方面也是考虑的每个品类本身的CTR分布不同。
不过到这里有个坑(第四章末提到),如果品类(或者tag)的数目过多,也就是分的太细,可能导致每一个品类(或者tag)下的item个数较少,出现长尾品类。这时候若计算每个品类下的α和β很可能会求出特别大的α和β,因为数据可能只有一两条,方差很小导致beta分布以为数据足够置信,实际上并不是。针对这种情况的解决方法是:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。