目录
2.4 基于最大深度的 Leaf-wise 的垂直生长算法(带深度限制的 Leaf-wise 算法)
1. LightGBM简介
LightGBM (Light Gradient Boosting Machine)由微软提出,主要用于解决 GDBT 在海量数据中遇到的问题,以便其可以更好更快地用于工业实践中。
常用的机器学习算法,例如神经网络等算法,都可以以mini-batch的方式训练,训练数据的大小不会受到内存限制。而GBDT在每一次迭代的时候,都需要遍历整个训练数据多次。如果把整个训练数据装进内存则会限制训练数据的大小;如果不装进内存,反复地读写训练数据又会消耗非常大的时间。尤其面对工业级海量的数据,普通的GBDT算法是不能满足其需求的。
GBDT (Gradient Boosting Decision Tree)的主要思想是利用弱分类器(决策树)迭代训练以得到最优模型,该模型具有训练效果好、不易过拟合等优点。GBDT不仅在工业界应用广泛,通常被用于多分类、点击率预测、搜索排序等任务。
LightGBM 是轻量级(Light)的梯度提升机(GBM),是一个实现GBDT算法的框架,其相对 XGBoost 具有训练速度快、内存占用低、支持高效率的并行训练、更好的准确率、支持分布式可以快速处理海量数据等优点。
下图分别显示了 XGBoost、XGBoost_hist(利用梯度直方图的 XGBoost) 和 LightGBM 三者之间针对不同数据集情况下的内存和训练时间的对比:来源:【机器学习】决策树(下)——XGBoost、LightGBM(非常详细) - 知乎 (zhihu.com)
2. LightGBM详细介绍
LightGBM在传统的GBDT算法上进行了如下优化:
- 单边梯度抽样算法;
- 直方图算法;
- 互斥特征捆绑算法;
- 基于最大深度的 Leaf-wise 的垂直生长算法;
- 类别特征最优分割(直接支持类别特征);
- 特征并行,数据并行和投票并行;
- 缓存优化(Cache命中率优化)。
具体而言:
2.1 单边梯度抽样算法
GBDT 算法的梯度大小可以反应样本的权重,梯度越小说明模型拟合的越好,单边梯度抽样算法(Gradient-based One-Side Sampling, GOSS)利用这一信息对样本进行抽样,减少了大量梯度小的样本,在接下来的计算过程中只需关注梯度高的样本,相比XGBoost遍历所有特征值节省了不少时间和空间上的开销。
GOSS 算法保留了梯度大的样本,并对梯度小的样本进行随机抽样,为了不改变样本的数据分布,在计算增益时为梯度小的样本引入一个常数进行平衡。具体算法如下所示:
GOSS 事先基于梯度的绝对值对样本进行排序(无需保存排序后结果),然后拿到前 a% 的梯度大的样本,和总体样本的 b%,在计算增益时,通过乘上 1−ab 来放大梯度小的样本的权重。一方面算法将更多的注意力放在训练不足的样本上,另一方面通过乘上权重来防止采样对原始数据分布造成太大的影响。
2.2 直方图算法
直方图算法的基本思想是:先把连续的浮点特征值离散化成k个整数,同时构造一个宽度为k 的直方图。在遍历数据的时候,根据离散化后的值作为索引在直方图中累积统计量,当遍历一次数据后,直方图累积了需要的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。
换种说法就是:
将连续的特征离散化为 k 个离散特征,同时构造一个宽度为 k 的直方图用于统计信息(含有 k 个 bin)。利用直方图算法我们无需遍历数据,只需要遍历 k 个 bin 即可找到最佳分裂点。
其实就是直方图统计,将大规模的数据放在了直方图中。
特征离散化具有很多优点,如存储方便、运算更快、鲁棒性强、模型更加稳定等。
对于直方图算法来说最直接的有以下两个优点:
- 内存占用更小: 直方图算法不仅不需要额外存储预排序的结果,而且可以只保存特征离散化后的值,而这个值一般用 8 位整型存储就足够了,内存消耗可以降低为原来的 1/8 。也就是说XGBoost需要用 32 位的浮点数去存储特征值,并用 32 位的整形去存储索引,而 LightGBM只需要用 8 位去存储直方图,内存相当于减少为1/8 ;(以k=256为例)
- 计算代价更小: 计算特征分裂增益时,XGBoost 需要遍历一次数据找到最佳分裂点,而 LightGBM 只需要遍历一次 k 次,直接将时间复杂度从 O(#data∗#feature) 降低到 O(k∗#feature) ,而我们知道 #data>>k 。
虽然将特征离散化后无法找到精确的分割点,可能会对模型的精度产生一定的影响,但较粗的分割也起到了正则化的效果,一定程度上降低了模型的方差。对最终的精度影响并不是很大,甚至有时候会更好一点。原因是决策树本来就是弱模型,分割点是不是精确并不是太重要;较粗的分割点也有正则化的效果,可以有效地防止过拟合;即使单棵树的训练误差比精确分割的算法稍大,但在梯度提升(Gradient Boosting)的框架下没有太大的影响。
- 直方图做差加速
一个叶子的直方图可以由它的父亲节点的直方图与它兄弟的直方图做差得到,在速度上可以提升一倍。通常构造直方图时,需要遍历该叶子上的所有数据,但直方图做差仅需遍历直方图的k个桶。在实际构建树的过程中,LightGBM还可以先计算直方图小的叶子节点,然后利用直方图做差来获得直方图大的叶子节点,这样就可以用非常微小的代价得到它兄弟叶子的直方图。
注意: XGBoost 在进行预排序时只考虑非零值进行加速,而 LightGBM 也采用类似策略:只用非零特征构建直方图
2.3 互斥特征捆绑算法
高维特征往往是稀疏的,而且特征间可能是相互排斥的(如两个特征不同时取非零值),如果两个特征并不完全互斥(如只有一部分情况下是不同时取非零值),可以用互斥率表示互斥程度。
互斥特征捆绑算法(Exclusive Feature Bundling, EFB)指出如果将一些特征进行融合绑定,则可以降低特征数量。
针对这种想法,我们会遇到两个问题:
- 哪些特征可以一起绑定?
- 特征绑定后,特征值如何确定?
对于问题1: EFB 算法利用特征和特征间的关系构造一个加权无向图,并将其转换为图着色算法。我们知道图着色是个 NP-Hard 问题,故采用贪婪算法得到近似解,具体步骤如下:
- 构造一个加权无向图,顶点是特征,边是两个特征间互斥程度;
- 根据节点的度进行降序排序,度越大,与其他特征的冲突越大;
- 遍历每个特征,将它分配给现有特征包,或者新建一个特征包,是的总体冲突最小。
算法允许两两特征并不完全互斥来增加特征捆绑的数量,通过设置最大互斥率 γ 来平衡算法的精度和效率。EFB 算法的伪代码如下所示:
我们看到时间复杂度为 O(#feature2) ,在特征不多的情况下可以应付,但如果特征维度达到百万级别,计算量则会非常大,为了改善效率,我们提出了一个更快的解决方案:将 EFB 算法中通过构建图,根据节点度来排序的策略改成了根据非零值的技术排序,因为非零值越多,互斥的概率会越大。
对于问题2: 论文给出特征合并算法,其关键在于原始特征能从合并的特征中分离出来。假设 Bundle 中有两个特征值,A 取值为 [0, 10]、B 取值为 [0, 20],为了保证特征 A、B 的互斥性,我们可以给特征 B 添加一个偏移量转换为 [10, 30],Bundle 后的特征其取值为 [0, 30],这样便实现了特征合并。具体算法如下所示:
2.4 基于最大深度的 Leaf-wise 的垂直生长算法(带深度限制的 Leaf-wise 算法)
在建树的过程中有两种策略:
- Level-wise:基于层进行生长,直到达到停止条件;
- Leaf-wise:每次分裂增益最大的叶子节点,直到达到停止条件。
抛弃了大多数GBDT工具使用的按层生长 (level-wise) 的决策树生长策略,而使用了带有深度限制的按叶子生长 (leaf-wise) 算法。
XGBoost 采用 Level-wise 的增长策略,该策略遍历一次数据可以同时分裂同一层的叶子,容易进行多线程优化,也好控制模型复杂度,不容易过拟合。但实际上Level-wise是一种低效的算法,因为它不加区分的对待同一层的叶子,实际上很多叶子的分裂增益较低,没必要进行搜索和分裂,因此带来了很多没必要的计算开销。
图:按层生长的决策树
LightGBM采用Leaf-wise的增长策略,该策略每次从当前所有叶子中,找到分裂增益最大的一个叶子,然后分裂,如此循环。
Leaf-wise的优点是:
在分裂次数相同的情况下,Leaf-wise可以降低更多的误差,得到更好的精度;
Leaf-wise的缺点是:
可能会长出比较深的决策树,产生过拟合。因此LightGBM会在Leaf-wise之上增加了一个最大深度的限制,在保证高效率的同时防止过拟合。
图:按叶子生长的决策树
2.5 类别特征最优分割 (直接支持类别特征)
大部分的机器学习算法都不能直接支持类别特征,一般都会对类别特征进行编码,然后再输入到模型中。
常见的处理类别特征的方法为 one-hot 编码,但我们知道对于决策树来说并不推荐使用 one-hot 编码,尤其当类别特征中类别个数很多的情况下,会存在以下问题::
- 会产生样本切分不平衡问题,切分增益会非常小(即浪费了这个特征)。如,国籍切分后,会产生是否中国,是否美国等一系列特征,这一系列特征上只有少量样本为 1,大量样本为 0。这种划分的增益非常小:较小的那个拆分样本集,它占总样本的比例太小。无论增益多大,乘以该比例之后几乎可以忽略;较大的那个拆分样本集,它几乎就是原始的样本集,增益几乎为零;
- 影响决策树学习:决策树依赖的是数据的统计信息,而独热码编码会把数据切分到零散的小空间上。在这些零散的小空间上统计信息不准确的,学习效果变差。本质是因为独热码编码之后的特征的表达能力较差的,特征的预测能力被人为的拆分成多份,每一份与其他特征竞争最优划分点都失败,最终该特征得到的重要性会比实际值低。
如下图所示:独热编码会把数据切分到很多零散的小空间上(左图);但如果使用下图右边的切分方法,数据会被切分到两个比较大的空间,进一步的学习也会更好。下图右边叶子节点的含义是或者放到左孩子,其余放到右孩子。
图:左图为基于 one-hot 编码进行分裂,右图为 LightGBM 基于 many-vs-many 进行分裂
而类别特征的使用在实践中是很常见的。且为了解决one-hot编码处理类别特征的不足,LightGBM优化了对类别特征的支持,可以直接输入类别特征,不需要额外的展开。
LightGBM采用 many-vs-many 的切分方式将类别特征分为两个子集,实现类别特征的最优切分。假设某维特征有 k 个类别,则有 种可能,时间复杂度为
,LightGBM 基于 Fisher的《On Grouping For Maximum Homogeneity》论文实现了
的时间复杂度。
其基本思想在于每次分组时都会根据训练目标对类别特征进行分类,根据其累积值对直方图进行排序,然后在排序的直方图上找到最佳分割。此外,LightGBM 还加了约束条件正则化,防止过拟合。
图:LightGBM求解类别特征的最优切分算法
在Expo数据集上的实验结果表明,相比0/1展开的方法,使用LightGBM支持的类别特征可以使训练速度加速8倍,并且精度一致。更重要的是,LightGBM是第一个直接支持类别特征的GBDT工具。
2.6 特征并行和数据并行
2.6.1 特征并行
特征并行的主要思想是: 不同机器在不同的特征集合上分别寻找最优的分割点,然后在机器间同步最优的分割点。XGBoost使用的就是这种特征并行方法。
这种特征并行方法有个很大的缺点:就是对数据进行垂直划分,每台机器所含数据不同,然后使用不同机器找到不同特征的最优分裂点,划分结果需要通过通信告知每台机器,增加了额外的复杂度。
LightGBM 则不进行数据垂直划分,而是在每台机器上保存全部训练数据,在得到最佳划分方案后可在本地执行划分而减少了不必要的通信。
具体过程如下图所示。
2.6.2 数据并行
传统的数据并行策略主要为水平划分数据,然后本地构建直方图并整合成全局直方图,最后在全局直方图中找出最佳划分点。
这种数据划分有一个很大的缺点:通讯开销过大。如果使用点对点通信,一台机器的通讯开销大约为 O(#machine∗#feature∗#bin) ;如果使用集成的通信,则通讯开销为 O(2∗#feature∗#bin) ,
LightGBM 采用分散规约(Reduce scatter)的方式将直方图整合的任务分摊到不同机器上,从而降低通信代价,并通过直方图做差进一步降低不同机器间的通信。
2.6.3 投票并行
基于投票的数据并行则进一步优化数据并行中的通信代价,使通信代价变成常数级别。在数据量很大的时候,使用投票并行的方式只合并部分特征的直方图从而达到降低通信量的目的,可以得到非常好的加速效果。
具体过程如下图所示。
大致步骤为两步:
- 本地找出 Top K 特征,并基于投票筛选出可能是最优分割点的特征;
- 合并时只合并每个机器选出来的特征
2.7 Cache命中率优化
XGBoost对cache优化不友好,如下图所示。在预排序后,特征对梯度的访问是一种随机访问,并且不同的特征访问的顺序不一样,无法对cache进行优化。同时,在每一层长树的时候,需要随机访问一个行索引到叶子索引的数组,并且不同特征访问的顺序也不一样,也会造成较大的cache miss。为了解决缓存命中率低的问题,XGBoost 提出了缓存访问算法进行改进。
而 LightGBM 所使用直方图算法对 Cache 天生友好:
- 首先,所有的特征都采用相同的方式获得梯度(区别于XGBoost的不同特征通过不同的索引获得梯度),只需要对梯度进行排序并可实现连续访问,大大提高了缓存命中率;
- 其次,因为不需要存储行索引到叶子索引的数组,降低了存储消耗,而且也不存在 Cache Miss的问题。
图:LightGBM增加缓存命中率
3. LightGBM的优缺点
3.1 优点
(1)速度更快
- LightGBM 采用了直方图算法将遍历样本转变为遍历直方图,极大的降低了时间复杂度;
- LightGBM 在训练过程中采用单边梯度算法过滤掉梯度小的样本,减少了大量的计算;
- LightGBM 采用了基于 Leaf-wise 算法的增长策略构建树,减少了很多不必要的计算量;
- LightGBM 采用优化后的特征并行、数据并行方法加速计算,当数据量非常大的时候还可以采用投票并行的策略;
- LightGBM 对缓存也进行了优化,增加了缓存命中率;
(2)内存更小
- XGBoost使用预排序后需要记录特征值及其对应样本的统计值的索引,而 LightGBM 使用了直方图算法将特征值转变为 bin 值,且不需要记录特征到样本的索引,极大的减少了内存消耗;
- LightGBM 采用了直方图算法将存储特征值转变为存储 bin 值,降低了内存消耗;
- LightGBM 在训练过程中采用互斥特征捆绑算法减少了特征数量,降低了内存消耗。
3.2 缺点
- 可能会长出比较深的决策树,产生过拟合。因此LightGBM在Leaf-wise之上增加了一个最大深度限制,在保证高效率的同时防止过拟合;
- Boosting族是迭代算法,每一次迭代都根据上一次迭代的预测结果对样本进行权重调整,所以随着迭代不断进行,误差会越来越小,模型的偏差(bias)会不断降低。由于LightGBM是基于偏差的算法,所以会对噪点较为敏感;
- 在寻找最优解时,依据的是最优切分变量,没有将最优解是全部特征的综合这一理念考虑进去;
4. python实例
4.1 安装LightGBM依赖包
pip install lightgbm
4.2 LightGBM分类和回归
分类代码:原生接口和Scikit-learn接口
import lightgbm as lgb
from sklearn import datasets
from sklearn.model_selection import train_test_split
import numpy as np
from sklearn.metrics import roc_auc_score, accuracy_score
# 加载数据
iris = datasets.load_iris()
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.3)
# 转换为Dataset数据格式
train_data = lgb.Dataset(X_train, label=y_train)
validation_data = lgb.Dataset(X_test, label=y_test)
# 参数
params = {
'learning_rate': 0.1,
'lambda_l1': 0.1,
'lambda_l2': 0.2,
'max_depth': 4,
'objective': 'multiclass', # 目标函数
'num_class': 3,
}
# 模型训练
gbm = lgb.train(params, train_data, valid_sets=[validation_data])
# 模型预测
y_pred = gbm.predict(X_test)
y_pred = [list(x).index(max(x)) for x in y_pred]
print(y_pred)
# 模型评估
print(accuracy_score(y_test, y_pred))
from lightgbm import LGBMClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import GridSearchCV
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.externals import joblib
# 加载数据
iris = load_iris()
data = iris.data
target = iris.target
# 划分训练数据和测试数据
X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=0.2)
# 模型训练
gbm = LGBMClassifier(num_leaves=31, learning_rate=0.05, n_estimators=20)
gbm.fit(X_train, y_train, eval_set=[(X_test, y_test)], early_stopping_rounds=5)
# 模型存储
joblib.dump(gbm, 'loan_model.pkl')
# 模型加载
gbm = joblib.load('loan_model.pkl')
# 模型预测
y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration_)
# 模型评估
print('The accuracy of prediction is:', accuracy_score(y_test, y_pred))
# 特征重要度
print('Feature importances:', list(gbm.feature_importances_))
# 网格搜索,参数优化
estimator = LGBMClassifier(num_leaves=31)
param_grid = {
'learning_rate': [0.01, 0.1, 1],
'n_estimators': [20, 40]
}
gbm = GridSearchCV(estimator, param_grid)
gbm.fit(X_train, y_train)
print('Best parameters found by grid search are:', gbm.best_params_)
回归代码:原生接口和Scikit-learn接口
用Kaggle比赛中回归问题:House Prices: Advanced Regression Techniques,地址:www.kaggle.com/c/house-pri… 来进行实例讲解。
该房价预测的训练数据集中一共有81列,第一列是Id,最后一列是label,中间79列是特征。这79列特征中,有43列是分类型变量,33列是整数变量,3列是浮点型变量。训练数据集中存在缺失值。
import pandas as pd
from sklearn.model_selection import train_test_split
import lightgbm as lgb
from sklearn.metrics import mean_absolute_error
from sklearn.preprocessing import Imputer
# 1.读文件
data = pd.read_csv('./dataset/train.csv')
# 2.切分数据输入:特征 输出:预测目标变量
y = data.SalePrice
X = data.drop(['SalePrice'], axis=1).select_dtypes(exclude=['object'])
# 3.切分训练集、测试集,切分比例7.5 : 2.5
train_X, test_X, train_y, test_y = train_test_split(X.values, y.values, test_size=0.25)
# 4.空值处理,默认方法:使用特征列的平均值进行填充
my_imputer = Imputer()
train_X = my_imputer.fit_transform(train_X)
test_X = my_imputer.transform(test_X)
# 5.转换为Dataset数据格式
lgb_train = lgb.Dataset(train_X, train_y)
lgb_eval = lgb.Dataset(test_X, test_y, reference=lgb_train)
# 6.参数
params = {
'task': 'train',
'boosting_type': 'gbdt', # 设置提升类型
'objective': 'regression', # 目标函数
'metric': {'l2', 'auc'}, # 评估函数
'num_leaves': 31, # 叶子节点数
'learning_rate': 0.05, # 学习速率
'feature_fraction': 0.9, # 建树的特征选择比例
'bagging_fraction': 0.8, # 建树的样本采样比例
'bagging_freq': 5, # k 意味着每 k 次迭代执行bagging
'verbose': 1 # <0 显示致命的, =0 显示错误 (警告), >0 显示信息
}
# 7.调用LightGBM模型,使用训练集数据进行训练(拟合)
# Add verbosity=2 to print messages while running boosting
my_model = lgb.train(params, lgb_train, num_boost_round=20, valid_sets=lgb_eval, early_stopping_rounds=5)
# 8.使用模型对测试集数据进行预测
predictions = my_model.predict(test_X, num_iteration=my_model.best_iteration)
# 9.对模型的预测结果进行评判(平均绝对误差)
print("Mean Absolute Error : " + str(mean_absolute_error(predictions, test_y)))
import pandas as pd
from sklearn.model_selection import train_test_split
import lightgbm as lgb
from sklearn.metrics import mean_absolute_error
from sklearn.preprocessing import Imputer
# 1.读文件
data = pd.read_csv('./dataset/train.csv')
# 2.切分数据输入:特征 输出:预测目标变量
y = data.SalePrice
X = data.drop(['SalePrice'], axis=1).select_dtypes(exclude=['object'])
# 3.切分训练集、测试集,切分比例7.5 : 2.5
train_X, test_X, train_y, test_y = train_test_split(X.values, y.values, test_size=0.25)
# 4.空值处理,默认方法:使用特征列的平均值进行填充
my_imputer = Imputer()
train_X = my_imputer.fit_transform(train_X)
test_X = my_imputer.transform(test_X)
# 5.调用LightGBM模型,使用训练集数据进行训练(拟合)
# Add verbosity=2 to print messages while running boosting
my_model = lgb.LGBMRegressor(objective='regression', num_leaves=31, learning_rate=0.05, n_estimators=20,
verbosity=2)
my_model.fit(train_X, train_y, verbose=False)
# 6.使用模型对测试集数据进行预测
predictions = my_model.predict(test_X)
# 7.对模型的预测结果进行评判(平均绝对误差)
print("Mean Absolute Error : " + str(mean_absolute_error(predictions, test_y)))
5. 相关论文
LightGBM论文解读:
【1】Ke G, Meng Q, Finley T, et al. Lightgbm: A highly efficient gradient boosting decision tree[C]//Advances in Neural Information Processing Systems. 2017: 3146-3154.
【2】Taifeng Wang分享LightGBM的视频,地址:v.qq.com/x/page/k036…
【3】开源|LightGBM:三天内收获GitHub 1000+ 星,地址:mp.weixin.qq.com/s/M25d_43gH…
【4】Lightgbm源论文解析:LightGBM: A Highly Efficient Gradient Boosting Decision Tree,地址:blog.csdn.net/anshuai_aw1…
【5】快的不要不要的lightGBM - 王乐的文章 - 知乎 zhuanlan.zhihu.com/p/31986189
【6】『 论文阅读』LightGBM原理-LightGBM: A Highly Efficient Gradient Boosting Decision Tree,地址:blog.csdn.net/shine199308…
LightGBM算法讲解:
【7】【机器学习】决策树(下)——XGBoost、LightGBM(非常详细) - 阿泽的文章 - 知乎 zhuanlan.zhihu.com/p/87885678
【8】入门 | 从结构到性能,一文概述XGBoost、Light GBM和CatBoost的同与不同,地址:mp.weixin.qq.com/s/TD3RbdDid…
【9】CatBoost vs. Light GBM vs. XGBoost,地址:towardsdatascience.com/catboost-vs…
【10】机器学习算法之LightGBM,地址:www.biaodianfu.com/lightgbm.ht…
LightGBM工程优化:
【11】Meng Q, Ke G, Wang T, et al. A communication-efficient parallel algorithm for decision tree[C]//Advances in Neural Information Processing Systems. 2016: 1279-1287.
【12】Zhang H, Si S, Hsieh C J. GPU-acceleration for Large-scale Tree Boosting[J]. arXiv preprint arXiv:1706.08359, 2017.
【13】LightGBM的官方GitHub代码库,地址:github.com/microsoft/L…
参考资料:
- 《XGBoost: A Scalable Tree Boosting System》
- 陈天奇论文演讲 PPT
- 机器学习算法中 GBDT 和 XGBOOST 的区别有哪些? - wepon的回答 - 知乎
- LightGBM: A Highly Efficient Gradient Boosting Decision Tree
- LightGBM 文档
- 论文阅读——LightGBM 原理
- 机器学习算法之 LightGBM
- 关于sklearn中的决策树是否应该用one-hot编码? - 柯国霖的回答 - 知乎
- 如何玩转LightGBM
- A Communication-Efficient Parallel Algorithm for Decision Tree.
- 【机器学习】决策树(下)——XGBoost、LightGBM(非常详细) - 知乎 (zhihu.com)
- www.zhihu.com/question/26…
- www.jianshu.com/p/765efe2b9…
- www.nowcoder.com/ta/review-m…
2.编辑