达达快送A/B实验可信度问题治理实践

时间:2023-01-18 17:07:15

  背景

  A/B实验是互联网企业的标配, 众多运营决策和产品设计都依赖于A/B实验, 所以实验结果的可信度至关重要. 如果系统研发过程中忽视这个问题, 往往会造成公司各项运营成本的浪费. 达达在实验系统研发早期就重视这个问题, 并结合统计学理论和机器学习方法在技术落地中不断优化.

  什么是A/B实验

  A/B实验又称随机对照实验, 核心在于随机地将用户拆分为互斥的实验组和对照组, 对实验组施加单一的干预, 以达到控制单一变量的目的, 最终对实验干预和实验效果进行准确的因果推断.  

达达快送A/B实验可信度问题治理实践

  我们从以下三个实验环节治理可信度问题:

  1. 流量分组: 能否均衡地随机分组, 会影响分组之间是否有足够的可比性.

  2. 数据收集: 分组结果和其他业务数据在收集保存的过程中, 有可能出现丢失、不一致等问题.

  3. 数据分析: 不严谨的分析手段会得出不可信的结论, 比如不做显著性检验、不做异质性人群分析等.

  本文将举例5个迭代中遇到的问题加以说明.

  架构设计

  我们有一套较完整的A/B实验系统, 覆盖了上述三个实验环节, 并打通了公司内其他营销系统.  

达达快送A/B实验可信度问题治理实践

  问题与解决方案:

  1. 如何检验分组算法的均衡性

  在流量分组环节, 我们总结了一套通过检验实验间流量正交性, 来评估分组算法均衡性的方法论.

  系统上线初期对分组均衡性的质疑较多, 偶有出现各分组之间在实验开始前, 指标就存在一定的偏差, 这会影响实验结果的归因判断. 排除其他因素前, 我们先检验分组算法本身是否足够均衡.难点在于如何制定一个衡量标准. 很多人会着眼于比较各组样本数是否符合配置比例, 这并不是一个能影响可信度的问题, 用户的分布均衡不等于用户数的均衡. 观察单一实验的用户分布, 因为随机性较强也不可取.

  我们的分组算法是对流量标签(如用户id)加盐, 计算MD5值再取模得到分组. 我们的检验思路为: 假设算法本身没有显著分组偏差, 则同一批用户顺序进行多个实验的分组后, 不同分组路径的用户在最后一个实验的各个分组上,应该是足够分布均匀的, 即所谓的流量"正交". 否则, 说明算法分组不够均衡.  

达达快送A/B实验可信度问题治理实践

  如上图为简单理解, 设一批用户先后进行两个五五分实验的随机分组, 若算法每次都能均匀打散, 则理想情况下有:  

达达快送A/B实验可信度问题治理实践

  若只存在随机误差, 则四个频数与等式右边期望值的偏差, 总体应服从均值为0方差未知的正态分布, 此时变成了一个假设检验问题, 可以借助常用的非参数检验-卡方检验来验证假设. 以下收集100万左右的用户id, 在Python3中分别对MD5, SHA256, CityHash64, SpookyHash四个业界常用的A/B系统哈希算法, 每个运行3个不同实验两两组合, 做三轮卡方检验:  

达达快送A/B实验可信度问题治理实践  

达达快送A/B实验可信度问题治理实践  

达达快送A/B实验可信度问题治理实践  

达达快送A/B实验可信度问题治理实践

  四个算法的p-value都算不上小概率(显著性水平取0.05), 即未能证明各频数与期望值有统计学上的显著差异.从偏差的波动性(变异系数)和运行速度上MD5基本够用, 可以说明当前算法对分组不均没有系统性影响.

  2. 如何解决前后端分组日志不一致问题

  在数据收集环节, 客户端实验对实验配置的拉取, 因公网传输会存在部分失败, 我们优化了前后端交互逻辑来降低这一问题对数据分析的影响.

  A/B实验通常要求可动态改变分组比例, 所以客户端上的实验免不了要通过不可靠的公网与后端实验系统交互. 请求失败时为了用户体验, 需降级成某一固定分组(如对照组). 然而后端无法感知客户端的超时, 落库的分组结果就会与用户看到的实际分组存在偏差. 表现为用户实际看到的某一分组偏多, 且无法从数据库中区分哪些用户出现了不一致. 由于一些考虑, 目前我们分析数据以实验系统的分组结果为主, 所以这个问题对我们影响较大.

  曾经问题最突出的一个实验, 是在客户端查询订单详情超时, 根据A/B分组选择是否重试, 以计算请求成功率的提升幅度.但超时说明用户可能处于弱网环境, 此时再去请求分组接口大概率也会超时, 用户被降级到对照组. 最终因前后端分组不一致的比例高达10%以上, 让实验结果完全不可信.

  最初想到ack机制, 被ack的分组结果才是可信的. 但刻意追求一个状态的多个副本间的一致性, 总是会引入较大的系统复杂性, 也会增加前后端交互成本.

  最简单有效的方案是客户端本地运行分组算法, 放弃修改分组比例, 分组结果在离线重跑算法来重现. 但大部分实验场景仍需方便地进行配置管理, 该方法仅适合对一致性要求极高的实验.

  最后我们考虑尽量缩小不一致的比例, 分析了APP代码不够完善的地方:

  1. 请求分组接口与使用分组的代码之间完全异步, 有可能使用分组时接口请求还未返回.

  2. 请求失败的情况下缺乏接口重试.

  通过将接口调用时机提前至APP冷启动, 以及增加重试和缓存机制, 最终将不一致的比例缩小至千分之2左右, 在大部分客户端实验中不至于掩盖实验效果.  

达达快送A/B实验可信度问题治理实践

  除了前后端不一致, 在内网环境下, 也会存在同一请求链路上, 各服务调用分组接口出现部分失败的问题. 我们约定尽量由链路前置服务或客户端统一请求分组结果, 再将其透传下去. 虽然朴素但可有效避免问题.

  3. 如何自动运行A/B指标的显著性校验

  在数据分析环节, 有别于人工编写A/B指标, 我们采用抽象语法树解析SQL的方式, 动态创建和扩展A/B指标以节省人力.

  A/B实验最终是为数据分析服务的, 以往BI要编写大量重复SQL以计算实验指标, 经常成为资源排期的瓶颈. 我们通过提炼常用核心指标的SQL模板, 解析成抽象语法树后, 自动生成具体需要的指标. 使用的解析器是开源数据库连接池Druid的SQL解析模块, 对比AntlrV4和Apache-Calcite, 它在开发效率和性能上都很优秀.  

达达快送A/B实验可信度问题治理实践

  将SQL字符串转为结构化对象, 可方便和正确地调整逻辑, 以适配不同指标的查询.如上图的分组统计订单价格语句, 加上高单价的限制条件, 就变成了另一个指标 .  

达达快送A/B实验可信度问题治理实践

  我们还可进行SQL翻译, 例如一些近实时统计的需求, 我们会将标准SQL转化为ES的DSL再进行查询. 有时候也需要做一些语法分析阶段的检查和优化. 比如在ES做去重统计时, 为了防止分组太多导致内部布隆过滤器撑爆内存, 根据group by的维度数量和where字句的条件数量去动态减小precisionThreshold. 而像谓词下推, 列裁剪等优化项目, Spark等成熟大数据引擎都可以自行处理.

  如果只停留在对计算出的指标做大小比较, 却不考虑指标的方差波动, 就无法判断差值有多大概率是随机误差造成的. 所以我们还需要实现A/B实验必不可少的显著性检验功能.

  不同类型的指标需要用不同概率分布的显著性检验, 比如Welch's T检验和大样本比例检验, 都可以从它们的公式中将因子(如样本的均值, 方差和size等)转化为一个个指标SQL, 借助自动化的指标生成和计算能力, 很容易实现了显著性检验计算.  

达达快送A/B实验可信度问题治理实践

  4.如何进一步减小分组不均

  很多小样本头部用户的实验, 在流量分组阶段更难做到均衡分组, 我们通过降低A/A实验的成本, 尽量缩小这一问题的影响.

  问题1中, 虽然分组算法对用户id无显著分组偏差, 但id背后的用户属性是另一回事. 例如将福布斯富豪榜前10位做一个五五分随机分组, 两组间的人均资产差值不管怎么分, 都要比第11-20名做相同随机分组对比来得更大. 互联网的很多业务指标也类似, 各自所服从的概率分布, 往往在右侧有较长的尾部. 越接近右侧的用户群体, 指标方差越大, 越难以均衡分组. 这是随机分组算法无法解决的.  

达达快送A/B实验可信度问题治理实践

  通过剔除头部用户, 或改用低方差的衡量指标(如指标取对数或者仅看数量级)等, 都可以快速改善问题. 但并非所有场景都适用, 比如专门针对高频高单价用户的实验.

  运行A/A实验可以在事前计算分组偏差程度, 即先不对实验组施加干预, 与对照组保持一致, 运行一段时间后检验指标是否有无法接受的组间偏差, 有则重新分组或剔除离群用户, 直至偏差较小.

  定向人群实验做A/A实验比较方便, 因为人群固定, 可直接计算各组在实验前一段时间的指标. 但大部分这类实验需要在实验开始前一天才确定人群. 但达达的数仓是T+1抽数, 当天产生的A/B分组记录无法T+0地落到Hive表, 就不能其他离线表join. 而离线集群长期资源紧张, 任务排队延时比较夸张, 即使提高抽数频率, 也很难保障每次都达到业务方分钟级的时效要求.

  所以我们的自动化指标模块在A/A实验中除了替代人力降本增效外, 更要解决这个数据关联问题.我们最后采用的是比较经济可行的Presto联邦查询, 对MySQL的非读写备库和Hive表直接join. 查询任务会在Java服务中提交到延时队列, 延迟几分钟查询避免可能的MySQL主从延迟.

  解决数据时效性问题后, 结合自动化指标功能, 大大降低了A/A实验的在定向人群实验中的人力成本.

  5.如何解释实验结果

  很多实验在获得结果指标后, 往往就结束了, 对实验结果的解释和知识沉淀着力较少, 有部分原因是没有找到对应的工具. 所以我们尝试使用因果树等新技术, 来做一些智能化的实验结果解释.

  实验结果的解释我们认为, 可转化为查看实验干预对不同子人群的影响是否有所差异. 比如某个前端交互的改变没有带来下单转化率的显著提高, 原因可能是打击到了老用户的使用习惯, 虽然在新用户中有正向效果, 但该人群占比不如老用户. 很多A/B实验系统提供了对实验指标的维度下钻, 也是基于这样的思路.

  但该方法有两个问题:

  1. 可选特征非常多, 如何选择特征组合强依赖于人的经验. 比如老用户中, 是否可以用其他特征维度切分出有正向提升的子人群.

  2. 寻找连续型特征的合适分层切分点, 用人工费时费力. 如上述场景中, 以注册多少天来划分新老用户效果好?

  这两个问题很容易联想到决策树中的树生长算法, 即寻找关键特征以及每个特征的最优切分点.因果树也是一种决策树, 特点在于分裂标准的调整, 可以简单理解为, 选择能让左右子树的CATE差值最大的数值点进行分裂. CATE(Conditional Average Treatment Effect) 可以理解为子人群中实验组用户与对照组用户的指标差值, 越大则实验效果对该子人群越正向. 因果树和普通决策树一样有较强的可解释性, 如上图直接输出树规则, 就能解释不同类型的子人群对实验干预的不同反应.  

达达快送A/B实验可信度问题治理实践

  因果树可以用AUUC指标衡量性能, 在保证一定性能的情况下, 我们控制不让树的深度过大, 因为模型的可解释性与性能往往是冲突的.如果树过度生长, 虽然AUUC可能更优, 但并不利于向实验人员解释.

  实践中, 我们发现难点在于是否收集到足够的强相关特征, 以及如何不稀释实验干预的因果效应. 另外如果实验本身就没有效果, 模型如何调参都挖掘不出有价值的结论. 这些与我们在提升优惠券ROI中的Uplift建模, 所获得的经验是类似的. 由于篇幅限制待后续分享.