
最近这段时间好几个做策略的朋友都在问同一个问题营销预算就那么点券不能乱发、push不能乱推到底应该把资源花在哪些人身上传统做法是搭一个响应模型预测用户转化概率然后按概率从高到低去触达。可你仔细想一下这种排序会把一类用户排在最前面——他们本来就会买你触达不触达结果都一样。真正的利润空间藏在那些“你不动他他就不动”的用户里。这里面的核心就是增量模型也就是 Uplift Model。这篇文章我尽量用做过的项目经验来说事。内容围绕非随机观测数据场景下的 Uplift 工业级实践重点讲清楚三件事为什么传统的响应模型在策略优化里不够用、非随机观测数据到底会引入什么问题、以及从数据准备、模型训练到离线评估和上线验证工业界实际跑通这条路需要做什么。适合正在做推荐、营销触达、用户运营策略的数据科学和算法工程师也适合想从“预测”思维转向“因果”思维的偏策略产品同学阅读。1. 先搞清楚Uplift 到底在优化什么1.1 从用户分层说起响应模型为什么不够用拿最常见的发券场景举例子。假设你有三个用户群体同一时间窗口内他们的自然转化率和发券后转化率如下用户群不触达时的转化率触达后的转化率差值增量A类用户0.1%0.9%0.8%高潜用户B类5%5.4%0.4%普通用户C类0.5%2.0%1.5%如果按传统响应模型模型大概率会把 B 类用户排在最高优先级因为他们的绝对转化率最高。可是仔细算一笔账B 类用户就算不触达他也会买这 5% 的转化是自然发生的你发的券基本是“白送”C 类用户虽然绝对转化率低但触达前后转化率从 0.5% 提到 2.0%多出来的 1.5 个百分点才是这次触达真正带来的生意增量。策略同学关心的是增量的钱而不是总转化率的钱。这就是 Uplift 要解决的问题它不预测“用户会不会转化”而是预测“我的触达行为能让某个用户的转化概率提升多少”。用公式表示就是[ \tau(x) P(Y1 \mid T1, Xx) - P(Y1 \mid T0, Xx) ]这个 (\tau(x)) 就是我们常说的 CATEConditional Average Treatment Effect也就是条件平均处理效应。Uplift 模型说白了就是去估计这个差值。1.2 增量视角下的决策逻辑搞清楚这个基础概念之后你就能理解为什么很多团队从响应模型切到 Uplift 之后会经历一个阵痛期模型的 AUC 可能没有之前好看了线上整体转化率甚至可能微降但营销成本降了ROI 反而涨了。这不是模型变差了而是优化目标变了。在实际决策里资源有限的时候按增量排序和按响应概率排序完全是两套逻辑。增量排序天然会把“自然转化率低、但容易被触达打动”的用户找出来。对一个成熟的营销系统来说触达通常是有成本的无论是短信费、优惠券补贴还是用户骚扰投诉的风险容忍度你要把预算分配给每个用户身上“单价增量利润最高”的那批人。所以我一直建议在做 Uplift 之前团队内部得先把指标语言统一以后比的是增量效果而不是绝对转化率。2. 非随机观测数据的问题到底出在哪2.1 随机实验是黄金标准但现实往往没有如果给我一个完全随机分组的 A/B 实验数据那 Uplift 建模会简单很多因为 treatment 和对照组在期望意义上没有系统性差异直接用两个组的转化率差就可以估计增量再配合一些模型方法去刻画个体差异即可。但工业界的真实情况往往是我们手里根本没有干净的随机实验样本。大量数据来自业务自然运行产生的日志这里面有人是运营挑出来去触达的有人是自己点进来的有人被渠道规则屏蔽了。这些样本都不是随机分配的我们拿到的数据是“观测数据”而不是“实验数据”。换句话说我们观测到的不是随机分组下的表现而是各种规则、算法、用户自选择共同作用后的结果。这里有个核心难点既然是观测数据那么用户被触达这件事本身是有偏的。某些高价值用户更容易被运营选中或者某些特定活跃度的用户更容易点击广告这样一来treatment 组和 control 组的人群本来就不一样直接比较他们的转化率得到的是相关性而不是因果效应。2.2 一个例子看懂选择偏差我之前做过一个会员权益项目当时的原始数据里有非常明显的选择偏差。业务团队喜欢邀请高活跃用户领取权益理由是他们在平台上最“听话”响应率高。结果数据落库之后我们看到“领了权益的人”次月留存率显著高于“没领权益的人”看起来权益带来了巨大的提升。可你再往深看一层高活跃用户即使不领权益次月留存率也很高。真正需要权益刺激的低活跃、中长尾用户业务团队反而很少触达他们因为觉得他们“唤不醒”。这时如果我们直接拿历史数据训练 Uplift 模型模型会学到什么它会学到“被触达的人都是高价值人群”从而把高价值用户排到最前面——这恰恰是我们要避免的错误。这就是非随机观测数据引入的选择偏差。形式化一点说我们用 potential outcomes 的框架来描述每个用户有两个潜在结果 (Y(1)) 和 (Y(0))分别代表触达和不触达时的转化结果但我们只能观测到其中一个。如果 treatment 分配和潜在结果之间有关系那么直接用观测到的 (Y) 去估计增量就会得到有偏的结果。2.3 工业级数据里的典型混杂来源在真实业务里常见的造成 treatment 非随机的因素有几种基本逃不出这个范围运营规则干预业务方手工挑人群、黑名单、白名单、灰度放量规则这些都会让触达人群产生结构性偏向。用户自选择用户是否点击 banner、是否完成领取动作和用户自身的偏好、活跃度强相关。很多所谓“领取用户”其实是本来就对权益感兴趣的人。渠道和场景差异App 推送、短信、弹窗等触达渠道本身触达的人群不同。比如短信可能偏高年龄段push 偏年轻用户这些差异容易和产品偏好混杂在一起。时间趋势不同时期的用户群体构成不一样历史 campaign 的对照组经常来自不同月份直接拿跨期样本对比会混入季节效应和产品版本带来的变化。这些混杂因素就是我们常说的 confounder。模型要做的事情就是尽量把这些 confounder 控制住让 treatment 组和 control 组在一些关键维度上“看起来像”随机分组。3. 把非随机数据掰正的几种主流思路3.1 倾向得分加权让两组人群重新可比最基本的处理思路是用倾向得分Propensity Score来调整两组人群在特征分布上的差异。倾向得分定义为[ e(x) P(T1 \mid Xx) ]也就是在给定特征 (x) 的条件下这个用户被触达的概率。我们可以用逻辑回归、GBDT 这类分类模型来估计这个概率。有了倾向得分之后可以用 IPWInverse Probability Weighting给样本赋权把两组人群重新配平。理论上在强可忽略性假设下给定 (X) 后treatment 分配与潜在结果独立IPW 可以给出无偏的估计。在实际操作里我一般会先看倾向得分的重叠区间。如果两个组的倾向得分分布完全不重叠那说明数据里存在几乎确定要触达和几乎确定不触达的人群这两个人群之间进行因果推断非常危险。这时候指望一个权重公式去修正是不太靠谱的。3.2 元学习器家族T-learner、S-learner、X-learner在工业界落地 Uplift最常用的还是元学习器Meta-Learner方案因为它的思路非常直观可以复用大量现成的机器学习基建。T-learner用 treatment 组数据训练一个模型 (f_1(x)) 预测转化率再用 control 组数据训练另一个模型 (f_0(x))然后 ( \hat{\tau}(x) f_1(x) - f_0(x) )。这个方案的优点是逻辑简单、两个模型可以完全独立调优缺点是会把样本量一分为二在小样本场景下模型容易欠拟合。S-learner在一个模型里把 treatment 作为普通特征喂进去即用 ((X, T)) 预测 (Y)然后预测 (T1) 和 (T0) 两个结果算差。好处是不用牺牲样本量但在很多业务场景里treatment 特征太弱容易被其他强特征淹没导致增量被压缩得接近 0。X-learner它先分别训练两个模型预测转化率然后用观测数据去算“缺失的增量”再训练第三个模型去拟合这个增量。X-learner 对实验组和对照组样本不均衡的情况更稳健。如果你的数据里 treatment 组很小但又想尽量利用对照组的信息X-learner 是个不错的选择。给一个 T-learner 的伪代码方便直观理解from lightgbm import LGBMClassifier model_treat LGBMClassifier(...) model_control LGBMClassifier(...) model_treat.fit(X_train[treat_mask], y_train[treat_mask]) model_control.fit(X_train[~treat_mask], y_train[~treat_mask]) # 推断阶段所有人分别预测两种状态下的转化概率 tau_pred model_treat.predict_proba(X_test)[:, 1] - model_control.predict_proba(X_test)[:, 1]实际项目中T-learner 是我最常用的 baseline。它的下限很高而且每个子模型都可以做很成熟的特征工程和超参调优生产系统里部署也方便。3.3 树模型家族因果森林为什么在工业界吃香近年来因果森林Causal Forest在工业界的热度上升很快。它本质上是一种专门为估计 CATE 修改过的随机森林在切分节点的时候不是按照普通的分类误差或方差减少来选特征和阈值而是专门优化“组间增量差异”的异质性。因果森林比较实用的好处有几个一是它可以直接输出每个用户的 CATE 估计值和方差方便你做置信度判断二是它对特征重要性的解释天然比深度学习模型好业务方愿意看三是做“数据诚实honest”处理之后过拟合控制比手工调 T-learner 要省心一些。但因果森林不是银弹。它在高维稀疏用户特征上的表现不如 GBDT 类模型如果特征工程做得很粗糙模型效果会很拉胯。我的建议是把它和 T-learner 放到一起做 ensemble或者作为交叉验证的对照模型不用一上来就替换掉原来的方案。4. 工业级实操一个完整的建模流程拆解4.1 数据准备与样本定义落到工程上第一步是定义清楚三条线用户对象、触达行为、目标事件。这里最容易踩雷的就是“什么叫触达”。拿短信营销举例你发出去了但用户没看到算不算触达如果算那 treatment 组里混了大量“无效触达”用户增量会被严重拉低如果不算又涉及用“用户是否点击”来定义 treatment这本身就和用户偏好强相关会引入自选择偏差。我的经验是从业务口径出发把 treatment 定义为“成功触达”一般用下发成功且没有被屏蔽这类确定性事件。如果业务方一定要看“有效阅读”那最好单独建一个“触达-有效”的标记通过特征来体现而不是直接改 treatment 定义。样本时间窗口也要反复确认。比如目标事件是“领取后 14 天内是否完成首购”那么处理组的样本必须从 14 天前那个时间点开始回溯对照组样本不能只取最近没被触达的用户因为最近没被触达很可能意味着他在黑白名单之外这与业务规则强相关会带来新的偏差。4.2 特征工程里的坑做 Uplift 的特征工程和普通转化模型有共通之处但多了很多细节要小心。我会把特征分成几个大块用户静态属性性别、年龄、注册渠道、设备类型、城市等级等。这些特征比较稳定主要用来刻画人群的固有差异。历史行为特征近 7/14/30 天的登录频率、浏览深度、交易金额、退款次数、优惠券使用历史。这类特征非常强对预测自然转化率特别有用。营销接触历史近 30 天收到多少条短信、打开邮件的比例、历史上点击过哪些 campaign 的素材。这些特征是处理非随机分配的关键尽量把“历史上为什么这个用户老是被选中触达”的信息捕捉进来以减小选择偏差。时间上下文特征星期几、是否大促前夕、最近一次活动距离今天的天数等。这里最大的坑是特征泄漏。我见过最典型的例子是用“用户是否在触达后访问了落地页”来作为特征预测转化。这等于把半条结果当成了输入。再就是时间穿越比如用未来 14 天的行为特征去预测未来 14 天是否转化训练的时候 AUC 高得离谱上线后立刻崩掉。处理这种问题最好的办法是设定严格的“特征截止时间”保证所有特征的时间戳都早于 treatment 发生时间。4.3 模型训练与调参细节模型层面我一般先用 T-learner 跑通基线然后配合倾向得分加权做一些对比。下面是训练阶段一个比较完整的流程骨架# 1. 用全量数据训练倾向得分模型 # 目标估计 P(T1 | X) ps_model LGBMClassifier( n_estimators200, learning_rate0.05, max_depth4, num_leaves15 ) ps_model.fit(X, T) propensity ps_model.predict_proba(X)[:, 1] # 2. 训练 T-learner 的两个子模型 model_control LGBMClassifier( n_estimators500, learning_rate0.05, max_depth6, num_leaves31, colsample_bytree0.8, subsample0.9 ) model_control.fit( X[~T_mask], y[~T_mask], sample_weight1.0 / (1.0 - propensity[~T_mask]) ) model_treat LGBMClassifier(...) model_treat.fit( X[T_mask], y[T_mask], sample_weight1.0 / propensity[T_mask] ) # 3. 预测增量 uplift_pred model_treat.predict_proba(X)[:, 1] - model_control.predict_proba(X)[:, 1]这段代码里的 sample_weight 就是用倾向得分做加权作用是让两组人群在特征分布上更接近。实际用的时候要小心倾向得分太极端的时候权重会非常大导致样本方差爆炸。一般的处理办法是做权重裁剪clipping比如把权重限制在 [0.1, 10] 这个范围或者对倾向得分做截断处理。调参上GBDT 类模型没有太多捷径。我的习惯是先固定一个较浅的树深4-6用早停和比较小的学习率去控制过拟合然后粗调叶子节点数和特征采样比例最后再根据验证集上的 AUUC 做一轮微调。注意验证集不能用简单的随机切分对于有时间结构的数据要按时间切分否则会因为信息穿越导致评估结果虚高。4.4 从增量到决策不能只看排序模型训练出来之后产出的是一个增量分数。但在实际业务里光有一个分数是不够的。你不能只把增量分数最高的人拉出来发券因为那些人可能自然转化率也很低即使有增量绝对利润也撑不起触达成本。所以工业界一般会做一个二维决策矩阵横轴是自然转化率 (P(Y1|T0))纵轴是增量 (\tau(x))。根据两条线切出四块象限自然转化率增量策略第一象限高高重点触达这是最优质客群第二象限低高选择性触达看成本与利润空间第三象限低低不触达省下资源第四象限高低不触达他们自己会做决策时还要算上边际利润一个用户的期望净利润等于触达带来的期望增量利润 (\tau(x) \times) 单次转化毛利 - 触达成本只有这个值大于 0发券或者 push 才是划算的。这一步很多团队容易漏最后模型得分很高业务却不见钱就是因为没有把成本和毛利算进去。5. 效果评估没有实验数据怎么证明增量价值5.1 Qini 曲线和 AUUCUplift 模型的离线评估不能只看 AUC因为 AUC 衡量的是排序能力里“绝对转化概率”的排序能力而不是“增量”的排序能力。业界一般用 Qini 曲线。Qini 曲线的思想是把测试集样本按模型预测的增量从高到低排序然后逐批累加增量收益。横轴是触达的人数比例纵轴是累积增量。理想情况下最前面的用户应该带来最高的增量所以曲线应该快速上升然后趋于平缓。曲线下面积就是 AUUC。计算时要注意对照组没有增量所以累积增量的计算要用处理组里的实际转化人数减去对照组按比例折算出来的“如果没被触达也会转化的人数”。核心代码如下def qini_curve(df, treatment_col, outcome_col, uplift_col): # df 必须包含处理组和对照组uplift_col 是模型预测的增量分 df df.sort_values(uplift_col, ascendingFalse).reset_index(dropTrue) n_treat (df[treatment_col] 1).sum() n_control (df[treatment_col] 0).sum() cum_treat_outcome df[df[treatment_col] 1][outcome_col].cumsum() cum_control_outcome df[df[treatment_col] 0][outcome_col].cumsum() # 对累计下标做比例折算 # 实际工程里建议用分桶计算避免逐行 cumsum 在大数据量下的性能问题 sample_idx np.arange(1, len(df) 1) adjusted_control cum_control_outcome / n_control * n_treat qini cum_treat_outcome - adjusted_control return qini一句话总结Qini 曲线越高、越早拉起说明模型把真正有增量的人排到了前面。AUUC 则是对 Qini 曲线做归一化后的面积指标作为模型调参时的主要参考。5.2 没有随机实验时怎么粗略验证现实情况里我们经常没有现成的随机实验数据来做评估。这时候可以想一些变通的办法。一种做法是找“局部随机”的样本。比如某一次活动因为产品 bug 导致随机分流失败、实际上变成了一半人收到触达一半人没收到虽然形式上不完美但这个样本的可信度远高于普通观测数据。历史上这种小事故留下的数据反而可能是珍贵的因果评估资产。另一种做法是倾向得分匹配。拿训练好的倾向得分模型在处理组和对照组里做近邻匹配构造一个“伪随机”的评估样本然后在这个样本上计算 Qini。这个做法有它的缺陷只能控制已观测到的混淆变量遗漏变量还是会有影响但作为粗验证已经够了。最关键的还是上线闭环。Uplift 模型如果只停留在离线评估说服力是不够的。就算数据再有限也建议在正式放量前做一个小流量的随机对照实验把高增量组和随机组做个对比看实际增量是否和离线预估一致。我见过太多团队省掉这一步直接全量上线结果效果打折都不知道是模型问题还是执行问题。6. 踩坑记录与后续扩展6.1 六个真实踩过的坑这里列几个我在实际项目中吃过大亏的点给后来人提个醒。第一把响应模型和 Uplift 模型混为一谈。这并不是概念问题而是直接导致错误决策的问题。一次项目中同事用历史报名率模型筛选触达人群线上转化率看起来还不错但去掉自然转化后增量几乎为零钱全花在补贴“本来就会买”的用户身上了。第二严重忽略倾向得分的重叠区间。有一版数据里运营基本只挑高活用户做触达对照组全是低活用户。训练 Uplift 时倾向得分重叠区间非常小虽然权重技巧可以强行让模型收敛但上线后一测就失真了。第三混淆变量缺失。最典型的案例是没有把用户历史优惠券敏感度放进特征。喜欢领券的用户更容易被触达本身的转化行为也和普通用户完全不同如果不控制这类变量模型的增量估计里混进了“用户类型效应”风险非常大。第四时间穿越。我用过一次未来 7 天的访问特征来预测未来 14 天购率线下 AUUC 高得离谱一上线上就扑街。后来严格按特征截止时间重做了特征效果才回归正常。第五用训练集的数据做 Qini 评估。模型在训练集上记住了个体特征Qini 曲线会很好看但放到新数据上增量会严重衰减。一定留出独立的验证集最好按时间留出最近一段样本。第六上线之前不做 A/B 验证。不管离线指标做得多好没有随机实验的验证就无法证明模型在真实环境中带来了增量。哪怕只上 5% 流量也要先跑一个短周期的 A/B。我把这些常见问题整理成了一个速查表方便日常排查问题现象排查方向特征泄漏离线指标虚高线上失效检查特征时间戳是否早于 treatment选择偏差高增量人群集中在高活用户查看倾向得分分布与重叠区间混淆变量缺失模型增量估计不合理检查是否缺少用户价值、优惠券敏感度等关键特征评估方法错误AUUC 不稳定确认验证集是否独立、是否按时间切分线上无验证增量无法归因尽快补随机分层实验成本未计入有增量但不赚钱把边际利润和触达成本纳入决策公式6.2 方法扩展从单次触达到长效策略上面说的思路可以延伸到更复杂的场景。比如多臂multi-treatment问题不只有“触达/不触达”两种状态而是短信、push、弹窗、券包不同类型的选择这时候可以用 DR-learner 或者 R-learner 这类基于残差化的方法对多个 treatment 分别建模最后择优分配。再比如时序场景用户的转化行为有长期依赖单纯的截面模型可能不够可以考虑把触达策略和用户生命周期的状态结合起来做强化学习式的策略优化。不过这种方案工程复杂度高团队没有足够资源时我不建议一上来就上。还有一个实用的方向是“组级增量”建模。当个体粒度数据太稀疏、或者触达是面向家庭/企业级进行时可以先把用户聚合成群组在群组粒度上做 Uplift。工业界不少 To B 营销场景用这个方法效果明显好于个体级模型。从我的实际体会来说Uplift 模型在工业级落地的关键并不在于把某个高级算法调得多好而在于你愿不愿意把因果推断的思维贯穿到数据理解、指标定义、评估闭环和上线实验里。先把随机实验的基础设施搭起来哪怕最开始只是小流量后面所有观测数据建模的靠谱程度都会往上走一个台阶。最后再分享一个小技巧如果团队刚开始接触 Uplift不要急着上因果森林先用 T-learner 加倾向得分加权跑通一套离线评估和在线 A/B 的闭环把过程中的数据问题全部暴露一遍再逐步引入更复杂的模型会顺畅得多。模型是锦上添花数据口径和评估逻辑才是这根线的地基。