ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AB测试进阶:分层实验、Holdout与反转实验如何支撑推荐系统迭代

AB测试进阶:分层实验、Holdout与反转实验如何支撑推荐系统迭代 做推荐系统的人到一定阶段都会碰到一个尴尬实验做得越多反而越难判断线上到底哪个改动在起作用。尤其在内容型产品里排序策略、召回策略、图文混排、互动引导几乎每个模块都在同时做着灰度实验。就拿小红书这类社区产品的曝光链路来说用户每次刷新都可能被四五层策略同时影响如果实验体系设计得不好业务方拿到的结论往往是“每个实验都涨了大盘却没什么变化”。这篇文章我想聊聊工业界实战里的AB测试策略重点拆解三个关键机制分层、Holdout和反转实验。它们不是同一层面的东西分层解决的是“流量怎么复用”Holdout解决的是“系统整体到底变好了多少”反转实验解决的是“已上线策略能不能撤下来验证归因”。三个东西合在一起才是一套能支撑几十个算法同学并行迭代的实验体系。如果你是做推荐、做策略、做数据工程的这篇文章应该能帮你把实验平台的底层逻辑串起来。1. 为什么推荐系统需要的不只是开实验组1.1 一个实验体系崩坏的典型场景想象一下这个场景召回团队说新召回让曝光PV提升了排序团队说新精排让互动率涨了重排团队说多样性能提升时长但这些实验是各自在不同时间段、不同流量比例下开的。问题来了召回变了之后排序模型看到的特征分布也变了排序实验里观察到的“互动率提升”到底是排序模型自己的功劳还是召回实验引入的新内容带来的增量答案往往是分不清的。我见过最夸张的情况是一个产品线同时开了十几个实验每个实验单独看都有正向指标但整体业务月报里的核心数据反而在缓慢下滑。后来复盘发现实验之间互相污染两个团队在同一个用户群里同时改排序逻辑各自的对照组其实都已经被对方的实验“污染”了。这就是没有完整实验体系时最典型的症状单点都对全局崩溃。所以AB测试在推荐系统里不是“开个分组、跑两周、看结果”这么简单。它必须建立在流量随机化、样本独立、分桶稳定这些底层保证之上还要考虑多策略叠加时的交互效应。分层、Holdout、反转实验本质上都是在跟“实验之间的相关性”做斗争。1.2 三个工具分别解决哪个环节的痛点先给一个整体认知方便大家对号入座工具解决的核心问题典型使用时机最常见误区分层实验多个实验并行时流量如何复用多个团队同时改不同模块以为分层后所有实验互不影响Holdout整个实验体系累计带来多少真实增量季度/年度复盘管理层要数据拿单实验对照组当全局基准反转实验已上线策略是否真的可归因、可持续策略上线后要复盘或考虑回退把反转当成普通AB照搬流程这三个工具的使用时机是递进的分层管“实验进行中”Holdout管“长期核算”反转管“上线后的二次验证”。我以前总把它们拆开理解后来发现它们必须配合使用。比如Holdout组的流量本身要放在分层体系的高层不能被一般实验占用反转实验要开在小流量的层里避免影响主线实验。2. 分层实验让上百个实验并行不打架2.1 层内互斥、层间正交的设计思路分层实验的核心概念是“层”。一个层可以理解为一块独立的流量哈希空间比如0到1023一共1024个桶某个实验占用其中的一段桶比如128到255。同一层内的不同实验必须瓜分互斥的桶段不能重叠这叫层内互斥。层与层之间则要求正交。所谓正交就是一个用户在第1层被分到实验组A在第2层被分到实验B或对照组两次分配在统计上是相互独立的。这样设计的好处是召回层在跑召回实验精排层同时可以跑精排实验互不占用对方流量因为同一个用户在两层各有各的分桶结果。实现正交最简单可靠的做法是哈希盐值。每个层有自己的salt值分桶时用hash(user_id layer_salt)对桶数取模。不同层用不同salt哈希结果天然打散同一用户在不同层的分组组合近似随机。这里有个细节我必须强调如果两个层用相同salt同一个用户就会被分到同样的桶层间样本高度相关实验结论就不独立了。所以每层一个独立salt是基础配置。伪代码大概是这样的逻辑def assign_bucket(user_id, layer_salt, total_buckets1024): hash_input f{user_id}|{layer_salt} bucket murmurhash3(hash_input) % total_buckets return bucket这里用稳健的murmurhash3而不是自带的hash()是因为Python的hash()对字符串会随机加盐运行环境一变结果就不同无法保证分桶稳定性。2.2 怎么划分层从召回、粗排、精排到重排层的划分最好顺着推荐链路的模块边界走。小红书这类内容型产品推荐模块大致是召回 → 粗排 → 精排 → 重排 → 分发。每个模块的改动对用户的影响路径不同放在不同层里互不干扰这是最理想的状态。常见的分层划分方式可以是这样召回层负责从海量笔记池里捞候选集实验目标是召回率、覆盖率。粗排层负责用轻量模型快速筛选候选实验目标是候选质量、过滤率。精排层负责CTR/CVR预估实验目标最直接关联用户点击和互动。重排层负责多样性打散、去重、商业化插入实验目标是体验类指标和商业指标。分发/UI层负责双列/单列、卡片样式、封面展示实验目标是浏览深度。实际配置实验时会为每个实验分配一个桶区间。比如精排层有1024个桶当前线上一共有三个实验在跑那么平台配置大概是{ experiment_id: ctr_model_v3, layer: reranking_layer, allocation: [ {group: control, bucket_range: [0, 767]}, {group: treatment, bucket_range: [768, 959]}, {group: reserved, bucket_range: [960, 1023]} ] }这个配置的意思是精排层内实验组占192个桶覆盖18.75%的流量其余都是对照。同一个层如果再开新实验新实验只能从剩下的桶里切或者等待其他实验下线。这种基于桶区间的管理方式非常灵活调整实验比例时不需要重新哈希分桶只需要改配置。2.3 分层并非万能交互效应和流量分配的限制很多人以为建了分层系统就万事大吉实际上层间正交是一个近似理想不是绝对保证。因为推荐链路的模块之间本身有强依赖召回策略一变精排模型的预测分布就会跟着变精排的结果一变重排看到的候选质量也会变。就算两层哈希正交实验内容之间的行为影响也没法完全隔离。所以有一类策略必须放到同一层里互斥测试比如两个互相替代的精排模型。如果A组用新模型、B组用旧模型它们必须抢同一批桶不能让同一用户在脚本里同时接受两个模型的叠加影响。不同层的实验虽然正交但是如果两个层都在修改排序权重即使哈希正交用户同时看到两种权重叠加最终结果依然不可解释。分层的另一个限制是流量不够用。每层有1024个桶实验一多每个实验分到的桶就少样本量不足很多微弱但真实的效果根本检查不出来。这里可以做一个最小可检测效应的估算。以点击率这个二分类指标为例假设基线CTR是4%想检测出0.1个百分点的提升也就是4.0%变成4.1%显著性水平0.05、检验效能0.8的情况下每组大概需要的样本量是import math alpha 0.05 beta 0.2 z_alpha 1.96 z_beta 0.84 p 0.04 delta 0.001 n 2 * ((z_alpha z_beta) ** 2) * p * (1 - p) / (delta ** 2) print(f每组需要样本量: {n:.0f})算下来每组需要60万左右的用户。如果产品日活是100万分20%流量测一组10万用户至少得跑6天才能累积够样本。所以精度要求越高的实验越需要大流量或者长周期。这里也解释了为什么算法团队喜欢做涨几个点的改动因为小改动要验证的成本实在太高了。3. Holdout核算所有策略叠加后的真实增量3.1 Holdout流量池的设计和维护Holdout这个概念在不同场景下意思不一样离线机器学习里它是“留出验证集”在线实验体系里它指的是一块长期不接受任何实验的流量池。这块流量池从上到下永远跑基础版本策略不做任何灰度改动。为什么要专门留一块流量不动原因很简单单个实验的对照组只能证明“这个策略在一个固定环境下的增量”但推荐系统是一个持续演化的系统。今天上线策略A明天上线策略B后天上线策略C看起来每个实验都是正向的但三者的交互效应可能让最终叠加效果小于单点效果之和甚至为负。然而业务管理层要的是“我们这一整年做了这么多算法迭代日活跃用户和人均时长到底涨了多少里面有多少是算法的功劳”。这个问题只有Holdout能回答。Holdout的做法是在流量分配的最高层就切一块固定的桶区间比如5%到20%的流量这些用户在后续所有层都不参与任何实验不进入任何实验组或对照组。日常打点数据照常产出但策略永远走Base。复盘时拿整体大盘指标和Holdout指标做对比差距就是实验体系带来的真实增量。我举一个简化的计算方式。假设过去半年整体大盘用户的人均使用时长从300秒涨到了315秒而Holdout组只从300秒涨到了305秒。那这10秒的差值就是半年来所有策略迭代共同作用下的净增量。如果只看大盘从300到315会以为算法半年做了15秒的提升但扣掉环境因素和内容供给的自然增长实际只有10秒。这个“5秒”的差异经常是决定整个算法团队绩效的关键数字。所以我一直建议Holdout不是可选项而是实验体系的基建。3.2 怎么让Holdout的结论不骗人Holdout看起来简单但细节里全是坑。第一Holdout比例不能太小。如果只留1%的流量波动会非常大一个头部作者的内容分发波动都可能让指标跳两个点根本没法区分实验体系和随机误差。我的经验是日活百万量级的产品Holdout留5%到10%才比较稳日活千万以上的超大产品可以适当降低到3%到5%但低于2%基本就不太可信了。第二Holdout组要随机抽样不能用某个端、某个地域、某种用户标签来选。否则Holdout组和实验组在用户结构上就不一致对比毫无意义。常见的做法是在用户ID哈希后直接预留特定桶号这和分层实验里的分桶逻辑是统一的。第三要注意时间窗口。Holdout是一个动态的参照系它会随着内容生态的演化而变化。用户长期看到基础策略他的行为也会慢慢适应这种“学习效应”会压低实验组和Holdout组之间的差异。所以核算增量时最好按周看趋势而不是只盯某一个时间点的截断值。如果发现Holdout组近期指标突升别急着归因先排查是不是有实验配置误把Holdout流量放进去了。第四Holdout不能覆盖所有场景。比如双列转单列这种全局UI变化一旦整体切换Holdout组用户也很难完全隔离UI层的影响。类似这种产品形态级的改动Holdout只能作为参考不能完全兜底。这个局限要提前跟管理层对齐否则到季度复盘时不好解释。4. 反转实验给已上线策略做一次“停药观察”4.1 反转实验的价值凭什么还敢把新策略撤下来反转实验我习惯叫它“停药观察”。常规AB测试的逻辑是从旧到新、从对照到实验看新策略是不是更好反转实验反过来是从新回到旧把已经在线上的策略临时关掉看指标会不会变差。为什么明明都上线了还要做这个动作因为常规AB只能证明“在某个时间窗口内新策略相对旧策略有差异”证明不了“这个差异一定是策略本身产生的”。线上环境是动态的用户行为会形成路径依赖。有些策略上线后看起来涨了可能只是赶上了内容供给的旺季或者某类用户在这个阶段本来就活跃。这时候做反转实验把一个已经满量跑的策略临时回退到旧版本如果指标立刻出现可感知的下滑那基本可以确认这个策略是有效果的如果指标没什么变化说明之前的“效果”很可能有水分。反转实验还有一个价值是识别策略依赖。有些策略会改变用户和系统之间的交互习惯比如推荐流里把互动按钮位置调得更明显用户点顺手之后一旦恢复正常版本互动率会掉得很难看。这种“策略依赖”本身不是坏事但它会让实验者误判策略的长期价值。反转实验可以帮助你判断一个策略是“在给用户创造新价值”还是“在透支用户的心理惯性”。4.2 反转实验的实操设计与风险控制反转实验听着简单真操作起来比普通AB测试更需要谨慎因为你要主动把已经验证有效的策略撤掉这涉及真金白银的业务风险。实操步骤大致是确定要反转的策略范围最好只关闭某一个独立模块。不要一次性反转一堆策略否则出问题都不知道是哪个策略引起的。从当前满量策略用户里随机抽一部分流量作为反转组比例建议不超过20%。反转组临时切回旧策略。保留一组同样比例的用户继续使用新策略作为参照这样反转组和参照组是同一时期、同一批抽样逻辑下的对比能控制时序干扰。设置熔断阈值。比如反转组的负向核心指标下跌超过1%立刻恢复新策略避免业务受损扩大。观察窗口要足够长。刚切换的头一两天指标往往有“新鲜感效应”用户突然看到旧版本会有新奇感短期指标反而可能异常别因为头两天的数字就急着下结论。反转实验不适合所有策略。如果策略改变的是用户的长期资产比如关注关系、好友关系、收藏夹结构反转实验基本没什么用因为用户不会因为策略回退就把已经建立的关系删掉。这类策略更适合做模拟推演或者用因果推断的方法去做异质性效果分析。再有反转实验的“旧版本”必须确保是健康的如果旧版本曾经有性能Bug切换回去很可能引入新的噪声最后实验没做明白还引了一堆线上事故。我在实际工作中反转实验最常见的应用场景是对“看起来很美”的策略做审计。比如某个推荐策略上线后互动指标涨得很快但用户反馈和投诉也在上升常规看板看不出因果反转一下就能快速验证到底是不是这个策略在牺牲用户体验换互动。5. 一次完整推荐策略AB测试从流量预算到结果复盘5.1 定义实验目标和指标体系前面讲了理论框架这一章我用一个具体的策略实验来做串联。假设当前小红书类内容社区的重点指标是互动我们想验证一个更激进的多样性打散策略在重排阶段降低连续展示同一作者或同一主题笔记的概率希望促进用户浏览更多不同主题的内容从而提升互动率。实验上线前先要定指标不能只看一个数。我一般会把指标分成三层层级指标预期方向说明核心指标人均互动数点赞收藏评论正向策略的主要目标辅助指标人均浏览笔记数、封面点击率正向验证多样性是否真的促进浏览护栏指标人均时长、负反馈率、举报率不能显著变差防止多样性牺牲消费深度和体验这里特别强调护栏指标。很多人做实验只盯核心指标忽略了护栏指标结果核心指标涨了1%但人均时长掉了3%长期看第二天留存一定会受影响。建议大家上线实验时至少配一个护栏指标并且提前写清楚不可接受的下跌阈值。5.2 流量预算与显著性计算目标确定了接下来算流量。假设基线的人均互动数是10次标准差是8次我们预期多样性打散能带来3%的相对提升也就是人均提升0.3次。显著性水平取0.05检验效能取0.8每组需要的样本量是import math mu 10 sigma 8 effect_rel 0.03 delta mu * effect_rel # 0.3 z_alpha 1.96 z_beta 0.84 n 2 * ((z_alpha z_beta) ** 2) * (sigma ** 2) / (delta ** 2) print(f每组需要样本量: {n:.0f})算出来每组大约需要1.1万用户。这个数字看起来不大但别急着开心因为实验最少要跑一个完整的自然周覆盖工作日和周末两种用户行为模式。哪怕日活有100万取10%流量测一组5万人第二天就能累积够样本我也建议最少跑7天。因为短期的样本方差容易被特殊事件带偏比如某天上了热搜话题互动量整体暴涨只看一两天会把实验效应和话题效应混在一起。实验配置上这个实验的重排层有1024个桶我给实验组分配128个桶对照组分配128个桶其余桶走正常策略。实验组和对照组共占25%流量剩余75%保证日常策略不受影响。5.3 实验上线、SRM校验与决策复盘实验上线后第一步不是看结果而是做SRMSample Ratio Mismatch校验。SRM指的是实际进入各组的人和设计比例不符。比如我们设计了实验组和对照组各占12.5%流量跑了三天一看对照组明显少于实验组这时候实验结果必须废弃检查原因。SRM的卡方校验用Python可以这样写from scipy.stats import chisquare observed [48213, 46805] expected_ratio [0.5, 0.5] total sum(observed) expected [total * r for r in expected_ratio] chi2_stat, p_value chisquare(observed, f_expexpected) print(f卡方值: {chi2_stat:.4f}, p值: {p_value:.4f})如果p值小于0.05基本可以判定出现SRM。常见原因包括实验配置时桶范围写错、某些端没有正确下发策略、用户重复注册导致一个实体分到多组、白名单用户误入实验、日志在某个阶段丢失。我在实际排查看比例失真的问题经常会追溯到打点SDK上线版本不一致测试包和正式包走了不同的分桶逻辑。校验通过后进入观察期。观察期结束做显著性分析我会把每日指标聚合之后做一个双侧t检验同时给出置信区间。结果可能是指标实验组均值对照组均值相对提升p值结论人均互动数10.3210.013.1%0.021显著人均浏览笔记数22.521.83.2%0.015显著人均时长182秒185秒-1.6%0.11不显著这个结果其实很微妙核心互动指标在涨但人均时长在掉虽然没到显著水平但方向上是负的。这时候不能直接拍板上线需要进一步下钻。比如按用户活跃度分层看是不是只看重刷的高活跃用户贡献了互动而轻度用户消费时长变短了。如果轻度用户时长在显著下降说明多样性打散可能让首页内容不够“顺滑”打破了轻度用户的沉浸感。最终决策可能是上线策略但缩小打散力度先保住轻度用户时长。6. 常见问题与排查技巧实录6.1 实验组和对照组用户数严重偏离设计比例这类问题我见得太多。SRM出现时很多人的第一反应是调分桶配置或扩展日志但通常问题根源不在算法端。我的排查顺序是先检查实验平台配置的桶范围有没有重叠或漏配再检查客户端上报日志的userId是否稳定接着排查是否有预加载或缓存导致策略延迟生效最后看数据清洗逻辑是否误过滤了某一组用户。有一个经典案例某个实验上线后实验组用户数量一直是对照组的1.02倍刚开始觉得无伤大雅但后来发现这是因为双端App版本不同旧版客户端不认识新的实验参数默认全走了对照逻辑导致对照组“膨胀”。这是比较老的话题了现在大厂都有兜底但如果你在自建实验平台一定要在SRM上线时就自动阻断实验而不是人工去看。6.2 实验指标今天显著、明天不显著我建议所有推荐算法团队都遵守一个规矩不要每天对实验结果做显著性检验而是等实验跑完一周后再整体看。因为流量指标本身有日周期性、周周期性还有突发的热点效应。一天内显著很可能只是当天人群构成或者内容供给的变化不是实验本身的效果。如果指标反复横跳还有一个容易被忽视的原因是打点延迟。有些客户端打点是上报到服务端的网络差的时候打点会延迟一两天到达导致当天的实验组和对照组数据量本身就不同步。此时SRM通常也会出问题先排查数据链路再做指标分析。6.3 分层后两个实验的结果“互相打架”分层做到位了但实验结果还会互相打架这种情况也多。一个典型场景是召回层在跑一个“提新”实验精排层在跑一个“保时长”实验两者单独开都显著正向放在一起后发现“提新”实验的互动指标不显著了。问题在于两个层虽然样本正交但策略在业务上是链式影响的。新召回策略改变了精排模型的输入数据分布精排模型原本学出来的参数可能就失真了。解决思路有两个一是把高度耦合的策略放到同一层互斥测试二是对强依赖场景设计“联动实验”让两个实验共享同一个分层但支持二维交叉分桶用双因素方差分析去评估主效应和交互效应。后者更复杂但能拿到更多信息。6.4 反转实验遇到业务反噬怎么办反转实验的风险在于如果我们反转到旧策略后发现指标暴跌业务损失已经发生了。所以做反转实验之前设置好熔断机制比什么都重要。具体来说反转组和参照组的核心指标差值一旦超过预设阈值比如1%系统自动把流量切回新策略不用等人去看板。另外反转实验尽量避免在业务大促、节假日、重点内容活动期间做。大促期间用户的消费行为会被活动机制强烈驱动反转实验的结果很容易被各种混杂因素淹没如果还叠加了策略波动很容易误判。同样的实验放在业务平稳期做结论会干净得多。还有一个小技巧如果反转实验的目的是审计某个策略依赖可以不做完整反转而是只调整策略里的某个连续参数到中间值。比如重排多样性阈值当前是0.8可以临时改成0.5跑几天看指标是不是单调变化。这种“软反转”比二进制切换更安全也能看到剂量反应关系信息量更大。我个人在实际操作中最深的体会是推荐系统里的实验体系本质是一个公司的“算法账本”。分层保证账目清晰Holdout保证账本可信反转实验则负责审计账目里那些“可疑的漂亮数字”。把这个账本管明白了算法迭代的速度和质量才会真正稳定下来。实验体系本身不像模型那样能带来立竿见影的涨点但它决定了你每一个改动到底有没有被准确计量。这也是我在所有策略项目里最先花时间搭好实验体系的根本原因。
返回列表