翻译自Understanding LightGBM Parameters (and How to Tune Them)
我已经使用lightGBM有一段时间了。对于大多数扁平数据问题,这是我的首选算法。它有很多突出特性,我建议你浏览一下。
但我一直很想了解哪些参数对性能的影响最大,以及我应该如何调整 lightGBM 参数以充分利用它。
我想我应该做一些研究,更多地了解 lightGBM 参数…并分享我的研究过程。
具体来说我做了以下事项:
在此过程中,我获得了有关 lightGBM 参数的更多知识。我希望在阅读完这篇文章后,您将能够回答以下问题:
使用 LightGBM,您可以通过boosting
参数指定使用不同类型的 Gradient Boosting 方法,例如:GBDT、DART 和 GOSS
在接下来的部分中,我将解释和比较这些方法。
该方法是传统的梯度提升决策树,首次在这篇文章中提出,XGBoost 和 pGBRT 等一些优秀算法库都基于它实现。
如今,gbdt 因其准确、效率和稳定性而被广泛使用。您可能知道 gbdt 是决策树的集成模型,但它到底是什么意思?
相关文章:理解梯度裁剪(以及它如何解决爆炸梯度问题)
我总结的要点如下。
它基于三个重要原则:
基于第一个原则, gbdt 方法中有很多决策树(弱学习器)。这些树是按如下顺序构建的:
所有这些树都是通过在整个系统中传播误差梯度来训练的。
gbdt 的主要缺点是在每个树节点中找到最佳分割点是耗时且消耗内存的操作,其他 Gradient Boosting 方法试图解决该问题。
在这篇出色的论文中,您可以了解有关 DART 梯度提升的所有信息,它通过使用神经网络中的 dropout、standard方法, 来改进模型正则化并处理一些其他less-obvious方法。
也就是说,gbdt 存在过拟合的问题,也就是在迭代末尾处添加的树往往只会影响少数实例的预测,而对剩余实例的预测影响甚微。添加 dropout 会使后续迭代中的树更泛化,从而提高性能。
lightgbm名字的由来最重要的原因就是使用了基于这篇论文的Goss方法,而Goss 是更新和更轻(lighter)的 gbdt 实现(因此是light gbm)。
标准 gbdt 是可靠的,但在大型数据集上不够快。因此,goss 提出了一种基于梯度的采样方法,以避免搜索整个搜索空间。我们知道,对于每个数据实例,当梯度较小时,意味着不用担心数据是否经过良好训练,而当梯度较大时,应该再次重新训练。所以我们这里有两类数据实例,具有大梯度和小梯度的数据实例。因此,goss 保留所有具有大梯度的数据,并对具有小梯度的数据进行随机抽样(这就是为什么它被称为 One-Side Sampling)。这使得搜索空间更小,goss 可以更快地收敛。最后,为了更深入地了解 goss,您可以查看这篇博文。
让我们将这些差异放在一个表格中:
方式 | 注意 | 需要改变的参数 | 优势 | 劣势 |
---|---|---|---|---|
Lgbm gbdt | 这是默认的boosting方式 | 因为 gbdt 是 lgbm 的默认参数,所以您不必为它更改其余参数的值(但是tuning仍是必须的!) | 过拟合、耗时、耗内存 | |
Lgbm dart | 尝试解决gbdt中过拟合的问题 | drop_seed: 选择dropping models 的随机seed uniform_dro: 如果你想使用uniform drop设置为true, xgboost_dart_mode: 如果你想使用xgboost dart mode设置为true, skip_drop: 在boosting迭代中跳过dropout过程的概率 drop_rate: dropout率,在 dropout 期间要丢弃的先前树的一小部分? |
更高的准确率 | 配置太多 |
Lgbm goss | goss 通过分离出具有较大梯度的实例,为 GBDT 提供了一种新的采样方法 | top_rate:大梯度数据的保留率 other_rate:小梯度数据的保留率 |
更快的收敛速度 | 数据集较小时过拟合 |
笔记:如果将 boosting 设置为 RF,则 lightgbm 算法的行为类似于随机森林,而不是 boosting 树!根据文档,要使用 RF,您必须使用 bagging_fraction 和 feature_fraction 小于 1。
在本节中,我将介绍 lightgbm 的一些重要的正则化参数。显然,这些是您需要调整以对抗过度拟合的参数。
您应该知道,对于小型数据集(<10000 条记录),lightGBM 可能不是最佳选择。调整 lightgbm 参数可能对您没有帮助。
此外,lightgbm 使用leaf-wise树生长算法,而 XGBoost 使用了depth-wise树生长算法。Leaf-wise 方法允许树更快地收敛,但增加了过度拟合的风险。
也许来自 PyData 会议之一的这个演讲让您对 Xgboost 和 Lightgbm 有更多的了解。值得观看!
Note:如果有人问你 LightGBM 和 XGBoost 的主要区别是什么?您可以轻松地说,它们的区别在于它们的实现方式。
根据lightGBM 文档,当面临过度拟合时,您可能需要进行以下参数调整:
在接下来的部分中,我将更详细地解释这些参数中的每一个。
Lambda_l1(和 lambda_l2)对 l1/l2 的控制以及与 min_gain_to_split 一起用于对抗过度拟合。我强烈建议您通过调参(在后面的部分中探讨)来找出这些参数的最佳值。
当然num_leaves是控制模型复杂性的最重要参数之一。使用它,您可以设置每个弱学习器拥有的最大叶子数。大的 num_leaves 增加了训练集的准确性,也增加了过拟合的风险。根据文档,一种简单的计算方法是num_leaves = 2^(max_depth)但是,考虑到在 lightgbm 中,叶子树比层次树更深,您需要小心过度拟合!因此,需要将num_leaves与max_depth一起调优。
lightgbm 文档上的照片
使用subsample(或 bagging_fraction),您可以指定每次树构建迭代使用的行的百分比。这意味着将随机选择一些行来拟合每个学习器(树)。这提高了泛化能力,并且也提高了训练速度。
我建议对基线模型使用较小的子样本值,然后在完成其他实验(不同的特征选择,不同的树结构)后增加这个值。
feature_fraction或 sub_feature 处理列采样,LightGBM 将在每次迭代(树)上随机选择特征子集。例如,如果将其设置为 0.6,LightGBM 将在训练每棵树之前选择 60% 的特征。
此功能有两种用法:
此参数控制每个训练树的最大深度,并将影响:
注意如果您使用较大的max_depth值,您的模型可能会过拟合 。
分箱是一种在离散视图(直方图)中表示数据的技术。Lightgbm 使用基于直方图的算法在创建弱学习器的同时找到最佳分割点。因此,每个连续的数字特征(例如视频的观看次数)都应该被分成离散的 bin。
此外,在这个GitHub r epo 中,您可以找到一些全面的实验,这些实验充分解释了更改 max_bin 对 CPU 和 GPU 的影响。
500 次迭代后的时钟时间 – GitHub 存储库
如果您将 max_bin 定义为 255,则意味着每个特征最多可以有 255 个唯一值。较小的 max_bin 会带来更快的速度,较大的值会提高准确性。
当你想训练你的 lightgbm时 ,可能会遇到一些典型问题:
在本节中,我们将尝试详细解释这些要点。
num_iterations 指定提升迭代的次数(要构建的树)别名num_boost_round。您构建的树越多,您的模型就越准确,其代价是:
从较少数量的树开始构建基线,然后通过增加树的数量挤压出性能。
建议使用较小的learning_rate和较大的num_iterations。此外,使用 early_stopping_rounds参数(早停法),解决num_iterations过高却没有学到任何有用东西的问题。
如果验证指标在最后一轮提前停止后没有改善,则此参数将停止训练。这应该与迭代次数成对定义。如果将其设置得太大,则会增加过度拟合的机会(但您的模型可能会更好)。
经验法则是将其设置为 num_iterations 的 10%。
使用 lightgbm 的优点之一是它可以很好地处理分类特征。是的,这个算法非常强大,但是你必须小心如何使用它的参数。lightgbm 使用一种特殊的整数编码方法(由Fisher提出)来处理分类特征
实验表明,这种方法比常用的one-hot encoding带来更好的性能。
它的默认值是“auto”,这意味着:让 lightgbm 决定这意味着 lightgbm 将推断哪些特征是分类的。
它并不总是很好用(一些实验说明了为什么在这里和这里),我强烈建议您使用此代码手动设置分类特征
cat_col = dataset_name.select_dtypes(‘object’).columns.tolist()
但是幕后发生了什么以及 lightgbm 如何处理分类特征?
根据 lightgbm 的文档,我们知道树学习器不能很好地使用一种热编码方法,因为它们在树中生长得很深。在所提出的替代方法中,树学习器是最优构造的。例如,对于具有 k 个不同类别的一个特征,有 2^(k-1) – 1 个可能的分区,并且使用Fisher方法可以 通过在值的排序直方图上找到最佳分割方式来改进**k * log(k)**在分类特征中。
您在二元分类问题中可能面临的问题之一是如何处理不平衡的数据集。显然,您需要平衡正/负样本,但您如何在 lightgbm 中做到这一点?
lightgbm 中有两个参数可以让你处理这个问题is_unbalance 和 scale_pos_weight,但它们之间有什么区别以及如何使用它们?
sample_pos_weight = 负样本数/正样本数
有时你想定义一个自定义的评估函数来衡量你的模型的性能,你需要创建一个feval
函数。
Feval 函数应该接受两个参数:
并返回
让我们逐步创建一个自定义指标函数。
定义一个单独的python函数
def feval_func (preds, train_data) :
# 定义一个计算结果
return ( 'feval_func_name' , eval_result, False )
将此函数用作参数:
print( '开始训练...' )
lgb_train = lgb.train(...,
metric=None,
feval=feval_func)
Note:要使用 feval 函数而不是 metric,您应该将 metric 参数设置为“None”。
我之前提到的大多数事情对于分类和回归都是正确的,但有些事情需要调整。
具体来说,您应该:
参数名称 | 针对分类的参数值 | 针对回归的参数值 |
---|---|---|
objective | binary或者multiclass | regression |
metric | Binary_logloss、AUC或其他 | RMSE、mean_absolute_error或其他 |
is_unbalance | True 或者 false | — |
scale_pos_weight | 仅在二分类或多分类中使用 | — |
num_class | 仅在多分类中使用 | — |
reg_sqrt | — | 用于拟合 sqrt(标签) 而不是大范围标签的原始值 |
我们已经在前面的部分中回顾并了解了一些关于 lightgbm 参数的知识,但是有关于提升树的文章,却没有提及来自 Laurae 的令人印象深刻的基准测试,都是不完整的。
您可以了解 lightGBM 和 XGBoost 的许多问题的最佳默认参数。
你可以在这里查看,但一些最重要的要点是:
参数名称 | 默认值 | 可选 | 参数类型 | 别名 | 约束或注意 | 用于 |
---|---|---|---|---|---|---|
objective | regression | regression, binary | 枚举值 | objective_type,app | 当你改变它会影响其他参数 | 指定模型类型 |
metric | null | 20多种参数 | 多枚举 | metrics, metric_types | null 表示将自动使用与指定objective对应的指标 | 指定metric,支持多个metric |
boosting | gbdt | gbdt, rf, dart, goss | 枚举值 | boosting_type | 如果您将其设置为rf,相当于使用bagging approach方法 | boosting方法 |
lambda_l1 | 0.0 | [0, ∞] | 浮点数 | reg_alpha | lambda_l1 >= 0.0 | 正则化 |
bagging_fraction | 1 | [0, 1] | 浮点数 | subsample | 0.0 < bagging_fraction <= 1.0 | 随机选择部分数据而不重新采样 |
bagging_freq | 0 | [0, ∞] | 整形 | subsample_freq | 要启用 bagging,bagging_fraction 也应设置为小于 1.0 的值 | 0 表示禁用 bagging;k 表示在每 k 次迭代中执行 bagging |
num_leaves | 31 | [1, ∞] | 整形 | num_leaf | 1 < num_leaves <= 131072 | 指定一棵树的最大叶子数 |
feature_fraction | 1.0 | [0,1] | 浮点数 | sub_feature | 0.0 < feature_fraction <= 1.0 | 如果将其设置为 0.8,LightGBM 将选择 80% 的特征 |
max_depth | -1 | [-1, ∞] | 整形 | max_depth | 越大通常越好,但过拟合风险会增加 | 限制树模型的最大深度 |
max_bin | 255 | [2, ∞] | 整形 | histogram binning | max_bin > 1 | eal with over-fitting? |
num_iterations | 100 | [1, ∞] | 整形 | num_boost_round, n_iter | num_iterations >= 0 | boosting迭代次数 |
learning_rate | 0.1 | [0, 1] | 浮点数 | eta | learning_rate > 0.0,常用:0.05 | 在 dart 中,它也会影响dropped trees的归一化权重 |
early_stopping_round | 0 | [0, ∞] | 浮点数 | early_stopping_round | 如果validation在最近一次early_stopping 中没有改善,将停止训练 | 模型性能、迭代次数、训练时间 |
categorical_feature | 空字符串 | 指定列索引值 | 多整数或字符串 | cat_feature | — | 处理类别特征 |
bagging_freq | 0 | [0, ∞] | 整形 | subsample_freq | 0表示禁用 bagging;k 表示在每 k 次迭代中执行 bagging | 要启用 bagging,bagging_fraction 也应设置为小于 1的值 |
verbosity | 0 | [-∞, ∞] | 整形 | verbose | < 0: Fatal, = 0: Error (Warning), = 1: Info, > 1: Debug | 调试时使用 |
min_data_in_leaf | 20 | min_data | 整形 | min_data | min_data_in_leaf >= 0 | 可用于处理过拟合 |
Note:您永远不应将任何参数值设为默认,应该根据您的问题进行调整。也就是说,这些参数是您的调整算法超参数的一个很好的起点
也可以看看
可视化机器学习实验的指标和超参数的最佳工具
Python 中的超参数调整:完整指南 2020
最后,在解释了所有重要参数之后,是时候进行一些实验了!
我将使用流行的 Kaggle 竞赛之一:Santander Customer Transaction Prediction。
我将使用这篇文章来解释如何在 Python中对任何脚本运行超参数调整。
值得一读!
在我们开始之前,一个重要的问题!我们应该调整哪些参数?
Note:为超参数创建两个字典是个好主意,一个包含您不想调整的参数和值,另一个包含您想要调整的参数和值范围。
SEARCH_PARAMS = { 'learning_rate':0.4,
'max_depth':15,
'num_leaves':20,
'feature_fraction':0.8,
'subsample':0.2 }
FIXED_PARAMS={ 'objective' : 'binary' ,
'metric' : 'auc' ,
'is_unbalance' : True ,
'boosting' : 'gbdt' ,
'num_boost_round' : 300 ,
'early_stopping_rounds' : 30 }
通过这样做,您可以将基线值与搜索空间分开!
请注意,由于最近的API 更新,这篇文章也需要一些更改——我们正在努力!同时,请查看Neptune 文档,其中所有内容都是最新的!
现在,这就是我们要做的。
第一步,我们在Notebook中生成代码。它是公开的,您可以下载它。
第二步,我们在Neptune.ai上跟踪每个实验的结果。
见Naptune
可能有用: 如何借助 Neptune-LightGBM 集成跟踪模型训练元数据
如果您看了上一节,您会注意到我已经对数据集进行了超过 14 次不同的实验。在这里,我将解释如何逐步调整超参数的值。
创建基线训练代码:
from sklearn.metrics import roc_auc_score, roc_curve
from sklearn.model_selection import train_test_split
import neptunecontrib.monitoring.skopt as sk_utils
import lightgbm as lgb
import pandas as pd
import neptune
import skopt
import sys
import os
SEARCH_PARAMS = {'learning_rate': 0.4,
'max_depth': 15,
'num_leaves': 32,
'feature_fraction': 0.8,
'subsample': 0.2}
FIXED_PARAMS={'objective': 'binary',
'metric': 'auc',
'is_unbalance':True,
'bagging_freq':5,
'boosting':'dart',
'num_boost_round':300,
'early_stopping_rounds':30}
def train_evaluate(search_params):
# you can download the dataset from this link(https://www.kaggle.com/c/santander-customer-transaction-prediction/data)
# import Dataset to play with it
data= pd.read_csv("sample_train.csv")
X = data.drop(['ID_code', 'target'], axis=1)
y = data['target']
X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.2, random_state=1234)
train_data = lgb.Dataset(X_train, label=y_train)
valid_data = lgb.Dataset(X_valid, label=y_valid, reference=train_data)
params = {'metric':FIXED_PARAMS['metric'],
'objective':FIXED_PARAMS['objective'],
**search_params}
model = lgb.train(params, train_data,
valid_sets=[valid_data],
num_boost_round=FIXED_PARAMS['num_boost_round'],
early_stopping_rounds=FIXED_PARAMS['early_stopping_rounds'],
valid_names=['valid'])
score = model.best_score['valid']['auc']
return score
使用您选择的超参数优化库(例如 scikit-optimize)
neptune.init('mjbahmani/LightGBM-hyperparameters')
neptune.create_experiment('lgb-tuning_final', upload_source_files=['*.*'],
tags=['lgb-tuning', 'dart'],params=SEARCH_PARAMS)
SPACE = [
skopt.space.Real(0.01, 0.5, name='learning_rate', prior='log-uniform'),
skopt.space.Integer(1, 30, name='max_depth'),
skopt.space.Integer(10, 200, name='num_leaves'),
skopt.space.Real(0.1, 1.0, name='feature_fraction', prior='uniform'),
skopt.space.Real(0.1, 1.0, name='subsample', prior='uniform')
]
@skopt.utils.use_named_args(SPACE)
def objective(**params):
return -1.0 * train_evaluate(params)
monitor = sk_utils.NeptuneMonitor()
results = skopt.forest_minimize(objective, SPACE,
n_calls=100, n_random_starts=10,
callback=[monitor])
sk_utils.log_results(results)
neptune.stop()
尝试不同类型的配置并在Neptune中跟踪您的结果
最后,在下表中,您可以看到参数发生了哪些变化。
超参数 | 调参前 | 调参后 |
---|---|---|
learning_rate | 0.4 | 0.094 |
max_depth | 15 | 10 |
num_leaves | 32 | 12 |
feature_fraction | 0.8 | 0.1 |
subsample | 0.2 | 0.75 |
boosting | gbdt | dart |
Score(auc) | 0.8256 | 0.8605 |
长话短说,你学到了:
和一些其他的东西有关更详细的信息,请参阅资源。