ARTICLE DETAIL

资讯详情

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

推荐系统AB测试:分层实验、Holdout与反转实验的实战策略

推荐系统AB测试:分层实验、Holdout与反转实验的实战策略 做推荐系统的同学应该都有过这种经历模型离线指标涨得漂亮AUC提升了0.01召回率涨了2%结果一上线上核心业务指标纹丝不动甚至还有下跌。这种情况十有八九不是模型本身出了问题而是实验评估环节的干扰太多——你精心设计的实验被其他团队的实验撞了车或者被系统的流量分配机制“污染”了。我在小红书参与推荐系统建设时感受最深的一点是推荐系统的成败很大程度取决于你怎么做实验。小红书的信息流是双列笔记流用户一次看到两篇笔记展现位次、封面图、标题长度、内容类型都会影响用户点击行为而排序模型、召回策略、内容理解模型又相互交织。在这套复杂的系统上做AB测试如果只想“开个桶、分50%流量、跑一周看指标”那基本是瞎忙。这篇文章我就把工业界做推荐系统AB测试的核心策略拆开讲清楚重点聊聊三件事分层实验体系、Holdout机制、反转实验。这三件事是一套组合拳搞明白它们你就不容易再被线上AB实验的结果骗了。1. 推荐系统AB测试的特殊性为什么它比普通实验难那么多1.1 推荐系统的指标链路比你想象的更脆弱普通业务做AB比如改个按钮颜色、调整下单流程核心指标往往只有一个实验链路也短。推荐系统完全不是这样。一次排序模型升级影响的链路是这样的召回阶段决定候选池 → 粗排筛掉大部分笔记 → 精排产出最终顺序 → 混排策略平滑内容品类 → 用户看到结果 → 产生点击、时长、关注、转化等一系列行为。每一步都会引入干扰因素。精排模型提升了但召回侧没有调整候选池里的优质笔记没被捞回来精排再准也白搭。混排策略如果对不同品类有强约束比如必须保证一定比例的图文和视频混合那排序模型的调整空间就非常有限。更麻烦的是推荐系统的指标之间有强关联性。点击率提升可能只是把用户的注意力从“深度阅读”转移到了“泛点击”上结果互动时长反而跌了。人均时长涨了可能是某个大爆款视频拉的跟你的模型升级没关系。这就是为什么在推荐系统上做AB你绝不能只看一两个指标必须构建一个完整的效果观测体系。1.2 流量划分为什么不能“想怎么切就怎么切”做过推荐系统实验的人都知道最头疼的问题就是“大家都要流量”。召回团队要流量测新的召回策略排序团队要流量测新的精排模型内容理解团队要流量测新的标签体系生态策略团队还要流量调审核规则。如果每个团队都独立切割10%的用户这些实验的风控条件和样本就会重叠。假设你有1000万日活A团队切了10%测精排B团队也切了10%测召回两个团队的实验人群完全重叠。A实验组里有B实验的干扰B实验组里有A实验的干扰。最后两个团队都开了实验指标都涨了你以为是自己模型好实际上可能是对方的改动带来的红利。这就是推荐系统AB测试和普通业务实验最大的区别一套科学的实验框架体系不是工具层面的需求而是基础设施层面的需求。没有分层和隔离机制所有实验结果都不可信。2. 实验分层体系把流量划分的数学逻辑讲清楚2.1 正交与互斥两个必须吃透的基础概念在讲分层之前有两个基础概念必须先搞清楚互斥Disjoint和正交Orthogonal。互斥的意思是两个实验的流量不能重叠。你实验用的用户完全不在我的实验里出现。说白了就是“这块地你种那块地我种咱俩互不干扰”。互斥的优点是隔离彻底缺点是流量利用率低。1000万用户5个团队做实验如果要完全互斥每个团队只能分到200万很多实验根本跑不出统计显著性。正交的意思是两个实验的流量可以有交集但交集的组合是均匀分布的。严格来说正交的前提是“重随机化”或者“多层哈希”。举例来说用户A既可能出现在精排实验的“精细排序组”也可能出现在召回实验的“新召回策略组”但他同时出现在哪个组合里是均匀随机分布的。从统计上看每个实验组的用户构成和其他实验的分配方式完全无关因此可以认为其他实验的效果是对称干扰不影响对比结论。推荐系统工业界通常把这两种方式混用核心设计就是“同层互斥跨层正交”。2.2 分层实验体系的标准架构在实际落地时我们一般把整个实验流量分成若干“层”Layer每一层做一件事。每层里再切出若干“桶”Bucket每个桶承载一个实验或者一组实验。一个典型的推荐系统分层架构长这样第一层用户层实验。用于测试涉及用户APP全局体验的实验比如首页改版、视觉主题切换、交互方式调整。这一层的改动影响面大通常常驻且一般与其他全局改动互斥。第二层召回层实验。测试不同的召回策略比如双塔模型的不同变体、向量召回与规则召回的组合比例、多路召回的融合权重。召回层的实验通常和排序层正交。第三层粗排层实验。这是很多团队容易忽略的。粗排模型从几百上千个召回结果里筛出几十个进入精排它的效率直接影响精排的计算压力。粗排实验的流量可以和精排实验正交。第四层精排层实验。这是最核心的一层排序模型的迭代基本都在这里。精排实验通常使用点击、互动、时长等核心指标评估。第五层混排策略层实验。做内容生态的团队常用比如控制视频/图文的混排比例、限制某类内容的连续出现条数。跨层之间正交同层之内互斥这是工业界比较通用也比较稳妥的方案。每一层都用哈希函数把用户映射到不同的桶里不同层的哈希使用不同的盐值salt。2.3 哈希分桶的具体实现方式做分桶时几个核心参数要提前想清楚实验ID或层ID、哈希函数、桶数量、盐值。假设精排层有100个桶你的新模型要切20%流量那就分配20个桶给实验组另外20个桶给对照组剩下60个桶留给未来。每次新实验进来从空闲桶里分配老实验结束桶回收继续复用。在具体编码上核心逻辑大概是import hashlib def hash_bucket(user_id: str, layer_id: str, salt: str, num_buckets: int 100) - int: key f{user_id}:{layer_id}:{salt} hash_val int(hashlib.md5(key.encode()).hexdigest()[:8], 16) return hash_val % num_buckets这里的关键在于层ID加盐。精排层用“layerrank,saltabc”去算哈希混排层必须用“layerblend,saltxyz”去算哈希。这样同一个用户在精排层分到的是20号桶在混排层可能是68号桶两个层之间没有相关性才能实现跨层正交。注意不要用同一个盐值算所有层的哈希否则用户在所有分层里都会被分到同样的桶跨层正交直接失效。这是我见过最隐蔽的坑。2.4 怎么输出一张分层图让团队一目了然实验体系搭好了还需要一张分层图用来同步团队。很多团队的分层图都是停留在文档里的流程图更新不及时最后成了摆设。我自己的习惯是用表格维护一张“分层总表”每个团队都能看到自己的实验位在哪里和谁共享流量。层名称层用途哈希盐值互斥范围正交范围当前状态用户层全局体验实验saltuser用户层内互斥与其余所有层正交常驻召回层召回策略实验saltrecall召回层内互斥与排序/混排正交占用40%粗排层粗排模型实验saltcoarse粗排层内互斥与精排/召回正交空闲精排层精排模型实验saltrank精排层内互斥与召回/混排正交占用75%混排层内容生态实验saltblend混排层内互斥与其余所有层正交占用20%表格做出来之后我建议在实验平台上直接可视化可以画一个横向的柱状图来表示每一层的占用比例方便实验管理员快速判断“这层还能不能开新实验”。分层图不只是一张图它本身就是实验管理规范的一部分。3. Holdout让长期影响无所遁形的公共保留机制3.1 为什么普通对照组靠不住分层体系解决的是多实验并发时的干扰问题但它有一个盲区它只负责把实验流量隔离干净却没办法回答“实验上线一段时间后对大盘的整体影响是什么”。举个例子你在精排层切了20%的流量做实验对照组也是20%。实验跑了三周实验组指标显著优于对照组于是全量上线。问题来了实验组用户跑了三周的新策略对照组用户跑了三周的旧策略两组用户的行为已经产生了路径依赖。实验组习惯了新排序带来的内容消费节奏对照组还在旧节奏里。上线后新策略真正全量铺开所有用户都要经历一次“环境切换”这个切换成本会拉低最终表现。这就是普通对照组评估不了长期效果的典型案例。推荐系统里用户的偏好是被系统塑造的你给他看什么他就慢慢变成什么。三周的实验期实验组用户的偏好已经开始偏离对照组。3.2 Holdout组的核心设计思路Holdout机制就是为了解决这个问题。它的核心思路是在整个用户池里长期固定抽取一小部分用户比如2%、5%这部分用户永远不进任何实验分组永远只接收当前线上的全量最优策略。具体来说Holdout组要满足两个条件第一它要从所有实验分层之外独立划分任何实验都拿不到这部分流量第二它要长期存在不是某一轮实验的对照组而是持续存在的“基准线”。有了Holdout组之后衡量某次实验的全量效果就简单了上线之前比较实验组与普通对照组上线之后第四周、第八周重新比较“曾经实验组”和“Holdout组”。如果第四周时两组核心指标趋近说明新策略的切换成本可接受如果“曾经实验组”明显高于Holdout组说明新策略确实带来了稳定的额外收益。我自己的体感是Holdout组最值钱的地方在于“回头验”。很多时候实验报告跑完就归档了没人回头检查。有了Holdout组可以定期回溯过去三个月上线的所有策略看看哪些策略是“实验一时爽全量火葬场”哪些才是真正稳定持久的操作。3.3 Holdout组大小怎么定Holdout组不是越大越好。它占用的流量是不参与任何实验的对业务来说这部分流量是“固定消耗”但如果太小统计功效又不够。我一般建议Holdout组占全流量的2%到5%。大厂日活过亿2%已经有两百万用户足够跑大多数核心指标了。中小规模产品建议取5%否则一个月的用户活跃波动就可能把Holdout组的数据噪声拉大。选用户时最好用哈希取模的方式固定下来。比如用user_id算哈希模100小于5的全进Holdout组。这样Holdout组的人群是固定的长期追踪的用户群是同一批可比性最强。注意Holdout组千万不能允许业务方“临时接入”。一旦某个团队觉得“这个新策略很成熟直接放到Holdout组里验证一下吧”那这个Holdout就废了因为它失去了“长期只接收全量最优策略”的纯净性。4. 反转实验验证上线决策的终极试金石4.1 反转实验到底是什么反转实验这个名字听起来有点唬人实际上逻辑非常简单当实验组表现出显著正向效果后把两个组的策略对调实验组跑原对照组策略对照组跑原实验组策略再跑一段周期看指标是否“反转”。什么情况下用反转实验主要是在面临重大上线决策时。普通的AB实验只能告诉你“在实验期内实验组策略是否优于对照组策略”但无法排除“新奇效应”——用户对新东西天然有好奇新策略上线头两周表现好两周后热度退了就打回原形。反转实验可以部分解决这个问题如果实验组和对照组策略互换后指标也随之翻转那说明效果来自策略本身而不是实验时间窗口的偶然因素。为什么叫“反转”因为它的设计逻辑是“用反事实来验证因果”。普通AB的核心是比较“有策略A的用户”和“没有策略A的用户”反转实验则更进一步比较“原策略A的用户切换到策略B”和“原策略B的用户切换到策略A”通过策略互换来检验因果关系的真实性。4.2 反转实验的适用场景反转实验不是每次上线都需要做它是有成本的。实验期翻倍人力投入翻倍而且如果策略效果本来就是微弱的反转实验很可能跑不出显著性反而干扰决策。我总结下来反转实验主要在两类场景下最有价值第一类是高风险核心链路改动。比如精排模型的主框架更新从双塔切换到Transformer结构这种改动影响面太广一旦上线再回退成本极高。上线前做一轮反转实验相当于多一层保险。第二类是用户感知强烈的策略改动。比如信息流混排策略中视频的占比突然增加了20%用户可能短期内因为新鲜感频繁点视频但长期是否真的喜欢这必须用反转实验来验证。4.3 反转实验的三个关键注意事项反转实验看着简单实操中要注意三个问题。反转周期不能太短。反转实验一般要求每个阶段至少跑两周。第一阶段的策略适应期要覆盖用户从“旧策略”迁移到“新策略”带来的体验波动。如果反转阶段只跑三天数据根本稳定不下来。反转不能反复做。反转实验做一次就够了做完之后必须尽快确定上线方案。用户的耐心有限一会儿看策略A一会儿看策略B体验颠簸会直接造成用户流失。反转实验的目的不是反复横跳而是通过一次互换获得确定性。反转实验的评估指标要选准。反转实验期间用户行为处于切换状态短期指标比如点击率容易出现剧烈波动。建议以中长周期指标为准比如次周留存、均月活跃天数这些指标更能反映用户对策略的真实接受度。5. 从实验设计到决策统计原理与实操指标5.1 样本量和实验时长的预估方法很多做推荐算法的同学对实验设计中的统计学部分非常薄弱。我见过太多人开实验之前完全不计算最小样本量跑了两周看p值没有小于0.05就下结论“策略无效”。这是很可惜的因为很可能只是样本量不足策略本身是有效的。最小样本量的公式一般长这样import math def min_sample_size(effect_size: float, alpha: float 0.05, beta: float 0.2) - int: z_alpha 1.96 # 对应 alpha0.05 双侧检验 z_beta 0.84 # 对应 beta0.2即统计功效 0.8 return int(math.ceil((2 * (z_alpha z_beta) ** 2) / (effect_size ** 2)))比如你的核心指标是点击率实验组和对照组点击率的差异期望是0.5%即effect_size0.005代入公式得到最小样本量约62.7万。如果你的实验组只有10万用户跑两周就跑不动了根本不具备统计说服力。实验时长的计算要考虑两个因素一是到达最小样本量所需的天数二是用户行为周期。推荐系统一般建议至少覆盖一个完整的自然周因为工作日和周末的用户行为差异巨大。如果一个实验要评估留存类指标则至少要跑14天这样才有机会看到次周留存。5.2 显著性检验的实操陷阱p值小于0.05并不代表实验成功了。推荐系统实验遇到最多的陷阱有三个第一多重比较问题。你一次实验看了50个指标其中两三个p值小于0.05这很可能是随机波动。我现在处理多指标时一般会对p值做Benjamini-Hochberg校正FDR控制或者干脆只看预注册的几个核心指标。第二指标早停问题。实验跑了三天实验组点击率显著高于对照组于是提前停止实验放量。这种操作风险极高因为前三天往往是新奇效应最明显的时期。除非你非常确定策略机制没有副作用否则不要因为早期指标好就仓促全量。第三逆指标忽略问题。实验组点击率涨了5%但人均时长跌了3%这个过程经常被忽略。真实决策中我一般会盯一个“综合收益”指标把点击、时长、留存、消费金额折算成统一的收益值避免单一指标绑架决策。5.3 指标涨跌矛盾的时候怎么拍板当指标出现冲突时不能拍脑袋要有可追溯的决策规则。我在团队内部推行过一种“收益-成本对照表”的方式把实验结果的结构化信息列成表格再逐条审议。指标族具体指标实验组 vs 对照组判定权重结论互动指标点击率4.2%*30%正向消费指标人均时长-1.1%30%负向留存指标次周留存0.3%25%正向生态指标负面反馈率-0.2%15%正向综合加权之后如果正向收益大于负向损失且置信区间不跨越零点才放量上线。这个表格的好处是它逼着每个同学把实验的“判定逻辑”摆到台面上来而不是靠“我感觉”“我觉得”来谈结论。6. 常见问题与排查技巧实录6.1 实验效果“凭空消失”有段时间我们上线了一个新精排模型实验跑了三天点击率显著提升。第五天再看显著效果没了。查了半天发现第五天刚好有另一个团队上线了新的用户分层策略而用户入口在用户层做了互斥切分我们精排层的实验组用户结构被间接改变了。排查方法很简单看实验组和对照组的用户画像分布是否在实验中期发生突变。比如性别比例、活跃度分布、新老用户占比。一旦发现这些分布在中期发生跳变基本可以断定是上游实验或分桶逻辑出了问题。解决方法是把用户特征分布作为实验监控指标一旦特征分布偏差超过阈值自动告警。6.2 分桶不均造成实验结果不可信哈希分桶一般不会出问题但哈希函数处理某些特殊ID时也可能出现偏差。比如user_id是递增的如果直接用取模分桶那么最后一位数字的分布就不均匀。这也是为什么我们一定要在user_id之后拼接salt再做哈希最大程度地扰动原始ID序列。分完桶之后一定要先做一次AA实验验证。AA实验就是用两个空的实验桶跑同一套策略理论上指标应该无差异。如果AA实验就能看到显著差异说明分桶逻辑有问题先修分桶再做正式实验。6.3 实验“穿透”导致数据污染所谓“穿透”就是实验组用户通过分享、关注关系等社交链路把自己看到的内容传播给了对照组用户。小红书这种内容社区特别容易出现。现象是A用户被分到实验组看到一条对“新排序策略”有正向反馈的内容点赞之后这条内容被B用户对照组看到了B用户的点击行为实际上受到A用户实验策略的影响。穿透问题基本无法完全消除但可以通过“内容层面的交叉隔离”来缓解。也就是计算实验效果时只统计用户直接消费的笔记不考虑好友转发带来的间接消费。如果间接消费占比过高需要单独拆出来分析。6.4 反转实验过程中用户流失反转实验最怕的就是用户流失。第一阶段实验组适应了策略A第二阶段突然切回策略B部分用户可能直接卸载APP。这里有一个很实用的技巧第二阶段尽量做“收敛反转”即不是完全切回最老的策略而是切换到一个“保守但合理的折中策略”既能验证策略方向是否有效又能控制用户流失风险。写在最后的个人体会聊了这么多我把小红书做AB测试的经验浓缩成一句话AB测试是推荐系统的照妖镜但照妖镜自己也得经常擦。分层体系、Holdout、反转实验本质上都是为了让这面镜子更干净让我们看到的东西更接近真实。推荐系统领域网上有大量关于“模型结构设计”“损失函数改进”的教程但关于实验评估体系的内容相对少也相对杂。我希望这篇文章能帮到那些刚进入推荐算法领域、或者正在搭建实验平台的团队。如果你的团队现在还在用“拍脑袋切流量拍脑袋看数据”的方式做AB建议先从分层体系开始把实验基础设施搭好再谈模型迭代。最后再分享一个我的习惯每做完一个实验不只是保存实验报告把分层配置、分桶参数、样本量计算过程、上线决策表一起归档。半年后再看你会感谢当初记录了这么多细节的自己。
返回列表