譯者 | 朱先忠
#審查| 孫淑娟
在我之前的部落格中,我們已經了解如何使用因果樹來評估政策的異質 處理效應。如果你還沒有閱讀過,我建議你在閱讀本文前先讀一遍,因為我們 #在本文中認為你已經了解了這篇文章中的部分與本文相關的內容。
為什麼是異質處理效應(HTE:heterogenous treatment effects)呢#?首先,對異質處理效應的估計允許我們根據它們的預期結果(疾病、公司收入、客戶滿意度等等)選擇提供處理(藥物、廣告、產品等)的使用者(病患、使用者、客戶等)。換句話說,估計HTE有助於我們進行目標定位。事實上,正如我們將在文章後面所看到的那樣,一種處理方法正在為一部分用戶帶來積極益處的同時,平均來說可能無效甚至適得其反。相反的情況也可能是這樣的:一種藥物平均來說是有效的,但是如果我們##明確它對其有副作用的使用者訊息的話,#此藥物的有效性就會進一步提高。
在本文中,我們將探討因果樹的一個擴展-因果森L。正如隨機森林透過將多個自助樹平均在一起來擴展回歸樹一樣,因果森林也擴展了因果樹。主要的區別來自於推理的角度,它不那麼直接。我們還將了解如何比較不同HTE估計算法的輸出,以及如何將其用於政策目標。
線上折扣案例在本文的其餘部分,我們繼續使用我上一篇有關因果樹文章中使用的玩具範例:我們假設我們是一家線上商店,我們有興趣了解向新客戶提供折扣是否會增加他們在商店中的支出。
為了了解折扣是否划算,我們進行了以下隨機實驗或A/B測試:每次新客戶瀏覽我們的線上商店時,我們都會將其隨機分配給一個處理條件。我們為受處理的用戶提供折扣;而為控制用戶,我們不提供折扣。我從檔案src.dgp導入資料產生程序dgp_online_discounts()。我還從src.utils庫導入一些繪圖函數和函式庫。為了不僅包括程式碼,還包括資料和表格等內容#,我使用了Deepnote框架,這是一個類似Jupyter的基於Web的協作筆電環境。
我們有100000名線上cstore訪客的數據,我們觀察他們造訪網站的時間、使用的設備、瀏覽器和地理區域。我們也會觀察到他們是否得到了折扣,我們的處理方式,他們的花費是多少,以及其他一些感興趣的結果。
由於實驗#是隨機分配的,我們可以使用簡單的平均差估計來估計實驗效果。我們期望實驗組和對照組相似,但折扣除外,因此我們可以將支出的任何差異歸因於折扣。
折扣##似乎是有效的:實驗組的平均花費增加了1.95美元。但是所有的顧客都受到同樣的影響嗎?
為了回答這個問題,我們想估計異質處理效果#,可能是在個體層面。
計算#異質處理效應有許多不同的選擇。最簡單的方法是以異質性的維度與感興趣的結果互動。這種方法的問題在於選擇哪個變數。有時,我們擁塞有可能指導我們行動事先的資訊;例如,我們可能知道行動用戶的平均花費比桌面用戶多。其他時候,出於商業原因,我們可能對某個維度感興趣;例如,我們可能希望在某個地區投資更多。然而,當我們沒有額外的資訊時,我們希望這個過程是數據驅動的。
在上一篇文章中,我們探討了一種資料驅動的方法來估計#異質處理效應 ——因果樹。我們現在將把它們擴展到因果森林。然而,在我們開始之前,我們必須介紹它的非因果表親——隨機森林。
隨機森林,顧名思義,是回歸樹的延伸,在其上增加了兩個獨立的隨機性來源。特別是,隨機森林演算法能夠對許多不同的迴歸樹進行預測,每個樹都在資料的 自助樣本上#進行訓練,並將它們平均在一起。這個過程通常稱為引導聚集演算法,又稱為裝袋演算法,可以應用於任何預測演算法,而不是隨機森林特有的。隨機性的額外來源來自特徵選擇,因為在每次分割時,只有所有特徵X的隨機子集被考慮用於最優分割。
這兩個額外的隨機性來源非常重要,有助於提高隨機森林的表現。首先,裝袋演算法允許隨機森林透過對多個離散預測進行平均來產生比回歸樹更平滑的預測。相反,隨機特徵選擇允許隨機森林更深入地探索特徵空間,允許它們發現比簡單回歸樹更多的交互作用。事實上,變數之間可能存在交互作用,這些交互作用本身不具有很強的預測性(因此不會產生分裂),但共同作用非常強大。
因果森林相當於隨機森林,但用於估計#異質處理效應,與因果樹和迴歸樹完全相同。正如因果樹一樣,我們有一個基本問題:我們有興趣預測一個我們沒有觀察到的物件:個體處理效果τᵢ。解決方案是建立一個輔助結果變數Y*,其每一次觀察的預期值剛好是處理效果。
輔助結果變數
如果你想更多地了解為什麼這個變數對個體處理效果沒有加上偏移的話,請看一下#我之前的文章篇文章中進行了詳細的介紹。簡而言之,您可以認為
Yᵢ*作為單一觀測的平均差估計量。 一旦我們有了一個結果變量,為了使用隨機森林來估計異質處理效應,我們還需要做一些事情。首先,我們需要建造在每片葉子上具有相同數量的處理單元和控制單元的樹。其次,我們需要使用不同的樣本來建立樹並對其進行評估,即計算每片葉子的平均結果。這個過程通常被稱為誠實樹(honest trees),因為我們可以將每個葉子的樣本視為獨立於樹狀結構,所以它對推論非常有用。
在進行評估之前,讓我們先為分類變數device、browser和region產生虛擬變數。
df_dummies = pd.get_dummies(df[dgp.X[1:]], drop_first=True) df = pd.concat([df, df_dummies], axis=1) X = ['time'] + list(df_dummies.columns)現在,我們可以使用隨機森林演算法來估計異質處理效應。幸運的是,我們不必手動完成所有這些,因為在微軟公司的EconML套件中已經提供了一個很好的因果樹和森林實現。我們將使用其中的CausalForestML函數。 #########
from econml.dml import CausalForestDML np.random.seed(0) forest_model = CausalForestDML(max_depth=3) forest_model = forest_model.fit(Y=df[dgp.Y], X=df[X], T=df[dgp.D])
与因果树不同,因果森林更难解释,因为我们无法可视化每一棵树。我们可以使用SingleTreeateInterpreter函数来绘制因果森林算法的等效表示。
from econml.cate_interpreter import SingleTreeCateInterpreter intrp = SingleTreeCateInterpreter(max_depth=2).interpret(forest_model, df[X]) intrp.plot(feature_names=X, fnotallow=12)
因果森林模型表示
我们可以像因果树模型一样解释树形图。在顶部,我们可以看到数据中的平均$Y^*$的值为1.917$。从那里开始,根据每个节点顶部突出显示的规则,数据被拆分为不同的分支。例如,根据时间是否晚于11.295,第一节点将数据分成大小为46878$和53122$的两组。在底部,我们得到了带有预测值的最终分区。例如,最左边的叶子包含40191$的观察值(时间早于11.295,在非Safari浏览器环境下),我们预测其花费为0.264$。较深的节点颜色表示预测值较高。
这种表示的问题在于,与因果树的情况不同,它只是对模型的解释。由于因果森林是由许多自助树组成的,因此无法直接检查每个决策树。了解在确定树分割时哪个特征最重要的一种方法是所谓的特征重要性。
显然,时间是异质性的第一个维度,其次是设备(特别是移动设备)和浏览器(特别是Safari)。其他维度无关紧要。
现在,让我们检查一下模型性能如何。
通常,我们无法直接评估模型性能,因为与标准的机器学习设置不同,我们没有观察到实际情况。因此,我们不能使用测试集来计算模型精度的度量。然而,在我们的案例中,我们控制了数据生成过程,因此我们可以获得基本的真相。让我们从分析模型如何沿着数据、设备、浏览器和区域的分类维度估计异质处理效应开始。
def compute_discrete_effects(df, hte_model): temp_df = df.copy() temp_df.time = 0 temp_df = dgp.add_treatment_effect(temp_df) temp_df = temp_df.rename(columns={'effect_on_spend': 'True'}) temp_df['Predicted'] = hte_model.effect(temp_df[X]) df_effects = pd.DataFrame() for var in X[1:]: for effect in ['True', 'Predicted']: v = temp_df.loc[temp_df[var]==1, effect].mean() - temp_df[effect][temp_df[var]==0].mean() effect_var = {'Variable': [var], 'Effect': [effect], 'Value': [v]} df_effects = pd.concat([df_effects, pd.DataFrame(effect_var)]).reset_index(drop=True) return df_effects, temp_df['Predicted'].mean() df_effects, avg_effect_notime = compute_discrete_effects(df, forest_model)
对于每个分类变量,我们绘制了实际和估计的平均处理效果。
fig, ax = plt.subplots() sns.barplot(data=df_effects, x="Variable", y="Value", hue="Effect", ax=ax).set( xlabel='', ylabel='', title='Heterogeneous Treatment Effects') ax.set_xticklabels(ax.get_xticklabels(), rotatinotallow=45, ha="right");
作者提供的每个分类值的真实和估计处理效果
因果森林算法非常善于预测与分类变量相关的处理效果。至于因果树,这是预期的,因为算法具有非常离散的性质。然而,与因果树不同的是,预测更加微妙。
我们现在可以做一个更相关的测试:算法在时间等连续变量下的表现如何?首先,让我们再次隔离预测的处理效果,并忽略其他协变量。
def compute_time_effect(df, hte_model, avg_effect_notime):
df_time = df.copy() df_time[[X[1:]] + ['device', 'browser', 'region']] = 0 df_time = dgp.add_treatment_effect(df_time) df_time['predicted'] = hte_model.effect(df_time[X]) + avg_effect_notime return df_time df_time = compute_time_effect(df, forest_model, avg_effect_notime)
我们现在可以复制之前的数字,但时间维度除外。我们绘制了一天中每个时间的平均真实和估计处理效果。
sns.scatterplot(x='time', y='effect_on_spend', data=df_time, label='True') sns.scatterplot(x='time', y='predicted', data=df_time, label='Predicted').set( ylabel='', title='Heterogeneous Treatment Effects') plt.legend(title='Effect');
沿时间维度绘制的真实和估计的处理效果
我们现在可以充分理解因果树和森林之间的区别:虽然在因果树的情况下,估计基本上是一个非常粗略的阶跃函数,但我们现在可以看到因果树如何产生更平滑的估计。
我们现在已经探索了该模型,是时候使用它了!
假设我们正在考虑向访问我们在线商店的新客户提供4美元的折扣。
cost = 4
折扣对哪些客户有效?我们估计平均处理效果为1.9492美元。这意味着,平均而言折扣并不真正有利可图。然而,现在可以针对单个客户,我们只能向一部分新客户提供折扣。我们现在将探讨如何进行政策目标定位,为了更好地了解目标定位的质量,我们将使用因果树模型作为参考点。
我们使用相同的CauselForestML函数构建因果树,但将估计数和森林大小限制为1。
from econml.dml import CausalForestDML np.random.seed(0) tree_model = CausalForestDML(n_estimators=1, subforest_size=1, inference=False, max_depth=3) tree_model = tree_model.fit(Y=df[dgp.Y], X=df[X], T=df[dgp.D])
接下来,我们将数据集分成一个训练集和一个测试集。这一想法与交叉验证非常相似:我们使用训练集来训练模型——在我们的案例中是异质处理效应的估计器——并使用测试集来评估其质量。主要区别在于,我们没有观察到测试数据集中的真实结果。但是我们仍然可以使用训练测试分割来比较样本内预测和样本外预测。
我们将所有观察结果的80%放在训练集中,20%放在测试集中。
df_train, df_test = df.iloc[:80_000, :], df.iloc[20_000:,]
首先,让我们仅在训练样本上重新训练模型。
np.random.seed(0) tree_model = tree_model.fit(Y=df_train[dgp.Y], X=df_train[X], T=df_train[dgp.D]) forest_model = forest_model.fit(Y=df_train[dgp.Y], X=df_train[X], T=df_train[dgp.D])
现在,我们可以确定目标策略,即决定我们向哪些客户提供折扣。答案似乎很简单:我们向所有预期处理效果大于成本(4美元)的客户提供折扣。
借助于一个可视化工具,它可以让我们了解处理对谁有效以及如何有效,这就是所谓的处理操作特征(TOC)曲线。这个名字可以看作是基于另一个更著名的接收器操作特性(ROC)曲线的修正,该曲线描绘了二元分类器的不同阈值的真阳性率与假阳性率。这两种曲线的想法类似:我们绘制不同比例受处理人群的平均处理效果。在一个极端情况下,当所有客户都被处理时,曲线的值等于平均处理效果;而在另一个极端情况下,当只有一个客户被处理时曲线的值则等于最大处理效果。
现在让我们计算曲线。
def compute_toc(df, hte_model, cost, truth=False): df_toc = pd.DataFrame() for q in np.linspace(0, 1, 101): if truth: df = dgp.add_treatment_effect(df_test) effect = df['effect_on_spend'] else: effect = hte_model.effect(df[X]) ate = np.mean(effect[effect >= np.quantile(effect, 1-q)]) temp = pd.DataFrame({'q': [q], 'ate': [ate]}) df_toc = pd.concat([df_toc, temp]).reset_index(drop=True) return df_toc df_toc_tree = compute_toc(df_train, tree_model, cost) df_toc_forest = compute_toc(df_train, forest_model, cost)
现在,我们可以绘制两个CATE估算器的处理操作特征(TOC)曲线。
def plot_toc(df_toc, cost, ax, color, title): ax.axhline(y=cost, lw=2, c='k') ax.fill_between(x=df_toc.q, y1=cost, y2=df_toc.ate, where=(df_toc.ate > cost), color=color, alpha=0.3) if any(df_toc.ate > cost): q = df_toc_tree.loc[df_toc.ate > cost, 'q'].values[-1] else: q = 0 ax.axvline(x=q, ymin=0, ymax=0.36, lw=2, c='k', ls='--') sns.lineplot(data=df_toc, x='q', y='ate', ax=ax, color=color).set( title=title, ylabel='ATT', xlabel='Share of treated', ylim=[1.5, 8.5]) ax.text(0.7, cost+0.1, f'Discount cost: {cost:.0f}$', fnotallow=12) fix, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6)) plot_toc(df_toc_tree, cost, ax1, 'C0', 'TOC - Causal Tree') plot_toc(df_toc_forest, cost, ax2, 'C1', 'TOC - Causal Forest')
处理操作特性曲线
正如预期的那样,两种估算器的TOC曲线都在下降,因为平均效应随着我们处理客户份额的增加而降低。换言之,我们在发布折扣时越有选择,每个客户的优惠券效果就越高。我还画了一条带有折扣成本的水平线,以便我们可以将TOC曲线下方和成本线上方的阴影区域解释为预期利润。
这两种算法预测的处理份额相似,约为20%,因果森林算法针对的客户略多一些。然而,他们预测的利润结果却大相径庭。因果树算法预测的边际较小且恒定,而因果林算法预测的是更大且更陡的边际。那么,哪一种算法更准确呢?
为了比较它们,我们可以在测试集中对它们进行评估。我们采用训练集上训练的模型,预测处理效果,并将其与测试集上训练模型的预测进行比较。注意,与机器学习标准测试程序不同,有一个实质性的区别:在我们的案例中,我们无法根据实际情况评估我们的预测,因为没有观察到处理效果。我们只能将两个预测相互比较。
def compute_effect_test(df_test, hte_model, cost, ax, title, truth=False): df_test['Treated'] = hte_model.effect(df_test[X]) > cost if truth: df_test = dgp.add_treatment_effect(df_test) df_test['Effect'] = df_test['effect_on_spend'] else: np.random.seed(0) hte_model_test = copy.deepcopy(hte_model).fit(Y=df_test[dgp.Y], X=df_test[X], T=df_test[dgp.D]) df_test['Effect'] = hte_model_test.effect(df_test[X]) df_test['Cost Effective'] = df_test['Effect'] > cost tot_effect = ((df_test['Effect'] - cost) * df_test['Treated']).sum() sns.barplot(data=df_test, x='Cost Effective', y='Treated', errorbar=None, width=0.5, ax=ax, palette=['C3', 'C2']).set( title=title + 'n', ylim=[0,1]) ax.text(0.5, 1.08, f'Total effect: {tot_effect:.2f}', fnotallow=14, ha='center') return fix, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5)) compute_effect_test(df_test, tree_model, cost, ax1, 'Causal Tree') compute_effect_test(df_test, forest_model, cost, ax2, 'Causal Forest')
因果树模型似乎比因果森林模型表现得更好一些,总净效应为8386美元——相对于4948美元。从图中,我们也可以了解差异的来源。因果森林算法往往限制性更强,处理的客户更少,没有误报的阳性,但也有很多误报的阴性。另一方面,因果树算法看起来更加“慷慨”,并将折扣分配给更多的新客户。这既转化为更多的真阳性,也转化为假阳性。总之,净效应似乎有利于因果树算法。
通常,我们讨论到这里就可以停止了,因为我们可以做的事情不多了。然而,在我们的案例情形中,我们还可以访问真正的数据生成过程。因此,接下来我们不妨检查一下这两种算法的真实精度。
首先,让我们根据处理效果的预测误差来比较它们。对于每个算法,我们计算处理效果的均方误差。
from sklearn.metrics import mean_squared_error as mse def compute_mse_test(df_test, hte_model): df_test = dgp.add_treatment_effect(df_test) print(f"MSE = {mse(df_test['effect_on_spend'], hte_model.effect(df_test[X])):.4f}") compute_mse_test(df_test, tree_model) compute_mse_test(df_test, forest_model)
结果是,随机森林模型更好地预测了平均处理效果,均方误差为0.5555美元,而不是0.9035美元。
那么,这是否意味着更好的目标定位呢?我们现在可以复制上面所做的相同的柱状图,以了解这两种算法在策略目标方面的表现。
fix, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5)) compute_effect_test(df_test, tree_model, cost, ax1, 'Causal Tree', True) compute_effect_test(df_test, forest_model, cost, ax2, 'Causal Forest', True)
这两幅图非常相似,但结果却大相径庭。事实上,因果森林算法现在优于因果树算法,总效果为10395美元,而非8828美元。为什么会出现这种突然的差异呢?
为了更好地理解差异的来源,让我们根据实际情况绘制TOC。
df_toc = compute_toc(df_test, tree_model, cost, True) fix, ax = plt.subplots(1, 1, figsize=(7, 5)) plot_toc(df_toc, cost, ax, 'C2', 'TOC - Ground Truth')
处理操作特性曲线。
正如我们所看到的,TOC是倾斜度非常大的,存在一些平均处理效果非常高的客户。随机森林算法能够更好地识别它们,因此总体上更有效,尽管目标客户较少些。
在這篇文章中,我們學習#了一個非常強大的估計異質處理效應的演算法#——#因果森林。因果森林建立在與因果樹相同的原則上,但受益於對參數空間和裝袋演算法的更深入探索。
此外,我們也了解# #異質處理效應的估計值來執行政策定位。透過識別具有最高處理效果的用戶,我們能夠確保一項政策有利可圖。我們也看到了政策目標與異質處理效應評估目標的不同,因為分佈的尾部可能比平均值#有更強的相關性愛。 #參考文獻
朱先忠,51CTO社群編輯,51CTO專家部落格、講師,濰坊一所高校電腦教師,自由程式設計界老兵一枚。
原文標題:#From Causal Trees to Forests,作者:Matteo Courthoud
以上是基於因果森林演算法的決策定位應用的詳細內容。更多資訊請關注PHP中文網其他相關文章!