贝叶斯平滑:解决点击率预测中稀疏样本问题的经典方法

发布时间:2026/9/16 1:08:48
贝叶斯平滑:解决点击率预测中稀疏样本问题的经典方法 做排序和广告的同学大概率都被同一个现象折磨过同样是点击率展示5次点了3次的商品算出来60%展示1000次点了200次的商品只有20%。如果你直接按这个数排序60%那个会冲到最前面等流量真的灌进去真实点击率立刻被打回2%~3%。这种“看着很美放大就崩”的问题本质上不是运气问题而是小样本下的频率估计方差太大。贝叶斯平滑就是用来解决点击率预测里这类稀疏样本问题最经典、也最实用的一套方法。我前前后后在多个业务场景里用过它从广告创意到商品推荐都试过一轮今天把这套方法的原理、参数估计、工程落地和踩过的坑完整过一遍希望对正在做CTR特征、排序策略或者冷启动的同学有帮助。1. 不解决稀疏问题点击率预测从一开始就是错的1.1 一个小样本直接统计点击率结果有多离谱先看一个具体例子。一个新上线的商品A因为运气好前5次曝光来了3次点击直接统计点击率60%。商品B是老品曝光1000次点击200次点击率20%。按点击率排序A应该排在B前面。可现实是A的60%几乎完全来自偶然——5次曝光里3次点击置信区间跨度极大B的20%则经过了1000次曝光的检验五个点以内的波动都不大。这就是点击率预测里最典型的场景越是需要判断的新品、长尾品数据越少数据越少直接统计的点击率越不可靠。你把一个统计量当成真实点击率去排序等于把大量噪声当成了信号。1.2 大数定律的“另一半”被忽略了很多人知道大数定律频率会随着样本量增大趋于真实概率。但现实里我们面对的都是有限样本尤其是冷启动阶段展示量可能只有个位数。此时频率估计MLE的期望虽然是无偏的方差却大得吓人。在点击率预测中你排序用的不是“真实点击率”而是“点击率的估计值”这个估计值本身波动越剧烈排序就越不稳定。反过来说哪怕给你100万次曝光如果点击率本身只有0.1%估计的绝对误差依然不可忽视。所以点击率预测的前置工作就是先把“统计点击率”变成“平滑点击率”让估计的方差降下来让数值更接近真实概率。1.3 直接排序造成的恶性循环没有做平滑的线上系统经常会出现一个循环小样本商品因为偶尔几次点击拿到很高点击率排到前面排序靠前获得大量曝光曝光一上来真实点击率回归正常水平排名迅速下降系统又重新寻找下一批“高点击率”的小样本继续循环。这个循环不仅浪费流量还会让模型的训练标签充满噪声。因为你在某个时刻采集到的“曝光后点击率”可能根本不是这个商品稳定状态下的点击率。这也是为什么我说不解决稀疏问题点击率预测从一开始就是错的。2. 贝叶斯平滑的数学内核从Beta分布到后验均值2.1 为什么偏偏选Beta分布贝叶斯平滑的核心思想是给点击率p一个先验分布。点击行为本身很好建模n次曝光c次点击可以看成二项分布Binom(n, p)。问题在于p未知我们需要对p做估计。如果直接做贝叶斯推断最省事的做法是选一个跟二项分布“共轭”的先验——共轭的意思是先验和后验的分布形式相同计算起来非常方便。二项分布的共轭先验正是Beta分布记为p ~ Beta(α, β)。其中α和β可以直观理解为“先验点击次数”和“先验未点击次数”。Beta分布的表达能力也不弱α和β取不同的值可以拟合均匀分布、集中在某一均值附近的尖峰分布、甚至偏向两端的分布。这让它非常适合表达先验的“强弱”和“中心位置”。2.2 后验公式贝叶斯平滑最核心的一行假设一个item展示了n次点击了c次那么似然函数是L(p) p^c * (1-p)^(n-c)先验是P(p) ∝ p^(α-1) * (1-p)^(β-1)两者相乘后后验依然是Beta分布p | c, n ~ Beta(αc, βn-c)用后验均值作为点估计就得到了贝叶斯平滑最经典的公式smooth_ctr (c α) / (n α β)这个公式看起来简单含义却很深。αβ可以理解为“等效曝光量”α是其中的“等效点击数”。当真实曝光n很小时平滑结果主要由先验决定当n很大时先验的影响逐渐被稀释平滑结果会逼近真实统计点击率c/n。我用一个直观例子说明假设全局估计α20β580均值3.3%先验强度600。商品A展示5次点击3次直接统计60%平滑后是(320)/(5600)3.8%几乎被拉回到先验附近商品B展示1000次点击200次直接统计20%平滑后是(20020)/(1000600)13.75%虽然仍高于先验但也没有疯狂到20%。这个收缩效果正是我们想要的。2.3 点估计为什么取均值而不是众数贝叶斯推断里点估计可以取后验均值、后验中位数或最大后验概率MAP也就是众数。对Beta分布来说众数的公式是mode (αc-1) / (αβn-2)分子分母都要减1会带来一个隐蔽的问题当αc和βn-c恰好小于1时众数可能跑到0或1的边界导致极不合理的估计。比如一个商品展示1次点击0次如果先验αβ0.5MAP直接是0相当于完全不给他机会后验均值却会给出一个温和的平滑值。取均值的好处是它在“完全相信数据”和“完全相信先验”之间做了软折中既不会无限偏向0/1也不会让小样本特征主导排序。这在点击率预测这种需要稳健性的场景里比MAP更实用。3. 超参数α和β的估计工业级落地最关键的环节3.1 矩估计法从样本均值和方差反推参数公式给出后下一个问题是α、β从哪来。一种偷懒的做法是拍脑袋比如设α1、β1均匀先验或者设α5、β95假设先验均值5%。但每个业务的点击率水平完全不一样拍脑袋容易出错所以我们希望从历史数据中估计出合理的α、β。工业界最常用的方式是矩估计思路是让理论均值和方差等于样本均值和方差然后反解参数。Beta分布的均值和方差公式如下E[p] α / (αβ) Var[p] αβ / ((αβ)^2 * (αβ1))实际操作时统计所有item的总曝光和总点击计算出加权平均点击率μ用曝光数加权的方差估计点击率的样本方差σ²令M αβ根据公式σ² μ(1-μ)/(M1)解出M再算出α μMβ (1-μ)M。写成Python代码如下import numpy as np def estimate_beta_params(impressions, clicks): impressions np.asarray(impressions, dtypenp.float64) clicks np.asarray(clicks, dtypenp.float64) # 过滤掉无效曝光 mask impressions 0 impressions, clicks impressions[mask], clicks[mask] # 加权平均点击率 mu clicks.sum() / impressions.sum() # 曝光数加权的点击率方差 p_i clicks / impressions sigma2 (impressions * (p_i - mu) ** 2).sum() / impressions.sum() # 方差边界保护Beta分布的理论方差不会超过 mu*(1-mu) max_sigma2 mu * (1 - mu) if sigma2 max_sigma2: sigma2 max_sigma2 * 0.999 # 反解参数 m mu * (1 - mu) / sigma2 - 1 if m 0: # 兜底样本方差过大时退化为一个很弱的先验 m 1.0 alpha mu * m beta (1 - mu) * m return alpha, beta这段代码里的边界保护很重要。为什么因为样本点击率经常会有大量0和少量1直接计算方差很容易超过μ(1-μ)导致M为负数α、β也变成负数那后面所有平滑结果都不可用了。经验做法是先过滤掉展示量极低的样本再做方差估计如果方差还是异常就做截断或者改用后面说的极大似然法。3.2 矩估计的失效场景矩估计虽然快但确实有失效场景。最典型的是数据里混入了极端离群值比如某个无效流量包拉高单个item的点击率。因为方差对离群值非常敏感算出来的σ²会偏大进而导致αβ偏小平滑力度不够整个模型对小样本噪声的抑制能力大打折扣。更麻烦的是矩估计对“点击率服从Beta分布”这个假设比较依赖。实际业务中不同类目、不同位置的点击率差异非常大如果全部混在一起估计得到的α、β就会是一个“平均”的结果热门品被压得过狠冷门品又平滑得不够。这种情况下我建议至少按类目、按广告位、按新老品分层各估一套α、β再应用到对应层级的item上。3.3 想要更稳定就最大化边际似然矩估计的另一个替代方案是直接最大化边际似然。把每个item的曝光n_i、点击c_i带入边际似然可以表达成一组Beta函数的乘积。对大样本业务可以用不动点迭代也可以用梯度优化直接求解。最简单的方式是写一个负对数似然函数丢给SciPy的L-BFGS-B优化器import numpy as np from scipy.special import gammaln from scipy.optimize import minimize def beta_binomial_loss(theta, impressions, clicks): alpha np.exp(theta[0]) beta np.exp(theta[1]) res 0.0 for n, c in zip(impressions, clicks): res gammaln(n 1) - gammaln(c 1) - gammaln(n - c 1) res gammaln(alpha c) gammaln(beta n - c) - gammaln(alpha beta n) res - gammaln(alpha) gammaln(beta) - gammaln(alpha beta) return -res def estimate_beta_mle(impressions, clicks): impressions np.asarray(impressions, dtypenp.float64) clicks np.asarray(clicks, dtypenp.float64) mu clicks.sum() / impressions.sum() result minimize( beta_binomial_loss, x0[np.log(mu), np.log(1 - mu)], args(impressions, clicks), methodL-BFGS-B ) alpha np.exp(result.x[0]) beta np.exp(result.x[1]) return alpha, beta实际项目中我只在数据量不算特别大、且对精度要求高的场景用极大似然。数据量超过百万级item时跑一遍L-BFGS-B也不便宜矩估计加上清洗流程已经足够没必要为了完美而牺牲效率。4. 在真实点击率预测管线里的落地姿势4.1 离线场景每日全量CTR基准表最经典的落地路径是每天离线跑一遍全量item的平滑点击率产出一张多维度的宽表供给线上特征和离线训练。表结构通常长这样字段说明item_id商品、创意或广告的唯一IDexposure_count近N天累计曝光数click_count近N天累计点击数raw_ctr直接统计点击率smooth_ctr贝叶斯平滑后的点击率(α, β来自对应分层)confidence_score可根据展示量、平滑后分母计算的置信度这张表的作用不止是提供一个“更准的CTR”它还能做排序候选过滤、做冷启动预估基值、做特征工程的原料。平滑后的smooth_ctr相比raw_ctr在排序场景里更容易让新老品站在同一条起跑线上。4.2 在线场景参数滚动更新线上实时点击率预测不建议直接把α、β固定死就不再更新。我的经验是取最近14天或者30天的数据每天或者每周重新估计一次α、β更新到配置中心。这样先验会跟着大盘点击率的季节性变化走不会出现“大促前后先用同一套参数”的尴尬。滑动窗口还会带来另一个细节新旧参数的切换要平滑不要直接重灌。比如大促刚结束整体点击率变化剧烈如果用14天窗口可能某一天突然把先验均值拉高一大截导致全量item的平滑CTR集体跳动排序波动变大。我习惯做一个参数混合新参数计算完成后和线上正在用的参数按0.1~0.2的权重做加权平均再发布上线。这样既跟得上趋势又不会让线上指标突然抖动。4.3 平滑CTR在模型里的用法很多同学以为平滑完成后把smooth_ctr当一个特征喂给GBDT就结束了。实际上还有几个更好用的姿势同时输入曝光数或log(曝光数)。树模型可以从中学习到“这个特征值是否可信”把(αβn)这个等效总样本量作为权重或者辅助特征让模型在样本充足时对smooth_ctr有更高的置信度对分层先验做个衍生特征比如“item平滑CTR减去所属类目平滑均值的差”能帮助模型捕捉不同层级上的相对竞争力。在线排序模型如果用LR或者深度模型smooth_ctr直接做连续特征也完全没问题只要做好分箱或归一化。树模型对数值尺度不敏感所以直接给原值就够了。4.4 分层平滑不同业务场景别共用一套先验不同类目、不同广告位的点击率天然有差异首页banner的点击率可能5%详情页商品的点击率可能0.5%如果你把它们放在一起估计全局α、β两边的结果都会被扭曲。我推荐的做法是两层甚至三层结构全局一套α、β作为底层每个类目/广告位单独算属于自己的α_c、β_citem自己的平滑优先用类目参数。当某个类目样本太少时再向全局参数收缩。这个“向高层收缩”的思想其实就是贝叶斯层级模型的简化版本落地起来并不复杂——只需要在参数表里多维护一个level字段。5. 同类平滑手段怎么选以及我踩过的几个坑5.1 拉普拉斯平滑、Wilson区间、贝叶斯平滑的对比很多人会问平滑点击率是不是只有贝叶斯平滑一种办法。实际上常用的还有拉普拉斯平滑和Wilson分数区间。我单独用一张表总结它们的使用场景方法计算公式优点缺点适用场景拉普拉斯平滑(c1)/(n2)实现简单零成本先验不自适应接近均值时偏差大快速验证小规模特征Wilson区间区间下界或中点自带置信度边缘解释清晰不是点估计需要二次处理低曝光过滤、类目基准贝叶斯平滑(cα)/(nαβ)先验从数据中学习收缩自适应需要估计α、β有一定实现成本大规模CTR/全链路排序如果你只需要粗粒度过滤低质量itemWilson区间的下界很好用但如果你希望输出一个稳定、可解释、能进排序模型的特征贝叶斯平滑是更合适的方案。5.2 坑一矩估计的样本方差被高估平滑形同虚设这是我第一次做贝叶斯平滑时踩得最深的坑。当时我以为只要套公式就能拿到α、β结果算出来的αβ只有十几平滑力度弱得可怜。查了半天发现是因为我没有过滤展示量极少的item大量曝光只有个位数的样本把点击率方差拉高了M被压缩到很小。后来我把估计α、β的样本限制在“展示量大于等于某个阈值”比如n≥50的范围里方差才回归正常。这个步骤不能省阈值根据业务点击率整体水平来定我的经验是让阈值内的样本覆盖全量曝光的80%以上即可。5.3 坑二点击和曝光口径混在一起估计点击率的口径远没有想象中统一。raw click、valid click、filtered click过滤掉机器人流量差异巨大曝光也有“展示即算”和“可见曝光”两种口径。如果训练数据里用的是valid click而统计α、β时用的是raw click那整体点击率会被高估平滑结果整体偏大排序时会有系统性偏差。更隐蔽的问题是不同位置的曝光质量不同混在一起估计会让先验丧失业务意义。我现在的做法是每个业务线独立清洗流量再按口径分别估计α、β宁可多维护几组参数也不要“一刀切”共用一组。5.4 坑三先验强度过大把真正爆款给压制了贝叶斯平滑引入先验后天然会让估计值向均值收缩。但如果αβ设得非常大比如根据全量数据算出M5000以上而某个新品的曝光已经达到数千次、真实点击率确实很高平滑后的值会被严重拉低。你不是在去噪而是在抹杀真实信号。这个问题的本质是样本量增加后应该让数据自己说话。解决方式有两种一是随着item曝光量增加逐渐降低先验的权重比如用权重因子 V/(VM) 加权原始频率估计和平滑估计二是分新老品各自估计先验新品用较弱的先验强度老品用较强但稳定的先验强度。第一种方式效果直接第二种方式更细腻实际使用中可以结合。5.5 一个经常被忽略的进阶操作把置信度交给下游平滑之后得到的其实不只是点估计还有后验分布。用后验均值做特征只是最基础的用法更进阶的做法是把每个item置信度的高低也作为一个维度传给下游排序模型。比如smooth_ctr差异不大时模型更倾向选择曝光量更大、置信度更高的item或者反向选择故意试探置信度低的潜力品。这种用法在一些探索性排序策略里效果好到出奇也是贝叶斯平滑相比纯拉普拉斯平滑更强的地方——你不是只有一个数字你有一个分布分布能告诉你一个item当前被验证得够不够充分。回到我自己的经验如果你是从零开始搭建这套能力第一版建议直接走“分层的矩估计每日更新参数平滑CTR宽表”这三板斧跑通拿到收益后再去考虑极大似然、在线更新或置信度特征。克制很关键因为贝叶斯平滑解决的只是“小样本方差大”这一个问题它不解决所有排序问题。先把噪声摁住了再来谈调优顺序不能反。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询