A/B测试

简介

学习一下A/B测试,综合转载于:

科学精神

小故事

我们先讲两个历史上真实发生过的小故事。

第一个故事是关于黄热病。

黄热病是一种严重的传染病,曾在1793年袭击美国费城。当时,费城有一位著名的人物,他叫本杰明·拉什[1]。拉什曾经签署过《独立宣言》,是美国的开国元勋之一。同时,他还是著名的教育家和出色的外科医生。在费城的黄热病爆发的时候,他坚信放血疗法可以治疗这种疾病。于是,他用手术刀或水蛭吸血的办法给病人放血。当拉什自己染上这种疾病的时候,他也采用同样的方法给自己治疗。

第二个故事是关于坏血病。

坏血病在历史上曾是严重威胁人类健康的一种疾病,尤其在远洋航行的水手中尤为严重。在18世纪,一位英国船长发现,在一些地中海国家的海军舰艇上服役的船员没有坏血病。他注意到,这些船员的食物配给中含有柑橘类(citrus)水果。于是,为了弄清真正的原因,这位船长将他的船员们随机分为两组:其中一组在日常饮食中加入酸橙(limes),而另外一组维持原来的食谱不变。结果通过对比发现,定期食用酸橙的一组船员,真的防止了坏血病的发生。后来,定期食用柑橘类水果就变成了英国水手必须遵循的一种规定。这种做法越来越普遍,以至于在后来的美国英语中产生了一个新的词汇——limeys,用来代指任何一个英国人。这个词翻译成中文称为「英国佬」。

现在让我们来比较一下这两个故事有什么不同。

在第一个故事中,本杰明·拉什坚信他的放血疗法可以治疗黄热病。实际上,他确实「治」好了一些病人。当然,也有一些病人死掉了。事情的解释就演变成这样:如果病人情况好转,那么就被作为放血疗法有效的证据;反之,如果病人死掉了,拉什就解释说是病情太严重,已经无药可救了。后来,有评论家指出,他的疗法甚至比疾病本身更加危险。

而在第二个故事中,船长将船员分成了两组,进行了对照实验 (controlled experiment)。现代医学已经证实,这位船长通过对照实验得到的结论确实是有效的。坏血病的致病原因是由于缺乏维生素C,而柑橘类水果中含有大量的维生素C。

在这两个故事中,当事人都试图在寻找事物之间的一种因果关系 (causal relationship)。本杰明·拉什相信放血疗法和治愈黄热病之间存在因果关系;而第二个故事中的船长则发现了食用酸橙和防止坏血病之间的因果关系。那为什么拉什没有找到真正的因果关系,而那位英国船长找到了?关键就在于对照实验。

进一步追问,为什么使用对照实验就能得到真正的因果关系?这本质上是一个哲学问题,涉及到「科学」之所以成为科学的本质原因。接下来,我们就先展开来聊一聊,什么是真正的因果关系以及这个问题所涉及到的科学本质;然后再回到现实,跟大家讨论一下在互联网的业务中关于如何进行对照实验的各种技术。

因果律的陷阱

每当我们得出一个因果性结论的时候,都应该倍加小心。比如,看到苹果落地,怎么就能得出万有引力是导致苹果落地的原因的?为什么不是其它原因导致的?想一想我们可以考虑的别的因素还有很多啊,比如天气啊,地理位置啊,苹果的品种啊,等等,它们为什么就不能是苹果落地的「原因」呢?为什么我们不能得出结论说:因为今天天气晴朗,所以苹果就落到地上了,而明天可能一下雨,苹果就自己飞到天上去了?为什么我们不能得出结论说:我们看到的这个苹果因为它生长在河北省,所以就落到地上了,而对于生长在湖南省的苹果,也许就未必了?

你肯定会说,这样想问题太荒谬了。没错,是很荒谬。但那是因为现在我们已经很清楚苹果落地的原因了,我们对科学给出的「万有引力」这个解释已经深信不疑了,所以才会认为这些想法很荒谬。试想一下,在人们发现「万有引力」之前,是不是考虑「天气」、「地理位置」这些现有的因素也许是更自然的想法呢?

上面苹果落地的例子表明,找到真正的因果关系,并不容易。在牛顿时代之前,人们对于苹果落地这一司空见惯的现象已经观察了成百上千年,也没能得出现象背后的真正原因到底是什么。而弄错了因果关系,就会得到很荒谬的结论。

实际上,这还不是最糟的。当我们在讨论苹果落地的「原因是什么」的时候,已经首先假定了苹果落地是「存在一个因」的。而在科学和哲学的发展史上,因果关系本身存在的合理性甚至都被怀疑过。苏格兰历史上有一位怀疑一切的哲学家,名叫休谟。他比较极端,认为世界上根本不存在因果律这种东西。还是以苹果落地为例,按照休谟的观点,就算你观察到苹果离开树枝后落到地上的这一事实发生了一万次,也不能说明第一万零一次苹果离开树枝就一定会落到地上。换句话说,他根本不相信苹果落到地上是存在某种「必然」的原因的,而只能看成是偶然事件,哪怕它发生了再多次也是一样。结论听起来仍然很荒谬。但哲学家都是聪明人,绝不是头脑混乱才胡言乱语(实际上休谟是个天才,人家12岁就上大学了)。休谟是沿着怀疑主义前后一贯的逻辑进行推论的。罗素就曾经评论说:“他把洛克和贝克莱的经验主义哲学发展到了它的逻辑终局,由于把这种哲学作得自相一致,使它成了难以相信的东西。”

《三体》小说中提到过一个「农场主假说」,跟休谟的这种观点类似。这个假说是这样描述的:

一个农场里有一群火鸡,农场主每天中午十一点来给它们喂食。火鸡中的一名科学家观察这个现象,一直观察了近一年都没有例外,于是它发现了自己宇宙中的伟大定律:“每天上午十一点,就有食物降临。”它在感恩节早晨向火鸡们公布了这个定律,但这天上午十一点食物没有降临,农场主进来把它们都捉去杀了。

这只可怜的火鸡认为自己发现了一个「因果律」:每天上午十一点(这是因),就有食物降临(这是果)。但最后事实证明,「上午十一点」和「食物降临」这两个事件之间并没有必然的因果关系,它们只是在过去一年内恰好同时发生(最后在感恩节这天发生了变化)。这个故事含有一丝神秘的意味,它暗示了,我们人类发现的一些「因果规律」,也许是某个更高等级的文明或者某个神秘力量随意设置的。

我们很快就发现了,按照这样的方式思考问题,完全不承认这个世界上有因果律存在,那么科学就没法玩了。科学研究要进行下去,就必须承认因果律是普遍存在的,因为科学定律基本都是对因果规律的总结。我们之所以应该承认和尊重科学,是因为科学「有用」,它在过去几个世纪内深入地影响了人类的生活。我们必须转向实用主义的观点,否则就会陷入虚无的诡辩。总而言之,我们应当及时放弃休谟的极端观点,回过头来相信因果规律的存在。

关键的一点是,科学必须有一套行之有效的方法和标准,来验证科学定律的正确性,来检验它是否真正反映了事物的因果关系。实际上,科学定律都不是「空中楼阁」,都是被实践验证过的理论。牛顿的三大定律,被无数的实验和观察验证过;爱因斯坦的相对论,也被很多天文观测所验证。这种「科学的」验证方法就是前面提过的「对照实验」。

什么是对照实验呢?通俗来说,对照实验就是将实验的物体或人群随机分成两个组,一个实验组 (treatment group),一个对照组 (control group)。对实验组人为地修改某个控制变量(也就是实施实验),而对照组只是用来做对比(维持它原来的状态)。以文章开头提到的坏血病故事为例,英国船长就是实施了一组对照实验。他将船员们随机分为两组:在日常饮食中加入酸橙的一组是实验组,食谱维持不变的一组是对照组。最终实验结果是:实验组的船员发生坏血病的比率减少了。这个结果的「因」,也就是在实验组和对照组之间有差异的那个因素——酸橙。至此,船长得到了结论:酸橙可防止坏血病。

我们再来看一个对照实验的例子。

《哲学家们都干了些什么》这本书中描写了一个案例。有人调查了大学生的体重和交友数据之后发现,越是胖的人,身边的朋友就越多。然后就得出了一个结论:身体胖是朋友多的原因,也就是说,身体越胖,越有魅力。这个调查其实也可以设想成一组对照实验:我们找两组大学生,一组体重超标(实验组),一组体重正常(对照组)。然后我们对比两个组的交友数据,发现实验组的学生朋友比较多。于是,我们这样分析,「朋友多」是一个结果,它的「因」就是在实验组和对照组之间有差异的那个因素——身体胖。

仔细的读者一定已经发现了这里的一个陷阱。在做对照实验的时候,一定要保证实验组和对照组除了实施实验所必需的那个控制变量之外,其它所有特征均相同。刚才这个案例的陷阱就在于,实验组和对照组的选择是有问题的,两个组除了体重不同之外,还可能有很多其它不同的特征。这些其它的不同特征,也都有可能是造成两组朋友多寡的原因。这个案例的真正答案可能是这样的,身体胖的人喜欢参加饭局,而喜欢参加饭局是因为他们社交范围比较广,社交范围比较广的表现就是朋友比较多。这是一个典型的「第三变量」问题。「身体胖」和「朋友多」都和第三变量「喜欢参加饭局」有关,所以「身体胖」和「朋友多」之间并不存在直接的因果关系,它们之间只是具有「相关性」。

统计学上有一个经典的结论:相关性不代表因果性。我们在做数据分析时,很容易发现两个变量之间的相关性,比如一个变量随着另一个变量的增大而增大,或者一个变量随着另一个变量的增大而减小,但具有相关性的两个变量之间是否具有因果关系,决不能草率地下结论。

我们再分析一下为什么会陷入把相关性当成因果性的陷阱。刚才提到,在分析大学生体重和交友数据的例子中,实验组和对照组的选择是有问题的,我们把这个问题称为「选择性偏差」。我们对于实验组(也就是胖子组)的选择,掺杂了太多人为的因素,导致实验组和对照组之间差异巨大,根本不存在可比性。也许,胖子组里面有钱的学生更多,或者胖子组里面女生比男生更多(或者反过来),照这样分析,我们也许会认为「有钱金多」或「性别」都是「朋友多」的原因呢。那为什么文章开头坏血病的对照实验是成功的呢?那是因为船长对于船员进行分组时有一个很关键的细节:是「随机」分组的。首先,两个组的船员都来自同一艘船,他们平常的生活条件大体相同。如果不是来自同一艘船,就会引入额外的干扰因素,比如船员的航线不同,出身不同,整体健康状况可能也不同,最终导致实验失败。其次,两个组是随机分派的,这样就避免了「选择性偏差」。

说了这么多,无非就是为了告诫我们自己,当我们设计实验来探索或验证因果律的时候,需要加倍小心。不管是科学实验本身就经常遭遇的相关性陷阱和选择性偏差,还是来自哲学意义上的对于因果律根本性的挑战,都说明对于事物之间因果关系的分析是一个非常棘手的课题。而科学正是由于它的这种「谨慎」,才让它在近现代历史的发展中大放异彩。

A/B测试

在程序开发的领域,严格的对照实验,我们一般称之为A/B实验。实际上,在不同的场合,它至少有半打以上不同的名字。下面的列表列出了这些名字:

  • Controlled experiments(对照实验)
  • Randomized experiments(随机实验)
  • A/B tests(A/B测试,A/B实验)
  • Split tests
  • Control/Treatment tests
  • MultiVariable Tests(MVT)
  • Parallel flights

假设我们做了一个 A/B 实验,然后我们会想知道自己的实验是否能得到显著的效果。你可以通过经验判断,但更严谨的方法是通过统计学公式计算 p 值是否小于设定的显著性水平 ,从而判断实验结果是否显著。

举个例子,我们有以下四格表:

对照组 实验组 合计
转换 15 8 23
不转换 35 42 77
合计 50 50 100

通过以上统计结果我们来尝试计算一下 p 值。

z 值的计算公式:

其中,$x_1, x_2, n_1,n _2$ 分别为转换 A/B 测试的转换数和样本总数。

然后将统计值($x_1=15,x_2=8,n_1=n_2=50$)带入到公式中并计算出 z 值为 1.66,

然后用excel来计算:$p=2\times (1-\text{NORMSDIST(abs(}z\text{))})=0.096$,大于0.05不显著。

NORMSDIST 可以计算标准正态分布的累积概率。

以上便完成了 A/B 测试的计算。

如果事情到此就结束,那岂不是太浅薄了。

这边问几个简单的问题:

  • A/B 测试不是基于假设检验的吗?假设在哪儿呢?检验在哪儿?
  • 这个 z 值公式哪来的?

为了搞清楚这些问题,我们继续探究。

A/B测试原理

假设检验

假设检验是研究如何根据抽样后获得的样本来检查抽样前所作假设是否合理,A/B测试从本质上来说是一个基于统计的假设检验过程,它首先对实验组和对照组的关系提出了某种假设,然后计算这两组数据的差异和确定该差异是否存在统计上的显著性,最后根据上述结果对假设做出判断。

我们再用上面的数据走下流程。

首先,我们做一个假设

  • 原假设$H_0$:实验组和对照组无显著差异;
  • 备择假设$H_1$:实验组和对照组存在显著差异。

假设检验的核心是证伪,所以原假设是统计者想要拒绝的假设,无显著差异我们也可以理解为:实验组和对照组的统计差异是由抽样误差引起的(误差服从正态分布)。

然后,我们需要选一个检验方法来对我们做的假设进行验证。

常用的假设检验方法有 z 检验、t 检验和卡方检验等,不同的方法有不同的适用条件和检验目标。比如我们常说 z 检验适用大样本而 t 检验适用于小样本。实验组的样本容量 > 30,所以我们这里采用 z 检验。又由于我们检验的目标是两组样本(区别于单组样本),所以其 z 值的计算公式为:

由于我们的数据是转换/不转换,所以我们可以令转换的样本为 1,不转换的样本为 0,从而算出$z=1.67$,(通过正态分布的累积概率分布)得到单边概率 $P(0<x<1.67)=0.4525$。我们的是双侧检验,所以 $p=1-2\times 0.4525=0.095$。

1
2
3
4
5
6
7
8
9
x1 = np.array([1]*15 + [0]*35)
x2 = np.array([1]*8 + [0]*42)

# ddof = 1 用于计算样本标准差,区别于总体标准差
z = (x1.mean() - x2.mean()) / np.sqrt(x1.var(ddof = 1)/50 + x2.var(ddof = 1)/50)
z
"""
1.6699
"""

以上便是假设检验的基本步骤。我可能还会有一些疑问,比如说:

  • 什么是双侧检验?
  • 为什么 z 检验适用大样本,而 t 检验适用小样本?
  • 四格表不是应该用卡方检验的吗?

检验方式

检验方式分为两种:双侧检验和单侧检验。单侧检验又分为两种:左侧检验和右侧检验。

这几种检验方式的划分很简单:

  • 双侧检验:备择假设没有特定的方向性,形式为“≠”这种检验假设称为双侧检验;
  • 单侧检验:备择假设带有特定的方向性 形式为”>””<”的假设检验,称为单侧检验 “<”称为左侧检验 “>”称为右侧检验。

我们的备择假设:实验组和对照组存在显著差异。其实就是在说是实验组 ≠ 对照组。所以是双侧检验(此时原假设也称为零假设)。

然后我们根据检验方式不同,得到不同的 p 值计算方式。(p 值其实就是计算拒绝域的概率值,放一张图,方便大家更好的理解)

检验统计量

我们来看下假设检验的原假设:实验组和对照组无显著差异。那么该怎么去描述两组实验有差异呢?我们需要一个统计量来描述两组实验。

在 A/B 测试中我们关注的两组实验转换率的差异,所以我们可以通过样本的转换率 $\rho$ 来衡量两组实验的差异。所以原假设可以翻译为: $\rho_1-\rho_2=0$(两者转换率没差别)。

转换率是样本统计量的一种,此外还可以用均值、方差等作为样本统计量。

我们引入检验统计量的概念:根据样本观测结果计算得到的,并据以对原假设和备择假设做出决策的某个样本统计量,称为检验统计量。所以本次 A/B 测试中检验统计量为 $\rho_1-\rho_2$。

检验统计量是用于假设检验计算的统计量,实际上是对总体参数的点估计量,但点估计量不能直接作为检验统计量,只有将其标准化后,才能用于度量它与原假设的参数值之间的差异程度。所以我们将检验统计量标准化:

$\bar X_i$ 为独自地抽自总体 $X_i\sim N(\mu_i, \sigma_i)$ 的一个容量为 $n_i$ 的样本的均值。则变量 $\bar X_1-\bar X_2$的均值和方差为:

样本均值的方差:

总体的均值为我们的原假设,即为 0($\rho_{1}-\rho_{2}=0$),当样本容量较大(n>30)时,我们可以用样本标准差 $s$ 来表示总体标准差 $\sigma$,此时我们便得到了 z 检验统计量:

z 检验虽然能够进行样本统计量的差异性检验,但是它要求样本容量足够大,这是不一定能够做到。

这时候 t 检验就粉墨登场了,在两独立样本均值检验中,t 值计算公式如下(推导略):

根据自由度 $\text{df} = n-1$,查 t 值表,找出规定的 t 理论值并进行比较,然后确定显著性水平。

t 检验在使用前需要注意三点:

  1. 分析的数据对象需要满足正态分布或近似正态分布;
  2. 得知样本均值和样本标准差;
  3. 已知总体均值(由原假设可知总体均值为 0);

t 检验统计量较 z 检验统计量多了一个自由度的变量,用来惩罚小样本,增加其拒绝 $H_0$ 的难度,因而小样本下采用 t 检验,优于 z 检验。

当样本数量增加时,t 检验会快速收敛于 z 检验。(所以这里用 t 检验算出来也是 1.67)

置信区间与统计误差

我们再看下假设检验的原假设:实验组和对照组无显著差异。这一小节我们来讲下显著的概念。

我们统计出的实验组的转化率为 0.3,而对照组的转换率为 0.16。我们是否可以说明实验组比对照组的转换效果要好 0.14?其实更准确的说法是:实验组比对照组的转换效果要好 0~0.28 ($0.14 \pm 0.14$),置信度为 90%

[0, 0.28] 是我们所说的置信区间 $\left[\left(\bar{X}_{1}-\bar{X}_{2}\right) \pm Z_{\frac{\alpha}{2}} \times \sqrt{\frac{S_{1}^{2}}{n_{1}}+\frac{S_{2}^{2}}{n_{2}}}\right]$,这里的第一个 0.14 是两组样本的均值 $\left(\bar{X}_{1}-\bar{X}_{2}\right)$,第二个 0.14 是统计误差,计算方式为 $Z_{\frac{\alpha}{2}} \times \sqrt{\frac{S_{1}^{2}}{n_{1}}+\frac{S_{2}^{2}}{n_{2}}}$,置信度是我们的算出接受域的概率值 90%。我们可以说:我们有 90% 的把握实验组比对照组的转换效果要好 0~0.28。从概率的角度来解释:我们做 100 次实验,有 90 次的转换率差异是在 0~0.28 之间。

置信区间是一个区间,使得重复实验 n 次具有一定概率(这个概率就是置信度)的结果都落在此区间内。根据置信区间的不同表现,我们可以来判断试验结果显著与否:如果置信区间的上下限同为正/负,则说明试验结果是统计显著的;如果置信区间为一正一负,则说明版本间差异不大。

值得注意的是,置信区间同为正或负时,只能说明试验是统计显著的(也就是试验组和对照组有差异),但是这个差异有可能是非常小,在实际应用中微不足道的。因此,只有兼备统计显著和效果显著两个特征的结果,才能说明该实验是可用,并且值得发布的。

中心极限定律

中心极限定理是概率论的重要定理,我们来复习下:

中心极限定理指的是:给定一个任意分布的总体。每次从这些总体中随机抽取 n 个抽样,一共抽 m 次。然后把这 m 组抽样分别求出平均值,这些平均值的分布接近正态分布。

我们注意总体本身的分布不要求正态分布,下图很形象的表达这个点:

从上图我们看到:随着抽样次数增多,样本均值的抽样分布趋向于服从正态分布,且其均值越接近于总体的平均值。

所以正是因为有了中心极限定律,我们才能使用 A/B 测试:通过样本均值来估计总体均值。

而检验统计量这块,我们可以看到当抽样次数达到 30 时,样本均值可以视为总体均值。这也是为什么 z 检验和 t 检验以样本量为 30 做个分界。(当然划分界线没那么严格,29 次也可以用 z 检验)

补充学习大数定律:在无数次独立同分布的随机事件中,事件的频率趋于一个稳定的概率值,这是大数定律。两者在不同的维度上。

z 检验的计算方式

上面是通过手算检验统计量,更简单的方法是写个 python 脚本:

1
2
3
4
5
6
7
8
9
10
11
12
x1 = 15
x2 = 8
n1 = n2 = 50

import numpy as np
from statsmodels.stats.proportion import proportions_ztest as ztest
ztest(np.array([x1, x2]), np.array([n1, n2]))

"""
# 源代码计算方式,和评估组同学给出的计算方式一致
输出:(1.663369597, 0.09623847)
"""

x1 x2 顺序倒过来只影响 z 值(相反数),不影响 p 值。

互联网中的A/B测试

互联网产品通常是逐渐迭代、慢慢进化的,而通常情况下选择都是比较多的。那么自然就产生了一个问题,产品在每一步到底应该选择向着什么方向迭代,才是最有利于业务发展的?在互联网业务的开发和运营当中,我们经常需要面对这样的问题进行决策。比如,对产品首页进行改版,可能有不同的方案,那么到底应该选择哪个方案?再比如,对推荐算法进行新版本升级,能不能保证提高数据指标?又比如,在商业化的过程中,想在产品中投放广告,怎样做能够收益最大而对用户体验的影响最小?

这些事情本质上也是在寻找事物之间的因果关系。我们发布了某个新的产品feature,或者对产品进行了优化,这是「因」;然后我们观察到了数据指标的变化,比如用户留存率或活跃度变高了,这是「果」。根据上一节的讨论,我们已经知道了,分析因果关系需要加倍小心,否则很容易陷入思维的陷阱。通常产品持续在迭代,当我们对产品进行某个更改之后,其它的变化也在发生(比如另一个团队也对产品进行了修改),周围的环境也在发生变化(比如节假日和社会热点事件的影响),我们就很难分析出最后数据指标变化的原因到底是来源于哪些变化因素。

在一个缺乏完善的对照实验机制的环境中,人们通常会对照不同的时间段进行数据分析。人们经常会这样宣称,你看,之前的某段时间内,用户留存率是这样的,但从某天开始,我们做了一个实验(发布了一个产品改动,或者改变了某个运营策略),结果留存率提升了1个百分点。这样的分析在很多情况下都是比较危险的。这相当于在时间维度上先后选取了「对照组」和「实验组」,它们未必就有可比性。首先,当产品每天都在迭代更新,甚至多个改动同时并存的时候,你很难将留存率的提升归因于某个具体的产品改变。其次,就算你在做实验的时候确定没有别的干扰因素了,但某些隐藏的因素的变化,你根本没办法识别出来,比如群体的统计特征也很可能在随着时间变化。这样得到的结论,未必反映了真实的因果关系,也许换一个时间、换一个环境就不适用了。这时候我们能做的,也只有对数据进行各种各样的解释,如果解释不通,就叠加更多的补充因素和假定条件来解释。

A/B测试实验一般有 2 个目的:

  1. 判断哪个更好:例如,有 2 个 UI 设计,究竟是 A 更好一些,还是 B 更好一些,我们需要实验判定;
  2. 计算收益:例如,最近新上线了一个直播功能,那么直播功能究竟给平台带了来多少额外的 DAU,多少额外的使用时长,多少直播以外的视频观看时长等。

我们一般比较熟知的是上述第 1 个目的,对于第 2 个目的,对于收益的量化,计算 ROI,往往对数据分析师和管理者非常重要

ROI(Return On Investment):投资回报率

对于一般的 A/B测试实验,其实本质上就是把平台的流量均匀分为几个组,每个组添加不同的策略,然后根据这几个组的用户数据指标,例如:留存、人均观看时长和基础互动率等等核心指标,最终选择一个最好的组上线。

实验的几个基本步骤一般如下:

流量分配

实验设计时有两个目标:

  • 希望尽快得到实验结论,尽快决策
  • 希望收益最大化,用户体验影响最小

因此经常需要在流量分配时有所权衡,一般有以下几个情况:

  • 不影响用户体验:如 UI 实验、文案类实验等,一般可以均匀分配流量实验,可以快速得到实验结论
  • 不确定性较强的实验:如产品新功能上线,一般需小流量实验,尽量减小用户体验影响,在允许的时间内得到结论
  • 希望收益最大化的实验:如运营活动等,尽可能将效果最大化,一般需要大流量实验,留出小部分对照组用于评估 ROI

根据实验的预期结果,大盘用户量,确定实验所需最小流量,可以通过一个网站专门计算所需样本量:

  • 以次日留存率为例,目前大盘次日留存率 80%,预期实验能够提升 0.2pp(这里的留存率可以转换为点击率、渗透率等等,只要是比例值就可以,如果估不准,为了保证实验能够得到结果,此处可低估,不可高估,也就是 0.2pp 是预期能够提升地最小值
  • 网站计算,最少样本量就是 63W(这里的最少样本量,指的是最少流量实验组的样本量
  • 如果我们每天只有 5W 的用户可用于实验(5W 的用户,指最少流量实验组是 5W 用户),63/ 5 = 13 天,我们需要至少 13 天才能够得到实验结论

如果我们预期提升的指标是人均时长、人均 VV 等,可能就比较复杂了,我们需要运用 t 检验反算,需要的样本量

img

实验效果

我们以一个稍复杂点的运营活动实验为例,活动有方案 1、方案 2,同时为了量化 ROI,对照组没有运营活动。

img

需要回答几个问题:

  1. 方案 1 和方案 2,哪个效果更好?
  2. 哪个 ROI 更高?
  3. 长期来看哪个更好?
  4. 不同群体有差异吗?

1. 方案 1 和方案 2,哪个效果更好?

还是要运用假设检验,对于留存率、渗透率等漏斗类指标,采用卡方检验

对于人均时长类等均值类指标,采用t 检验

通过上假设检验,如果结论置信,我们就能够得到方案 1 和方案 2 在哪像指标更好(有显著性差异), 对于不置信的结论,尽管方案 1 和方案 2 的指标可能略有差异,但可能是数据正常波动产生。

2. 哪个 ROI 更高?

一般有活动相比无活动,留存、人均时长等各项指标均会显著,我们不再重复上述的假设检验过程。

对于 ROI 的计算,成本方面,每个实验组成本可以直接计算,对于收益方面,就要和对照组相比较,假定以总日活跃天(即 DAU 按日累计求和)作为收益指标,需要假设不做运营活动,DAU 会是多少,可以通过对照组计算,即:

  • 实验组假设不做活动日活跃天 = 对照组日活跃天 * (实验组流量 / 对照组流量)
  • 实验组收益 = 实验组日活跃天 - 实验组假设不做活动日活跃天

这样就可以量化出每个方案的 ROI。

3. 长期来看哪个更好?

这里就要考虑新奇效应的问题了,一般在实验上线前期,用户因为新鲜感,效果可能都不错,因此在做评估的时候,需要观测指标到稳定态后,再做评估。

例如有的时候出现,刚刚上线前期,实验组效果更好,但是经过一段时间,用户的新鲜感过去了,实验组的效果可能更差,因此,从长远收益来看,我们应该选择对照组,是实验组的新奇效应欺骗了我们,在做实验分析时,应剔除新奇效应的部分,待平稳后,再做评估。

4. 不同用户群体有差异吗?

很多情况下,对新用户可能实验组更好,老用户对照组更好;对年轻人实验组更好,中年人对照组更好,

作为数据分析师,分析实验结论时,还要关注用户群体的差异。

实验结束

实验结束后需要:

  • 反馈实验结论,包括直接效果(渗透、留存、人均时长等)、ROI
  • 充分利用实验数据,进一步探索分析不同用户群体,不同场景下的差异,提出探索性分析
  • 对于发现的现象,进一步提出假设,进一步实验论证

高级实验

关于互联网业务中的A/B实验,有两篇论文做过详尽的论述:

  • 《Overlapping Experiment Infrastructure: More, Better, Faster Experimentation》,作者是谷歌的Diane Tang, Ashish Agarwal, Deirdre O’Brien, Mike Meyer。
  • 《Controlled experiments on the web: survey and practical guide》,作者是微软的Ron Kohavi, Roger Longbotham, Dan Sommerfield, Randal M. Henne。

现在我们就逐步展开来看看这两篇论文给出的A/B实验方案是怎样的。

假设,最开始整个系统里就只有两个实验(1个实验组和1个对照组)。注意,我们统一下词汇,不管是实验组还是对照组,我们都称为1个实验。虽然对照组只是用来做对比,分配到对照组的用户看不到任何产品或运营策略改动,但我们也把它称为1个「实验」。

我们随机分出了10%的流量作为对照组,又随机分出了10%的流量作为实验组。这时剩下了80%的流量是空闲的,没有进行任何实验。从用户的角度来看,在实验组内的这10%的用户,能够看到正在进行的实验(产品或运营策略改动),而在对照组和剩下的空闲流量中的用户(总共90%),是看不到产品或运营策略改动的。如下图:

一般情况下,我们需要保持用户体验的前后一致。也就是说,一旦一个用户被分到了实验组,那么他发起的后续请求应该都落到实验组的流量里面。我们需要一种会话保持(session sticky)的机制,而这有很多种可能不同的方案。如果用户是有登录状态的,我们可以拿用户ID来取模,如下:

1
mod = 用户ID % 1000

而如果用户是没有登录态的,就像谷歌的搜索服务一样,那么可以拿cookie变换成一个数值再取模,如下:

1
mod = f(cookie) % 1000

当然还有很多计算mod的方案(关键是达到随机的效果)。但不管是上面哪一种取模的方式,我们只需要将mod[0, 99]之间的请求分配到实验组,将mod[100, 199]之间的请求分配到对照组,就分别获得了10%的随机流量。

假设现在人们尝到了A/B实验的甜头,这种机制在公司内推广开来了,越来越多的项目开始进行A/B实验。于是我们将空闲的那80%流量也安排了实验。下图展示了10个实验(9个实验组和1个对照组)同时进行的情形:

这时,我们看到,所有的流量都被用光了。任何一个请求,都被分配到了某一个实验当中。假设我们现在要加入第11个实验,发现没有流量可用了。

当我们建立起这样一套A/B实验系统之后,当然希望尽可能多的产品迭代都先经过A/B实验验证过数据效果之后,再推广给全量用户使用。于是,这个系统能同时容纳的实验个数,就决定了产品迭代和创新的速度。而流量是宝贵的,一部分实验把流量全部占用之后,其它实验就没法同时进行了(这个现象叫starvation)。容易想到的一个方法是,我们减少每个实验的流量占比是不是就行了。比如前面10个实验,每个组改为占用5%的流量,就能够总共容纳20个实验同时进行了。但是,每组的流量不能太少,否则就可能失去统计特性,得不到有效的数据结果了。每家公司的产品,它们各自的用户数也不尽相同,用户多的产品也许可以分的组多一点,用户少的产品,能够分的组就少一点。这显然不是我们想要的结果。

仔细分析上面的情况,造成「流量不够用」这个问题的核心原因在于,每一个请求都最多只能被分配到一个实验当中。如果我们允许一个请求被同时分配到多个实验当中,也就能复用流量了,于是我们的系统能够同时容纳的实验数量也就没有上限了。

为了做到流量复用,我们将实验进行分层(layer),如下图所示,展示了14个实验(12个实验组和2个对照组)同时进行的情况(分布在两层):

在上图的实验配置中,我们将14个实验分布在了两层之中(Layer A和Layer B)。这意味着,同一个请求,可能同时被分配到Layer A中的某个实验,同时又被分配到Layer B中的某个实验。显然,实验在两个层之间的分布是不能随意放置的,有依赖关系的两个实验就不能分布到两个不同的层中。

对于完全独立的两个实验,比如对两个完全无关联的产品模块的改动,我们就可以把它们放到不同的层。这时候,来自不同层的两个实验是完全正交的。「正交」是什么意思呢?这个词本身来源于数学,表示两个向量互相垂直,或者说它们各自向对方的投影为零,这里引申过来可以理解为,两个实验互相没有影响。到底是怎么做到没有影响的呢?实际是从宏观统计上来看的。还是以上图为例,通过Layer A中某个实验的流量,会重新打散,随机地分配到Layer B的每个实验中。比如对于Layer B中的实验组10中的某个用户来说,他看到Layer A中每个实验的概率都是10%。换句话说,Layer A中的实验对于Layer B中的实验,不是说真的没有影响,而是在统计上来说,Layer A中的某个实验对于Layer B中的每个实验的影响都是均等的,所以相当于没有影响。结果就是,Layer B中的每个实验在统计特征上仍然是具有可比性的。

那加入层的概念之后应该怎样分配流量,既能做到会话保持,又能做到层与层之间的影响正交呢?方法仍然不是唯一的,谷歌的论文中是这样计算mod的:

1
mod = f(cookie, layer) % 1000

将层的编号放入计算当中,容易看出,这样可以保证在同一层内,会话是可以保持的,也就是说cookie值相同的不同请求会落到同一个实验组或对照组中;而不同层之间的流量又是随机打散的。

我们前面说,只有相互没有依赖关系的两个实验,才能放到不同的层中。但在实际中,有时候实验之间是有依赖关系的。比如一个实验修改了页面背景颜色,而另一个实验修改了字体颜色,至少它们各自的颜色设置不能太接近,否则整个页面显示上就有问题了。再比如,一个实验给App的第一级页面增加了一个Tab,而第二个实验修改了某个二级页面,并且这个二级页面的入口是在第一个实验增加的那个Tab里面。这样第二个实验就依赖第一个实验。

这时候我们应该怎么做呢?应该把有依赖关系的实验放到同一层当中,这样它们会被分配不同的流量(即流量没有交集),从而消除互相的影响。如果有必要的话,也可能需要对实验重新进行设计。

在谷歌的论文中,一个实验被看成是对于系统参数(parameter)的修改,比如字体颜色是一个参数,再比如某个新feature的开关也是一个参数。按照这样来抽象概念的话,整个系统相当于有一个可修改(或者说可配置)的参数集合,而一个实验相当于在一部分流量上对于一个参数子集进行修改。每个层则相当于把整个的参数集合分成了若干个互不相交的子集,每个子集与一个层对应,而处于这个层中的所有实验都只能修改跟这个层对应的那个参数子集。

有些读者应该已经在上面的图中注意到一个细节了:对照组和对应的实验组都在同一层。实际上这个要求是必须的,「对照组A」只能和Layer A中的各个实验组进行对比,「对照组B」只能和Layer B中的各个实验组进行对比。而且,实验组和相应的对照组的流量占比也应该相同。

分层嵌套

这个分层实验模型还可以更加灵活,支持互相嵌套。比如系统中处于上下游的不同子系统,它们平常都是在不同层中进行各自的实验的(互相没有依赖)。但有一天,我们想做一个比较大的实验,需要同时修改来自多个子系统的参数。这就产生矛盾了,因为各个子系统的可修改参数集合在不同的层中,我们这个实验放在哪个层都不合适。

这时候怎么办呢?谷歌的论文中提出了Domain的概念。下图是一个例子:

Domain代表了一部分切分好的流量,里面可以针对这部分流量再进行分层Domain(域)、Layer(层)和实验(experiment)这三个概念的关系就演变成:Domain可以包含若干Layer,一个Layer可以包含若干个实验,一个Layer也可以包含Domain。于是,Domain和Layer就可以层层嵌套下去。

总之,Domain和实验,与流量分配有关;Layer,与系统参数划分子集有关。

以上图为例,整个100%的流量先分成了Domain 1和Domain 2,它们各占80%和20%的流量。Domain 1内部包含了两个层:Layer A和Layer B;Domain 2只包含了一个Layer C。我们前面提到的「比较大的实验」,就可以在Layer C中进行,它可以修改所有的系统参数。

上图还有一个值得注意的点:Layer B中又嵌套了一个流量占比40%的Domain 3,Domain 3中可以继续分层,相当于把Layer B对应的参数子集再进一步划分子集。

condition和trigger

有些特殊的实验,根据业务本身的要求,是不能曝光给所有用户的。比如,一个多语种的国际化互联网服务,根据用户语言设置的不同,给用户展示的页面内容也会不同。如果一个实验是只针对日语用户的,那么随机切分的一份流量就不能全部用于这个实验,因为随机切分的流量里面也包含了非日语用户的请求。这时候就需要一个条件,称为condition,用来在随机切分的这部分流量中把日语用户的流量过滤出来。下图展示了一个例子:

在上图的实验配置中,注意一下Layer B中随机切分了两部分流量,各有20%,分别用于对照组和实验组。但这各自20%的随机流量里面,使用两个不同的condition分别过滤出来了英语流量和日语流量,并分别运行了针对英语用户和日语用户的两个实验(还有两个对应的对照组实验)。

由于这一类实验对流量有特殊的要求,所以使用了condition在随机切分的流量中进一步切分流量。这种方式带来了一个好处,不同的若干个condition,只要它们的过滤条件互不相交,就可以共用同一份随机流量,从而提高了流量利用率。就像上图中的「英语流量实验组」和「日语流量实验组」一样,是分别使用「英语」和「日语」两个condition,在同一份20%的随机流量中进一步切分出了各自需要的流量。

一般来说,针对部分用户进行的定向实验,都可以认为是根据一个condition来切分流量。常见的condition包括:地域、人群、浏览器类型、客户端版本等等,只要经过合理的抽象,它们都可以在一个A/B实验系统中统一支持。

使用condition会对流量分配带来一个重要的影响:一份随机流量被condition切分出去一部分流量后剩余的流量,不能再分配给别的实验了。以上图为例,起初切分出来的20%的随机流量,除去「英语流量实验组」和「日语流量实验组」用掉的流量之外,剩余的空闲流量(即上图中浅粉色的部分)不能再分配给别的实验组了,因为这部分流量已经失去了随机特性了(缺少了英语和日语用户的请求)。否则就会产生前面提到过的选择性偏差。作为对比,我们注意一下上图中Layer B中那40%的空闲流量,它里面没有出现condition,所以是可以继续分配给别的实验(或Domain)的。

除了condition这种方式之外,还有一种情况,也需要在一份随机流量之中再过滤出一部分流量。谷歌的论文中提到了一个例子,假设有一个实验是为了测试一个搜索请求何时应该返回天气信息。这种情况下,A/B实验系统并不知道如何把需要返回天气信息的请求过滤出来,分配给这个实验,因为是否返回天气信息是由这个实验本身的逻辑来决定的。这是一个非常动态的过滤条件,A/B实验系统只能把切分出来的一份完整的随机流量都分配给这个实验,然后由这个实验本身的逻辑再来决定要不要「触发」天气信息的返回。这里的触发过程,称为trigger。trigger是需要实验本身的逻辑来动态决策的,因此无法用condition的方式由A/B实验系统统一支持。

trigger会引发一个非常微妙的问题:为了进行实验分析,我们必须把「触发」的请求记录(logging)下来。由于分配到实验组的所有请求,除了「触发」的请求,剩下的「未触发」的请求都是没有产品改动的(即跟对照组一样,用户感受不到实验),所以如果基于整个实验组和对照组的流量进行实验对比分析,会对实验结果的度量产生稀释。正确的做法肯定是对比实验组中真正「触发」的流量和对照组中「本应触发」的流量。什么叫「本应触发」呢?就是说按照实验本身的逻辑,本来一个请求应该触发,但由于当前是对照组,所以才没有触发。

最后需要说明的一点是,上面的例子中,我们的实验都是10%或20%的流量,这只是为了方便举例,实际中的一个实验可能用不了这么大的流量比例,可能1%就够了(针对具体情况而定)。

几个例子

类似与上面这种层次设计,在推荐系统中较为常见,在某一些产品或系统中,贯穿层不能够完全没有策略,那么采用去年或上个季度的策略,代表着基准值,从而量化新一个周期的增量贡献

我们可以量化:

  • 每个小迭代对整个系统的贡献:实验层中的实验组 VS 对照组
  • 周期内,系统全部迭代与上个周期的比较:实验填充层 VS 贯穿层 1(或贯穿层 2)
  • 同时,可以量化去年策略的自然增长或下降,以衡量旧有系统是否具有长期的适用性(作为系统设计者,更应鼓励设计具有长期适应性的系统):贯穿层 1(上个季度的策略)VS 贯穿层 2(去年的策略)

我们可以:

  • 量化每一个实验迭代为系统带来的增量贡献
  • 量化每一类迭代(如 UI 迭代、策略迭代),在一个阶段的增量贡献
  • 量化系统整体在上一个周期(季度、年)的增量贡献
  • 量化任务福利中心的整体 ROI(本质上,是给用户一些激励,促进用户活跃,获得更多商业化收益,所以和推荐系统不同的是,需要有完全没有任务福利中心的对照组,用户量化 ROI)

A/B测试的两类错误

我们知道假设检验的有两类错误:

H0 正确(真实情况) H0 错误(真实情况)
拒绝 H0(研究结论) I 类错误 正确
接受 H0(研究结论) 正确 II 类错误

I 类错误称之为弃真:实验组和对照组没有显著差异,但我们接受了方案推了全量。减少这种错误的方法就是提高显著性水平,比如 p 值小于 0.05 才算显著,而不是小于 0.1,显著性水平是人为给定的犯一类错误的可以接受的上限(p 值为犯 I 类错误的概率$\alpha$ )。

如果在实验刚开始时,统计显著性的波动非常明显,这可能受到新奇效应(Novelty Effect)的影响。对于用户有感知的 A/B 测试,如 UI 改版、新的运营方案、新功能上线等,实验组做的任何改变都可能引起用户的注意,好奇心驱使他们先体验一番,从而导致 A/B 测试中实验组效果一开始优于对照组,p 值极小,实验效果非常显著。但是一段时间过去后,用户对于新的改版不再敏感,实验组效果回落,显著性可能会下降,最后趋于稳定。所以我们可以通过增加实验周期从而避免这种新奇效应的影响。

II 类错误称之为存伪:实验组和对照组有显著差异,但我们没有接受方案。

II 类错误和统计功效(power) 有关,统计功效可以简单理解为真理能被发现的可能性。统计功效 = $1-\beta$,而 $\beta$ 为犯第二类错误的概率。影响统计功效的因素有很多,主要的有三个:统计量、样本量和 I 类错误的概率 $\alpha$。

  • 统计量好理解,比如上面的实验统计量为:[0, 0.28],如果你的实验的统计量为 [0.2, 0.48],自然犯 II 类错误的概率会小很多;
  • 增加样本容量可以同时减少两类错误,这个也很好理解;
  • 与 I 类错误有关是因为,显著性水平越高,对统计量的要求就越高。

下图展示了两类错误的联系(这里的 $\alpha$ 为单边假设,区别于 AB 测试中的双边假设):

一般来说,I 类错误的危害要比 II 类错误的危害要大。

增加样本容量可以同时减少两类错误,那么我们应该需要多少样本容量呢?

统计学里给出了最小样本量计算的公式:

其中,$\beta$ 为 II 类错误的概率,$\alpha$ 为 I 类错误的概率,$Z$ 为正态分布的分位数函数,$\sigma$ 为标准差,$\Delta$ 为两组数值的差异 $(\rho_1-\rho_2)$。从这个公式可以知道,在其他条件不变的情况下,如果实验两组数值差异越大或者数值的波动性越小,所需要的样本量就越小。

实际 A/B 测试中,我们关注的较多的一类是比例类的数值,如点击率、转化率、留存率等,也就是说结果非 A 即 B。比例类数值的假设检验在统计学中叫做两样本比例假设检验。其最小样本量计算的公式为:

通常设定 $\alpha$ 为 0.05,$\beta$ 为 0.2。

用 python 算一下:

1
2
3
4
5
6
7
8
from statsmodels.stats.power import zt_ind_solve_power
from statsmodels.stats.proportion import proportion_effectsize as es

zt_ind_solve_power(effect_size=es(prop1=0.3, prop2=0.16), alpha=0.05, power=0.8, alternative="two-sided")

"""
138.8426
"""

可以看到,我们应该需要准备 139 个样本。

A/B测试的一些误区

最后,讲一下关于AB测试的一些误区,通过对这些误区的理解希望能够恰如其分的应用AB测试提升网站和产品的转化等指标。

  1. AB测试运用成本过高,可以通过灰度发布的方式来进行AB测试,进而避免同时维护不同版本的情况。

    灰度发布是应用发布通常采用的方式,即对一个pool的部分服务器发布新版本的代码,而其余的服务器采用老版本代码,在确认应用正常的情况下逐步将新版本发布到pool的全部服务器。这样的方式的确可以起到分流的作用,但是这样的分流是不稳定的,用户的两次访问很有可能会被分到新老两个版本上。同时,灰度发布的分流单位通常是以服务器的流量为最小单位的,不能做到对测试流量的精确分配。

  2. 用参加实验的部分用户的体验质疑AB实验结果的正确性。

    经常碰到产品经理或是业务人员提出某些用户在新版本的实验中没有转化,而实际实验数据体现新版本效果好于老版本的情况,从而质疑实验的结果。AB实验是基于统计的结果,是基于大样本量的有效统计结果,实验结果的好坏是针对参与实验的大多数样本而言的,个例不具备代表性。

  3. AB测试是优化Web应用的利器,应该在所有场合都应用AB测试进行优化。

    AB测试从实验的设计、实施和实验结果的收集通常需要一个不短的阶段,且进行AB实验需要在线上维护多个不同的版本,所以不应该所有场景下都采用AB测试对Web应用进行优化迭代。对于那些明显的bug修复,或者对于那些能够明显改善用户体验的功能,应该采用尽快上线并监控关键数据指标的方式。此外,AB测试也不是silver bullet, 通常AB测试的时间不会延续很长时间,对于一些长期效果很难做到有效的监测和对比。例如,某OTA对机票进行捆绑销售产生的收益进行了为期一年的多版本AB测试,测试的目标是在用户转化率没有显著下降的情况下提升用户客单价。在实验中,通过对价格非敏感用户的个性化展示、默认勾选等方式的确客单价有了很显著的提升,同时用户的线上转化率并没有显著变化甚至有了略微的提升。但是,这种捆绑销售的方式从长远来看可能对用户是有伤害的,这种情况在低频消费的场景下很难在实验的结果上有所体现。而且,这种捆绑销售的产品为媒体和公众所诟病,这些都不是AB测试能够体现的。

  4. 产品要求开AABB实验

    • 首先,不科学。抽样产生的误差本身就已经在我们的计算概率里了。为啥还要专门开4组实验对比?
    • 其次,不聪明。多样本进行对比更可能犯错。比如说,一次抽样有5%的可能犯错,四次抽样,产生6组对比(A1A2,A1B1,A1B2,A2B1,A2B2,B1B2),一组对比时不犯错的概率95%,‍假设各组对比结果相互独立,至少一组犯错的概率[1 -(1-0.05)^6] =0.265,远大于0.05。多来几次抽样,犯错的概率增加。更别提评估成本了——本来只用评估两组,现在需要看6组。
    • 最后,不好使。AABB实验可能会影响实验的灵敏度。流量不变则意味着各组样本流量减少一半,灵敏度下降;加大流量则更多用户进组,有可能引入风险。因此不管怎么说都是加大成本的。
  5. 实验做了有效果,上线没有效果

    有可能犯第一类错误。你看到的显著可能不是真的,只是抽样的随机误差带来的。

一分一毛,也是心意。