LightGBM的参数详解以及如何调优

lightGBM能够用来解决大多数表格数据问题的算法。有不少很棒的功能,而且在kaggle这种该数据比赛中会常用。python

但我一直对了解哪些参数对性能的影响最大以及我应该如何调优lightGBM参数以最大限度地利用它很感兴趣。web

我想我应该作一些研究,了解更多关于lightGBM的参数…并分享个人旅程。算法

我但愿读完这篇文章后,你能回答如下问题:api

  • LightGBM中实现了哪些梯度加强方法,它们有什么区别?
  • 通常来讲,哪些参数是重要的?
  • 哪些正则化参数须要调整?
  • 如何调整lightGBM参数在python?

梯度提高的方法

使用LightGBM,你能够运行不一样类型的渐变加强提高方法。你有:GBDT、DART和GOSS,这些能够经过“boosting”参数指定。网络

在下一节中,我将对这些方法进行解释和比较。dom

梯度提高决策树(GBDT)ide

该方法是本文首先提出的传统梯度提高决策树,也是XGBoost和pGBRT等优秀库背后的算法。svg

因为其精度高、效率高、稳定性好,目前已获得普遍的应用。你可能知道gbdt是一个决策树的集合模型可是它究竟是什么意思呢?函数

让我来告诉你要点。post

它基于三个重要原则:

  • 弱学习者(决策树)
  • 梯度优化
  • 提高技术

因此在gbdt方法中,咱们有不少决策树(弱学习者)。这些树是按顺序构建的:

  • 首先,树学习如何适应目标变量
  • 第二棵树学习如何适合残差(差别)之间的预测,第一棵树和地面真相
  • 第三棵树学习如何匹配第二棵树的残差,以此类推。

全部这些树都是经过传播整个系统的偏差梯度来训练的。

gbdt的主要缺点是,在每一个树节点中找到最佳分割点很是耗时,并且会消耗内存。其余的提高方法试图解决这个问题。

DART 梯度提高

在这篇优秀的论文中(arxiv/1505.01866),你能够学习全部关于DART梯度提高的东西,这是一种使用dropout(神经网络中的标准)的方法,来改进模型正则化和处理一些其余不太明显的问题。

也就是说,gbdt存在过分专门化(over-specialization)的问题,这意味着在之后的迭代中添加的树每每只会影响对少数实例的预测,而对其他实例的贡献则能够忽略不计。添加dropout会使树在之后的迭代中更加难以专门化那些少数的示例,从而提升性能。

lgbm goss 基于梯度的单边采样

事实上,将该方法命名为lightgbm的最重要缘由就是使用了基于本文的Goss方法。Goss是较新的、较轻的gbdt实现(所以是“light”gbm)。

标准的gbdt是可靠的,但在大型数据集上速度不够快。所以goss提出了一种基于梯度的采样方法来避免搜索整个搜索空间。咱们知道,对于每一个数据实例,当梯度很小时,这意味着不用担忧数据是通过良好训练的,而当梯度很大时,应该从新训练。这里咱们有两个方面,数据实例有大的和小的渐变。所以,goss以一个大的梯度保存全部数据,并对一个小梯度的数据进行随机抽样(这就是为何它被称为单边抽样)。这使得搜索空间更小,goss的收敛速度更快。

让咱们把这些差别放在一个表格中:

注意:若是你将加强设置为RF,那么lightgbm算法表现为随机森林而不是加强树! 根据文档,要使用RF,必须使用bagging_fraction和feature_fraction小于1。

正则化

在这一节中,我将介绍lightgbm的一些重要的正则化参数。显然,这些是您须要调优以防止过拟合的参数。

您应该知道,对于较小的数据集(<10000条记录),lightGBM可能不是最佳选择。在这里,调优lightgbm参数可能没有帮助。

此外,lightgbm使用叶向树生长算法,而xgboost使用深度树生长算法。叶向方法使树的收敛速度更快,但过拟合的概率增长。

注意:若是有人问您LightGBM和XGBoost之间的主要区别是什么?你能够很容易地说,它们的区别在于它们是如何实现的。

根据lightGBM文档,当面临过拟合时,您可能须要作如下参数调优:

  • 使用更小的max_bin
  • 使用更小的num_leaves
  • 使用min_data_in_leaf和min_sum_hessian_in_leaf
  • 经过设置bagging_fraction和bagging_freq使用bagging_freq
  • 经过设置feature_fraction使用特征子采样
  • 使用更大的训练数据
  • 尝试lambda_l一、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中叶状树比层次树更深,你须要当心过分拟合!所以,必须同时使用max_depth调优num_leaves。

子采样

经过子样例(或bagging_fraction),您能够指定每一个树构建迭代使用的行数百分比。这意味着将随机选择一些行来匹配每一个学习者(树)。这不只提升了泛化能力,也提升了训练速度。

我建议对基线模型使用更小的子样本值,而后在完成其余实验(不一样的特征选择,不一样的树结构)时增长这个值。

feature_fraction

特征分数或子特征处理列采样,LightGBM将在每次迭代(树)上随机选择特征子集。例如,若是将其设置为0.6,LightGBM将在训练每棵树以前选择60%的特性。

这个功能有两种用法:

  1. 能够用来加速训练吗
  2. 能够用来处理过拟合吗

max_depth

该参数控制每棵通过训练的树的最大深度,将对:

  • num_leaves参数的最佳值
  • 模型的性能
  • 训练时间

注意,若是您使用较大的max_depth值,那么您的模型可能会对于训练集过拟合。

max_bin

装箱是一种用离散视图(直方图)表示数据的技术。Lightgbm在建立弱学习者时,使用基于直方图的算法来寻找最优分割点。所以,每一个连续的数字特性(例如视频的视图数)应该被分割成离散的容器。

此外,在这个GitHub repo(huanzhang12/lightgbm-gpu)中,你能够找到一些全面的实验,彻底解释了改变max_bin对CPU和GPU的影响。

若是你定义max_bin 255,这意味着咱们能够有255个惟一的值每一个特性。那么,较小的max_bin会致使更快的速度,较大的值会提升准确性。

训练参数

当你想用lightgbm训练你的模型时,一些典型的问题可能会出现:

  1. 训练是一个耗时的过程
  2. 处理计算复杂度(CPU/GPU RAM约束)
  3. 处理分类特征
  4. 拥有不平衡的数据集
  5. 定制度量的须要
  6. 须要对分类或回归问题进行的调整

在本节中,咱们将尝试详细解释这些要点。

num_iterations

Num_iterations指定加强迭代的次数(要构建的树)。你创建的树越多,你的模型就越精确,代价是:

  • 较长的训练时间
  • 过拟合的可能性更高

从较少的树开始构建基线,而后当您想从模型中挤出最后的%时增长基线。

建议使用更小的learning_rate和更大的num_iteration。此外,若是您想要更高的num_iteration,那么您应该使用early_stopping_rounds,以便在没法学习任何有用的内容时中止训练。

early_stopping_rounds

若是验证度量在最后一轮中止后没有改进,此参数将中止训练。这应该与一些迭代成对地进行定义。若是你把它设置得太大,你就增长了过拟合的变化(但你的模型能够更好)。

经验法则是让它占num_iterations的10%。

lightgbm categorical_feature

使用lightgbm的优点之一是它能够很好地处理分类特性。是的,这个算法很是强大,可是你必须当心如何使用它的参数。lightgbm使用一种特殊的整数编码方法(由Fisher提出)来处理分类特征

实验代表,该方法比经常使用的单热编码方法具备更好的性能。

它的默认值是“auto”,意思是:让lightgbm决定哪一个表示lightgbm将推断哪些特性是绝对的。

它并不老是工做得很好,我强烈建议您简单地用这段代码手动设置分类特性

cat_col = dataset_name.select_dtypes(‘object’).columns.tolist()

可是在幕后发生了什么,lightgbm是如何处理分类特征的呢?

根据lightgbm的文档,咱们知道树学习器不能很好地使用一种热编码方法,由于它们在树中深度生长。在提出的替代方法中,树形学习器被最优构造。例如,一个特征有k个不一样的类别,有2^(k-1) -1个可能的划分,经过fisher方法,能够改进到k * log(k),经过找到分类特征中值排序直方图的最佳分割方式。

is_unbalance vs scale_pos_weight

其中一个问题,你可能面临的二分类问题是如何处理不平衡的数据集。显然,您须要平衡正/负样本,但如何在lightgbm中作到这一点呢?

lightgbm中有两个参数容许你处理这个问题,那就是is_unbalance和scale_pos_weight,可是它们之间有什么区别呢?

当您设置Is_unbalace: True时,算法将尝试自动平衡占主导地位的标签的权重(使用列集中的pos/neg分数)

若是您想改变scale_pos_weight(默认状况下是1,这意味着假设正负标签都是相等的),在不平衡数据集的状况下,您可使用如下公式来正确地设置它

sample_pos_weight = number of negative samples / number of positive samples

lgbm函数宏指令(feaval)

有时你想定义一个自定义评估函数来测量你的模型的性能,你须要建立一个“feval”函数。

Feval函数应该接受两个参数:

preds 、train_data

并返回

eval_name、eval_result、is_higher_better

让咱们一步一步地建立一个自定义度量函数。

定义一个单独的python函数

def feval_func(preds, train_data):
   # Define a formula that evaluates the results
    return ('feval_func_name', eval_result, False)

使用这个函数做为参数:

print('Start training...')
lgb_train = lgb.train(..., 
                      metric=None, 
                      feval=feval_func)

注意:要使用feval函数代替度量,您应该设置度量参数 metric “None”。

分类参数与回归参数

我以前提到的大多数事情对于分类和回归都是正确的,可是有些事情须要调整。

具体你应该:

lightgbm最重要的参数

咱们已经在前面的部分中回顾并了解了有关lightgbm参数的知识,可是若是不说起Laurae使人难以置信的基准测试,那么关于加强树的文章将是不完整的。

您能够了解用于lightGBM和XGBoost的许多问题的最佳默认参数。

你能够查看这里(https://sites.google.com/view/lauraepp/parameters),但一些最重要的结论是:


注意:绝对不要理会任何参数值的默认值,并根据您的问题进行调整。 也就是说,这些参数是超参数调整算法的一个很好的起点。

Python中的Lightgbm参数调整示例

最后,在解释完全部重要参数以后,该进行一些实验了!

我将使用最受欢迎的Kaggle竞赛之一:Santander Customer Transaction Prediction. 交易预测

我将使用本文介绍如何在任何脚本中的Python中运行超参数调整。

在开始以前,一个重要的问题! 咱们应该调整哪些参数?

请注意您要解决的问题,例如,Santander 数据集高度不平衡,在调整时应考虑到这一点!

一些参数是相互依赖的,必须一块儿调整。 例如,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}

这样,您就能够将基线值与搜索空间分开!

若是您查看了上一节,则会发现我在数据集上进行了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.ai平台,因此有一些neptune的api

完整代码在这里 https://ui.neptune.ai/mjbahmani/LightGBM-hyperparameters/experiments?viewId=standard-view&utm_source=medium&utm_medium=crosspost&utm_campaign=blog-lightgbm-parameters-guide

尝试不一样类型的配置并在Neptune中跟踪结果

最后,在下表中,您能够看到参数中发生了什么变化。

总结

长话短说,您了解到:

  1. lightgbm的主要参数是什么,
  2. 如何使用feval函数建立自定义指标
  3. 主要参数的默认值是多少
  4. 看到了如何调整lightgbm参数以改善模型性能的示例

做者:Kamil Kaczmarek

deephub 翻译组