DoubleML+FLAML实现双重机器学习超参数的自动调优(python实现路径)


DoubleML 结合 FLAML------如何在DoubleML中自动调优学习器

自动化机器学习(AutoML)的最新进展让机器学习估计器的超参数自动调优变得更简单。这些经过优化的学习器可用于 DoubleML 框架内的估计环节。在本笔记本中,我们将探索如何借助 AutoML 为 DoubleML 框架调优学习器。

本笔记本将使用FLAML,但也有许多其他 AutoML 框架可供选择。对 DoubleML 而言,尤其实用的是那些能以sklearn风格导出模型的工具包,例如:TPOTautosklearnH20Gama

数据生成

我们使用make_plr_CCDDHNR2018()函数生成合成数据,包含 1000 个观测值、50 个协变量、1 个处理变量和 1 个结果变量。我们对数据生成过程进行校准,以凸显超参数调优的重要性。

python 复制代码
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from doubleml.plm.datasets import make_plr_CCDDHNR2018
import doubleml as dml
from flaml import AutoML
from xgboost import XGBRegressor

# 生成合成数据
data = make_plr_CCDDHNR2018(alpha=0.5, n_obs=1000, dim_x=50, return_type="DataFrame", a0=0, a1=1, s1=0.25, s2=0.25)
data.head()

在全样本上调优

在本节中,我们将手动使用 FLAML 为部分线性回归模型(PLR)调优两个XGBoost模型。在 PLR 模型(使用默认评分函数)中,我们需要估计干扰项 η,其构成如下:

η : = m 0 ( x ) , ℓ 0 ( x ) = E [ D ∣ X ] , E [ Y ∣ X ] η:={m₀(x), ℓ₀(x)} = {E[D|X], E[Y|X]} η:=m0(x),ℓ0(x)=E[D∣X],E[Y∣X]

我们初始化两个FLAML AutoML 对象并完成拟合。调优完成后,将这些学习器传入DoubleML

步骤 1:初始化并训练 AutoML 模型

注:该代码块将总共耗时 8 分钟优化干扰项模型。

python 复制代码
# 初始化用于结果模型(ml_l)的AutoML:基于X预测Y
automl_l = AutoML()
settings_l = {
"time_budget": 240,
"metric": 'rmse',
"estimator_list": ['xgboost'],
"task": 'regression',
}
automl_l.fit(X_train=data.drop(columns=["y", "d"]).values, y_train=data["y"].values, verbose=2, **settings_l)

# 初始化用于处理模型(ml_m)的AutoML:基于X预测D
automl_m = AutoML()
settings_m = {
"time_budget": 240,
"metric": 'rmse',
"estimator_list": ['xgboost'],
"task": 'regression',
}
automl_m.fit(X_train=data.drop(columns=["y", "d"]).values, y_train=data["d"].values, verbose=2, **settings_m)

步骤 2:评估调优后的模型

FLAML会将训练过程中的最优损失值存储为best_loss属性。更多细节可参考FLAML 官方文档

python 复制代码
rmse_oos_ml_m = automl_m.best_loss
rmse_oos_ml_l = automl_l.best_loss
print("调优过程中的最优RMSE(ml_m):",rmse_oos_ml_m)
print("调优过程中的最优RMSE(ml_l):",rmse_oos_ml_l)
复制代码
调优过程中的最优RMSE(ml_m):1.002037900301454
调优过程中的最优RMSE(ml_l):1.0929369228758206

步骤 3:创建并拟合 DoubleML 模型

我们基于数据集创建DoubleMLData对象,指定 y 为结果变量、d 为处理变量。随后,使用调优后的FLAML估计器初始化DoubleMLPLR模型(分别用于处理项和结果项)。DoubleML会在每个折(fold)上创建配置相同的模型副本。

python 复制代码
obj_dml_data = dml.DoubleMLData(data, "y", "d")

obj_dml_plr_fullsample = dml.DoubleMLPLR(obj_dml_data, ml_m=automl_m.model.estimator,
                                           ml_l=automl_l.model.estimator)

print(obj_dml_plr_fullsample.fit().summary)
复制代码
       coef(系数)   std err(标准误)          t值         P>|t|(P值)     2.5 %(95%CI下限)    97.5 %(95%CI上限)
d  0.490689  0.031323  15.665602  2.599586e-55  0.429298  0.552081

DoubleML内置的学习器评估功能会报告交叉拟合过程中的样本外误差。我们可将该指标与上文调优过程中的最优损失值对比。

python 复制代码
rmse_dml_ml_l_fullsample = obj_dml_plr_fullsample.evaluate_learners()['ml_l'][0][0]
rmse_dml_ml_m_fullsample = obj_dml_plr_fullsample.evaluate_learners()['ml_m'][0][0]

print("DoubleML评估的RMSE(ml_m):", rmse_dml_ml_m_fullsample)
print("DoubleML评估的RMSE(ml_l):", rmse_dml_ml_l_fullsample)
复制代码
DoubleML评估的RMSE(ml_m):1.0124105481660435
DoubleML评估的RMSE(ml_l):1.103179163001313

自动化调优过程中的最优 RMSE 与干扰项预测的样本外误差数值相近,这表明模型未发生过拟合。我们本就不预期出现严重过拟合------因为 FLAML 内部使用交叉验证,且报告的是验证集上的最优损失值。

在折上调优

除了在外部调优FLAML学习器,我们也可在 DoubleML 内部完成 AutoML 学习器的调优。为此,需要定义自定义类以将FLAML集成到DoubleML中。调用DoubleMLfit()方法时,调优过程会自动启动。由于训练过程会在 K 个折上各执行一次,因此每个折都会得到一组个性化的最优超参数。

步骤 1:DoubleML 中 FLAML 模型的自定义接口

以下接口旨在为回归和分类任务提供自动化机器学习模型调优能力。但在本示例中,由于处理变量是连续型的,我们仅需回归器接口。

python 复制代码
from sklearn.utils.multiclass import unique_labels

class FlamlRegressorDoubleML:
    _estimator_type = 'regressor'

    def __init__(self, time, estimator_list, metric, *args, **kwargs):
        self.auto_ml = AutoML(*args, **kwargs)
        self.time = time
        self.estimator_list = estimator_list
        self.metric = metric

    def set_params(self, **params):
        self.auto_ml.set_params(**params)
        return self

    def get_params(self, deep=True):
        dict = self.auto_ml.get_params(deep)
        dict["time"] = self.time
        dict["estimator_list"] = self.estimator_list
        dict["metric"] = self.metric
        return dict

    def fit(self, X, y):
        self.auto_ml.fit(X, y, task="regression", time_budget=self.time, estimator_list=self.estimator_list, metric=self.metric, verbose=False)
        self.tuned_model = self.auto_ml.model.estimator
        return self

    def predict(self, x):
        preds = self.tuned_model.predict(x)
        return preds

class FlamlClassifierDoubleML:
    _estimator_type = 'classifier'

    def __init__(self, time, estimator_list, metric, *args, **kwargs):
        self.auto_ml = AutoML(*args, **kwargs)
        self.time = time
        self.estimator_list = estimator_list
        self.metric = metric

    def set_params(self, **params):
        self.auto_ml.set_params(**params)
        return self

    def get_params(self, deep=True):
        dict = self.auto_ml.get_params(deep)
        dict["time"] = self.time
        dict["estimator_list"] = self.estimator_list
        dict["metric"] = self.metric
        return dict

    def fit(self, X, y):
        self.classes_ = unique_labels(y)
        self.auto_ml.fit(X, y, task="classification", time_budget=self.time, estimator_list=self.estimator_list, metric=self.metric, verbose=False)
        self.tuned_model = self.auto_ml.model.estimator
        return self

    def predict_proba(self, x):
        preds = self.tuned_model.predict_proba(x)
        return preds

步骤 2:调用 DoubleML 的.fit()方法时使用该接口

我们初始化一个FlamlRegressorDoubleML对象,无需提前拟合即可传入 DoubleML 对象。调用 DoubleML 对象的.fit()方法时,接口对象的副本会在每个折上创建,且每个折都会生成一组独立的最优超参数。由于我们需要在 K 个折上各拟合一次,因此相应减少单次调优的计算时间,以与全样本调优的总耗时保持一致。

python 复制代码
# 定义FlamlRegressorDoubleML对象
ml_l = FlamlRegressorDoubleML(time=24, estimator_list=['xgboost'], metric='rmse')
ml_m = FlamlRegressorDoubleML(time=24, estimator_list=['xgboost'], metric='rmse')

# 使用新的回归器创建DoubleMLPLR对象
dml_plr_obj_onfolds = dml.DoubleMLPLR(obj_dml_data, ml_m, ml_l)

# 拟合DoubleMLPLR模型
print(dml_plr_obj_onfolds.fit(store_models=True).summary)
复制代码
       coef   std err          t         P>|t|     2.5 %    97.5 %
d  0.488455  0.031491  15.510971  2.924232e-54  0.426734  0.550176
python 复制代码
rmse_oos_onfolds_ml_l = np.mean([dml_plr_obj_onfolds.models["ml_l"]["d"][0][i].auto_ml.best_loss for i in range(5)])
rmse_oos_onfolds_ml_m = np.mean([dml_plr_obj_onfolds.models["ml_m"]["d"][0][i].auto_ml.best_loss for i in range(5)])
print("调优过程中的最优RMSE(ml_m):",rmse_oos_onfolds_ml_m)
print("调优过程中的最优RMSE(ml_l):",rmse_oos_onfolds_ml_l)

rmse_dml_ml_l_onfolds = dml_plr_obj_onfolds.evaluate_learners()['ml_l'][0][0]
rmse_dml_ml_m_onfolds = dml_plr_obj_onfolds.evaluate_learners()['ml_m'][0][0]

print("DoubleML评估的RMSE(ml_m):", rmse_dml_ml_m_onfolds)
print("DoubleML评估的RMSE(ml_l):", rmse_dml_ml_l_onfolds)
复制代码
调优过程中的最优RMSE(ml_m):1.0060715124549546
调优过程中的最优RMSE(ml_l):1.1030891095588866
DoubleML评估的RMSE(ml_m):1.0187512020118494
DoubleML评估的RMSE(ml_l):1.1016338581630878

与上文案例类似,我们未发现过拟合的迹象。

与短时间 AutoML 调优、未调优 XGBoost 学习器的对比

短时间 AutoML 调优

作为基准对比,我们将上文调优时间为 2 分钟的学习器,与仅使用 10 秒调优时间的学习器进行对比。

注:这些调优时长仅为示例。在本实验设置下,我们发现 10 秒的调优时间是不足的,而 120 秒则足够。通常,所需调优时间取决于数据复杂度、数据集规模、所用设备的计算能力等因素。如需正确使用FLAML,请参考其官方文档相关论文

python 复制代码
# 初始化用于结果模型的AutoML(与上文类似,但缩短时间预算)
automl_l_lesstime = AutoML()
settings_l = {
"time_budget": 10,
"metric": 'rmse',
"estimator_list": ['xgboost'],
"task": 'regression',
}
automl_l_lesstime.fit(X_train=data.drop(columns=["y", "d"]).values, y_train=data["y"].values, verbose=2, **settings_l)

# 初始化用于处理模型的AutoML(与上文类似,但缩短时间预算)
automl_m_lesstime = AutoML()
settings_m = {
"time_budget": 10,
"metric": 'rmse',
"estimator_list": ['xgboost'],
"task": 'regression',
}
automl_m_lesstime.fit(X_train=data.drop(columns=["y", "d"]).values, y_train=data["d"].values, verbose=2, **settings_m)
python 复制代码
obj_dml_plr_lesstime = dml.DoubleMLPLR(obj_dml_data, ml_m=automl_m_lesstime.model.estimator,
                                           ml_l=automl_l_lesstime.model.estimator)

print(obj_dml_plr_lesstime.fit().summary)
复制代码
       coef   std err          t         P>|t|     2.5 %    97.5 %
d  0.461493  0.031075  14.851012  6.852592e-50  0.400587  0.522398

我们可再次检查模型性能:

python 复制代码
rmse_dml_ml_l_lesstime = obj_dml_plr_lesstime.evaluate_learners()['ml_l'][0][0]
rmse_dml_ml_m_lesstime = obj_dml_plr_lesstime.evaluate_learners()['ml_m'][0][0]

print("调优过程中的最优RMSE(ml_m):", automl_m_lesstime.best_loss)
print("调优过程中的最优RMSE(ml_l):", automl_l_lesstime.best_loss)
print("DoubleML评估的RMSE(ml_m):", rmse_dml_ml_m_lesstime)
print("DoubleML评估的RMSE(ml_l):", rmse_dml_ml_l_lesstime)
复制代码
调优过程中的最优RMSE(ml_m):0.9386744462704798
调优过程中的最优RMSE(ml_l):1.0520233166790431
DoubleML评估的RMSE(ml_m):1.0603268864456956
DoubleML评估的RMSE(ml_l):1.111352344760325

我们发现,AutoML 调优的最优 RMSE 与 DoubleML 评估的样本外 RMSE 之间差异更大,这可能表明学习器发生了欠拟合(即调优时间不足)。

未调优(默认参数)的 XGBoost

作为另一组基准,我们构建使用默认超参数(即未调优)XGBoost 学习器的 DoubleML 模型:

python 复制代码
xgb_untuned_m, xgb_untuned_l = XGBRegressor(), XGBRegressor()
python 复制代码
# 使用未调优XGBoost创建DoubleMLPLR对象
dml_plr_obj_untuned = dml.DoubleMLPLR(obj_dml_data, xgb_untuned_l, xgb_untuned_m)
print(dml_plr_obj_untuned.fit().summary)

rmse_dml_ml_l_untuned = dml_plr_obj_untuned.evaluate_learners()['ml_l'][0][0]
rmse_dml_ml_m_untuned = dml_plr_obj_untuned.evaluate_learners()['ml_m'][0][0]
复制代码
       coef   std err          t         P>|t|     2.5 %    97.5 %
d  0.429133  0.031692  13.540578  9.007789e-42  0.367017  0.491249
对比与总结

我们汇总不同模型的结果:全样本调优 AutoML、折上调优 AutoML、未调优 XGBoost、短时间调优 AutoML。

python 复制代码
summary = pd.concat([obj_dml_plr_fullsample.summary, dml_plr_obj_onfolds.summary, dml_plr_obj_untuned.summary, obj_dml_plr_lesstime.summary],
                     keys=['全样本调优', '折上调优', '默认参数', '短时间调优'])
summary.index.names = ['模型类型', '指标']

summary
绘制系数与 95%置信区间

本节生成可视化图表,对比不同模型类型的系数及 95%置信区间。

python 复制代码
# 提取模型标签和系数值
model_labels = summary.index.get_level_values('模型类型')
coef_values = summary['coef'].values

# 计算误差(置信区间上下限与系数的差值)
errors = np.full((2, len(coef_values)), np.nan)
errors[0, :] = summary['coef'] - summary['2.5 %']
errors[1, :] = summary['97.5 %'] - summary['coef']

# 绘制系数与95%置信区间
plt.figure(figsize=(10, 6))
plt.errorbar(model_labels, coef_values, fmt='o', yerr=errors, capsize=5)
plt.axhline(0.5, color='red', linestyle='--')  # 绘制参考线(真实系数值)
plt.xlabel('模型')
plt.ylabel('系数与95%置信区间')
plt.title('不同模型的系数及95%置信区间对比')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
对比干扰项估计的指标

本节对比不同模型的干扰项估计指标,并绘制柱状图可视化性能差异。

python 复制代码
fig, axs = plt.subplots(1,2,figsize=(10,4))
axs = axs.flatten()

axs[0].bar(x = ['全样本调优', '折上调优', '默认参数', '短时间调优'],
           height=[rmse_dml_ml_m_fullsample, rmse_dml_ml_m_onfolds, rmse_dml_ml_m_untuned, rmse_dml_ml_m_lesstime])

axs[1].bar(x = ['全样本调优', '折上调优', '默认参数', '短时间调优'],
           height=[rmse_dml_ml_l_fullsample, rmse_dml_ml_l_onfolds, rmse_dml_ml_l_untuned, rmse_dml_ml_l_lesstime])

axs[0].set_xlabel("调优方式")
axs[0].set_ylim((1,1.12))
axs[0].set_ylabel("RMSE")
axs[0].set_title("不同调优方式下的样本外RMSE(ml_m)")

axs[1].set_xlabel("调优方式")
axs[1].set_ylim((1.1,1.22))
axs[1].set_ylabel("RMSE")
axs[1].set_title("不同调优方式下的样本外RMSE(ml_l)")

fig.suptitle("不同调优方式下干扰项估计的样本外RMSE")
fig.tight_layout()

结论

本笔记本表明,超参数调优在 DoubleML 框架中至关重要,且可通过 FLAML AutoML 轻松实现。在我们最新的研究中,我们进一步验证了 AutoML 调优的有效性------尤其是在所有验证场景中,全样本调优的表现与折上调优相近,因此可通过外部调优节省调优时间和复杂度。

此外,可参考我们为 DoubleML 打造的全自动 AutoML 调优接口AutoDoubleML,该工具可从 Github 下载并在 Python 中使用。

参考文献

Bach, P., Schacht, O., Chernozhukov, V., Klaassen, S., & Spindler, M. (2024, March). Hyperparameter Tuning for Causal Inference with Double Machine Learning: A Simulation Study. In Causal Learning and Reasoning (pp. 1065-1117). PMLR.

相关推荐
容智信息2 小时前
容智信息加入大模型产业联盟,Hyper Agent推动企业级智能体规模化落地
大数据·人工智能·自然语言处理·自动驾驶
core5122 小时前
决策树 (Decision Tree):像“猜猜看”游戏一样的AI算法
人工智能·算法·决策树
catchadmin2 小时前
使用 Laravel Workflow 作为 MCP 工具提供给 AI 客户端
人工智能·php·laravel
艾醒(AiXing-w)2 小时前
大模型原理剖析——矩阵吸收优化:LLM推理加速的核心原理与实践
人工智能·线性代数·语言模型·矩阵
龙腾AI白云2 小时前
知识图谱构建(2)四、知识推理五、知识表示六、图数据库七、NL2SQL#人工智能#具身智能#VLA#大模型
人工智能
攻城狮之路人甲2 小时前
用pycharm写的程序,点击.py无法运行闪退
ide·python·pycharm
syt_biancheng2 小时前
大规模考试系统性能优化与风险评估
python·功能测试·selenium·性能优化·postman
元智启2 小时前
企业AI智能体:生态融合重构生产力,中国方案领跑全球智能化转型——从单点突破到产业协同的范式革命
人工智能·重构
TG:@yunlaoda360 云老大2 小时前
华为云国际站代理商VIAS主要有什么作用呢?
数据库·人工智能·华为云