一、背景
1.1 什么是CUPED
CUPED(Controlled-experiment Using Pre-Existing Data)是一种运用实验前数据来降低AB实验方差的方差缩减技术。降低方差可以提升AB实验的灵敏度。
1.2 为什么要降低实验的灵敏度
灵敏度代表了检测AB实验真实效果的能力。实验灵敏度越高,实验结果越容易达到显著,反之灵敏度越低则越难以观测到实验结果显著。
如果实验足够灵敏:
- 我们就更容易发现那些微小正向的实验,而不至于因为指标方差过大而被判别为统计功效不足、统计不显著;
- 能及时制止那些略微负向,但因为指标方差过大而被认为负向不显著的实验全量上线;
- 对于大流量的在线实验来说,一点点微小的正向或负向作用的影响可能都非常大。
1.3 什么是方差缩减
方差缩减是提高实验灵敏度的主要方式之一。此处的方差指的是实验组与对照组的差异(例如构造估计量:实验组指标 - 对照组指标),用数学语言描述可以表述为: V a r ( x ) = E [ ( x − E ( x ) ) 2 ] Var(x)=E[(x-E(x))^2] Var(x)=E[(x−E(x))2],其中x为表征实验组与对照组差异的估计量。
我们此前已经说过了,只要能降低实验组与对照组差异的方差,就能够提高实验的灵敏度。如果是对统计学有一定背景的同学,当一谈到降低方差,你们的第一个反应一定是提高样本量。诚然,扩大样本量确实能带来方差的缩减,但扩量实验同时也意味着要付出更大的成本。因此在强调“降本增效”的广大互联网公司中,经常会采用其他方式来降低方差。这时就轮到我们本章的主角——方差缩减技术大显身手了。
通常来说,常用的方差缩减技术包括但不限于:
- 实验前:通过分层抽样、控制实验前差异等离线分流方法,提升对照组和实验组的同质性;
- 实验中/后:使用事后分层、CUPED等方法,构造新的方差更小的估计量。
所以CUPED其实是众多方差缩减方法中的一个分支。
既然有这么多的方法,我们又该如何衡量方差缩减的效果呢?
我们将使用方差缩减技术(如CUPED)所构造新估计量的方差相比原估计量方差相对减少的百分比作为方差缩减的效果,称其为方差缩减比例。
方差缩减比例
=
100
%
∗
(
1
−
V
a
r
(
Y
e
^
‾
−
Y
c
^
‾
)
V
a
r
(
Y
e
‾
−
Y
c
‾
)
)
方差缩减比例 = 100\%*(1-\cfrac{Var(\overline{\hat{Y_e}}-\overline{\hat{Y_c}})}{Var(\overline{Y_e}-\overline{Y_c})})
方差缩减比例=100%∗(1−Var(Ye−Yc)Var(Ye^−Yc^))
不难看出,方差缩减比例越大,该方法的方差缩减效果越好。
二、CUPED详解
2.1 符号定义
定义:
Y Y Y为我们所关注的指标,例如订单,后简称单量
Y ( i ) Y(i) Y(i):第i个用户在实验过程中的单量(包括实验组和对照组)
Y e ( i ) Y_e(i) Ye(i):实验组中第i个用户在实验过程中的单量
Y c ( i ) Y_c(i) Yc(i):对照组中第i个用户在实验过程中的单量
Y ‾ = ∑ i = 1 N + M Y ( i ) N + M \overline {Y} = \cfrac{\sum_{i=1}^{N+M}{Y(i)}}{N+M} Y=N+M∑i=1N+MY(i):实验过程中单量均值(假设总用户数=N+M)
Y e ‾ = ∑ i = 1 N Y e ( i ) N \overline {Y_e} = \cfrac{\sum_{i=1}^{N}{Y_e(i)}}{N} Ye=N∑i=1NYe(i):实验组在实验过程中单量均值(假设实验组用户数为N)
Y c ‾ = ∑ i = 1 M Y c ( i ) M \overline {Y_c} = \cfrac{\sum_{i=1}^{M}{Y_c(i)}}{M} Yc=M∑i=1MYc(i):对照组在实验过程中单量均值(假设对照组用户数为M)
X X X是与 Y Y Y高度相关的协变量
X ( i ) X(i) X(i):第i个用户在实验前的协变量(不区分实验组/对照组)
X e ( i ) X_e(i) Xe(i):实验组中第i个用户在实验前的协变量
X c ( i ) X_c(i) Xc(i):对照组中第i个用户在实验前的协变量
X ‾ \overline {X} X、 X e ‾ \overline {X_e} Xe、 X c ‾ \overline {X_c} Xc同 Y ‾ \overline {Y} Y、 Y e ‾ \overline {Y_e} Ye、 Y c ‾ \overline {Y_c} Yc
θ θ θ:CUPED调整参数,是一个可选择的常数
2.2 CUPED实施步骤
-
找出协变量
找出与Y相关性较高的协变量X(一般是和Y相同的实验前指标),相关系数计算公式如下:
ρ = ∑ i = 1 M + N ( X ( i ) − X ‾ ) ( Y ( i ) − Y ‾ ) ∑ i = 1 M + N ( X ( i ) − X ‾ ) 2 ∑ i = 1 M + N ( Y ( i ) − Y ‾ ) 2 ρ=\cfrac {\sum_{i=1}^{M+N}(X(i)-\overline{X})(Y(i)-\overline{Y})} {\sqrt{\sum_{i=1}^{M+N}{(X(i)-\overline{X})^2}} \sqrt{\sum_{i=1}^{M+N}{(Y(i)-\overline{Y})^2}}} ρ=∑i=1M+N(X(i)−X)2∑i=1M+N(Y(i)−Y)2∑i=1M+N(X(i)−X)(Y(i)−Y)
在使用最优参数的情况下,方差缩减比例可达 ρ 2 ρ^2 ρ2,因此X和Y的相关程度决定了方差缩减效果的上限。 -
计算CUPED调整后指标Ŷ
Y ( i ) ^ = Y ( i ) − θ X ( i ) + K \hat{Y(i)}= Y(i)-θX(i)+K Y(i)^=Y(i)−θX(i)+K
其中:- θ:CUPED调整参数,为可选择的常数
最优方差缩减效果时,θ 等价于“用X对Y作线性回归的斜率:
θ = c o v ( X , Y ) V a r ( X ) = ∑ i = 1 M + N ( X ( i ) − X ‾ ) ( Y ( i ) − Y ‾ ) ∑ i = 1 M + N ( X ( i ) − X ‾ ) 2 θ=\cfrac{cov(X,Y)}{Var(X)}= \cfrac {\sum_{i=1}^{M+N}(X(i)-\overline{X})(Y(i)-\overline{Y})} {\sum_{i=1}^{M+N}{(X(i)-\overline{X})^2}} θ=Var(X)cov(X,Y)=∑i=1M+N(X(i)−X)2∑i=1M+N(X(i)−X)(Y(i)−Y) - K:可选择的常数
微软2013年论文中使用 θ × X ‾ θ×\overline{X} θ×X,优点是可使 Y ^ ‾ \overline{\hat{Y}} Y^和 Y ‾ \overline {Y} Y的量级保持一致 - 注意:
- 选择不同参数θ会有不同方差缩减效果,存在最优θ,但并不只有最优θ才能降低方差
- 常数K的选择实际上并不影响计算结果或方差缩减效果
- θ:CUPED调整参数,为可选择的常数
-
对调整后指标Ŷ进行检验
a. 计算实验组和对照组Ŷ的均值和两组均值之差(CUPED估计量)
Y e ^ ‾ − Y c ^ ‾ = ( Y e ‾ − Y c ‾ ) − θ ( X e ‾ − X c ‾ ) \overline{\hat{Y_e}} - \overline{\hat{Y_c}}= (\overline{Y_e}-\overline{Y_c})-θ(\overline{X_e}-\overline{X_c}) Ye^−Yc^=(Ye−Yc)−θ(Xe−Xc)
注意:- 根据该表达式,由于对照组K和实验组K相互抵消,因此估计量和常数K并不相关
- 由于有非零调整项 θ ( X e ‾ − X c ‾ ) θ(\overline{X_e}-\overline{X_c}) θ(Xe−Xc)的存在,因此CUPED估计量 ≠ 简单估计量,两者的正负向可能不同
b. 对CUPED估计量进行t检验,判断是否显著:
t ′ = Y e ^ ‾ − Y c ^ ‾ s ′ 2 ( 1 M + 1 N ) = ( Y e ‾ − Y c ‾ ) − θ ( X e ‾ − X c ‾ ) s ′ 2 ( 1 M + 1 N ) ∼ N ( 0 , 1 ) t'=\cfrac{\overline {\hat{Y_e}} -\overline {\hat{Y_c}}}{\sqrt{s'^2(\cfrac{1}{M}+\cfrac{1}{N})}} = \cfrac{(\overline{Y_e}-\overline{Y_c})-θ(\overline{X_e}-\overline{X_c})}{\sqrt{s'^2(\cfrac{1}{M}+\cfrac{1}{N})}} \sim N(0,1) t′=s′2(M1+N1)Ye^−Yc^=s′2(M1+N1)(Ye−Yc)−θ(Xe−Xc)∼N(0,1)
其中: s ′ 2 = ∑ i = 1 N ( Y e ( i ) ^ − Y e ^ ‾ ) 2 + ∑ i = 1 M ( Y c ( i ) ^ − Y c ^ ‾ ) 2 M + N − 2 s'^2= \cfrac{\sum_{i=1}^{N}{(\hat{Y_e(i)}-\overline{\hat{Y_e}})^2}+\sum_{i=1}^{M}{(\hat{Y_c(i)}-\overline{\hat{Y_c}})^2}}{M+N-2} s′2=M+N−2∑i=1N(Ye(i)^−Ye^)2+∑i=1M(Yc(i)^−Yc^)2
2.3 CUPED与DID的区别与联系
CUPED与DID这两种方法在构造核心估计量的形式上较为相似,相对而言,CUPED关注两组绝对差异,DID关注两组相对差异。
- 当 D I D = ( Y e ‾ − Y c ‾ ) − ( X e ‾ − X c ‾ ) \space DID= (\overline{Y_e}-\overline{Y_c})-(\overline{X_e}-\overline{X_c}) DID=(Ye−Yc)−(Xe−Xc),此时DID可视为一种特殊的CUPED(θ = 1);
- 当 D I D = ( Y e ‾ − Y c ‾ ) Y c ‾ − θ 2 ( X e ‾ − X c ‾ ) X c ‾ DID=\cfrac{(\overline{Y_e}-\overline{Y_c})}{\overline{Y_c}}-θ_2\cfrac{(\overline{X_e}-\overline{X_c})}{\overline{X_c}} DID=Yc(Ye−Yc)−θ2Xc(Xe−Xc),此时DID衡量的是相对差异,原始估计量 Y e ‾ − Y c ‾ \overline{Y_e}-\overline{Y_c} Ye−Yc变为 ( Y e ‾ − Y c ‾ ) Y c ‾ \cfrac{(\overline{Y_e}-\overline{Y_c})}{\overline{Y_c}} Yc(Ye−Yc),还是一类CUPED的形式。
由此可以看出,DID同样有方差缩减的效果,且在选择合适参数时,方差缩减比例同样可以达到 ρ 2 ρ^2 ρ2。
序号 | 属性 | CUPED | (广义)DID |
---|---|---|---|
0 | 估计量表达式 | ( Y e ‾ − Y c ‾ ) − θ 1 ( X e ‾ − X c ‾ ) (\overline{Y_e}-\overline{Y_c})-θ_1(\overline{X_e}-\overline{X_c}) (Ye−Yc)−θ1(Xe−Xc) | ( Y e ‾ − Y c ‾ ) Y c ‾ − θ 2 ( X e ‾ − X c ‾ ) X c ‾ \cfrac{(\overline{Y_e}-\overline{Y_c})}{\overline{Y_c}}-θ_2\cfrac{(\overline{X_e}-\overline{X_c})}{\overline{X_c}} Yc(Ye−Yc)−θ2Xc(Xe−Xc) |
1 | 估计量的含义 | Y e Y_e Ye和 Y c Y_c Yc的绝对差异 | Y e Y_e Ye和 Y c Y_c Yc的相对差异(%差异) |
2 | 对标的原始估计量 | Y e ‾ − Y c ‾ \overline{Y_e}-\overline{Y_c} Ye−Yc | ( Y e ‾ − Y c ‾ ) Y c ‾ \cfrac{(\overline{Y_e}-\overline{Y_c})}{\overline{Y_c}} Yc(Ye−Yc) |
3 | 最优参数θ | ρ σ Y σ X ρ\cfrac{σ_Y}{σ_X} ρσXσY | ρ σ Y / E ( Y c ) σ X / E ( X ) ρ\cfrac{σ_Y/E(Y_c)}{σ_X/E(X)} ρσX/E(X)σY/E(Yc) |
4 | 方差缩减比例 | ρ 2 ρ^2 ρ2 | ≈ ρ 2 ≈ρ^2 ≈ρ2 |
5 | 是否无偏估计量 | 是 | 否,为渐进无偏估计量 |
6 | 数学性质 | 较简单 | 较复杂 |
7 | 是否具备成熟理论 | 是,有较多关于CUPED的论文 | 否,尚未找到关于DID的权威论文 |
8 | 是否容易估计方差 | 是,通过样本较易测算估计量方差 | 否,有理论表达式,但较难测算 |
9 | 是否有权威背书 | 是 | 否,尚未找到关于DID的权威论文 |
10 | 参数θ选择是否稳定 | 否 | 是 |
2.4 CUPED代码
import numpy as np
import pandas as pd
import hive
from scipy.stats import norm, t, ttest_ind
import statsmodels.formula.api as smf
# 原始数据
df_0 = hive.query_df("""
select
exp_user_id
, group_name
, test_id
-- 实验前数据
, sum(before_order_cnt) as before_order_cnt
, sum(before_match_cnt) as before_match_cnt
, sum(before_order_gtv)/100 as before_order_gtv_yuan
, sum(before_match_gtv)/100 as before_match_gtv_yuan
-- 实验后数据
, sum(after_order_cnt) as after_order_cnt
, sum(after_match_cnt) as after_match_cnt
, sum(after_order_gtv)/100 as after_order_gtv_yuan
, sum(after_match_gtv)/100 as after_match_gtv_yuan
from xxxxx
where test_id = xxxx
group by
exp_user_id
, group_name
, test_id
"""
)
df = df_0.copy()
# 定义待使用CUPED修正的指标列表
indicator_list = []
# 拟合系数theta
for v in indicator_list:
theta = smf.ols('after_{} ~ before_{}'.format(v, v), data=df).fit().params[1]
df['cuped_'+v] = (
df['after_'+v] -
theta * (df['before_'+v] - np.mean(df['before_'+v]))
)
df_control = df.query("group_name == '对照组'").copy()
df_treatment = df.query("group_name == '实验组'").copy()
df_testing = pd.DataFrame()
# 运行检验
for x in indicator_list:
df_temp = {}
for x_type in ['before', 'after', 'cuped']:
indicator = x_type + '_' + x
treatment_series, control_series = df_treatment[indicator], df_control[indicator]
diff = np.mean(treatment_series) - np.mean(control_series)
stdvar = np.sqrt(
(np.var(treatment_series, ddof=1)*(treatment_series.shape[0]-1) + np.var(control_series, ddof=1)*(control_series.shape[0]-1)) /
(treatment_series.shape[0]+control_series.shape[0]-2)
)
tstat = (
diff /
(stdvar * np.sqrt(1 / treatment_series.shape[0] + 1 / control_series.shape[0]))
)
p_value = 2-2*t.cdf(np.abs(tstat), df=treatment_series.shape[0]+control_series.shape[0]-2)
df_temp[x_type] = {
x+'_diff': diff,
x+'_tstat': tstat,
x+'_p_value': p_value,
x+'_is_significant': p_value < 0.05,
}
df_testing = df_testing.append(pd.DataFrame(df_temp))
# 打印CUPED修正后的检验结果
print(df_testing)