推荐系统-CTR平滑方式

背景


在电商领域中,经常要计算或者CTR(点击通过率),CVR。以点击率CTR为例,CTR根据统计指标 CTR = 点击量/ 曝光量;具体需要看公司要求,有的是点击uv/ 曝光uv ,有的是点击pv/曝光pv ;然而实际应用中,会遇到两个问题:

  1. 【新品问题】新商品点击率的预测和计算问题
    对于新上线的商品,其曝光为0,点击量也为0,此时这件商品的CTR应该为0 还是赋值一个初始值呢?
  2. 【数据不可信问题】不同商品点击率之间的比较
    有两件商品A和B ,商品A曝光了10次点击了5次,商品B曝光了100次点击率50次
    Click_a = Click_b 这样合理吗?从置信度的角度来说,Click_b更加可信。

理论基础


对于一件商品或者或者一个广告,对于某次曝光,用户要么点击,要么不点击,符合二项分布。因此下文对于点击率的贝叶斯平滑,都是基于以下假设:
对于某件商品或者广告X ,其是否点击是一个伯努利分布(Bernoulli): x ∈ B e r ( r ) x\in Ber(r) xBer(r)
X表示是否点击,当X=1时表示点击,当X=0时表示未点击,r表示某件商品被点击的概率,即点击率。

bayes & Wilson


指标含义
bayes贝叶斯平滑的最终落脚点是要估计Beta分布(点击率r服从的分布)中的参数α和β
Wilson指在一定置信度下,真实的 CTR 范围是多少

Bayes 分布

共轭先验

  1. 如果能够找到一个分布:π®,它是f(x│r)的共轭先验,那么r的后验分布π(r|x) 和先验分布π(r)会有一样的形式。这里的共轭指的是π® 和π(r|x) 通过 f(x|r) 联系起来了。

  2. 假设广告是否点击服从伯努利分布,参数为r ;对于点击次数服从二项分布,即 f(click,expose|r)∼Bin®。二项分布的共轭先验是beta分布,beta分布的参数是
    α和β,即π® =Beat(α,β) 。根据共轭先验的定义,r的后验分布π(r|x)的形式和其先验分布π®是一样的.

    1. π(r|x)=Beta(α, β)
  3. 为什么很多文章会假设点击率服从Beta分布的理由,因为最终的平滑的因子是Beta分布(先验分布)中的两个参数。

Beta 参数α和β的估计

贝叶斯平滑的最终落脚点是要估计Beta分布(点击率r服从的分布)中的参数α和β
利用矩估计可以得到:

  1. α = X ^ ( X ^ ( 1 − X ^ ) S 2 − 1 ) \alpha = \hat X(\frac{\hat X(1-\hat X)}{S^2}-1) α=X^(S2X^(1X^)1)
  2. β = ( 1 − X ^ ) ( X ^ ( 1 − X ^ ) S 2 − 1 ) \beta = (1-\hat X)(\frac{\hat X(1-\hat X)}{S^2}-1) β=(1X^)(S2X^(1X^)1)
  3. 取连续一段时间的数据,比如一周,然后在每天的数据中计算每件商品或者广告的点击率,之后求这些点击率的均值和方差,然后带入到公式中。

CTR的计算

c t r = c l i c k + α e x p o s e + α + β ctr = \frac{click+\alpha}{expose+\alpha+\beta} ctr=expose+α+βclick+α

import numpy
import random
import scipy.special as special
 
class BayesianSmoothing(object):
    def __init__(self, alpha, beta):
        self.alpha = alpha
        self.beta = beta
    
    def sample(self, alpha, beta, num, imp_upperbound):
        # 先验分布参数
        clicks = []
        exposes = []
        for clk_rt in numpy.random.beta(alpha, beta, num):
            imp = imp_upperbound
            clk = int(imp * clk_rt)
            exposes.append(imp)
            clicks.append(clk)
        return clicks, exposes
    
    def update(self, imps, clks, iter_num=1000, epsilon=1e-5):
        for i in range(iter_num):
            new_alpha, new_beta = self.__fixed_point_iteration(imps, clks, self.alpha, self.beta)
            if abs(new_alpha-self.alpha)<epsilon and abs(new_beta-self.beta)<epsilon:
                break
            self.alpha = new_alpha
            self.beta = new_beta
            
    def __fixed_point_iteration(self, imps, clks, alpha, beta):
        numerator_alpha = 0.0
        numerator_beta = 0.0
        denominator = 0.0
        
        for i in range(len(imps)):
            numerator_alpha += (special.digamma(clks[i]+alpha) - special.digamma(alpha))
            numerator_beta += (special.digamma(imps[i]-clks[i]+beta) - special.digamma(beta))
            denominator += (special.digamma(imps[i]+alpha+beta) - special.digamma(alpha+beta))
        return alpha*(numerator_alpha/denominator), beta*(numerator_beta/denominator)
    
def main():
    bs = BayesianSmoothing(1, 1)
#     clk, exp = bs.sample(500, 500, 10, 1000)
    clk = [5, 50, 500, 5000]
    exp = [10, 100, 1000, 10000]
    print('原始数据')
    for i, j in zip(clk, exp):
        print(i, j)
        
    bs.update(exp, clk)
    print('bayes光滑先验分布参数:', bs.alpha, bs.beta)
    fixed_ctr = []
    for i in range(len(clk)):
        origin_ctr = clk[i] / exp[i]
        new_ctr = (clk[i] + bs.alpha) / (exp[i]+bs.alpha+bs.beta)
        print('修正前{}, 修正后{}'.format(round(origin_ctr, 3), round(new_ctr, 3)))
    
if __name__ == '__main__':
    main()

Wilson方法

参数

指标含义
p概率,即点击率(ctr)
n样本总数,即曝光数(expose)
z在正态分布里, μ + z × σ \mu+z×\sigma μ+z×σ会有一定的置信度。例如z=1.96,就有95%的置信度

公式

在这里插入图片描述

import numpy as np

def wilson_ctr(clks, imps, z=1.96):
    
    origin_ctr = clks * 1.0 / imps
    
    if origin_ctr > 0.9:
        return 0.0
    
    n = imps
    
    first_part_numerator = origin_ctr + z**2 / (2*n)
    second_part_numerator_2 = np.sqrt(origin_ctr * (1-origin_ctr) / n + z**2 / (4*(n**2)))
    common_denominator = 1 + z**2 / n
    second_part_numerator = z * second_part_numerator_2
    

    new_ctr = (first_part_numerator-second_part_numerator)/common_denominator
    
    return new_ctr

test_case = [(5, 10), (50, 100), (500, 1000), (5000, 10000)]
for item in test_case:
    print(wilson_ctr(*item))
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值