笔记
异质性处理效应
异质性处理效应(HTE)
机器学习与因果推断
机器学习,我们知道其是一个预测工具,是为了估计条件期望函数。我们通过交叉验证,分为训练集与测试集,去优化参数,来使得模型预测能力最优。但,我们知道在预测学习的过程中,其是一种被动估计的方式,这意味着我们只能预测其最后输出的值,但我们却只能看着,没有办法去改变它。这很多时候就是不正确的做法,比如,我们可以通过一些措施来去影响最后的结果。
我们现在已经不再是一个观察者了,因为我们知道仅仅估计并不完整。我们知道在因果推断里面,我们对一些人进行处理,来去估计其效应。一样的,在机器学习里,我们也可以在条件期望函数种加入一项,而这个处理正是刻画我们对数据生成过程的影响,即处理效应:
我们对这里的和进行严谨的区分。 是情景特征或者外生特征,即我们无法控制的,比如年龄、性别等,而处理变量则是我们可以决定的或者干预的,比如我们对不对这个人用药。所以因果推断就是在给定情景下,估计与之间因果关系的过程。因此优化就只能是优化一个处理值:
现在举一个例子,假如我们知道教育对收入是有影响的,那么我们就会想,为教育付出多少代价是合理的,也就是说,我们实际上做的是教育的因果效应并对其进行优化:
在因果推断中,我们回答的是这个效应是正的,负的,还是没有因果效应。那么这里的是混杂效应,我们可以通过控制来识别因果效应;又或者,可以用来减小因果估计的方差。若是良好的预测变量,那么可以用它来解释的变异,使得因果效应更加明显。而在机器学习里,我们不再关注二元论的结果,我们想要的不仅仅是平均处理效应,还要到底对什么样的群体有什么不一样的作用。我们将允许处理对某些人有积极影响,对另一些人却没有。我们现在希望实现处理的个性化,只把处理给予对其反应最好的人群。所以这里我们提出了异质性处理效应(Heterogeneous Treatment Effect):指同一项干预或政策在不同特征的个体或子群体中产生的影响(或效果)存在差异的现象。
从平均处理效应到条件平均处理效应(CATE)
我们之前学习的时候,大多数接触的是ATE或者LATE:
其中: 是响应函数或结果的处理导数。现在,我们开始考虑另外一个问题,我们应该对什么样的人进行处理。此时,一个决策可以在不同个体之间变化了,对某个个体施加处理可能是有利的,但对另一个则未必。我们希望个性化处理。此时,我们要估计的就是**条件平均处理效应(Conditional Average Treatment Effect)**了:
在上条件意味着我们现在允许处理效应根据每个单位的特征而不同。同样在这里,我们认为并非所有个体对处理反应相同,我们想利用这种异质性来进行最优的分类。
那么,假设现在我们有一群人和一个处理变量(为了描述方便)。我们希望个性化处理,所以这时候,我们不再对所有个体一起处理,而是对客户进行分组,创建对处理反应不同的群组。那么不同个体对处理的反应由条件处理效应给出。
更严格地说,如果处理是二元的,例如是否发放优惠券、是否用药,那么我们关心的是:
这里的就是CATE。它回答的问题是:在特征为的人群中,如果从不处理变成处理,平均结果会改变多少。
如果处理是连续的,例如价格、贷款金额、学习时长、运动时间,那么我们关心的是结果对处理强度的边际变化:
这时CATE可以理解为“条件敏感度”:其含义是在给定个体特征的情况下,处理稍微变化一个单位,结果平均会变化多少。
这里有一个很重要的关系:
也就是说,平均处理效应是把所有人的条件处理效应再平均一遍。ATE告诉我们“总体平均有没有用”,CATE则进一步告诉我们“对谁更有用、对谁没用、对谁可能有害”。
CATE与预测
如果只做普通机器学习,我们通常建立的是:
然后让模型尽量预测。但是个性化处理真正想知道的不是本身,而是随变化的速度。也就是说,我们关注的是:
或者在二元处理下:
我们把结果函数拆成两个部分来理解:
其中表示这个人在不考虑处理变化时的基础水平,表示处理效应。普通预测模型可能非常擅长预测,因为很多特征本来就能解释结果的高低;但个性化决策真正依赖的是。如果模型只学会了“谁本来销量高、谁本来风险高”,却没有学会“谁对处理更敏感”,那么它对个性化处理就没有太大帮助。因此我们不是要把人按照预测结果分组,而是要按照预测处理效应分组。
预测敏感度的不可观察性
对第个个体,假设它实际接受的处理强度是,我们观察到的是:
如果想知道它对处理的敏感度,我们理想上需要比较同一个人在两个相近处理水平下的结果:
其中是一个很小的处理变化。
但问题在于,同一个人在同一时刻不可能既接受,又接受。这有回到了因果推断的根本问题:反事实结果是不能被观察到的。因此,个体层面的真实斜率不能直接作为标签交给机器学习模型学习。
那么这个解决思路就跟我们之前对反事实结果的处理的想法一样:先用数据估计条件期望函数,再用模型的预测结果构造处理效应:
如果处理是二元变量,则对应为:
这个思想很直观:既然真实世界不给我们同一个人的两个结果,那我们就让模型在两个处理水平下各预测一次,用两次预测值的差来近似处理效应。
不过,这个做法成立需要因果识别条件。即条件独立性假设:
即在控制之后,处理分配可以看作与潜在结果无关。换句话说,所有同时影响处理和结果的混杂因素都已经包含在里。在二元处理下,还需要重叠性:
对于连续处理,对应的是条件密度在关心的处理范围内不能为零:
可以理解为在给定附近,数据中确实存在足够多不同处理强度的样本。否则如果模型只能外推,CATE会很不可靠。
用线性模型理解“预测敏感度”
还是回到我们老朋友线性回归,对于为特征向量,而为特征向量的系数:
对处理变量求导:
这说明模型预测的处理效应对所有人都是同一个常数。它可以估计平均处理效应,但不能描述异质性,因为无论是什么,敏感度都一样。
为了让处理效应随特征变化,我们需要加入处理变量和特征之间的交互项:
其中表示把处理变量分别与中的每个特征相乘。此时再对求导:
这就得到了一个随变化的处理效应预测。也就是说,控制的是“哪些特征会改变处理效应”。
假设现在雪糕销量与雪糕价格和温度有关,而我们可以控制雪糕价格的涨幅(即处理),那么模型为:
那么价格敏感度为:
这说明价格对销量的影响会随温度变化。若,价格上升会降低销量;若,温度越高时,这个负向影响会被削弱,也就是天气越热,顾客可能越愿意为冰淇淋支付更高价格。
现在我们引入多一点的变量,而不是仅仅是只有温度一个变量:
- :天气温度
temp、星期几weekday、成本cost等背景变量
我们是一个雪糕贩子,想狠狠地赚钱,我也知道我赚的钱等于销量×单价,所以我想知道的不是“哪一天销量高”,而是“哪一天可以提高价格而不会损失太多销量”从而使得收入最大化。因此目标是:
因为处理变量是价格,所以这个导数通常是负数。数值越负,说明价格上升一点,销量下降越多,我们定义这类日期属于高价格敏感;数值越接近0,说明提价对销量影响较小,这类日期属于低价格敏感。
正如上面说的,我们现在加入交互项:
那么每一天的价格敏感度变成:
这样我们就可以把日期分为高价格敏感和低价格敏感两类:
- 高价格敏感:很负,提价会明显伤害销量,应该谨慎提价。
- 低价格敏感:接近0,提价不太伤害销量,可能更适合提高价格。
当然我们最后的目的是利润,而不是销量,则不能只看销量敏感度。利润可以写成:
此时最优价格应当来自:
所以CATE告诉我们“处理对结果的边际影响”,但最终决策还要结合成本、收益和风险。
总的来说
-
CATE不是单个个体的真实因果效应,而是给定特征为的一类个体的平均因果效应。个体处理效应通常不可识别,但CATE在合适假设下可以估计。
-
CATE的核心不是预测结果水平,而是预测处理带来的变化。一个人本来结果高,不代表他对处理反应强;一个人本来结果低,也不代表处理对他没用。
-
交互项是我们去理解HTE最直接的方式。当模型没有交互项时,我们得到的是ATE;有交互项时,处理效应才会随特征变化,也就得到了CATE。这里我们还需要知道,其实ATE就是 CATE对所有个体特征的期望值
-
个性化处理的目的是把处理给预测处理效应最大、且收益大于成本的人。若处理有成本,二元处理下的一个简单规则是:
如果处理是连续的,则应选择:
- CATE的估计必须放在因果推断框架里理解。机器学习只是一个方式来帮助我们灵活估计或。但机器学习与因果关系的发展还需要进一步研究。
Python实战
假设处理price是随机分配的,或者在控制temp、weekday和cost之后可以认为没有混杂。我们画出因果图
由于温度和假期在后门途径上,是混杂效应我们把它控制,而成本我们认为他不会直接影响到销量,但会直接影响到价格,我们有理由相信,成本对价格变化的变异解释性很强,来减少销量的残差,从而提高因果效用的估计精确度。
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:temp、price: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模型时,不能只看预测的或误差,还要看用预测效应分组后,不同组的真实价格响应是否确实不同。
一个简单的直观检查是:按照预测的把样本分组,然后在每个组里画和的关系。如果高敏感组中随变化的斜率明显更大,而低敏感组中斜率较小,那么这个CATE模型至少在排序上是有用的。
