东亚银行、岚图汽车带你解锁 AIGC 时代的数字化人才培养各赛道新模式! 了解详情
写点什么

Twitter 的 A/B 测试实践(三):检测和避免 A/B Test 中 bucket 不平衡问题

  • 2016-03-27
  • 本文字数:3336 字

    阅读完需:约 11 分钟

【编者的话】A /B 测试曾在多个领域产生深远的影响,其中包括医药、农业、制造业和广告。 在软件开发中, A/B 测试实验提供了一个有价值的方式来评估新特性对客户行为的影响。在这个系列中,我们将描述 Twitter 的 A/B 测试系统的技术和统计现状。

本文是该系列的第三篇,介绍了一种自动检测分桶不平衡问题的实验技术。

注:本文最初发布于 Twitter 博客,InfoQ 中文站获得作者授权,对文章进行了翻译。

正文

在前面的文章中,我们讨论了 Twitter 进行产品 A/B 测试的动机, A/B 测试如何帮助我们创新,和 A/B 测试框架 DDG 是如何实现的。本文我们将介绍一种自动检测潜在“错误(buggy)”实验的简单技术:对用户进入“实验桶(experiment bucket)”的不平衡进入率进行检验。

触发分析

A/B 测试将用户映射到“实验桶(treatment bucket)”。“控制桶(control bucket)”(“A”)是当前产品体验;实验桶(“B”)实现被测试的变更。你可以构建多个实验桶。

选择展示哪个桶似乎很简单:随机和确定地将所有用户 ID 分配到一些整数空间,并指派空间到桶的映射。

但是考虑到许多实验变更只是推送给一部分用户。例如,我们可能想对 iSO 应用程序中的照片编辑体验做些变动——但是不是所有的用户都使用 iOS,同时也不是所有的 Twitter iOS 用户都编辑照片。

包括所有用户,无论他们是否“触发”实验都会造成“稀释(dilution)”。即使你的特性是在被推送用户行为之下变更的,其实大部分都不是,所以影响很难观测。因此,需要通过只观测触发变更的用户来缩小分析。但是,这么做可能有危险,因为有条件地选择进入实验的用户易使我们有偏见。由于实验配置的微妙之处,使得不同实验桶样本总量不具有可比性,进而导致结果无效。

一个简单例子

让我们想象一下,工程师需要实现特定于美国的实验。通过使用以下(伪)代码,她确保实验桶不会推送给美国之外的用户:

由于所有国家的用户会被记录进控制桶或者实验桶,但是事实上只有美国 “实验桶”用户能够看到实验桶,这就产生了稀释实验。

在这一点上不存在偏差——仅仅是稀释。但是当在代码评审中指出时,代码变成如下:

新版本看上去避免了稀释问题,因为即使用户触发了实验桶,只要他们没有看到,他们就不会被记录。但是它有一个 bug——实验存在偏差。实验桶仅仅记录美国用户,而控制桶可以记录所有国家的用户。应用程序没有被破坏,但是两个桶不具有可比性。

无偏差的解决方法如下所示:

并非所有的分桶不平衡都有如此显而易见的原因。最近我们遇到一个实验,从客户端 Javascript 触发用户分桶日志异步调用。该实验中,实验桶需要加载某些额外资源,并进行其它调用,这会导致低速连接用户的分桶日志调用不太可能成功。这就造成偏差:控制桶比实验桶更可能推送给低速连接用户,会轻微扭曲结果。

识别分桶不平衡

有效检测分桶偏差的方法是对不平衡的桶的大小进行检验。我们的实验框架会使用两种方法自动检验桶的大小是否与预期大致一致。首先,我们使用多项拟合优度检验执行整体“健壮性检查(health check)”。健壮性检查会检查观测的桶的分配是否匹配预期流量分配。如果整体健壮性检查不理想,我们还会对每个桶执行二项检验,精确查明哪个桶可能有问题,显示新分桶用户的时间序列,以防实验者希望进一步挖掘。

使用多项检验执行整体健壮性检查

想象一下,某个实验分成 _k_ 个桶,其中向第 _i_ 个桶推送 p% 的流量(不同桶之间的 _p_ 可以不一样)。假定我们知道实验中分桶用户的总量,我们可以将每个桶中的用户数量共同建模为多项分布。此外,我们可以执行拟合优度检验,观察实际观测到的计数是否偏离预期计数。如果发现流量分配显著偏离预期,这就说明流量分割有问题或者分割存在偏差。

在数学上,多项式优度测试相当直观。

这里 ,_O_ 代表第 _i_ 个桶中观测到的分桶用户数量,_E_ 代表每个桶预期的分桶用户数量。统计数据捕获每个桶偏离其预期值的程度,总和数据捕获总体偏差。该检验统计遵循卡方分布(自由度为 k-1),所以我们可以判断,观测的偏差是否与桶分配的当前样本 _p_ 值一致。

需要注意的是,“多项检验(multinomial test)”是泛化的二项检验(通常用于只有一个控制和实验组的测试)。过去,我们使用二项检验将每个桶与其预期的桶计数对比。但是,在实验中有多个桶的情况下,我们很快就会遇到多重假设检验问题。接下来,我们根据 1 - (1 - p)^k,其中 _p_ 是获得假阳性的概率(通常设置成 5%),将假阳性作为独立桶数量的函数,绘制获得至少一个假阳性的概率图。即使是配置合理的试验,当拥有多个桶时,其假阳性率增加得相当快:在零假设、5 个桶的情况下,其中一个桶不平衡的概率超过 20%。

假阳性会浪费很多调查时间,会降低对工具的信任。为了说明这个问题,我们对二项检验切换成多项检验的改进进行了评估。

我们对 179 个实验做了元分析,并对多项和二项检验的桶的健壮性结果进行了对比。在二项检验的情况下,至少有一个桶的 p_<_0.05,实验就被视为不健壮,而在多项检验情况下,只有整体桶的 p<0.05,实验才会标记为不健壮。

我们发现有些实验的二项检验结论是不健壮,而多项检验的结论相反。我们检验了所有此类实验,发现它们有着共同的特征:

  • 所有实验中只有一个实验,并且只有一个桶的二项检验小于 5%。剩下的桶往往拥有一致的、健壮的 _p_ 值。

  • 所有被标记的实验至少有 4 个桶。

这些特征表明,二项结果可能是假阳性。接着,我们手动验证所有实验的配置都是正确的。切换到多项检验,提高了整体实验的健壮性,降低了估计 25% 的假阳性率。

标记独立的桶

多项检验可以让我们免受多重假设检验的困扰,但是它有一个缺点:它不能告诉我们哪个桶有问题。为了提供更多的指导,我们在多项检验的情况下还进行了另外的二项检验运行。

DDG 使用二项式分布的正态逼近来执行双面二项检验。

二项检验可以检查时刻 _t_ 的流量分配是否大致均衡,当实际流量在预期流量 95% 置信区间之外时,它会标记出异常的桶。

桶计数的时间序列

在最低粒度水平下,我们的工具也能够在 8 小时的批处理时间窗下呈现桶计数的时间序列。如下两个例子:健壮的时间序列和不健壮的时间序列

实例 1:健壮的时间序列

(点击放大图像)

在上述例子中,时间序列表明,不同桶之间每个桶的用户数量是均衡的,除了一小批次(颜色标签是基于每个时间段 _t_ 的二项检验)。其中的两个不平衡的批次不会产生影响:当显著性水平设置成5% 时,每20 个测试中会有一个假阳性。多项检验影响不明显。总体而言,我们没有看到有分桶偏差的任何证据。

实例2:不健壮的时间序列

(点击放大图像)

在这个实例中,我们看到实验开始几天后,新用户的数量开始偏离预期流量,表明出现 分桶偏差。看看右上角的整体桶健壮性,多项检验也表明测试不健壮。在这种情况下,工具会提醒用户在分析之前先调查设计。

相比任何一种类型的检验单独检测,批次层级和全局检验的结合,可以让我们检测出更多精细问题。

基于用户检验,而不是印象

由于实验效果或者实现细节,合理的设计和实现的实验可以让不同的桶拥有分桶印象整体数量。与研究总触发或者总访问相比,基于特别分桶用户对比桶不平衡,是更好的检验方法。

需要注意的是,在我们的时间序列检验中,我们基于第一次用户分桶检查桶不平衡,而不是分桶事件。实验本身可以引导用户继续触发实验,或者低于控制桶。这使得后验印象数据对比不恰当。在整个检验中,我们只对整体用户分桶数量进行对比,而不是记录桶之间的整体印象。

结论

分桶不平衡检测非常强大,它是一种判断实验是否正确配置的简单易行的方法。要验证实验结果,这是头等大事,将其内置到工具链中,可帮助我们节省大量的调查和分析时间。通过自动检查带有明显偏差证据的实验,大幅降低了检测问题所需的时间,同时增强了实验者对实验结果的信任度。

编后语

《他山之石》是InfoQ 中文站新推出的一个专栏,精选来自国内外技术社区和个人博客上的技术文章,让更多的读者朋友受益,本栏目转载的内容都经过原作者授权。文章推荐可以发送邮件到editors@cn.infoq.com。

查看英文原文: Detecting and avoiding bucket imbalance in A/B tests


感谢郭蕾对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016-03-27 17:557819
用户头像

发布了 92 篇内容, 共 22.8 次阅读, 收获喜欢 4 次。

关注

评论

发布
暂无评论
发现更多内容

让黑产无处遁形 京东智联云推出风险识别服务

京东科技开发者

人工智能 学习 风险识别

阿里P8大牛呕心沥血总结整理的《Java面经手册》,通过实践的方式向你深度讲解Java核心知识点

Java架构之路

Java 程序员 架构 面试 编程语言

面试大厂被面试官用MyBatis怼到“哑口无言”?这份MyBatis源码笔记助你吊打面试官!

Java架构之路

Java 程序员 架构 面试 编程语言

【架构师训练营 1 期】第四周作业及学习总结

诺乐

第四周作业

熊桂平

极客大学架构师训练营

Go发起HTTP2.0请求流程分析(中篇)——数据帧&流控制

Gopher指北

后端 HTTP2.0 Go 语言

成为一名合格的技术类产品经理

小清新同学

产品经理

出炉!华为18A自爆SpringCloud微服务分布式笔记

996小迁

Java 编程 架构 面试 SpringCloud

日常工作问题集锦

hasWhere

华为云数据安全中心正式公测,8大核心数据安全能力守护你的数据

华为云开发者联盟

华为 安全 数据

java安全编码指南之:Thread API调用规则

程序那些事

Java并发 多线程 java安全编码 java安全编码指南 java编码规范

从理论到工具:带你全面了解自动化测试框架

禅道项目管理

开源 DevOps 工具 自动化测试

mongodb 源码实现、调优、最佳实践系列-百万级高并发mongodb集群性能数十倍提升优化实践(下篇)

杨亚洲(专注MongoDB及高性能中间件)

MySQL nosql mongodb 架构 分布式 分布式数据库mongodb

深入理解 JVM 垃圾回收算法 - 复制算法

Java架构师迁哥

mongodb内核源码实现、性能调优、最佳运维实践系列-百万级高并发mongodb集群性能数十倍提升优化实践(上篇)

杨亚洲(专注MongoDB及高性能中间件)

MySQL 数据库 nosql mongodb 分布式数据库mongodb

Github惊现高星神作,两份算法宝典让你横扫大厂算法面试题

编程 程序员 算法 计算机

台湾地区为什么会丢包高?

德胜网络-阳

华为鲲鹏专家解读:90%代码如何移植到鲲鹏平台

华为云开发者联盟

软件 鲲鹏

第4周

paul

第四周学习心得

熊桂平

极客大学架构师训练营

想要高效搭建企业信息平台?教你轻松选择开发框架!

Marilyn

敏捷开发 快速开发

直播预告 | 云时代的数据库客户端——CloudQuery最佳实践

BinTools图尔兹

数据库 sql 安全 工具软件

SpringBoot有多重要?面试用SpringBoot把面试官唬住了要30k都行!

Java架构之路

Java 程序员 架构 面试 编程语言

算法判断循环链表、数据工程师练级攻略、python从入门到精通、UML精粹读后感、John 易筋 ARTS 打卡 Week 22

John(易筋)

ARTS 打卡计划 UML精粹 数据工程师必备技能 python从入门到精通 循环链表

几行代码轻松实现跨系统传递 traceId,再也不用担心对不上日志了!

程序员小航

Java 日志 链路追踪 工作笔记 traceId

LeetCode题解:98. 验证二叉搜索树,递归,JavaScript,详细注释

Lee Chen

大前端 LeetCode

Week 4 命题作业及总结

阿泰

灯下黑中的自己

非著名程序员

个人成长 管理 管理者

Linux下diff的操作详解

良知犹存

Linux

jvm笔记

pCat

Java JVM

成为 Apache 贡献者,So easy!

代立冬

Apache 贡献

Twitter的A/B测试实践(三):检测和避免 A/B Test中 bucket不平衡问题_语言 & 开发_Robert Chang_InfoQ精选文章