笔记

异质性处理效应

异质性处理效应(HTE)

机器学习与因果推断

机器学习,我们知道其是一个预测工具,是为了估计条件期望函数E[YX]E[Y|X]。我们通过交叉验证,分为训练集与测试集,去优化参数,来使得模型预测能力最优。但,我们知道在预测学习的过程中,其是一种被动估计的方式,这意味着我们只能预测其最后输出的值,但我们却只能看着,没有办法去改变它。这很多时候就是不正确的做法,比如,我们可以通过一些措施来去影响最后的结果。

我们现在已经不再是一个观察者了,因为我们知道仅仅估计E[YX]E[Y|X]并不完整。我们知道在因果推断里面,我们对一些人进行TT处理,来去估计其效应。一样的,在机器学习里,我们也可以在条件期望函数种加入一项,而这个处理正是刻画我们对数据生成过程的影响,即处理效应:

E[YX,T]E[Y|X,T]

我们对这里的XXTT进行严谨的区分。 XX情景特征或者外生特征,即我们无法控制的,比如年龄、性别等,而处理变量TT则是我们可以决定的或者干预的,比如我们对不对这个人用药。所以因果推断就是在给定情景XX下,估计TTYY之间因果关系的过程。因此优化YY就只能是优化一个处理值TT

argmaxT E[YX,T]\underset{T}{argmax} \ E[Y|X, T]

现在举一个例子,假如我们知道教育对收入是有影响的,那么我们就会想,为教育付出多少代价是合理的,也就是说,我们实际上做的是教育的因果效应并对其进行优化:

argmaxEduc E[IncomeX,Educ]\underset{Educ}{argmax} \ E[Income|X, Educ]

在因果推断中,我们回答的是这个效应是正的,负的,还是没有因果效应。那么这里的XX是混杂效应,我们可以通过控制XX来识别因果效应;又或者,XX可以用来减小因果估计的方差。若XXYY良好的预测变量,那么可以用它来解释YY的变异,使得因果效应更加明显。而在机器学习里,我们不再关注二元论的结果,我们想要的不仅仅是平均处理效应,还要到底对什么样的群体有什么不一样的作用。我们将允许处理对某些人有积极影响,对另一些人却没有。我们现在希望实现处理的个性化,只把处理给予对其反应最好的人群。所以这里我们提出了异质性处理效应(Heterogeneous Treatment Effect):指同一项干预或政策在不同特征的个体或子群体中产生的影响(或效果)存在差异的现象。

从平均处理效应到条件平均处理效应(CATE)

我们之前学习的时候,大多数接触的是ATE或者LATE:

E[Y1Y0]orE[y(t)]E[Y_1 - Y_0] \quad or \quad E[y'(t)]

其中: y(t)y'(t)是响应函数或结果的处理导数。现在,我们开始考虑另外一个问题,我们应该对什么样的人进行处理。此时,一个决策可以在不同个体之间变化了,对某个个体施加处理可能是有利的,但对另一个则未必。我们希望个性化处理。此时,我们要估计的就是**条件平均处理效应(Conditional Average Treatment Effect)**了:

E[Y1Y0  X]orE[y(t)X]E[Y_1 - Y_0 \ |\ X] \quad\text{or}\quad E[y'(t)\,|\,X]

XX上条件意味着我们现在允许处理效应根据每个单位的特征而不同。同样在这里,我们认为并非所有个体对处理反应相同,我们想利用这种异质性来进行最优的分类。

那么,假设现在我们有一群人和一个处理变量(为了描述方便)。我们希望个性化处理,所以这时候,我们不再对所有个体一起处理,而是对客户进行分组,创建对处理反应不同的群组。那么不同个体对处理的反应由条件处理效应YT\frac{\partial Y}{\partial T}给出。

更严格地说,如果处理是二元的,例如是否发放优惠券、是否用药,那么我们关心的是:

τ(x)=E[Y(1)Y(0)X=x]\tau(x)=E[Y(1)-Y(0)\mid X=x]

这里的τ(x)\tau(x)就是CATE。它回答的问题是:在特征为xx的人群中,如果从不处理变成处理,平均结果会改变多少。

如果处理是连续的,例如价格、贷款金额、学习时长、运动时间,那么我们关心的是结果对处理强度的边际变化

τ(x,t)=E[Y(t)X=x]torE[y(t)X]\tau(x,t)=\frac{\partial E[Y(t)\mid X=x]}{\partial t} \quad or \quad E[y'(t)\mid X]

这时CATE可以理解为“条件敏感度”:其含义是在给定个体特征XX的情况下,处理TT稍微变化一个单位,结果YY平均会变化多少。

这里有一个很重要的关系:

ATE=E[τ(X)]ATE=E[\tau(X)]

也就是说,平均处理效应是把所有人的条件处理效应再平均一遍。ATE告诉我们“总体平均有没有用”,CATE则进一步告诉我们“对谁更有用、对谁没用、对谁可能有害”。

CATE与预测

如果只做普通机器学习,我们通常建立的是:

μ(x,t)=E[YX=x,T=t]\mu(x,t)=E[Y\mid X=x,T=t]

然后让模型尽量预测YY。但是个性化处理真正想知道的不是YY本身,而是YYTT变化的速度。也就是说,我们关注的是:

μ(x,t)t\frac{\partial \mu(x,t)}{\partial t}

或者在二元处理下:

μ(x,1)μ(x,0)\mu(x,1)-\mu(x,0)

我们把结果函数拆成两个部分来理解:

μ(x,t)=m(x)+tτ(x)\mu(x,t)=m(x)+t\cdot \tau(x)

其中m(x)m(x)表示这个人在不考虑处理变化时的基础水平τ(x)\tau(x)表示处理效应。普通预测模型可能非常擅长预测m(x)m(x),因为很多特征本来就能解释结果的高低;但个性化决策真正依赖的是τ(x)\tau(x)。如果模型只学会了“谁本来销量高、谁本来风险高”,却没有学会“谁对处理更敏感”,那么它对个性化处理就没有太大帮助。因此我们不是要把人按照预测结果YY分组,而是要按照预测处理效应τ(X)\tau(X)分组。

预测敏感度的不可观察性

对第ii个个体,假设它实际接受的处理强度是TiT_i,我们观察到的是:

Yi=Yi(Ti)Y_i=Y_i(T_i)

如果想知道它对处理的敏感度,我们理想上需要比较同一个人在两个相近处理水平下的结果:

YiTiYi(Ti+ϵ)Yi(Ti)ϵ\frac{\partial Y_i}{\partial T_i} \approx \frac{Y_i(T_i+\epsilon)-Y_i(T_i)}{\epsilon}

其中ϵ\epsilon是一个很小的处理变化。

但问题在于,同一个人在同一时刻不可能既接受TiT_i,又接受Ti+ϵT_i+\epsilon。这有回到了因果推断的根本问题:反事实结果是不能被观察到的。因此,个体层面的真实斜率YiTi\frac{\partial Y_i}{\partial T_i}\\不能直接作为标签交给机器学习模型学习。

那么这个解决思路就跟我们之前对反事实结果的处理的想法一样:先用数据估计条件期望函数μ(x,t)\mu(x,t),再用模型的预测结果构造处理效应:

τ^(x,t)=μ^(x,t+ϵ)μ^(x,t)ϵ\hat{\tau}(x,t) = \frac{\hat{\mu}(x,t+\epsilon)-\hat{\mu}(x,t)}{\epsilon}

如果处理是二元变量,则对应为:

τ^(x)=μ^(x,1)μ^(x,0)\hat{\tau}(x)=\hat{\mu}(x,1)-\hat{\mu}(x,0)

这个思想很直观:既然真实世界不给我们同一个人的两个结果,那我们就让模型在两个处理水平下各预测一次,用两次预测值的差来近似处理效应。

不过,这个做法成立需要因果识别条件。即条件独立性假设:

Y(t)TXY(t)\perp T\mid X

即在控制XX之后,处理分配可以看作与潜在结果无关。换句话说,所有同时影响处理TT和结果YY的混杂因素都已经包含在XX里。在二元处理下,还需要重叠性:

0<P(T=1X=x)<10<P(T=1\mid X=x)<1

对于连续处理,对应的是条件密度在关心的处理范围内不能为零:

fTX(tx)>0f_{T\mid X}(t\mid x)>0

可以理解为在给定xx附近,数据中确实存在足够多不同处理强度的样本。否则如果模型只能外推,CATE会很不可靠。

用线性模型理解“预测敏感度”

还是回到我们老朋友线性回归,对于XiX_i为特征向量,而β2\beta_2为特征向量的系数:

Yi=β0+β1Ti+β2TXi+eiY_i=\beta_0+\beta_1T_i+\beta_2^TX_i+e_i

对处理变量TiT_i求导:

E[YiXi,Ti]Ti=β1\frac{\partial E[Y_i\mid X_i,T_i]}{\partial T_i}=\beta_1

这说明模型预测的处理效应对所有人都是同一个常数β1\beta_1它可以估计平均处理效应,但不能描述异质性,因为无论XiX_i是什么,敏感度都一样。

为了让处理效应随特征变化,我们需要加入处理变量和特征之间的交互项

Yi=β0+β1Ti+β2TXi+γT(TiXi)+eiY_i=\beta_0+\beta_1T_i+\beta_2^TX_i+\gamma^T(T_iX_i)+e_i

其中TiXiT_iX_i表示把处理变量TiT_i分别XiX_i中的每个特征相乘。此时再对TiT_i求导:

E[YiXi,Ti]Ti=β1+γTXi\frac{\partial E[Y_i\mid X_i,T_i]}{\partial T_i} = \beta_1+\gamma^TX_i

这就得到了一个随XiX_i变化的处理效应预测。也就是说,γ\gamma控制的是“哪些特征会改变处理效应”。

假设现在雪糕销量与雪糕价格和温度有关,而我们可以控制雪糕价格的涨幅(即处理),那么模型为:

salesi=β0+β1pricei+β2tempi+β3(priceitempi)+eisales_i=\beta_0+\beta_1price_i+\beta_2temp_i+\beta_3(price_i\cdot temp_i)+e_i

那么价格敏感度为:

E[salesipricei,tempi]pricei=β1+β3tempi\frac{\partial E[sales_i\mid price_i,temp_i]}{\partial price_i} = \beta_1+\beta_3temp_i

这说明价格对销量的影响会随温度变化。若β1<0\beta_1<0,价格上升会降低销量;若β3>0\beta_3>0,温度越高时,这个负向影响会被削弱,也就是天气越热,顾客可能越愿意为冰淇淋支付更高价格。

现在我们引入多一点的变量,而不是仅仅是只有温度一个变量:

  • XX:天气温度temp、星期几weekday、成本cost等背景变量

我们是一个雪糕贩子,想狠狠地赚钱,我也知道我赚的钱等于销量×单价,所以我想知道的不是“哪一天销量高”,而是“哪一天可以提高价格而不会损失太多销量”从而使得收入最大化。因此目标是:

SalesPrice\frac{\partial Sales}{\partial Price}

因为处理变量是价格,所以这个导数通常是负数。数值越负,说明价格上升一点,销量下降越多,我们定义这类日期属于高价格敏感;数值越接近0,说明提价对销量影响较小,这类日期属于低价格敏感。

正如上面说的,我们现在加入交互项

salesi=β0+β1pricei+γT(priceiXi)+β2TXi+eisales_i=\beta_0+\beta_1price_i+\gamma^T(price_iX_i)+\beta_2^TX_i+e_i

那么每一天的价格敏感度变成:

τ^(Xi)=SalesiPricei^=β^1+γ^TXi\hat{\tau}(X_i) = \widehat{\frac{\partial Sales_i}{\partial Price_i}} = \hat{\beta}_1+\hat{\gamma}^TX_i

这样我们就可以把日期分为高价格敏感和低价格敏感两类:

  • 高价格敏感:τ^(Xi)\hat{\tau}(X_i)很负,提价会明显伤害销量,应该谨慎提价。
  • 低价格敏感:τ^(Xi)\hat{\tau}(X_i)接近0,提价不太伤害销量,可能更适合提高价格。

当然我们最后的目的是利润,而不是销量,则不能只看销量敏感度。利润可以写成:

Profit=(PriceCost)SalesProfit=(Price-Cost)\cdot Sales

此时最优价格应当来自:

price(x)=argmaxprice Profit^(x,price)price^*(x)=\underset{price}{argmax}\ \widehat{Profit}(x,price)

所以CATE告诉我们“处理对结果的边际影响”,但最终决策还要结合成本、收益和风险。

总的来说

  • CATE不是单个个体的真实因果效应,而是给定特征为xx的一类个体的平均因果效应。个体处理效应Yi(1)Yi(0)Y_i(1)-Y_i(0)通常不可识别,但CATE在合适假设下可以估计。

  • CATE的核心不是预测结果水平,而是预测处理带来的变化。一个人本来结果高,不代表他对处理反应强;一个人本来结果低,也不代表处理对他没用。

  • 交互项是我们去理解HTE最直接的方式。当模型没有交互项时,我们得到的是ATE;有交互项时,处理效应才会随特征变化,也就得到了CATE。这里我们还需要知道,其实ATE就是 CATE对所有个体特征的期望值

  • 个性化处理的目的是把处理给预测处理效应最大、且收益大于成本的人。若处理有成本c(x)c(x),二元处理下的一个简单规则是:

d(x)=1{τ^(x)>c(x)}d(x)=\mathbb{1}\{\hat{\tau}(x)>c(x)\}

如果处理是连续的,则应选择:

t(x)=argmaxt μ^(x,t)t^*(x)=\underset{t}{argmax}\ \hat{\mu}(x,t)
  • CATE的估计必须放在因果推断框架里理解。机器学习只是一个方式来帮助我们灵活估计μ(x,t)\mu(x,t)τ(x)\tau(x)。但机器学习与因果关系的发展还需要进一步研究。

Python实战

假设处理price是随机分配的,或者在控制tempweekdaycost之后可以认为没有混杂。我们画出因果图

costpricesalestempweekday

由于温度和假期在后门途径上,是混杂效应我们把它控制,而成本我们认为他不会直接影响到销量,但会直接影响到价格,我们有理由相信,成本对价格变化的变异解释性很强,来减少销量的残差,从而提高因果效用的估计精确度。

import pandas as pd
import statsmodels.formula.api as smf
from sklearn.model_selection import train_test_split

df = pd.read_csv("data.csv") # 假设有一数据集
train, test = train_test_split(df, test_size=0.3, random_state=123)

# 只估计平均价格效应:所有样本的价格敏感度相同
m_ate = smf.ols(
    "sales ~ price + temp + C(weekday) + cost",
    data=train
).fit()

# 加入交互项:允许价格敏感度随 X 变化
m_cate = smf.ols(
    "sales ~ price * (temp + C(weekday) + cost)",
    data=train
).fit()

当我们使用线性模型时,可以直接从系数理解敏感度。如上面第二个模型中,price:tempprice:C(weekday)price:cost这些交互项都在改变价格效应。

更通用的做法是用模型做两次预测:一次使用原价格,另一次把价格提高一个单位,然后相减。

def pred_sensitivity(model, df, treatment="price", step=1.0):
    df_up = df.copy()
    df_up[treatment] = df_up[treatment] + step
    return (model.predict(df_up) - model.predict(df)) / step

test = test.copy()
test["price_sens"] = pred_sensitivity(m_cate, test, "price", step=1.0)

# 对价格敏感度分组。对价格来说,越负代表越敏感。
test["sens_group"] = pd.qcut(
    test["price_sens"],
    q=2,
    labels=["high_price_sensitivity", "low_price_sensitivity"]
)

若使用更复杂的机器学习模型,也可以使用同样的“扰动处理变量并比较预测值”的思想:

from sklearn.ensemble import GradientBoostingRegressor

features = ["price", "temp", "weekday", "cost"]

gbr = GradientBoostingRegressor(random_state=123)
gbr.fit(train[features], train["sales"])

def pred_sensitivity_sklearn(model, df, features, treatment="price", step=1.0):
    base = df[features].copy()
    up = base.copy()
    up[treatment] = up[treatment] + step
    return (model.predict(up) - model.predict(base)) / step

test["price_sens_ml"] = pred_sensitivity_sklearn(
    gbr, test, features, treatment="price", step=1.0
)

但需要注意的是,机器学习模型的高预测分数不等于好的CATE模型。它可能只是学会了哪些天销量高,而没有学会哪些天对价格更敏感。因此,评价CATE模型时,不能只看预测YYR2R^2或误差,还要看用预测效应分组后,不同组的真实价格响应是否确实不同。

一个简单的直观检查是:按照预测的τ^(X)\hat{\tau}(X)把样本分组,然后在每个组里画TTYY的关系。如果高敏感组中YYTT变化的斜率明显更大,而低敏感组中斜率较小,那么这个CATE模型至少在排序上是有用的。