我已经使用lightGBM一段时间了。这是我解决大多数结构化数据问题的首选算法。令人敬畏的功能列表很长,如果您还没有,我建议您看一下。
但我一直很想了解哪些参数对性能影响最大,以及我应该如何调整 lightGBM 参数以充分利用它。
我想我应该做一些研究,更多地了解 lightGBM 参数……并分享我的旅程。
具体我:
深入研究LightGBM 的文档
自己做了一些实验
在这样做的过程中,我获得了更多关于 lightGBM 参数的知识。我希望阅读本文后您能够回答以下问题:
LightGBM 中实现了哪些 Gradient Boosting 方法,它们有什么区别?
通常哪些参数很重要?
需要调整哪些正则化参数?
如何在 python 中调整 lightGBM 参数?
梯度提升方法
使用 LightGBM,您可以运行不同类型的梯度提升方法。您有:GBDT、DART 和 GOSS,它们可以用boosting参数指定。
在接下来的部分中,我将对这些方法进行解释和比较。
lgbm gbdt(梯度提升决策树)
这种方法是论文首次提出的传统梯度提升决策树,是 XGBoost 和 pGBRT 等一些优秀库背后的算法。
如今,gbdt 由于其准确性、效率和稳定性而被广泛使用。您可能知道 gbdt 是决策树的集成模型,但它到底是什么意思呢?
它基于三个重要原则:
弱学习者(决策树)
梯度优化
升压技术
所以在 gbdt 方法中我们有很多决策树(弱学习者)。这些树是按顺序构建的:
第一棵树学习如何适应目标变量
第二棵树学习如何适应第一棵树的预测与地面实况之间的残差(差异)
第三棵树学习如何拟合第二棵树的残差,依此类推。
所有这些树都是通过在整个系统中传播错误梯度来训练的。
gbdt 的主要缺点是在每个树节点中找到最佳分割点是一项耗时且耗内存的操作,其他提升方法试图解决该问题。
dart gradient boosting
在这篇出色的论文中,您可以了解有关 DART 梯度提升的所有内容,这是一种使用神经网络中的标准 dropout 来改进模型正则化并处理其他一些不太明显的问题的方法。
也就是说,gbdt 存在过度专业化的问题,这意味着在后期迭代中添加的树往往只会影响少数实例的预测,而对其余实例的贡献可以忽略不计。添加 dropout 会使树在以后的迭代中更难专注于那些少数样本,从而提高性能。
lgbm goss(基于梯度的单侧采样)
事实上,将这种方法命名为lightgbm最重要的原因是使用了基于这篇论文的Goss方法。Goss 是更新更轻的 gbdt 实现(因此是“light”gbm)。
标准 gbdt 是可靠的,但在大型数据集上速度不够快。因此,goss 提出了一种基于梯度的采样方法,以避免搜索整个搜索空间。我们知道,对于每个数据实例,当梯度很小时,这意味着不用担心数据训练得很好,而当梯度很大时,应该再次重新训练。所以我们这里有两个方面,具有大梯度和小梯度的数据实例。因此,goss 将所有数据都保留为大梯度,并在具有小梯度的数据上进行随机采样(这就是它称为单侧采样的原因)。这使得搜索空间更小,goss 可以更快地收敛。
让我们将这些差异放在表格中:
方法 | 笔记 | 需要更改这些参数 | 优势 | 坏处 |
Lgbm gbdt | 它是默认的提升类型 | 因为 gbdt 是 lgbm 的默认参数,所以您不必为它更改其余参数的值(仍然必须进行调整!) | 稳定可靠 | 过度专业化、耗时、耗内存 |
Lgbm dart | 尝试解决 gbdt 中的过度专业化问题 | drop_seed:选择丢弃模型的随机种子Uniform_dro:如果你想使用统一dropxgboost_dart_mode:设置为true,如果你想使用xgboost dart modeskip_drop:在提升迭代期间跳过dropout过程的概率max_dropdrop_rate:dropout率:在 dropout 期间要丢弃的先前树的一小部分 | 更好的准确性 | 太多的设置 |
Lgbm goss | goss 为 GBDT 提供了一种新的采样方法,将那些具有较大梯度的实例分开 | top_rate:大梯度数据的保留率other_rate:小梯度数据的保留率 | 更快收敛 | 数据集较小时过度拟合 |
笔记:
如果您将提升设置为 RF,那么 lightgbm 算法将表现为随机森林而不是提升树!根据文档,要使用 RF,您必须使用小于 1 的 bagging_fraction 和 feature_fraction。
正则化
在本节中,我将介绍 lightgbm 的一些重要正则化参数。显然,这些是您需要调整以对抗过度拟合的参数。
您应该知道,对于小型数据集(<10000 条记录),lightGBM 可能不是最佳选择。调整 lightgbm 参数可能对您没有帮助。
另外,lightgbm使用leaf-wise tree growth算法,而XGBoost使用depth-wise tree growth。Leaf-wise 方法允许树更快地收敛,但过度拟合的机会增加。
笔记:
如果有人问你 LightGBM 和 XGBoost 的主要区别是什么?您可以轻松地说,它们的区别在于它们的实现方式。
根据lightGBM 文档,当面临过度拟合时,您可能需要进行以下参数调整:
使用小的 max_bin
使用小的 num_leaves
使用 min_data_in_leaf 和 min_sum_hessian_in_leaf
通过设置 bagging_fraction 和 bagging_freq 使用 bagging
通过设置 feature_fraction 使用特征子采样
使用更大的训练数据
尝试 lambda_l1、lambda_l2 和 min_gain_to_split 进行正则化
尝试 max_depth 以避免生长深树
在接下来的部分中,我将更详细地解释每个参数。
lambda_l1
Lambda_l1(和 lambda_l2)控制 l1/l2 并与 min_gain_to_split 一起用于对抗过度拟合。我强烈建议您使用参数调整(在后面的部分中探讨)来找出这些参数的最佳值。
num_leaves
当然,num_leaves是控制模型复杂性的最重要参数之一。使用它,您可以设置每个弱学习器拥有的最大叶子数。大的 num_leaves 增加了训练集的准确性,也增加了因过度拟合而受伤的机会。根据文档,一种简单的方法是num_leaves = 2^(max_depth)但是,考虑到在 lightgbm 中,逐叶树比逐层树更深,您需要小心过度拟合!因此,有必要一起调整num_leaves和max_depth 。
![](https://i-blog.csdnimg.cn/blog_migrate/b80e17ad7137d24ebfaa1ed9a0e896ab.png)
![](https://i-blog.csdnimg.cn/blog_migrate/27efb1ae80c9b0d191426e330ff1731d.png)
subsample
使用subsample(或 bagging_fraction),您可以指定每次树构建迭代使用的行的百分比。这意味着将随机选择一些行来适合每个学习者(树)。这提高了泛化能力,也提高了训练速度。
我建议对基线模型使用较小的子样本值,然后在完成其他实验(不同的特征选择、不同的树结构)后增加该值。
feature_fraction
Feature fraction或 sub_feature 处理列采样,LightGBM 将在每次迭代(树)上随机选择一个特征子集。例如,如果将其设置为 0.6,则 LightGBM 会在训练每棵树之前选择 60% 的特征。
此功能有两种用法:
可用于加速训练
可用于处理过拟合
max_depth
此参数控制每棵训练树的最大深度,并将影响:
num_leaves 参数的最佳值
模型性能
训练时间
注意如果您使用较大的max_depth值,您的模型可能会过度拟合 训练集。
max_bin
分箱是一种以离散视图(直方图)表示数据的技术。Lightgbm 使用基于直方图的算法在创建弱学习器的同时找到最佳分割点。因此,每个连续的数字特征(例如视频的观看次数)应该被分成离散的容器。
此外,在这个GitHub repo 中,您可以找到一些全面的实验,这些实验完全解释了更改 max_bin 对 CPU 和 GPU 的影响。
![](https://i-blog.csdnimg.cn/blog_migrate/abbff05cffb2289fd89691c96856a651.png)
500 次迭代后的时钟时间 – GitHub 存储库
如果您将 max_bin 定义为 255,这意味着每个特征最多可以有 255 个唯一值。然后小的 max_bin 导致更快的速度和大的值提高准确性。
训练参数
训练时间!当您想使用 lightgbm 训练模型时,训练 lightgbm 模型时可能会出现的一些典型问题是:
培训是一个耗时的过程
处理计算复杂性(CPU/GPU RAM 限制)
处理分类特征
有一个不平衡的数据集
需要自定义指标
需要针对分类或回归问题进行的调整
在本节中,我们将尝试详细解释这些要点。
num_iterations
Num_iterations 指定提升迭代次数(要构建的树)。您构建的树越多,您的模型就越准确,但代价是:
更长的训练时间
过度拟合的可能性更高
从较少数量的树开始构建基线,然后在您想要从模型中挤出最后 一部分 时增加它。
建议使用较小的learning_rate和较大的num_iterations。此外,如果你想要更高的 num_iterations 停止你的训练,你应该使用 early_stopping_rounds 当它没有学到任何有用的东西时。
early_stopping_rounds
如果验证指标在最后一轮提前停止后没有改善,则此参数将停止训练。这应该与一些迭代一起定义。如果你将它设置得太大,你会增加过度拟合的机会(但你的模型可以更好)。
经验法则是将其设置为 num_iterations 的 10%。
lightgbm categorical_feature
使用 lightgbm 的优点之一是它可以很好地处理分类特征。是的,这个算法非常强大,但你必须小心如何使用它的参数。lightgbm 使用一种特殊的整数编码方法来处理分类特征
实验表明,这种方法比经常使用的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 is_unbalance 与 scale_pos_weight
在二进制分类问题中,您可能面临的问题之一是如何处理不平衡的数据集。显然,您需要平衡正/负样本,但在 lightgbm 中您究竟如何做到这一点?
lightgbm 中有两个参数可以让你处理这个问题is_unbalance 和 scale_pos_weight,但是它们之间有什么区别以及如何使用它们?
当您设置 Is_unbalace: True 时,该算法将尝试自动平衡主导标签的权重(与训练集中的正/负分数)
sample_pos_weight = 负样本数/正样本数
lgbm feval
有时你想定义一个自定义评估函数来衡量你的模型的性能,你需要创建一个feval函数。
Feval 函数应该接受两个参数:
预测值
训练数据
并返回
评估名称
评估结果
is_higher_better
让我们逐步创建自定义指标函数。
定义一个单独的python函数
def feval_func(preds, train_data):
# 定义一个计算结果的公式
return ('feval_func_name', eval_result, False)
使用此函数作为参数:
print('Start training...')
lgb_train = lgb.train(...,
metric=None,
feval=feval_func)
笔记:
要使用 feval 函数而不是 metric,您应该将 metric 参数设置为“None”。
分类参数与回归参数
我之前提到的大多数事情对于分类和回归都是正确的,但有些事情需要调整。
具体来说,你应该:
参数名称 | 分类说明 | 回归注意事项 |
objective | 将其设置为二进制或多类 | 设置回归 |
metric | Binary_logloss 或 AUC 等。 | RMSE 或 mean_absolute_error 等。 |
is_unbalance | 对或错 | – |
scale_pos_weight | 仅用于二进制和多类应用程序 | – |
num_class | 仅用于多类分类应用 | – |
reg_sqrt | – | 用于拟合 sqrt(label) 而不是大范围标签的原始值 |
最重要的lightgbm参数
我们在前面的部分中回顾并了解了一些关于 lightgbm 参数的知识,但是没有提到 Laurae 的令人难以置信的基准测试,任何增强树文章都不会完整 🙂
您可以了解 lightGBM 和 XGBoost 的许多问题的最佳默认参数。
参数名称 | 默认值 | 范围 | 参数类型 | 别名 | 约束或注释 | 用于 |
objective | regression | regression, binary | enum | objective_type,应用程序 | 当您更改它时会影响其他参数 | 指定 ML 模型的类型 |
metric | null | +20 种不同的指标 | multi-enum | 指标,metric_types | null 表示将使用与指定目标相对应的指标 | 指定指标,支持多个指标 |
boosting | gbdt | gbdt, rf, dart, goss | enum | boosting_type | 如果您将其设置为 RF,那将是一种装袋方法 | 升压法 |
lambda_l1 | 0.0 | [0, ∞] | double | reg_alpha | lambda_l1 >= 0.0 | 正则化 |
bagging_fraction | 1.0 | [0, 1] | double | subsample | 0.0 < bagging_fraction <= 1.0 | 随机选择部分数据而不重采样 |
bagging_freq | 0.0 | [0, ∞] | int | subsample_freq | 要启用装袋,bagging_fraction 也应设置为小于 1.0 的值 | 0 表示禁用装袋;k 表示在每 k 次迭代时执行装袋 |
num_leaves | 31 | [1, ∞] | int | num_leaf | 1 < 叶子数 <= 131072 | 一棵树的最大叶子数 |
feature_fraction | 1.0 | [0,1] | double | sub_feature | 0.0 < 特征分数 <= 1.0 | 如果设置为 0.8,LightGBM 将选择 80% 的特征 |
max_depth | -1 | [-1, ∞] | int | max_depth | 通常越大越好,但过拟合速度会增加 | 限制树模型的最大深度 |
max_bin | 255 | [2, ∞] | int | histogram binning | max_bin > 1 | 过拟合 |
num_iterations | 100 | [1, ∞] | int | num_boost_round,n_iter | 迭代次数 >= 0 | 提升迭代次数 |
learning_rate | 0.1 | [0, 1] | double | eta | learning_rate > 0.0典型值:0.05 | 在 dart 中,它还会影响丢弃树的归一化权重 |
early_stopping _round | 0 | [0, ∞] | double | early_stopping _round | 如果验证在上一个 early_stopping 中没有改善,将停止训练 _圆形的 | 模型性能、迭代次数、训练时间 |
categorical_feature | empty string | 为列索引指定一个数字 | 多整数或字符串 | cat_feature | – | 处理分类特征 |
bagging_freq | 0.0 | [0, ∞] | int | subsample_freq | 0 表示禁用装袋;k 表示在每 k 次迭代时执行装袋 | 要启用装袋,bagging_fraction 也应设置为小于 1.0 的值 |
verbosity | 0 | [-∞, ∞] | int | verbose | < 0:致命,= 0:错误(警告),= 1:信息,> 1:调试 | 对调试有用 |
min_data_in_leaf | 20 | min_data | int | min_data | min_data_in_leaf >= 0 | 可以用来处理过拟合 |
笔记:
您永远不应该认为任何参数值都是理所当然的,并根据您的问题进行调整。也就是说,这些参数是超参数调整算法的一个很好的起点
python中的lightgbm参数调优实例(lightgbm tuning)
最后,在所有重要参数的解释之后,是时候进行一些实验了!
我将使用一种流行的 Kaggle 竞赛:Santander Customer Transaction Prediction。
在我们开始之前,一个重要的问题!我们应该调整哪些参数?
注意你要解决的问题,例如 Santander dataset is highly imbalanced,并且应该在你的调整中考虑到这一点!Laurae2是 lightgbm 的贡献者之一,在这里很好地解释了这一点。
有些参数是相互依存的,必须一起调整或逐一调整。例如,min_data_in_leaf 取决于训练样本的数量和 num_leaves。
注意:为超参数创建两个字典是个好主意,一个包含您不想调整的参数和值,另一个包含您确实想要调整的参数和值范围。
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}
通过这样做,您可以将基线值与搜索空间分开!
现在,这就是我们要做的。
首先,我们在Notebook中生成代码。它是公开的,您可以下载它。
其次,我们跟踪neptune.ai中每个实验的结果。你可以在应用程序中看到这个例子,因为我将它创建为一个公共项目。
了解更多
查看文档以了解有关使用Neptune-LightGBM 集成跟踪模型构建元数据的更多信息。
结果分析
如果您查看了上一节,您会注意到我对数据集进行了超过 14 次不同的实验。在这里,我将逐步解释如何调整超参数的值。
注意:有关最新的代码示例,请参阅Neptune-LightGBM 集成文档。
创建基线训练代码:
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 应用程序中跟踪您的结果。
在 neptune.ai 中运行比较 | 在应用程序中查看
最后,在下表中,您可以看到参数发生了哪些变化。
超参数 | 调整前 | 调整后 |
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 |
总结
长话短说,您了解到:
lightgbm 的主要参数是什么
如何使用 feval 函数创建自定义指标
主要参数的默认值是多少
看到了如何调整 lightgbm 参数以提高模型性能的示例
还有其他一些事情🙂有关更多详细信息,请参阅资源。