Boosting 回归模型超参调优指南

XGBoostCatBoostLightGBM 等提升决策树算法是非常强大的回归任务机器学习方法。要获得最佳预测性能,需要进行超参数调优,例如网格搜索来探索数千种参数组合。

虽然暴力技术可以完成任务,但它们很快就会变得计算量过大,并且可能导致过拟合模型,使其无法泛化到未见过的数据。

另一方面,贝叶斯优化提供了一个更高效的替代方案,通过智能地导航超参数空间来减少计算负担。

值得注意的是,仅仅使用贝叶斯技术进行超参数优化也不足以保证模型的鲁棒性。适当的验证策略,包括独立验证集和交叉验证程序,对于评估模型在训练数据之外的泛化能力至关重要。

在本博客中,你将通过实战示例学习如何优化提升回归模型,同时最小化过拟合的风险。 我们将使用 HGBoost 库 来训练和优化基于 XGBoost 的回归模型,并使用贝叶斯优化技术。在工作流程中,我们将仔细地将数据分为训练集、测试集和独立验证集,结合内部优化循环和外部交叉验证循环来选择最鲁棒的模型。然后我们将解释并可视化分析优化后的超参数空间以及由此产生的模型性能指标。

在整个博客中,理论和实际示例的结合将帮助你逐步更好地理解为自己的数据集做出决策。

1、回归模型的超优化是一个多步骤过程

构建一个鲁棒的提升回归模型远不只是简单地将算法拟合到数据上。挑战是双重的:首先,我们想要识别一个能最大化预测性能的参数组合;其次,我们旨在防止过拟合并确保模型能泛化到未见过的数据。

超优化工作流是一个多步骤过程,可以分解为九个较小的步骤。第一步专注于准备数据,而剩余步骤(二到九)主要由 HGBoost 框架自动化。不过,你在所有步骤中都有足够的控制权!在以下部分中,我们将详细介绍每个步骤并解释它们如何有助于创建一个可靠且可解释的回归模型。

  1. 导入并预处理数据集 将数据集加载到你的 Python 环境中,并执行必要的预处理步骤,如处理缺失值、编码分类变量、移除不一致性以及为模型训练准备特征矩阵。
  2. 将数据集拆分为训练集、测试集和验证集 仔细地将数据划分为独立的子集以避免信息泄漏。训练集和测试集在超参数优化期间使用,而独立验证集保留用于最终对选定模型的无偏评估。
  3. 选择适当的评估指标 选择一个与问题目标一致的回归指标,如 RMSE、MAE 或 R²。所选指标将指导优化过程并决定模型性能的评估方式。
  4. 构建嵌套交叉验证框架 创建一个双重循环验证策略,其中内部循环执行贝叶斯超参数优化,外部循环使用交叉验证评估模型鲁棒性。这种方法有助于防止过于乐观的性能估计。
  5. 识别能够泛化的最佳性能模型 不仅根据预测准确性比较候选模型,还要根据它们在不同验证折中的持续泛化能力。目标是选择一个在未见数据上可靠执行的模型。
  6. 在独立验证集上评估选定模型 模型选择后,在未触动的验证集上评估最终性能,以获得真实世界预测性能的无偏估计。
  7. 在完整数据集上训练最终模型 一旦确定了最优超参数,使用完整数据集重新训练选定的提升回归模型,以最大化可用于学习的信息。
  8. 可视化超参数搜索空间和模型性能 生成信息丰富的图表以检查优化轨迹、比较模型配置,并更好地理解特定超参数如何影响预测性能。
  9. 解释结果和模型行为 分析结果,评估模型的优势和局限性,并解释学习到的模式,以获得关于优化过程和回归模型本身的实用洞察。

2、HGBoost 库

超优化梯度提升库(HGBoost)是一个用于 XGBoostLightGBMCatBoost 超参数优化的 Python 包。HGBoost 自动化了最密集的步骤,如上图所示(步骤 2 到 9)。

这带来了许多优势:

  1. 自动搜索能泛化的最佳模型
  2. 减少选择过训练模型的可能性
  3. 通过有洞察力的图表提供可解释的结果
  4. 深入检查超参数空间
  5. 了解所有评估模型的性能
  6. 确定 k 折交叉验证的准确率
  7. 最后但并非最不重要的,最佳决策树可以与最重要的特征一起绘制

示意图如下图所示。

3、回归挑战参数优化的实战用例

对于这个用例,我们将使用 Titanic 数据集。我选择这个众所周知的数据集是因为我想把重点放在超参数优化步骤上,而不一定是数据本身。因为我们已经见过这个数据集很多次,所以更容易理解我们采取的一些步骤以及如何优化结果。这可以帮助你将类似的解释映射到你自己的数据集上。

总结来说,Titanic 数据集是免费使用的,曾是 Kaggle 比赛 从灾难中学习机器学习 的一部分。它包含 891 个样本和 12 个特征。这个数据集经常被用来预测布尔型 生存 变量。在我们的案例中,将把 年龄设为目标值

在开始之前,设置你的环境并安装所需的包

第一步是使用 Conda、Venv 或 uv 创建一个新环境。我强烈建议始终为项目或实验创建新的干净环境。原因是旧环境可能包含特定版本的包,这些包可能与新安装冲突。然后在终端中从 PyPi 安装 HGboost

# 1. Install the library
pip install hgboost

# 2. Install hyperopt from GitHub source
git+https://github.com/erdogant/hyperopt.git

3.1 数据预处理

数据预处理可能是密集的,通常需要反复迭代,直到你清理、插补、移除变量。对于 Titanic 数据集,我们将移除 PassengerIdName 等特征,以及包含缺失值的行(样本)。预处理步骤后,有 714 行 x 203 列。

请注意,在实际项目中,建议仔细进行探索性数据分析、特征清理和特征工程。最大的性能提升通常来自这些初始步骤。

最大的性能提升通常来自特征清理和特征工程等步骤。超参数调优是为了榨出最后的(隐藏的)信息。

预处理步骤完成后,我们可以使用 HGboost 将数据集转换为 XGBoost 算法 可以使用的结构化形式。请参见下面代码块中的示例。

# Import the library
from hgboost import hgboost
import numpy as np

# Initialize
hgb = hgboost()

# Load example data set
df = hgb.import_example('titanic')

print(df)
#      PassengerId  Survived  Pclass  ...     Fare Cabin  Embarked
# 0              1         0       3  ...   7.2500   NaN         S
# 1              2         1       1  ...  71.2833   C85         C
# 2              3         1       3  ...   7.9250   NaN         S
# 3              4         1       1  ...  53.1000  C123         S
# 4              5         0       3  ...   8.0500   NaN         S
# ..           ...       ...     ...  ...      ...   ...       ...
# 886          887         0       2  ...  13.0000   NaN         S
# 887          888         1       1  ...  30.0000   B42         S
# 888          889         0       3  ...  23.4500   NaN         S
# 889          890         1       1  ...  30.0000  C148         S
# 890          891         0       3  ...   7.7500   NaN         Q

# [891 rows x 11 columns]

# Set age as target value, then remove from data set
y = df['Age'].values
I = ~np.isnan(y)

# Remove features
df.drop(['Age', 'PassengerId', 'Name'], axis=1, inplace=True)

# Perform pre-processing
X = hgb.preprocessing(df)

# New dataset
X = X.loc[I,:]
y = y[I]

##################################################################
print(X)

#      Survived_1.0  Pclass_1.0  ...  Embarked_Q  Embarked_S
# 0           False       False  ...       False        True
# 1            True        True  ...       False       False
# 2            True       False  ...       False        True
# 3            True        True  ...       False        True
# 4           False       False  ...       False        True
# ..            ...         ...  ...         ...         ...
# 885         False       False  ...        True       False
# 886         False       False  ...       False        True
# 887          True        True  ...       False        True
# 889          True        True  ...       False       False
# 890         False       False  ...        True       False

# [714 rows x 203 columns]

预处理函数导入 df2onehot 库 将分类值编码为独热编码,并保持连续值不变。请注意,预处理步骤是在考虑到我们将要训练 XGBoost 算法 的情况下执行的。这需要一个独热编码步骤,因为 XGBoost 无法处理非数值因子。阅读这篇博客以了解更多关于优势以及如何进行预处理的信息。

如何驯龙:不产生过拟合的梯度提升超参数调优使用 XGBoost、CatBoost 和 LightBoost 等提升决策树算法,你可能超越其他模型,但是……

3.2 拆分数据集

在初始化 HGBoost 时,我们可以更改默认输入参数(如下面的代码部分所示)。进行超参数优化时,更重要的步骤之一是将数据划分为独立的子集以避免数据泄漏。训练集和测试集在超参数优化期间使用,而独立验证集保留用于最终对选定模型的无偏评估。

总体而言,对于监督任务,将数据分成独立部分以避免在学习模型时过拟合是重要的。过拟合发生在模型过于好地学习了训练数据时,包括噪声和不能泛化的模式,导致在新的未见数据上表现不佳。

在训练过程中,数据集被分为训练集、测试集和独立验证集。验证集在整个训练过程中保持不变,只在最后用于客观地评估最终模型的性能。

在下面的示例中,数据集使用 20% 的测试比例和 20% 的验证比例进行拆分,由 test_size=0.2eval_size=0.2 定义。此外,训练期间应用了 5 折交叉验证以提高模型评估的鲁棒性。下图展示了数据集如何被划分为不同的拆分,代码输出还报告了分配给每个子集的确切样本数。

# Initialize library.
hgb = hgboost(
    max_eval=500,      # Search space is based  on the number of evaluations.
    threshold=0.5,     # Classification threshold. In case of two-class model this is 0.5.
    cv=5,              # k-folds cross-validation.
    test_size=0.2,     # Percentage split for the testset.
    val_size=0.2,      # Percentage split for the validationset.
    top_cv_evals=10,   # Number of top best performing models that is evaluated.
    is_unbalanced=True, # Control the balance of positive and negative weights, useful for unbalanced classes.
    random_state=None, # Fix the random state to create reproducible results.
    n_jobs=-1,         # The number of CPU jobs to run in parallel. -1 means using all processors.
    gpu=False,         # Compute using GPU in case of True.
    early_stopping_rounds=25,
    verbose='info',         # Print progress to screen.
)

# results = hgb.xgboost_reg(X, y, eval_metric='rmse')
# [hgboost.hgboost] [INFO] Start hgboost regression.
# [hgboost.hgboost] [INFO] Collecting %s parameters.
# [hgboost.hgboost] [INFO] method: xgb_reg
# [hgboost.hgboost] [INFO] eval_metric: rmse
# [hgboost.hgboost] [INFO] larger_is_better: False
# [hgboost.hgboost] [INFO] *****************************************************************************************
# [hgboost.hgboost] [INFO] Total dataset: (714, 202)
# [hgboost.hgboost] [INFO] Validation set: (143, 202)
# [hgboost.hgboost] [INFO] Test-set: (143, 202)
# [hgboost.hgboost] [INFO] Train-set: (428, 202)

3.3 选择适当的评估指标

为了评估和优化回归模型的性能,我们需要选择一个与问题目标一致的指标。常见的评估指标有均方根误差(RMSE)、均方误差(MSE)、平均绝对误差(MAE)和 R²。

所选指标决定了在训练期间如何衡量模型性能,并指导优化过程。在这个实现中,评估指标可以通过 eval_metric 参数指定。

如果不仔细拆分数据,你就有过拟合模型参数并将数据拟合(或学习)得太好的风险。然后它可能很容易在正确预测新的(未见过的)数据样本上失败。

3.4 拟合、优化超参数并选择最佳性能模型

在这一点上,我们已经为 XGBoost 预处理了数据并决定了使用哪个评估指标。我们现在可以开始拟合模型并优化超参数了。 HGBoost 中超参数优化的第一步是设置内部循环以使用贝叶斯优化超参数,以及设置外部循环以使用 k 折交叉验证测试最佳性能模型的泛化能力。

在我们的案例中,搜索空间取决于 XGBoost 可用的超参数。超参数优化的搜索空间由 HGBoost 处理。我将使用 MAE 评估指标,因为误差容易解释。

我们将预测年龄,当我们看到 MAE=10 时,这意味着平均而言,预测值与真实值的距离是 10 年(例如真实年龄可以是 40 岁,预测年龄是 30(或 50)岁)。

# Train a model with XGBoost
results = hgb.xgboost(X, y, pos_label=1, method='xgb_reg', eval_metric='mae')

# [hgboost.hgboost] [INFO] *****************************************************************************************
# [hgboost.hgboost] [INFO] Evaluate best [xgb_reg] model on validation dataset (143 samples, 20%)
# [hgboost.hgboost] [INFO] [mae]: 9.567 using optimized hyperparameters on validation set.
# [hgboost.hgboost] [INFO] [mae]: 10.15 using default (not optimized) parameters on validation set.
# [hgboost.hgboost] [INFO] *****************************************************************************************
# [hgboost.hgboost] [INFO] Retrain [xgb_reg] on the entire dataset with the optimal hyperparameters.
# [hgboost.hgboost] [INFO] Fin!

# Print summary of results
print(results['summary'])

# Plot the summary of all evaluated models
hgb.plot()

通过运行如代码块所示的 HGBoost,我们遍历了搜索空间并创建了 500 个不同的模型(max_eval=500),每个模型的性能对每组参数进行了评估(也见下面图表的 x 轴)。

接下来,我们可以根据模型性能进行排名,以使用 5 折交叉验证方案(cv=5)进一步调查排名前 10 的最佳性能模型的鲁棒性。从这个意义上说,我们的目标是防止找到过训练的模型。通过绘图函数 .plot(),我们可以获得 500 个模型性能(在这种情况下是 MAE)的洞察。

当我们看这张图时,可以看到一条绿色虚线,它描绘的是没有使用交叉验证方法的最佳性能模型,而红色虚线描绘的是使用交叉验证的最佳性能模型。模型性能在 MAE = [9.5–11.25] 范围内变化。我们选择的模型是红色虚线对应的那个。

当我们看排名前 10 的模型(红色的方形标记)时,它们在 k 折交叉验证期间的性能略差。换句话说,500 次迭代中的最佳模型用绿色虚线表示,但在 CV 中它的性能并不是最好的。因此,选择了前 10 个中能更好泛化的模型(红色虚线),即 k 折 CV 中平均 MAE 最低的那个。通过这种方式,我们旨在选择性能最好且能泛化的模型。

对于我们的数据集,500 次迭代中得分最高的模型得分为 MAE=9.567,而基于 5 折交叉验证的平均得分为 MAE=10.2。虽然我们没有选择得分最高的模型,但我们也避免了依赖一个可能过拟合的单一模型。我们现在有了一个准备好对新数据进行预测的模型。 但在做预测之前,让我们先尝试理解上面所有步骤中发生了什么(见下一节中的步骤 5)。

3.5 结果的解释

在这一点上,我们有了一个训练好的模型,并简要了解了基于交叉验证和独立验证集的回归结果。所有测试过的不同模型的超参数现在返回在 results['summary'] 中,可以进一步检查。摘要如下图所示。这里的每一行都是一个使用不同超参数集合学习的新模型。

创建图表对于深入理解模型性能以及调查超参数在贝叶斯优化过程中如何调优非常有帮助。这将有助于更好地理解模型参数与准确率之间的关系。

建立信任并获得对结果的直觉是知道模型参数是否被可靠选择的关键。制作图表并理解结果。

我们可以使用内置功能创建以下图表:

  1. 用于深入调查超参数空间的图表。
  2. 汇总所有评估模型的图表。
  3. 显示使用交叉验证方案的性能的图表。
  4. 绘制独立验证集的结果。
  5. 最佳模型和最佳性能特征的决策树图。

3.6 超参数调优的解释

在我们找到优化后的模型后,我们可以调查贝叶斯优化过程中调优的所选超参数的可靠性。使用 .plot_params() 函数,我们可以创建有洞察力的图表,如图 2 所示。

该图包含多个直方图(或核密度图),其中每个子图包含一个在 500 次模型迭代期间优化的单个参数。直方图底部的小条描绘了 500 次评估,而黑色虚线垂直线描绘了在排名前 10 的最佳性能模型中使用的特定参数值。

绿色虚线描绘的是没有使用交叉验证方法的最佳性能模型,而红色虚线描绘的是使用交叉验证的最佳性能模型。

让我们看下面的图。在其中一个子图中,有参数 subsample,其值范围从 0.4 到 1.1。在 0.7 附近有一个明显的峰值,表明贝叶斯优化更密集地探索了这些区域。我们最佳性能的模型似乎使用 subsample=0.72(红色虚线)。

但还有更多值得看的。当我们现在看同一个子图但在下面的图中,我们也有 subsample,其中每个点是 500 个模型之一。横轴是迭代次数,纵轴是优化后的值。对于这个参数,在优化过程的迭代中有一个明显的趋势。它首先探索了上界区域,然后移向下界区域,因为降低 subsample 值时模型得分明显更好。

通过这种方式,所有超参数都可以与不同模型关联起来进行解释。

3.7 独立验证集上模型性能的解释

为确保模型能在未见数据上泛化,我们使用独立验证集。下图描绘了回归线,预测结果没有显示出强异常值或持续的高估或低估。对于非常年轻的年龄组可能存在轻微低估,因为模型预测的年龄比真实年龄更小。

为了进一步调查我们最终模型的泛化能力,我们可以使用 .plot_cv() 函数绘制 5 折交叉验证的结果。这将创建不同折的 ROC 曲线,如图 5 所示。在这里我们可以看到,模型在不同折中的斜率或多或少相似。因此,我们的最终模型在 k 折 CV 上没有表现出异常。

3.8 最佳模型的决策树图

通过决策树图,我们可以更好地理解模型在分类中如何做出决策。它也可能提供一些关于这样的模型是否能泛化到其他数据集的直觉。请注意,默认返回的是最佳树,num_tree=0,但创建了许多树,可以通过指定输入参数 .treeplot(num_trees=1) 来返回。

此外,我们还可以绘制最佳性能特征,如下图所示。特征 女性性别SibSp=1 和 parch=1 是预测年龄的前 3 个重要特征。

3.9 对新数据进行预测

在拥有最终训练好的模型后,我们可以对新的未见数据进行预测。假设 X 是新数据,并且已经像训练过程中一样进行了类似的预处理,那么我们可以使用 hgb.predict(X) 函数进行预测。该函数返回分类概率和预测标签。

# Make prediction
predict = hgb.predict(X.loc[0:2,:])

# Predicted Age
array([24.329363, 44.43117 , 26.974583])

# True Age:
print(y[0:3])
array([22., 38., 26.])

4、保存和加载模型

保存和加载模型可能很有用。为了实现这一点,有两个函数:hgb.save() 和函数 hgb.load()

# Save model
status = hgb.save(filepath='hgboost_model.pkl', overwrite=True)
# [pypickle] Pickle file saved: [hgboost_model.pkl]
# [hgboost] >Saving.. True

# Import library when using a fresh start
From hgboost import hgboost

# Initialize hgboost
hgb = hgboost()

# Load the pickle file with model parameters and trained model.
results = hgb.load(filepath='hgboost_model.pkl')

# [pypickle] Pickle file loaded: [hgboost_model.pkl]
# [hgboost] >Loading succesful!

# Make predictions again
y_pred, y_proba = hgb.predict(X)

5、结束语

在本指南中,你学习了如何通过将数据集拆分为训练集、测试集和独立验证集来训练一个具有优化超参数的鲁棒机器学习模型。超参数优化在内部循环中使用贝叶斯优化进行,而外部循环使用 k 折交叉验证来评估模型对未见数据的泛化能力。这种方法有助于识别最准确和最可靠的模型。

在训练任何模型之前,仍然重要的是遵循标准的机器学习工作流程:执行探索性数据分析(EDA)、清理数据、工程有意义的特征以及应用特征选择。在实践中,最大的性能改进通常是在这些预处理步骤中实现的,而不是在模型调优本身。

HGBoost 支持回归、分类、多类学习和集成提升模型,全部使用相同的鲁棒优化程序。在底层,它依赖 Hyperopt 进行贝叶斯超参数优化,尽管近年来 Optuna 等替代方案也变得越来越流行。

我希望本指南为超参数优化和鲁棒模型选择背后的工作流程提供了清晰的概述。


原文链接: Hyperparameter Tuning of Boosting Regression Models Without Overfitting

汇智网翻译整理,转载请标明出处