没有AB实验怎么做因果推断?PSM倾向得分匹配实战指南
做AB实验做得久了你会慢慢意识到一个扎心的事实不是所有场景都能给你一个干净的随机分组。用户在产品里已经走了一圈你没法把他们“打回原形”重新抽签。这时候还硬套AB实验的框架出来的结论不是偏的就是假的甚至可能把业务带进沟里。我遇到过很多次这种情况新功能上线前没有预留实验位或者渠道投放已经按人群包跑了好几个月这时候业务方跑来问“你看这数据能证明新功能有效吗”。直接拍脑袋回答当然不行但如果因此就放弃数据分析又显得太浪费。后来我系统补了因果推断的课才意识到这类问题真正缺的不是实验而是在观察性数据里构造一个尽可能接近随机化的对照环境。而倾向得分匹配Propensity Score Matching简称PSM就是这套思路里最实用的工具之一。这篇文章我会把PSM讲透从它到底在解决什么问题到倾向得分怎么算、样本怎么匹配、平衡性怎么查再到实战里那些标准教程不会提醒你的坑。整个过程都是围绕“观察性数据”这个前提展开的适合已经做过基本AB实验、想往更高阶因果推断方向走的分析师和算法工程师也适合那些没有实验条件、却必须从历史数据里挖掘因果结论的业务团队。1. 为什么AB实验在观察性数据上失效1.1 随机化的本质它在消除什么要理解PSM的价值必须回到AB实验的底座——随机化。随机分组把用户分配到实验组和对照组时理论上两组在所有维度上是同分布的所谓“所有维度”包括你观测到的年龄、城市、登录频次也包括你没观测到的偏好、动机、习惯。正因为两组在干预前没有系统性差异实验后两组表现出的差异就只能归因于干预本身。这就像两个长跑水平几乎一致的人吃同样的饭、睡同样的觉唯一区别是一个人穿了新跑鞋一个人穿旧跑鞋。最终比赛成绩差多少当然是跑鞋的功劳。可如果其中一个人本来就跑得快另一个人本来跑得慢你再去看成绩差就没法说清是跑鞋还是人。观察性数据的麻烦就在这儿。它没有随机化这道工序数据里的“实验组”和“对照组”很可能在干预前就是两拨不同的人。用户不是被随机分配到某个运营活动里的而是自己决定要不要参加、要不要点击、要不要转化。这种自我选择会带来一个非常致命的东西——混淆变量它同时影响干预和结果让相关性不等于因果性。1.2 一个生活化的例子假设你是一家电商平台的分析师想评估“开通会员后是否提升了用户月度消费”。运营团队在后台一拉数据会员用户月均消费1200元非会员用户月均消费600元结论是会员提升了百分之百的消费。这个结论可信吗显然不可信。因为能开通会员的用户大概率本身就消费频次高、购买力强他们就算不开会员也会花不少钱。你看到的消费差异里有很大一块是用户本身的“消费潜力”带来的而不是会员权益带来的。收入、购物频次、活跃时长、历史客单价这些变量既让人倾向于开通会员又直接影响消费金额它们就是这里的混淆变量。在AB实验框架下你只需要随机开一批人的会员另一些人不开然后比较消费。但在观察性数据里你没法这样操作你只能看着已经发生的数据发呆。PSM就是来处理这个问题的——它试图从非会员用户中找到那些“和会员用户长得很像”的人重新搭出一个虚拟的对照组。2. PSM的核心原理与运行机制2.1 从条件独立到“可忽略性”PSM的理论基础来自Rosenbaum和Rubin在1983年提出的经典框架。它的核心假设有两个第一是可忽略性也叫无混淆性意思是给定一组可观测的协变量X干预分配和潜在结果是条件独立的。用公式表达就是( Y(1), Y(0) \perp T \mid X )通俗点说只要我足够了解用户所有影响用户选择和结果的因素都观测到了那么在这个条件下用户是不是被干预相当于随机决定的。第二个假设是重叠假设。对任何一组协变量X用户既有概率被干预也有概率不被干预( 0 P(T1|X) 1 )这个假设保证每个用户都能在另一组里找到“样貌”相似的人。如果某个用户群体100%都会被干预那你就根本找不到它们的对照匹配自然无法进行。这两个假设保证了“平行世界”在理论上是可以被重构的。现实里第一个假设很难完全满足因为总有一些变量你没测到所以怎么选协变量、怎么检验假设合理性就变成了实操里的重头戏。2.2 倾向得分把高维匹配变成一维匹配早期做匹配的人是真拿协变量逐个算距离。比如你有年龄、收入、活跃天数三个变量想在对照组里找一个和实验组用户最相似的人就得计算三维空间里的距离。但协变量一旦多了比如有30个高维空间的匹配样本会急剧稀疏距离计算也变得非常不稳定这被称为维度灾难。Rosenbaum和Rubin的天才之处在于他们证明了如果你在X条件下干预是条件独立的那在倾向得分e(X)条件下干预同样是条件独立的。倾向得分就是把一大堆协变量压缩成一个标量即给定用户特征X时用户进入实验组的概率( e(X) P(T1 \mid X) )这样匹配问题就从高维空间的距离计算简化成一维分数上的距离计算。你只需要在对照组里找倾向得分最接近的用户就能认为这两个用户在所有协变量维度上都很相似。这个压缩过程听着神奇其实背后的思路也很好理解。两个人虽然年龄、收入、活跃度不完全一致但只要他们进入实验组的概率一样那在统计意义上他们面对的选择环境就是一样的。就像两个申请同一所学校的学生一个因为成绩好一个因为体育特长最终录取概率一样那他们在“是否被录取”这件事上是可以互相比较的。2.3 PSM不是万能的必须提前泼一盆冷水PSM只能控制你能观测到的混淆变量。如果有重要变量没进模型比如用户的真实意图、动机、线下行为那这些未观测混淆因素仍然会污染结果。所以在读后面的实操流程之前先把这句话刻在脑子里PSM不是一个万能钥匙它的天花板由你的变量质量决定。3. 从0到1搭建PSM完整流程假设现在的场景是某内容社区想评估“参与创作者激励计划是否显著提升了用户的月度发布量”。用户可以选择要不要参与计划所以这就是典型的观察性数据。我们的任务是用PSM重构一个相对干净的对照组然后估计激励计划的因果效应。整个PSM流程可以拆成六步下面每一步我都会给可落地的做法和参考代码。3.1 第一步明确处理变量和结果变量首先要定义清楚什么是T什么是Y。T是处理变量即用户是否参与激励计划1/0。Y是结果变量即月度发布量。别小看这一步很多项目做到一半才发现定义模糊。比如“参与”指的是“申请通过”还是“实际发布过一次内容”“月度发布量”是按自然月还是按30天滚动窗口算这些都必须提前定好否则后面所有步骤的计算都会失真。3.2 第二步圈定协变量集合X协变量就是影响用户“是否参与计划”且同时影响“发布量”的变量。常见的候选包括历史行为类过去30天发布量、过去90天互动次数、登录天数用户属性类注册时长、账号等级、内容品类偏好设备渠道类设备类型、用户来源渠道、是否新用户协变量不是越多越好。加入太多无关变量会降低匹配效率甚至引入额外的噪声加入太多与处理变量高度相关的变量又可能导致倾向得分趋近于0或1让重叠假设失效。实操中我的做法是先用业务经验圈定20到30个变量再通过相关性分析和简单的逻辑回归显著性做一轮初筛。还有一个很微妙的点千万不要把处理之后才发生的变量放进协变量。比如用户加入计划之后获得的认证标识那是处理的结果不是混淆因素。把它放进模型等于在控制“处理的一部分效应”会严重低估真实效应这就是所谓的“过度控制偏差”。3.3 第三步计算倾向得分倾向得分通常用逻辑回归估计也可以用梯度提升树、随机森林这类机器学习模型但逻辑回归仍然是入门首选因为它简单、可解释、稳定。以Python为例import pandas as pd from sklearn.linear_model import LogisticRegression feature_cols [active_days_30, post_cnt_30, reg_days, level, source_channel] X df[feature_cols] y df[treatment] model LogisticRegression(max_iter1000, C1.0) model.fit(X, y) df[propensity_score] model.predict_proba(X)[:, 1]这里model输出的概率就是每个用户的倾向得分。C值控制正则化强度如果不确定就先用默认值然后看后面平衡性检验的结果再迭代。逻辑回归的优点是每个系数都能解释比如active_days_30的系数为正说明历史活跃天数越高参与激励计划的概率越大这一般符合业务直觉。如果系数方向跟业务直觉相悖要去查数据是不是有编码错误或者共线性问题。3.4 第四步选择匹配方法算出倾向得分后就要为每个实验组用户找对照。常见方法有这么几种匹配方法核心思路优点缺点最近邻匹配找倾向得分最接近的一个或多个对照简单高效样本利用率高可能匹配到距离较远的样本卡尺匹配设定一个最大距离阈值超过则不匹配控制匹配质量卡尺范围难定可能丢弃样本分层匹配按得分分成若干层在层内匹配操作直观稳定层数和边界对结果敏感核匹配用所有对照的加权平均作为对照样本信息利用充分权重选择较复杂计算量大最常用的是卡尺最近邻匹配即在卡尺范围内找最近的邻居。卡尺宽度一般取倾向得分标准差的0.2倍这是实证研究里效果比较好的默认值有相关模拟研究支持。from sklearn.neighbors import NearestNeighbors # 分离实验组和对照组倾向得分 treated_ps df.loc[df[treatment] 1, propensity_score].values.reshape(-1, 1) control_ps df.loc[df[treatment] 0, propensity_score].values.reshape(-1, 1) # 对每个实验组样本在对照组中寻找最近邻 nn NearestNeighbors(n_neighbors1, metriceuclidean) nn.fit(control_ps) distances, indices nn.kneighbors(treated_ps) # 构造匹配结果 matched_control_indices indices.flatten()需要注意一个对照能不能被重复匹配取决于你是否开启了“有效回”。对于样本量充足的情况我建议用无放回卡尺这样对照更独立如果对照组样本紧张用有放回能降低偏差但会损失独立性后续统计推断需要相应调整。3.5 第五步共同支撑域检验与样本修剪匹配完以后得检查两组倾向得分是否有足够的重叠区域。如果实验组里有大量用户得分接近1而对照组里几乎没有得分超过0.6的人那这部分实验组用户在对照世界里根本找不到“平行自我”匹配出来的结果也没有参考价值。画一张倾向得分的分布直方图最直观。我习惯把两组分布叠在同一张图里用不同透明度区分。重叠面积越大越好。如果发现尾部有大量不重叠样本直接修剪掉只保留共同支撑域内的样本这叫“修剪”操作。牺牲一点样本量换来更可信的因果效应估计这是划算的。3.6 第六步平衡性检验这是整个PSM流程的验收环节。匹配的目的就是让处理组和对照组的协变量分布尽量一致。怎么量化“一致”最常用的指标是标准化均值差Standardized Mean DifferenceSMD( SMD \frac{\bar{X}{treated} - \bar{X}{control}}{\sqrt{(s_{treated}^2 s_{control}^2)/2}} )经验法则是SMD的绝对值小于0.1就认为该变量在两组之间已经足够平衡。0.1到0.2属于临界需要结合业务判断大于0.2就得回头修倾向得分模型或匹配参数。除了SMD还可以看方差比和画QQ图但SMD简单直接团队汇报时最好解释。我之前做过一个项目第一次跑完匹配活跃天数的SMD还是有0.18后来把交互项加进逻辑回归、把卡尺缩紧才把SMD降到了0.06。这个过程多迭代几次很正常千万别指望一次就能匹配好。以下是平衡性检查的参考代码def compute_smd(df, feature_cols, treatment_col): treated df[df[treatment_col] 1] control df[df[treatment_col] 0] smd_dict {} for col in feature_cols: mean_t treated[col].mean() mean_c control[col].mean() var_t treated[col].var() var_c control[col].var() pooled_sd ((var_t var_c) / 2) ** 0.5 smd (mean_t - mean_c) / pooled_sd smd_dict[col] abs(smd) return pd.Series(smd_dict)4. 匹配之后估计因果效应与敏感性分析4.1 效应估计ATE还是ATT匹配完成后你可以比较匹配后两组的结果均值得出因果效应。但这里有个重要的口径选择ATEAverage Treatment Effect平均处理效应和ATTAverage Treatment Effect on the Treated受处理组的平均处理效应。PSM最常见的匹配策略是“用实验组的每个人找对照”所以最终估算出来天然是ATT也就是“对于真正参与了激励计划的人这个计划让他们多发了多少内容”。这在业务场景里通常是更关心的指标因为它贴合实际经营问题我打算继续运行这个计划的话对现有参与者的边际影响是多少。如果你需要ATE得用逆概率加权Inverse Probability WeightingIPW之类的方法。这里不展开但你必须知道两者的区别否则汇报时容易闹笑话。4.2 敏感性分析未观测混淆因素怎么办做PSM的人最怕评审会上的灵魂拷问“你们控制了年龄、活跃度和历史发布量那怎么证明用户对创作的热爱程度没影响结果”这个问题问得对因为PSM没法直接解决未观测混淆因素。这时候必须做敏感性分析。最常见的工具是Rosenbaum敏感性分析它通过引入一个参数Γ伽马来衡量要达到什么程度的未观测混淆才能推翻你的结论。比如Γ1.2意味着某个未观测变量需要让用户参与计划的几率提高20%并且还会显著影响结果才能让当前结论不成立。Γ越小结论越脆弱Γ越大结论越稳健。实操层面即使没有专门软件包你也可以通过改变协变量组合、调整匹配参数观察效应量是否稳定。如果稍微调整参数结论就翻转说明结果很脆弱汇报时务必提示风险。5. 真实项目里的关键陷阱与经验教训5.1 别用倾向得分做随机化分层的“替身”PSM和分析人员在设计实验时的思维完全不同。实验设计是先随机化再分组PSM是先看到结果再回溯匹配。有些人会把PSM当成“事后补实验”的万金油这是最大的理解偏差。PSM能做的是把可观测的混淆因素平衡掉它不能替代随机化解决所有问题。如果业务条件允许做前瞻性实验绝对优先做实验只有当实验不可行时才把PSM作为次优方案并且结论的不确定性要更大。5.2 过度匹配与样本损失匹配不是把每一个实验组样本都配上一个对照就行也不是匹配完就万事大吉。有一个很真实的陷阱如果对照组样本量不够你匹配出来的样本可能集中在少数“万能对照”身上导致这些对照被反复使用。虽然技术上允许有放回匹配但重复使用会降低对照的独立性结果的标准误被低估置信区间看起来比实际窄得多。另一个陷阱是过度修剪。修剪能提高共同支撑域的质量但修剪太多会让样本失去代表性最终结论只适用于满足条件的一小撮人群没法泛化到全体用户。所以修剪比例超10%的时候我一般会停下来重新思考模型设定。5.3 时间窗口与滞后效应观察性数据里的时间维度容易被忽略。用户参与激励计划是一个持续状态结果变量月度发布量也不会瞬间拉满。如果处理组的发布时间和结果测量时间太近效应很可能还没完全显现估计出来的效应会偏小。反过来如果数据回看窗口太长可能混入其他产品功能带来的影响。我的做法是定义处理组时严格限定“首次参与计划的时间”结果变量取参与后固定30天窗内的发布量对照组按同一时间窗对齐。这样才能保证两组比较的是“同一段经历”而不是各比个的。5.4 逻辑回归的系数解释别过度用逻辑回归算倾向得分的时候有人会盯着每个变量的系数做业务解读。比如看到“注册天数”系数为负就得出“老用户更不愿意参与激励计划”的结论。这有一定参考价值但倾向得分模型的核心任务是平衡协变量而不是解释用户行为。系数的符号和大小可能受到变量间相关性的影响别拿它当分析结论写进汇报里。6. PSM vs 其他观察性数据方法在实际工作里PSM并不是唯一的选择。经常被拿来对比的方法还有以下几种方法核心思想适用场景主要局限协变量直接回归把协变量作为控制变量放进回归模型协变量少、关系明确对模型设定依赖强容易漏掉非线性PSM以倾向得分匹配样本特征多、对照组样本充足依赖无混淆假设IPW以倾向得分对样本加权整体ATE估计极端权重会导致方差膨胀DID双重差分比较处理前后两组变化量之差有面板数据满足平行趋势假设平行趋势不满足时偏差大工具变量借助与干预相关但与结果无直接关联的工具变量存在强工具变量时找工具变量极其困难我看到很多团队在纠结到底用哪个方法。我的经验是把这几个方法当互为稳健性检验的组合而不是二选一的竞争。比如主分析方法用PSM再用IPW做一遍再用DID做一遍看结论是否一致。如果三种方法得出方向一致的结论汇报的说服力会大幅提升如果结果互相矛盾那就说明数据里的因果信息还不够强硬出结论很危险。7. 一个完整的案例复盘分享一个我实际参与过的案例方便你把前面所有步骤串起来。某内容平台推了一个“创作者扶植计划”报名的创作者会得到更多曝光和运营指导。产品团队想知道这个计划对作者的月度发帖数有没有正向因果效应。原始数据摆出来参与计划的创作者平均月发12条未参与的平均月发5条看起来是翻倍还多的提升。但参与计划的用户明显更活跃、更新手、更新手且更有动力直接对比的结论没人敢信。我们取了注册时长、历史30天发帖数、历史90天互动数、内容标签数量、设备类型、入站渠道共12个协变量跑了一个逻辑回归算倾向得分。匹配时用了卡尺最近邻卡尺宽度0.05按有放回方式匹配。匹配后样本量从原本2万实验组收缩到1.8万共同支撑域外的样本被修剪掉了。平衡性检验里全部12个协变量的SMD都在0.1以下其中11个低于0.05。这说明匹配后的对照组在可观测维度上和实验组已经很接近了。最终ATT估算的效应是参与扶植计划平均每月增加1.9条发帖置信区间在1.2到2.6之间p值显著。这个效应远低于表面差异的7条。说明用户本身的活跃程度贡献了很大一部分“虚假增量”。要不是PSM我们可能就会向业务宣传一个水分极大的数字。后来我们把PSM的结果和IPW做了交叉验证结论一致才敢正式输出到决策层。8. 个人实操的一些心得关于PSM最核心的收获不是学会了工具而是养成了一种因果思维拿到任何对比数据先问一句“这两组人真的可以比吗”。这句话让我少做了很多无用功。具体到操作上我可以给几个个人经验第一协变量的选择一定要和业务方反复确认请业务方列出“你觉得决定用户要不要参加这个活动的因素”这些往往是模型里最重要的变量。第二倾向得分模型的输出不是终点平衡性检验才是真正的考核关给自己定个死要求不过0.1不汇报。第三所有PSM结果都要配敏感性分析哪怕只是简单调整参数观察结论稳定性也一定要做。在评审会上这比任何漂亮的可视化都能增加可信度。PSM肯定不是因果推断的终点但它是一个能让你在观察性数据里走得更远、更踏实的起点。至少下次再有人拿着两组不具可比性的数据问我结论时我能给出一套完整的、经得起推敲的分析方案而不是一句苍白的“这个数据说明不了问题”。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →