资讯动态

因果推断与X-Learner:Uplift营销增益建模实战

发布时间:2026/10/23 1:07:48 来源:尧图企业网站定制
1. 为什么“发券”这件事九成人第一次做都亏了我先抛一个真实场景你看看是不是很熟悉运营同学拉了一张表把过去 30 天复购率低于 20% 的用户圈出来一口气发了 50 万张满 199 减 30 的券然后盯着一周后的核销率——12%看起来还行GMV 涨了几个点复盘会上皆大欢喜。但我通常只问一句这波人里有多少本来就会下单这个问题一问出来会议室基本就安静了。因为按照行业里被反复验证过的经验值促销活动的增量往往集中在很小一部分人身上剩下的大头是两类“白花钱”的人一类是无论发不发券都会买的铁杆用户券对他们来说纯属白送利润另一类是无论怎么发都不会买的低意图用户券对他们来说只是噪音甚至养成“不打折不下单”的坏习惯。真正因为这张券而多买一次、多买一点的人才是我们想要的增量客群而在整体人群里他们的占比经常只有个位数。这里就是因果推断要进场的地方。传统营销模型做的是“预测谁会买”本质是相关性建模——它找的是“高概率购买者”而高概率购买者大概率本来就是活跃用户。我们要的不是这个我们要的是Uplift 建模也就是营销增益建模它预测的是“因为干预而多买的那部分概率变化”。用一句大白话概括不是预测谁最容易买而是预测谁的购买行为会被这张券改变。这个差异听起来只是换了个目标变量实际落地时几乎是两套完全不同的工程逻辑、数据逻辑和评估逻辑。X-Learner 是我在这些项目里用得比较顺手的一种 Uplift 建模方法它特别适合那种“干预组样本远少于对照组”的场景——现实营销里太常见了一次 A/B 实验里对照组几十万人真正被推到优惠券的只有几万人比例悬殊得很。你如果直接上 S-Learner干预变量的信息会被样本量碾压模型很容易学出一个“干预几乎没影响”的结论然后所有增益分数挤在一坨完全没法排序。这篇内容适合谁看做过一些用户分层、跑过基础 A/B 实验、能从数据库里拉出用户特征和标签的分析同学或者想从“预测购买”升级到“预测增量”的算法同学。完全没接触过因果推断也能看懂我会把逻辑拆到能直接照做的程度如果你已经在做 Uplift 模型但增益分数总是“分不开”下面的评估体系和避坑技巧应该能帮你省掉几周的试错。我会把整条链路拆成四块方案选型背后的取舍逻辑、特征与标签构造的关键细节、X-Learner 的具体实现与参数选择、以及线上跑起来之后怎么排查问题。全过程会用模拟数据复现代码可以直接复制运行。2. 整体方案设计为什么是 X-Learner 而不是更“主流”的那些2.1 Uplift 建模的本质是一次反事实推理先把最容易含糊的概念钉死。Uplift 建模的核心量叫 CATE条件平均处理效应对应到营销场景就是一个用户在接受干预比如收到券和不接受干预两种情况下的购买概率之差。麻烦在于现实中同一个人不可能同时既收到券又没收到券我们只观测到其中一种结果这叫反事实不可观测。所有的 Uplift 方法本质上都是在用什么姿势去近似这个看不见的差值。按照 Meta-Learner 家族的经典分类大致有这么几种做法S-Learner 把干预变量当普通特征塞进一个模型简单但容易把干预效应“稀释”掉T-Learner 给干预组和对照组各训一个模型然后相减思路直观样本不均衡时会抖得厉害X-Learner 是 T-Learner 的加强版多了两步纠偏专门处理组间样本量差异大的情况还有直接对增益建模的类方法比如带变换的标签方法思路更讨巧但对数据质量要求更苛刻。我先把几种方案的差异摊开说清楚因为选型是后面所有工作的地基方案核心思路优势主要短板适用场景S-Learner干预变量作为特征进单一模型实现最快、样本利用率高干预效应被样本多的组主导易趋向于零组间均衡、特征维度低T-Learner两组分别建模后相减逻辑直观、易解释小组样本少时方差极大组间样本量接近X-Learner两组建模后互相插补残差再融合小组样本少时更稳、可输出个体级 CATE流程环节多、实现稍复杂干预组远小于对照组直接增益建模对变换后的标签直接回归目标直接、单模型强假设、标签噪声敏感实验设计干净、样本充足我做营销场景十有八九落在 X-Learner 这一栏。原因只有一个样本比例。真实投放里干预组的量永远是被预算卡死的而对照组可以随便扩30:1 甚至 100:1 都不稀奇。在这种失衡下T-Learner 中干预组的那个模型基本是靠几万条数据硬扛方差大得吓人X-Learner 多出来的插补步骤就是专门来压这个方差的。2.2 X-Learner 的两阶段逻辑用一句话讲清X-Learner 的做法可以拆成“先各自估、再交叉补、最后加权合”三步。设干预组为 T、对照组为 C。第一步分别在 T 组和 C 组上训练两个结果模型。记mu1(x)是干预组模型预测的购买概率mu0(x)是对照组模型预测的购买概率。第二步是它的精髓交叉插补残差。对于干预组里每一个真实用户我们观测到了他接受干预后的真实结果Y1但我们不知道他如果不接受干预会怎样于是用对照组的模型mu0(x)去估这个反事实值得到一个插补出来的个体增益D1 Y1 - mu0(x)。同理对对照组每个用户用干预组的模型去估他的反事实结果得到D0 mu1(x) - Y0。第三步用这两批插补出来的增益当作新标签各自再训一个模型然后按倾向得分就是这个人被分到干预组的概率加权融合成最终的 CATE 估计。这套设计的巧妙之处在于样本少的那一组干预组不再孤立地自证增益它会借用对照组模型做参照样本多的对照组也会借用干预组模型做参照。两边互相打光方差自然就压下来了。我第一次看到这个结构的时候觉得有点像“互相借作业抄”但数学上确实成立前提是结果模型本身估得不太离谱。2.3 一个必须提前说清的取舍X-Learner 不是银弹。它的稳定性建立在两个假设上一是无混淆假设即所有同时影响干预分配和结果的因素都被观测到并进了特征二是SUTVA即一个人的结果不受其他人是否被干预的影响。第二个在营销里往往会被打破——优惠券在社群里被转发、同事之间互相推荐都会造成溢出。这种情况下算出来的增益是有偏的而且偏向哪个方向不好说。我在项目里处理这个的办法很朴素一是尽量按用户 ID 做社交隔离圈选把强关联的用户尽量分到同一侧二是把实验窗口拉短减少传播时间。做不到的话至少在报告里明确写上“本次增益估计可能受溢出效应影响”别把数字说得太满。这一点很多人会跳过但我吃过亏某次大促期间社群里券被大规模分享模型算出来的增益高得离谱后来拆开看很大一部分“增量”其实是同一批人换号重复领取。提示如果你的干预是公开渠道投放信息流、开屏溢出效应基本无法避免这时 Uplift 分数更适合做排序参考而不是绝对增量预估。别拿它去算 ROI 的绝对值。3. 数据与特征Uplift 建模里最容易翻车的一段3.1 标签和时间窗口的定义比模型重要十倍Uplift 建模里最容易崩的不是模型是标签。我把这件事的顺序搞反过代价是重跑了两周。先说标签。这里有两列一列是干预标识一列是结果变量。结果变量要清楚定义“什么算买”——是下单、支付成功、还是核销券三者差别巨大。券的核销率天然低于下单率如果拿核销当标签模型学出来的是“谁会核销券”而不是“谁会被券推动下单”这两个人群不完全重叠。我的做法是主标签用支付成功核销作为辅助特征两个都保留效果对比后再定。时间窗口更讲究。窗口太短很多延迟购买的人被误判成“没买”增益被低估窗口太长混进来的自然复购和外部因素太多因果归因不清。我的经验值是快消、外卖类 7 天够用低频高客单家电、教育得 14 到 30 天订阅类要看续费周期至少覆盖一个完整周期。还有一个坑干预时间和结果时间的错位。券是周一发的用户周五才用中间他可能还做了什么如果他在周三自己搜了关键词进来那这次购买到底算不算券的功劳我在特征里会加一列“干预后是否发生主动访问”作为归因置信度的修正项必要时把这类样本单独切出来观察。3.2 特征工程把“羊毛党”和“真需求”区分开Uplift 模型的特征和普通预测模型特征高度重合但侧重点不同。普通模型关心“会不会买”Uplift 模型关心“会不会被改变”所以那些能刻画用户价格敏感度、决策惯性和历史促销响应的特征格外重要。我一般会分四组来构造基础画像年龄段、城市等级、注册时长、会员等级。这些是打底别指望它们有强解释力但少了会让模型缺少分层骨架。历史行为近 30/90 天订单数、客单价、加购未付款次数、浏览深度、最近一次下单距今天数。注意时序必须严格取干预时点之前的数据这条我单独强调凡是用到未来信息的特征整个模型结论全废。促销响应史过去 N 次促销活动中该用户是否参与、参与后的下单金额变化、近一年领券张数和核销率。这是 Uplift 模型的“灵魂特征”因为它直接刻画了这个人对优惠的敏感程度。价格相关历史订单的平均折扣率、是否曾购买高折扣商品、购物车里是否有降价提醒。这类特征在树模型里会把“羊毛党”和“真需求用户”切开。这里要特别提一个独家经验给每个用户算一个“历史领券核销率”和“历史无券下单占比”的交叉特征。领券核销率高、无券下单占比也高的人很可能是价格不敏感但爱占便宜的成熟用户领券核销率高、无券下单占比低的人才是典型的券驱动型。这个交叉特征在我做过的几次项目里重要性排名几乎都在前十。3.3 处理组与对照组的可比性检查在跑任何模型之前我必做一件事倾向得分分布对比 关键特征的标准差对比。如果两组的特征分布差异巨大说明分组本身就有偏模型学出来的差异可能来自“用户本来就不同”而不是“干预起了作用”。检查方法很土但极其有效把所有特征按组算均值和方差算标准化差异超过 0.1 的特征单独拿出来看。发现大的偏差就做倾向打分加权IPW或者按倾向分做分层。这一步花半天能省掉后面反复怀疑模型的几周。注意很多团队直接拿运营后台的“随机分流”当随机实验实际上分流逻辑里常有“排除高价值用户”“优先覆盖新客”之类的隐藏规则这不是真随机。分组不可比是 Uplift 项目最常见的“假失败”原因不是模型不行是数据从源头就歪了。4. X-Learner 实操从模拟数据到可解释的增益分数4.1 用模拟数据把链路跑通先用numpy和sklearn构造一个带真实增益的数据集方便验证模型是不是真的把增益学出来了。这里我故意把干预组做小模拟真实投放的失衡结构。import numpy as np import pandas as pd from sklearn.ensemble import GradientBoostingClassifier from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split rng np.random.default_rng(42) n 120000 # 5 个基础特征 X rng.normal(size(n, 5)) X[:, 0] X[:, 0] * 0.6 2.0 # 用户活跃度 X[:, 1] np.abs(X[:, 1]) * 1.5 # 价格敏感度 # 倾向得分干预组天然少于对照组 linear 0.9 * X[:, 0] - 0.7 * X[:, 1] - 1.8 pscore 1 / (1 np.exp(-linear)) T rng.binomial(1, pscore * 0.35) # 整体干预比例压到 ~12% # 真实增益只对部分人有效且随特征变化 tau np.where((X[:, 0] 2.0) (X[:, 1] 1.2), 0.18, 0.02) tau 0.04 * (X[:, 2] 0.5) # 基线购买概率 干预效应 base 1 / (1 np.exp(-(0.8 * X[:, 0] - 0.5 * X[:, 1] 0.3 * X[:, 2] - 1.2))) p np.clip(base T * tau, 0.01, 0.99) Y rng.binomial(1, p) df pd.DataFrame(X, columns[ff{i} for i in range(5)]) df[treatment] T df[label] Y print(df[treatment].mean(), df[label].mean())跑出来可以看到干预组占比在 12% 左右这个比例故意做得比真实场景温和一点方便调试。真实项目里我会在数据构造之后立刻检查增益的真实分布这里tau集中在 0.02 和 0.18 两档中间的过渡人群很少这正好可以用来检验模型是否能把人群切成清晰的几段。如果模型输出的增益分数是一坨高斯分布说明它没学到结构。4.2 基线模型与 X-Learner 的完整实现为了方便你抄作业我把 X-Learner 的核心步骤写成一个独立函数不依赖第三方因果库纯用sklearn拼出来逻辑透明出问题好改。def x_learner(df, feats, treat_coltreatment, label_collabel): d_t df[df[treat_col] 1] d_c df[df[treat_col] 0] # 阶段一两组的结局模型 m1 GradientBoostingClassifier( n_estimators200, learning_rate0.05, max_depth3, subsample0.8, random_state42) m0 GradientBoostingClassifier( n_estimators200, learning_rate0.05, max_depth3, subsample0.8, random_state42) m1.fit(d_t[feats], d_t[label_col]) m0.fit(d_c[feats], d_c[label_col]) # 阶段二交叉插补个体增益 d1 d_t[label_col].values - m0.predict_proba(d_t[feats])[:, 1] d0 m1.predict_proba(d_c[feats])[:, 1] - d_c[label_col].values # 若干预组较小两段加权前的两个增益模型 tau1_model GradientBoostingClassifier( n_estimators120, learning_rate0.05, max_depth2, random_state42) tau0_model GradientBoostingClassifier( n_estimators120, learning_rate0.05, max_depth2, random_state42) tau1_model.fit(d_t[feats], (d1 0).astype(int)) tau0_model.fit(d_c[feats], (d0 0).astype(int)) # 倾向得分模型用于加权融合 ps LogisticRegression(max_iter500) ps.fit(df[feats], df[treat_col]) g ps.predict_proba(df[feats])[:, 1] def predict_uplift(X_new): p1 tau1_model.predict_proba(X_new)[:, 1] p0 tau0_model.predict_proba(X_new)[:, 1] ps_new ps.predict_proba(X_new)[:, 1] return ps_new * p1 (1 - ps_new) * p0 return predict_uplift, (m1, m0, tau1_model, tau0_model, ps)有一点必须说明这也是我踩过的坑交叉插补得到的d1、d0是连续值不是 0/1 标签。上面代码为了用分类器把连续增益转成了“增益是否为正”的二分类问题这是我在实战里的简化处理好处是抗噪、输出稳定坏处是失去了增益的幅度信息。如果你需要精确的增量金额预估就把第二阶段的模型换成回归器如GradientBoostingRegressor直接拟合d1、d0。我在做“发多少面额”这类决策时会用回归版做“发给谁”这类排序决策时用分类版前者重幅度后者重排序别搞混。4.3 参数选择不是照着别人的配置抄X-Learner 的几个关键参数我按重要性排一下。第一阶段结果模型的深度。这里我用max_depth3理由是结果模型的职责是估准“基础购买概率”它不需要捕捉太复杂的高阶交互深了容易在小组样本上过拟合。我在一个 3 万干预组样本的项目里试过max_depth6叠加n_estimators400验证集上增益曲线的分桶单调性明显变差典型过拟合表现。第二阶段增益模型的深度。我用max_depth2更浅。原因是增益模型面对的是插补出来的噪声标签容噪能力比拟合能力重要。这一条我摔得最狠最开始按常规配置用深度 4线上跑出来 Top10% 人群的增益排序几乎随机把深度降到 2、树数降到 120 之后才稳定。倾向得分模型的复杂度。这个我倾向用逻辑回归而不是树模型。原因很实际倾向分数的用途是加权加权需要的是校准好的概率而树模型的概率输出在极端区域常常不够平滑加权时会把少数样本的权重放大到失真。逻辑回归加正则输出平滑是更稳的选择。采样策略。干预组样本少时我不做简单过采样而是用class_weight或者在数据构造阶段按倾向分分层重采样。SMOTE 这类方法我也试过在 Uplift 场景里效果不稳定因为它会在特征空间里插值造样本但增益是因果量两个真实样本中间插一个合成样本它的增益没有物理意义。参数我的常用值调大后的表现调小后的表现结果模型 max_depth3小组过拟合、排序抖动欠拟合、增益被压平结果模型 n_estimators200收益递减、训练变慢欠拟合增益模型 max_depth2噪声放大、排序失稳欠拟合、增益无区分度增益模型 n_estimators120边际收益低明显欠拟合学习率0.05需同步减少树数收敛不足4.4 怎么验证它真的学到了“增益”而不是“概率”这一步是分出内行和外行的分水岭。普通模型看 AUC 就行Uplift 模型看 AUC 会骗你——一个把“购买概率”学得很准的模型AUC 可以很高但它的增益排序可能完全是乱的。我用三类校验缺一不可第一类增益分桶校验。把人群按预测增益从高到低切 10 桶算每桶的真实增益用各桶内干预组和对照组的真实购买率之差来估。理想情况下这条线是单调下降的。这条线如果不单调先别急着调模型回头查分组可比性。第二类Qini 曲线与 Qini 系数。Qini 是 Uplift 建模的标准评估曲线横轴是按增益排序后覆盖的人群比例纵轴是累计增量。和随机基线之间的面积就是 Qini 系数。它比 AUC 更贴合“我要按排序投放”的实际目标。第三类Top 分位的人群画像回看。纯数据之外我一定会把 Top 10% 的人群特征拉出来看问一句这些人讲不讲得通如果 Top 人群全是“注册 7 天内、零订单、零浏览”的僵尸号那模型大概率在拟合噪声。我在一次项目里就发现 Top 人群全是“加购了但从未下单”的用户逻辑上非常合理——他们会因为券而下定决心于是把这批人单独拿出来做了定向投放效果比大盘好一大截。下面给出 Qini 的计算代码直接可用def qini_curve(y_true, t, uplift_score, n_bins100): order np.argsort(-uplift_score) y_sorted, t_sorted y_true[order], t[order] n len(y_true) frac np.arange(1, n 1) / n # 累计干预组转化 / 对照组转化按比例缩放 cum_t_y np.cumsum(y_sorted * t_sorted) cum_c_y np.cumsum(y_sorted * (1 - t_sorted)) n_t np.cumsum(t_sorted) n_c np.cumsum(1 - t_sorted) # 避免除零 n_c np.where(n_c 0, 1, n_c) n_t np.where(n_t 0, 1, n_t) treated_rate cum_t_y / n_t control_rate cum_c_y / n_c qini (treated_rate - control_rate) * frac return frac, qini用这段代码画图横轴是人群比例纵轴是累计增量观察它和随机线一条斜线的分离度。分离得越开、到达峰值的比例越靠前说明模型的投放效率越高。5. 上线之后分数怎么用以及问题怎么查5.1 分数落地分档策略比模型精度更有商业价值模型跑完最忌讳的就是拿一个连续分数直接扔给运营。我给运营的东西一定是分档的而且每档配了明确的动作。典型的分档逻辑是这样按预测增益的百分位切成四档高增益且高基线概率的档给小额券比如满 99 减 10因为这些人本来就大概率会买我们只需要一点点推动力高增益但低基线概率的档给大额券这批人需要用强刺激撬动低增益高基线的档不给券省下来的预算就是净赚这是 Uplift 最容易被低估的价值——它帮你省的钱往往比帮你赚的钱更多低增益低基线的档不进这次投放留着做长期培育。这个分档表我会直接做成运营后台可查的标签每周刷新。有意思的是第一次给运营看这个表的时候他们的反应通常是“怎么这么多人被标成不用发券”等看到试点结果——同等预算下增量提升了三成多——态度就变了。增益档基线概率档券策略预期增量备注高高小额券中省预算守住转化高低大额券高核心撬动对象低高不发极低白花钱区坚决砍掉低低不发/培育低长期运营不做短促5.2 问题排查速查表上线之后一定会遇到各种怪现象我把高频的整理成一张表方便你对照。现象最可能的原因排查动作解决方向增益分数几乎全集中在 0 附近特征里混入强相关变量把干预效应盖住检查是否有“是否领券”类特征被当作特征而非标签剔除后验特征增益分桶曲线不单调分组不可比或样本太少检查倾向分分布、各组样本量IPW 加权或扩样本线上效果远低于离线评估溢出效应或时间窗口定义错检查是否有社群传播、窗口是否过短缩短窗口、做社交隔离Top 人群画像不合理模型拟合噪声或标签泄漏抽查 Top 样本原始数据清理特征、修正标签换一周数据模型重跑排序大改干预组样本太少模型不稳定看每档人群规模补实验样本或做平滑增益为正但实际 GMV 没涨增量来自低毛利商品或券核销挤占拆分商品结构和毛利把利润而不是 GMV 作为优化目标我特别想强调最后一行。增益建模的目标如果设成“购买概率”模型会去找最容易转化的那批人如果设成“金额”它会导向高客单人群如果设成“毛利”结论又会变。优化目标的选择本身就是一个业务决策不要让算法同学默认用“购买概率”糊过去。我见过一个项目模型很漂亮增量也确实存在但增量全集中在低毛利品类扣掉券成本之后是亏的。这不是模型的问题是目标函数定错了。5.3 几个我认为最重要的实操心得第一先跑通 T-Learner 再去碰 X-Learner。别嫌 T-Learner 粗糙它的分桶曲线和 Qini 是 X-Learner 的天然对照两组结果放一起比对你才能确认多出来的那两步插补到底带来了多少实际收益。如果 T-Learner 和 X-Learner 结论差异巨大而你解释不了原因那多半是哪里写错了。第二留一组“老办法选出来的人”做对照。这是最难说服团队但最值得做的一步。拿历史规则圈人作为基线策略新模型圈人作为实验策略同一批预算同时投放直接比增量。这个设计能回答“模型到底有没有用”这个终极问题比任何离线指标都硬。第三别把增益分数当成真值。它是估计量有方差。我在分档时会在边界附近做软处理把相邻两档的分数用相近动作避免因为模型抖动导致同一类用户这周发券下周不发体验上很糟糕。稳定性和精度之间营销场景里我更偏稳定。第四定期重训但别频繁换模型结构。人群的价格敏感度会随大促节奏变化重训必要但每次重训都改结构你永远不知道效果变化来自数据还是结构。我的节奏是数据每月更新、结构半年评估一次。关于这条链路后续还能怎么扩展我自己在试的方向是把 Uplift 和券面额联合优化——不只是“发给谁”而是“给谁发多少”。思路是把面额也当成一个多值干预用多臂的 Uplift 方法做这样就从一个二分类问题变成了一个多处理问题复杂度上了一个台阶但在预算固定的场景里收益确实比单纯选人更明显。这个我还在摸索有稳定结论再单独整理。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑