共计 2291 个字符,预计需要花费 6 分钟才能阅读完成。
传统 AB 测试的业务痛点
去年双十一大促期间,我们的推荐算法团队上线了一个新策略,AB 测试显示 CTR 提升 12%。但大促结束后,全量上线效果反而下降 5%。复盘发现:

- 传统 AB 测试假设用户群体同质,但大促期间新增用户占比高达 40%
- 策略效果被节日流量放大,未考虑时间序列上的自相关性
类似场景还有:
- 外卖平台的补贴实验中,天气因素导致对照组天然订单量偏低
- 视频 APP 的 UI 改版测试,遇到暑期学生用户活跃度周期性上升
为什么选择 CCM 方法?
对比常见因果推断方法:
- PSM(倾向得分匹配):需要满足可忽略性假设,且难以处理动态干预
- DID(双重差分):依赖平行趋势假设,对间断性干预效果差
- CCM(收敛交叉映射):通过动力系统重构捕获非线性关系,特别适合:
- 存在时间延迟效应(如广告曝光到转化的时滞)
- 变量间存在双向因果关系(如用户活跃度与推荐多样性)
核心实现:从算法到工程化
Python 关键代码实现
import numpy as np
from scipy.spatial import distance
from sklearn.preprocessing import StandardScaler
def ccm_algorithm(X, Y, E=5, tau=1, L=100):
"""
X: 原因变量时间序列
Y: 结果变量时间序列
E: 嵌入维度(建议通过 FNN 算法确定)tau: 时间延迟(建议用互信息法确定)L: 最近邻数量(通常取 10-20% 数据量)"""
# 数据标准化
scaler = StandardScaler()
X = scaler.fit_transform(X.reshape(-1,1)).flatten()
Y = scaler.transform(Y.reshape(-1,1)).flatten()
# 构建影子流形
def build_manifold(series):
n = len(series) - (E-1)*tau
M = np.zeros((n, E))
for i in range(n):
M[i] = [series[i+j*tau] for j in range(E)]
return M
Mx = build_manifold(X)
My = build_manifold(Y)
# 收敛性检测
corr_list = []
for lib_size in range(50, len(Mx), 50):
pred_Y = []
for i in range(lib_size):
dists = distance.cdist([Mx[i]], Mx[:lib_size], 'euclidean')[0]
nn_indices = np.argpartition(dists, L)[:L]
weights = np.exp(-dists[nn_indices])
pred_Y.append(np.sum(weights * Y[[idx + (E-1)*tau for idx in nn_indices]]) / np.sum(weights))
corr = np.corrcoef(Y[(E-1)*tau:(E-1)*tau+lib_size], pred_Y)[0,1]
corr_list.append(corr)
return corr_list # 相关系数随样本量增加而提升则存在因果关系
PySpark 优化技巧
-
窗口函数预处理:
from pyspark.sql.window import Window import pyspark.sql.functions as F window = Window.partitionBy('user_id').orderBy('timestamp') df = df.withColumn('X_lag3', F.lag('X', 3).over(window)) .withColumn('Y_lead2', F.lead('Y', 2).over(window)) -
分布式最近邻搜索:
# 利用 KDTree 广播变量 kdtree = sc.broadcast(KDTree(manifold_array)) rdd.map(lambda x: find_neighbors(x, kdtree.value))
性能优化实战
我们在 100GB 点击流数据上的测试结果:
| 数据量 | 单机耗时 | Spark 集群(10 节点) | 优化策略 |
|---|---|---|---|
| 1GB | 3.2min | 1.1min | 原生实现 |
| 10GB | 内存溢出 | 6.5min | 分块计算 |
| 100GB | – | 28min | 采样 + 近似最近邻 |
内存优化方案:
- 对时间序列分段计算局部流形
- 对高维嵌入使用 PCA 降维
- 采用 BallTree 替代暴力搜索
避坑指南
非平稳序列处理
-
差分法:
# 一阶差分处理 X_diff = np.diff(X, n=1) # 结合 ADF 检验判断平稳性 from statsmodels.tsa.stattools import adfuller p_value = adfuller(X)[1] # <0.05 则认为平稳 -
滑动窗口标准化:
def rolling_standardize(s, window=30): return (s - s.rolling(window).mean()) / s.rolling(window).std()
因果方向验证
使用 替代数据检验:
1. 对 X 序列进行相位随机化(保持频谱但破坏因果)
2. 计算 CCM 指标,如果原始数据结果显著高于替代数据,则因果可信
开放性问题
当存在未观测的混杂变量(如用户手机型号影响 APP 使用时长):
- 能否结合用户画像构建代理变量?
- 如何设计敏感性分析量化遗漏变量偏差?
- 领域知识如何转化为先验分布融入模型?
在实际项目中,我们通过融合业务方对用户分层的经验,将潜在混杂变量显式建模,使 CCM 的因果强度指标提升了 37% 的稳定性。这也启示我们:因果推断既是技术问题,更是业务理解问题。
正文完
发表至: 未分类
近一天内
