共计 1469 个字符,预计需要花费 4 分钟才能阅读完成。
背景痛点:为什么传统 AB 测试会翻车?
传统 AB 测试通常直接比较实验组和对照组的均值差异,但这种方法暗藏两个致命漏洞:

- 用户自选择偏差:比如测试新功能时,活跃用户更可能主动开启实验组,导致两组用户本身存在系统性差异
- 外部因素干扰:促销活动期间进行测试,节日效应会同时影响实验组和对照组,造成效果误判
去年我们团队就踩过坑——某推荐算法 AB 测试显示点击率提升 12%,但上线后真实收益不足 3%。后来发现是测试期间恰逢春节,实验组中高频用户占比天然更高。
因果推断三剑客:DID/PSM/ 合成控制
| 方法 | 适用场景 | 最小样本量要求 | 支持并行实验数 |
|---|---|---|---|
| 双重差分法(DID) | 有明确政策冲击时间点 | 每组≥500 | 中 |
| 倾向得分匹配 | 非随机分流但特征数据完整 | 实验组≥200 | 高 |
| 合成控制法 | 实验组极少(如单个城市) | 对照组≥10 地区 | 低 |
表:三大方法对比,样本量指每组最低要求
Python 实战:PSM 完整实现
# 安装因果推断工具包
!pip install causalml
from causalml.match import NearestNeighborMatch
from sklearn.linear_model import LogisticRegression
import numpy as np
# 生成模拟数据(实际应替换为业务数据)np.random.seed(42)
X = np.random.normal(size=(1000, 5)) # 5 个特征变量
treatment = (X[:, 0] + X[:, 2] > 0).astype(int) # 非随机分配
# Step1: 计算倾向得分
ps_model = LogisticRegression()
ps_model.fit(X, treatment)
propensity_scores = ps_model.predict_proba(X)[:, 1]
# Step2: 最近邻匹配(caliper=0.2 限制最大得分差异)matcher = NearestNeighborMatch(
caliper=0.2, # WHY: 防止匹配差异过大
replace=False # 不放回抽样
)
matched_ids = matcher.match(propensity_scores, treatment)
# Step3: 平衡性检查(标准化差异 <0.1 为佳)from causalml.metrics import smd
for i in range(X.shape[1]):
print(f"特征 {i} 的 SMD:",
smd(X[matched_ids[:, 0], i],
X[matched_ids[:, 1], i]))
生产环境避坑指南
- 小样本救急方案:
- 用 Bootstrap 重复抽样 1000 次,取效果置信区间
-
示例:
np.quantile([bootstrap_ate() for _ in range(1000)], [0.025, 0.975]) -
非随机分流验证:
- Wald 检验公式:$\frac{ATE}{\sqrt{Var(治疗组)+Var(对照组)}}$
-
当统计量 >1.96 时认为分流有效
-
多重检验校正:
- Bonferroni 修正:将显著水平 α 除以实验次数
- 如同时测 5 个指标,则 p 值需 <0.01 才算显著
血泪教训:三大死亡陷阱
- 辛普森悖论:某功能整体提升 10%,但分层后发现 iOS 用户下降 5%,Android 用户上升 20%
- 标准误误用:匹配后的样本是相关的,必须用 bootstrap 或稳健标准误
- 效果衰减:使用 KM 曲线分析留存率,发现新用户前 3 天效果显著但 7 天后衰减
思考题
当实验组和对照组的特征分布完全重叠时,PSM 是否仍有必要?为什么?
(提示:考虑测量误差和模型误设的影响)
正文完
发表至: 未分类
近两天内
