超参数优化是一项艰巨的任务。 但是使用 Optuna 等工具可以轻松应对。
在这篇文章中,我将展示如何使用 Optuna 调整 CatBoost 模型的超参数。
Optuna 的超参数调整可视化
超参数常规参数是在训练期间通过机器学习算法学习的参数。而超参数是设置如何训练模型的参数,它们有助于训练出更好的模型。
以线性回归模型为例,线性回归通过训练参数来确定每个特征的权重。在构建模型时我们可以向模型添加正则化并附带一个超参数来控制正则化项的权重。这个带有超参数的附加项会改变模型的整体预测方式。所以更改这个超参数可以使模型变得更好或者更差,所以超参数的调整是非常重要并且必要的。
超参数可以改变模型的行为在树型模型中更为突出,例如树型模型超参数可以控制树的深度、叶的数量、如何分割以及许多其他选项。这些选项中的每一个的改变都会对模型的结构以及它如何做出决策产生巨大的影响。虽然都是决策树,但树的类型可能会有很大的不同。
对于更复杂集成模型来说,这些复杂的模型建立在许多不同的模型上,每个模型都有其超参数所以影响就更加的大了。需要为每个模型选择合适的超参数,如果人工来做工作量就会成倍增长,并且训练时间也会变得很长。
超参数优化在超参数优化方面有多种选择。最常用的方法是Grid Search和它的一些改进的方法。
Grid Search
Grid Search是一种简单的暴力方法,它对输入到搜索空间的每个超参数进行组合。为每个组合创建一个模型并进行比较。虽然听着没有任何问题,但有几个关键方面需要注意。
首先,确定最优超参数是一个NP-Hard问题,因为需要处理的是超参数的组合。就复杂性而言,暴力搜索的成本是无法接受的。
第二个需要注意的方面是,训练的模型在很大一部分搜索中性能一直表现得很差。但Grid Search的还是会继续建立和训练这些模型。
假设我们正在构建一棵决策树并使用Grid Search进行超参数的优化,在我们的超参数中包含了的“基尼系数”和”熵”的超参数设置。假设我们在训练时发前几个测试中,“基尼系数”的性能要优越得多。Grid Search还需会继续使用搜索空间中带有”熵”的参数进行训练。
Random Search
Grid Search的替代方法是Random Search。从名字看这似乎是比Grid Search更糟糕的选择。但是其实事实已经证明Random Search比Grid Search性能更好。
非常简单的基本原理是随机搜索避免了Grid Search执行的许多冗余操作。Random Search通过以不均匀的间隔搜索超参数空间,更有可能找到局部最优的超参数。
替代方案
由于前面的两种方法都没有包含任何结构化的方法来搜索最优超参数集,所以我们这里将要介绍新的包来优化他们的问题提高效率。scikit-optimization和Optuna这样的包为我们提供了超参数搜索的新方法。
OptunaOptuna是一个超参数的优化工具,对基于树的超参数搜索进行了优化,它使用被称为TPESampler“Tree-structured Parzen Estimator”的方法,这种方法依靠贝叶斯概率来确定哪些超参数选择是最有希望的并迭代调整搜索。
无论使用的模型是什么,使用Optuna优化超参数都遵循类似的过程。第一步是建立一个学习函数。这个函数规定了每个超参数的样本分布。
最常见的可用选项是categorical、integer、float或log uniform。想要检查 0.001、0.01 和 0.1 范围内的值时,可以使用log uniform,因为其中每个值都有相同的被选中概率。
Optuna的另一个优点是能够设置条件超参数。因为许多超参数只有在与其他超参数组合使用时才更加有效。单独改变它们可能不会产生预期的效果。
为了说明Optuna,我选择优化一个CatBoost模型。这个模型拥有数量惊人的超参数。虽然这篇文章只展示了其中的一部分,但是Optuna的许多特性,比如条件超参数都会被展示出来。
CatboostCatboost 是一种基于树的集成方法。 这是一个非常强大的模型。
与其他预测模型相比,CatBoost 的直接好处之一是可以直接处理分类变量。 因此,“cat”这个名字是 categorical 的缩写。
CatBoost 的这一特性使其成为懒惰数据科学家的理想选择。 将分类变量转换为数值变量可能需要一些时间,并且还需要在验证、测试和推理时做相同的事情。使用 CatBoost只需定义分类参数,然后调整超参数即可处理这些分类特征。
超参数“cat_features”设置哪些特征是分类的。 如果没有指定分类特征,CatBoost 将抛出一个错误,因为模型通常的输入必须是数字。
Catboost 超参数loss_function — 训练损失函数,对于回归可以使用 RMSE 或 MAE。
iterations — 设置迭代限制树的数量。其他超参数也可能会限制树的数量,从而导致总次数少于迭代次数。
learning_rate — 在优化期间使用学习率。
l2_leaf_reg— 指定正则化项的系数。这一项是 将L2 添加到成本函数中。
depth— 树的深度。
min_data_in_leaf— 指定何时停止分裂。当实例数低于此值时,该节点将变为叶子。
one_hot_max_size— 唯一值小于或等于该值的参数的 One-Hot 编码。
boosting_type — “Ordered”或“Plain” 。“Ordered”在较小的数据集上更好,但比普通方案慢。对于较大的数据集,建议使用“Plain”。
rsm— ‘Alias: colsample_bylevel’定义用于在分割时选择特征以及随机再次选择特征时使用的百分比。
bootstrap_type— 权重采样方法,‘Bayesian’, ‘Bernoulli’, ‘MVS’, ‘Poisson’, ‘No’
bagging_temperature — 定义贝叶斯采样的设置。当参数设置为 1 时,根据指数分布添加权重。
subsample— 当‘Poisson’、‘Bernoulli’或‘MVS’用于采样方法时,使用bagging的采样率。
Optuna示例在这个例子中,我们使用使用钻石数据集。 该数据集旨在根据其他属性预测钻石的价格。 一些变量是分类的这通常需要一些预处理。
https://www.kaggle.com/shivam2503/diamonds
使用 CatBoost无需任何预处理即可生成模型,甚至可以处理缺失值,所以使它是一个非常强大且易于使用的模型。
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score
from catboost import CatBoostRegressor
import optuna
def objective(trial):
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
param = {
"loss_function": trial.suggest_categorical("loss_function", ["RMSE", "MAE"]),
"learning_rate": trial.suggest_loguniform("learning_rate", 1e-5, 1e0),
"l2_leaf_reg": trial.suggest_loguniform("l2_leaf_reg", 1e-2, 1e0),
"colsample_bylevel": trial.suggest_float("colsample_bylevel", 0.01, 0.1),
"depth": trial.suggest_int("depth", 1, 10),
"boosting_type": trial.suggest_categorical("boosting_type", ["Ordered", "Plain"]),
"bootstrap_type": trial.suggest_categorical("bootstrap_type", ["Bayesian", "Bernoulli", "MVS"]),
"min_data_in_leaf": trial.suggest_int("min_data_in_leaf", 2, 20),
"one_hot_max_size": trial.suggest_int("one_hot_max_size", 2, 20),
}
# Conditional Hyper-Parameters
if param["bootstrap_type"] == "Bayesian":
param["bagging_temperature"] = trial.suggest_float("bagging_temperature", 0, 10)
elif param["bootstrap_type"] == "Bernoulli":
param["subsample"] = trial.suggest_float("subsample", 0.1, 1)
reg = CatBoostRegressor(**param, cat_features=categorical_features_indices)
reg.fit(X_train, y_train, eval_set=[(X_test, y_test)], verbose=0, early_stopping_rounds=100)
y_pred = reg.predict(X_test)
score = r2_score(y_test, y_pred)
return score
以上代码已经可以创建模型,但是如果使用Optuna还有一些参数需要设置,首先是采样器,需要使用前面讨论过的 TPESampler。 这种选择确保搜索将更加结构化和定向,而不是标准的Grid Search。
需要注意的其他一些选项是direction、n_trials 和timeout。
direction决定了优化的执行方式。 这个需要与正在使用的损失函数的预期优化相匹配。
接下来,n_trials 控制将执行多少个超参数空间的样本。 结合timeout,这两个因素会影响最终的运行时间。 如果发现训练的时间紧张,则需要设置这两个参数。
搜索的最终状态将都会被保存(并且稍后重新启动后可以加载),我们可以将长时间的训练任务分段执行。
import numpy as np
import pandas as pd
from optuna.samplers import TPESampler
df = pd.read_csv('diamonds.csv').drop(['index'],axis=1)
X = df.drop(['price'],axis=1)
y = df['price']
categorical_features_indices = np.where(X.dtypes != np.float)[0]
study = optuna.create_study(sampler=TPESampler(), direction="maximize")
study.optimize(objective, n_trials=10, timeout=600) # Run for 10 minutes
print("Number of completed trials: {}".format(len(study.trials)))
print("Best trial:")
trial = study.best_trial
print("\tBest Score: {}".format(trial.value))
print("\tBest params: ")
for key, value in trial.params.items():
print(" {}: {}".format(key, value))
一旦训练完成(无论是在达到最终迭代还是达到超时限制后)下一步是对结果进行可视化。
上面的脚本将输出最优的模型性能和使用的超参数。我们还可以使用Optuna内置的可视化功能查看搜索进
Hyper-Parameter重要性:确定哪些参数对模型的整体性能有最显著的影响。
optuna.visualization.plot_param_importances(study)
多次迭代的性能:模型在多次迭代中的性能。
optuna.visualization.plot_optimization_history(study)
单个超参数的性能:不同超参数在多次试验中的进展情况。
optuna.visualization.plot_slice(study, params=['depth', 'learning_rate', 'bootstrap_type'])
总结
如果不适当调整超参数模型的性能可能会受到很大影响,如果人工调整超参数则会产生很大的工作量。像 Optuna 这样的工具可以帮助我们将超参数过程变得简单而有效。
Optuna 提供了一种基于贝叶斯的方法来进行超参数优化和有效的搜索结构化,为模型的实际超参数调整提供了理想的解决方案。
作者:Zachary Warnes
,