Skip to content

第13章 进阶演进方向 ​

13.1 概述 ​

前 12 章实现了一个完整的优惠券发放系统。但这只是起点——工业界的发券系统在持续演进。本章讨论几个重要的演进方向,帮你理解从"能用"到"好用"还有多远的路。

13.2 Meta-Learner 全家族 ​

第 6 章实现的 T-Learner 是最简单的 Uplift 建模方法。实际上,Meta-Learner 有一个完整的家族,每个成员有不同的优缺点。

S-Learner (Single Model) ​

思路:只训练一个模型,把 treatment 标记 T 作为特征之一。

Y^=f(X,T)τ^(x)=f(x,1)−f(x,0)
python
# S-Learner 伪代码
X_with_T = np.column_stack([X, treatment])
model = LGBMClassifier()
model.fit(X_with_T, y)

# 预测增量
X_treat = np.column_stack([X, np.ones(len(X))])
X_ctrl = np.column_stack([X, np.zeros(len(X))])
uplift = model.predict_proba(X_treat)[:, 1] - model.predict_proba(X_ctrl)[:, 1]

优点:样本利用率高(全部数据训练一个模型) 缺点:如果 treatment 效果弱,模型可能忽略 T 这个特征,导致 uplift 全为 0

T-Learner (Two Models) — 本项目采用 ​

思路:分别训练 treatment 和 control 两个模型。

τ^(x)=μ^1(x)−μ^0(x)

优点:简单直接,各组独立建模 缺点:两个模型分别在一半数据上训练,样本量减半;各自的预测误差累加

X-Learner ​

思路:用对方组的模型来估计个体的 treatment effect,然后在各自组上训练增量模型。

第一步:和 T-Learner 一样训练两个基模型

μ^0(x),μ^1(x)

第二步:计算伪标签

D~1i=Y1i−μ^0(X1i)(treatment 组)D~0i=μ^1(X0i)−Y0i(control 组)

第三步:训练两个增量模型

τ^1(x)=f1(x)拟合 D~1τ^0(x)=f0(x)拟合 D~0

第四步:加权融合

τ^(x)=g(x)⋅τ^0(x)+(1−g(x))⋅τ^1(x)

其中 g(x)=P(T=1∣X=x) 是倾向性得分 (propensity score)。

优点:在 treatment 组和 control 组样本量不均衡时特别有效 缺点:实现复杂,需要额外训练倾向性得分模型

R-Learner ​

思路:直接对残差建模,去除混淆效应。

τ^(x)=arg⁡minτ∑i[(Yi−m^(Xi))⏟结果残差−(Ti−e^(Xi))⏟treatment 残差⋅τ(Xi)]2

其中 m^(x) 是结果基线模型,e^(x) 是倾向性得分。

优点:理论性质好,在观察性数据中更鲁棒 缺点:实现复杂,需要自定义损失函数

DR-Learner (Doubly Robust) ​

思路:结合结果模型和倾向性得分,具有"双重鲁棒性"——两个模型中只要一个接近正确,估计就是一致的。

τ^DR(x)=μ^1(x)−μ^0(x)+T(Y−μ^1(x))e^(x)−(1−T)(Y−μ^0(x))1−e^(x)

优点:统计性质最优,对模型误设定更鲁棒 缺点:倾向性得分接近 0 或 1 时数值不稳定

对比总结 ​

方法模型数样本效率实现复杂度适用场景
S-Learner1★★★★★★效果强、数据充足
T-Learner2★★★★★通用首选
X-Learner4+★★★★★★★样本不均衡
R-Learner3★★★★★★★★观察性数据
DR-Learner3★★★★★★★★★高精度需求

推荐路线

T-Learner → X-Learner → DR-Learner。T-Learner 是 MVP 首选(本项目已实现),随着数据和工程能力的增长逐步升级。

13.3 Causal Forest ​

Causal Forest(因果森林)是非参数异质性因果效应估计的前沿方法(Athey & Imbens, 2018)。

核心思想:在随机森林的基础上,对每次分裂进行修改——不是最大化预测准确度,而是最大化子节点间 treatment effect 的差异。

python
# 使用 econml 库
from econml.dml import CausalForestDML

cf = CausalForestDML(
    model_t=LGBMClassifier(),    # treatment 模型
    model_y=LGBMRegressor(),     # outcome 模型
    n_estimators=1000,
    min_samples_leaf=10,
)
cf.fit(Y=y, T=treatment, X=X)
uplift = cf.effect(X)

优点:

  • 非参数方法,不需要假设函数形式
  • 自带置信区间(可以知道 uplift 估计的不确定性)
  • 自动发现异质性子群

缺点:

  • 计算成本高,不适合百万级实时场景
  • 需要 econml 库,依赖较重

13.4 多目标优化 ​

当前系统只优化增量 GMV。但实际业务中,优惠券的效果是多维度的:

max  α × 增量GMV + β × 7日留存提升 + γ × 品类渗透率
s.t. 总成本 ≤ 预算

Pareto 最优 ​

多目标优化没有唯一的最优解,而是一组 Pareto 最优解(帕累托前沿):

           ┌──────────────────────────┐
           │          ●               │
  留存提升  │      ●      ●            │ ← Pareto 前沿
           │    ●          ●          │
           │  ●        ●              │
           │        ●      ●         │
           └──────────────────────────┘
                    增量 GMV

Pareto 前沿上的点,不存在另一个解能同时在所有目标上更好。业务方在前沿上选择一个符合当前阶段的权衡点。

实现方向 ​

  1. 加权求和法:最简单,α+β+γ=1,通过调整权重实现不同侧重
  2. 约束法:将部分目标转为约束(如"留存提升不低于 x%"),只优化 GMV
  3. 进化算法:NSGA-II 等多目标进化算法,直接求 Pareto 前沿

13.5 强化学习方向 ​

Contextual Bandit ​

将面额选择建模为 Contextual Bandit 问题:

  • Context:用户特征 x
  • Action:面额 a∈{5,10,15,20,30,50}
  • Reward:增量 GMV(或转化)
π(a∣x)=arg⁡maxaQ(x,a)

每次发券后观察真实奖励,更新策略。这天然解决了 exploration-exploitation 问题。

python
# 伪代码:LinUCB
class LinUCB:
    def select_action(self, x):
        """对每个面额计算 UCB 上界,选最大的."""
        best_a, best_ucb = None, -float("inf")
        for a in actions:
            theta = self.theta[a]
            ucb = theta @ x + alpha * sqrt(x @ self.A_inv[a] @ x)
            if ucb > best_ucb:
                best_ucb = ucb
                best_a = a
        return best_a

    def update(self, x, a, reward):
        """观察奖励后更新参数."""
        self.A[a] += np.outer(x, x)
        self.b[a] += reward * x
        self.A_inv[a] = np.linalg.inv(self.A[a])
        self.theta[a] = self.A_inv[a] @ self.b[a]

对比当前方案 ​

维度Uplift + 贪心(当前)Contextual Bandit
训练离线批量在线增量
探索固定探索组 15%自动平衡探索/利用
面额选择启发式规则直接优化
延迟反馈需等 7 天观察窗口可处理延迟奖励
工程复杂度简单复杂(需要实时反馈流)

13.6 实时化演进 ​

当前系统是离线 + 在线查表模式。下一步演进方向是实时化:

mermaid
graph LR
    A[用户浏览事件] -->|Kafka| B[Flink 实时特征]
    B --> C[实时模型推理]
    C --> D[预算实时管控]
    D --> E[发券]
    E -->|反馈| F[在线学习更新模型]
    F --> C

实时特征计算 ​

用 Flink 替代 Pandas 做特征计算,支持滑动窗口聚合:

每个用户维护一个实时特征向量:
- 近 1 小时 PV
- 近 1 小时详情页浏览数
- 当前 session 浏览商品数
- 最近浏览商品的均价

实时预算管控 ​

离线预算优化是"一次性分完当天预算"。实时化后需要分时段管控:

日预算 100 万,按小时分配:
  00:00-06:00  → 5%  (低流量时段)
  06:00-10:00  → 15%
  10:00-14:00  → 25% (午间高峰)
  14:00-18:00  → 20%
  18:00-22:00  → 30% (晚间高峰)
  22:00-24:00  → 5%

每小时结束时,如果预算有剩余,顺延到下一小时。如果超支,降低 Uplift 阈值(让更少的人收到券)。

13.7 模型升级路线图 ​

综合以上演进方向,推荐的升级路线是:

Phase 1(当前): T-Learner + 贪心/拉格朗日 + 离线分配
    ↓
Phase 2: X-Learner + 多面额响应曲线 + 实时特征
    ↓
Phase 3: DR-Learner + Contextual Bandit 探索 + 实时预算管控
    ↓
Phase 4: Causal Forest + 多目标 Pareto 优化 + 在线学习

每个阶段的升级都建立在前一个阶段的基础上。不要跳步——Phase 1 的基础设施(AB 实验、风控、配置管理)是后续所有升级的前提。

本章小结 ​

  • Meta-Learner 家族:S / T / X / R / DR-Learner,T-Learner 是通用首选,X/DR 适合高级场景
  • Causal Forest:非参数因果推断方法,自带置信区间,但计算成本高
  • 多目标优化:从单纯的增量 GMV 扩展到 GMV + 留存 + 品类渗透的 Pareto 优化
  • Contextual Bandit:把面额选择建模为在线决策问题,自动平衡探索和利用
  • 实时化:Flink 实时特征 + 分时段预算管控 + 在线学习
  • 演进路线:T-Learner → X-Learner → DR-Learner → Causal Forest,基础设施先行

思考题 ​

  1. X-Learner 在 treatment/control 比例为 90/10 时比 T-Learner 好多少?为什么?
  2. 如果业务方要求"留存提升不低于 5%",如何把这个约束加入到当前的预算优化框架中?
  3. Contextual Bandit 的一个挑战是"延迟奖励"——发券后 7 天才知道用户有没有转化。你会如何处理这个问题?

基于 Uplift Modeling 的电商优惠券智能发放系统