😄 三大竞赛杀器:XGBoost、LightGBM、CatBoost。之前我已更新完XGBoost的讲解,这次来讲讲LightGBM。我也是看网上看了多篇文章做总结的(我是遇到不会问题的就去搜,实在记不起来看过哪些,如果有侵权问题,可私信联系),网上文章没讲清楚甚至讲错的,我也做了修正和补充。如果想深入看,我的建议是先看XGBoost,因为LGB和XGB很多相似之处,都是GBDT的优化,但LGB也算是对XGB的优化,下面一些补充内容和对比,会涉及XGB里的一些知识。
注:我对XGB的讲解可看这:ML XGBoost详细原理及公式推导讲解+面试必考知识点
🚀 看完保证懂系列~
文章目录
- 1、LGB介绍
- 1.1、LGB提出动机
- 1.2、XGB缺点及LGB的优化
- XGB缺点
- LGB优化
- 2、LGB原理讲解
- 2.1、基于直方图的决策树算法(决策树)
- 2.1.1、直方图算法
- 2.1.2、直方图做差加速
- 2.2、带深度限制的leaf-wise算法(决策树)
- 2.3、单边梯度采样算法(减少样本)
- 2.4、互斥特征捆绑算法(减少特征)
- 怎么判定哪些特征应该绑在一起(build bundled)?
- 怎么把特征绑为一个(merge feature)?
- 3、LGB的工程优化
- 3.1、直接支持类别特征
- 3.2、支持高效并行
- 3.2.1、特征并行
- 3.2.2、数据并行
- 3.2.3、投票并行
- 3.3、Cache命中率优化
- 4、LGB代码实现方式
- 5、LGB面试高频提问与答案?
- 5.1、LGB的优点和缺点?
- 优点:
- 缺点:
- 5.2、LGB与XGB的联系和区别有哪些?
1、LGB介绍
🚀 在讲解XGB时我提到说XGB是GBDT的优化,是极致的GBDT。而LGB其实也是GBDT的优化算法,LGB支持高效率的并行训练,并且具有更快的训练速度、更低的内存消耗、更好的准确率、支持分布式可以快速处理海量数据等优点。
1.1、LGB提出动机
- GBDT每次迭代都要遍历全部训练数据,全部数据装进内存无疑很耗空间,若不装进内存反复读写则很耗时间。
- 毫无疑问,LGB主要是为了满足工业实践中的海量数据要求,使得能够更好更快地应用。
1.2、XGB缺点及LGB的优化
XGB缺点
- XGB是基于预排序的决策树算法。这种构建决策树算法的基本思想为:1、对所有特征按照特征数值进行预排序。2、在遍历分隔点时,用O(#data)的代价找到一个特征上的最优分割点。3、根据最优分割点分裂左右子节点。
- 这种预排序算法优点:能够精确找到分割点。
- 缺点:1、空间消耗大,需要保存数据的特征值,为了后序快速计算分割点,还保存了特征排序后的索引,那其实消耗了训练数据两倍的内存。2、时间消耗也大,因为遍历每一个分割带你时,都需要进行分裂增益的计算。3、对cache优化不友好。在预排序后,特征对梯度的访问是一种随机访问,并且不同的特征访问的顺序不一样,无法对cache进行优化。同时,在每一层长树的时候,需要随机访问一个行索引到叶子索引的数组,并且不同特征访问的顺序也不一样,也会造成较大的cache miss。
LGB优化
- 不损害精度来加速GBDT。
- 1、基于直方图的决策树算法。
- 2、单边梯度采样 Gradient-based One-Side Sampling(GOSS): 使用GOSS可以减少大量只具有小梯度的数据实例,这样在计算信息增益的时候只利用剩下的具有高梯度的数据就可以了,相比XGBoost遍历所有特征值节省了不少时间和空间上的开销。
- 3、互斥特征捆绑 Exclusive Feature Bundling(EFB): 使用EFB可以将许多互斥的特征绑定为一个特征,这样达到了降维的目的。
- 4、带深度限制的Leaf-wise的叶子生长策略: 大多数GBDT工具使用低效的按层生长 (level-wise) 的决策树生长策略,因为它不加区分的对待同一层的叶子,带来了很多没必要的开销。实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。LightGBM使用了带有深度限制的按叶子生长 (leaf-wise) 算法。
- 5、直接支持类别特征(Categorical Feature)
- 6、支持高效并行
- 7、cache命中率优化
2、LGB原理讲解
2.1、基于直方图的决策树算法(决策树)
2.1.1、直方图算法
- 直方图算法思想:1、把连续的浮点特征离散化成k个整数(如[0,0.5]归为0,[0.5,1]归为1),同时构造一个宽度为k的直方图,统计各离散值的数量。当遍历一次数据后,直方图累积了需要的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。【其实就是直方图统计,对数据分箱处理,将大规模的数据压缩在直方图中。】
- 直方图算法相当于对特征做了离散化操作,离散化有很多优点,如存储方便、运算更快、鲁棒性强、模型更加稳定等。当然直方图算法最直接的优点有如下两点:
- 优点1:内存占用更小。
- 优点2:计算代价更小。
- 缺点:很明显,直方图算法将特征离散化后带来的肯定是精度问题,离散化后无疑节点分裂时,找到的不是很精确的分割点。但在不同数据上实验表明,离散化的分割点对最终的精度影响并不是很大,甚至有时候会更好一点。 原因是决策树本来就是弱模型,分割点是不是精确并不是太重要;较粗的分割点也有正则化的效果,可以有效地防止过拟合;即使单棵树的训练误差比精确分割的算法稍大,但在梯度提升(Gradient Boosting)的框架下没有太大的影响。
2.1.2、直方图做差加速
- LGB的另一优化就是,可以利用直方图做差加速。原理也很简单,如下图所示。因为两个孩子节点是通过父亲分裂得到,直方图存储的是各区间的统计量,那毫无疑问,一个子节点的直方图可由父亲的直方图与它兄弟的直方图做差得到,在速度上可以提升一倍。通常在实际构建树的过程中,LGB可以先计算直方图小的叶子节点,然后利用直方图做差来获得直方图大的叶子节点的直方图,这样就可以进一步加速。
- 注意: XGBoost 在进行预排序时只考虑非零值进行加速,而 LightGBM 也采用类似策略:只用非零特征构建直方图。
2.2、带深度限制的leaf-wise算法(决策树)
-
在直方图算法上,LGB进行了进一步优化。LGB抛弃了大多数GBDT工具采用的按层生长 (level-wise)的决策树生长策略,采用了带有深度限制的按叶子生长(leaf-wise)的决策树生长策略。
-
我个人感觉,level-wise类似BFS,而leaf-wise有点像贪心+DFS
-
XGB里的level-wise生长策略,该策略遍历一次数据可以同时分裂同一层的叶子,容易进行多线程优化,也好控制模型复杂度,不易过拟合。但该策略算是一种低效的算法,因为它不加以区分对待同一层的叶子,某些叶子的分裂增益其实很低,没必要继续进行搜索和分裂了。所以会带来很多没必要的开销。
-
LGB采用的leaf-wise生长策略,该策略每次从当前叶子中,找出分裂增益最大的叶子,分裂如此循环。所以很明显,相比level-wise,该策略的优点就是:在分裂次数相同的情况下,leaf-wise可以降低更多的误差,得到更好的精度。缺点也很明显:可能生成出来的决策树会比较深,导致过拟合。所以LGB就加了个最大深度限制😄,在保证高效的同时防止过拟合。
2.3、单边梯度采样算法(减少样本)
Gradient-based One-Side Sampling 应该被翻译为单边梯度采样(GOSS)。
GOSS基本思想就是:从减少样本的角度出发,排除大部分小梯度的样本,仅用剩下的样本计算信息增益,算是一种减少数据量和保证精度上平衡的算法。
- 回顾一下,AdaBoost中,每个样本都有一个权重,训练过程总会更新每个样本的权值,提高那些被前一轮弱分类器错误分类的样本的权值,降低那些被正确分类的样本权值。但GBDT中没有样本权重。不过GBDT对于每个数据都有不同的梯度值,对采样有用。梯度小的样本,训练误差也比较小(从XGBoost的损失函数二阶泰勒展开后,节点分类时会选择损失函数减少最多的分裂方式,Δloss跟梯度有关,梯度大的样本对信息增益有更大影响),说明数据已经被模型学习得很好了,所以可以去掉这部分梯度小的样本,但直接去掉就改变了原始数据分布,因此GOSS算法就出现了。
- GOSS是一个样本的采样算法,目的是丢弃一些对计算信息增益没有帮助的样本留下有帮助的。GOSS算法步骤如下:
- 1、将要进行分裂的特征的所有样本按照梯度绝对值大小降序排序(XGBoost对特征值也进行了排序,但是LightGBM对梯度值进行排序,且不用保存排序后的结果),选取绝对值最大的a%个数据。
- 2、然后在剩下的较小梯度数据中随机选择b%个数据。
- 3、接着将这b%个数据乘以一个常数(1-a)/b,然后使用这(a+b)%个数据来计算信息增益。
- 为啥呢?
2.4、互斥特征捆绑算法(减少特征)
- 互斥特征捆绑算法:Exclusive Feature Bundling, EFB。
- 动机:高维数据往往是稀疏的,想办法设计一种无损方法来降维。将互斥的特征捆绑在一起,这样就是无损了。如果两个特征并不是完全互斥(部分情况下两个特征都是非零值),可以用一个指标对特征不互斥程度进行衡量,称之为冲突比率。当两特征的冲突比率较小时,我们可以选择把不完全互斥的两个特征捆绑,而不影响最后的精度。
- 互斥特征:特征不会同时为非零值,像one-hot只有一个维度为非零。简单的理解就是两个特征经常同时取0,同时取0的次数越多,说明冲突越小,越能进行捆绑
- 毫无疑问降维之后,直方图算法的时间复杂度又进一步优化了:O(#data * #feature)变为O(#data * #bundle)。
怎么判定哪些特征应该绑在一起(build bundled)?
将相互独立的特征进行绑定是一个 NP-Hard 问题,LightGBM的EFB算法将这个问题转化为图着色的问题来求解
- 1.构造一个加权无向图,顶点是特征,边有权重,其权重与两个特征间冲突相关;
- 2.根据节点的度进行降序排序,度越大,与其它特征的冲突越大(降序我觉得是因为:冲突大意味着,这些特征和其他特征不互斥,让他先独立成捆);
- 3.遍历每个特征,将它分配给现有特征包,或者新建一个特征包,使得总体冲突最小。
- 算法允许两两特征并不完全互斥来增加特征捆绑的数量,通过设置最大冲突比率r 来平衡算法的精度和效率。
怎么把特征绑为一个(merge feature)?
特征合并算法,其关键在于原始特征能从合并的特征中分离出来。
- 绑定几个特征在同一个bundle里需要保证绑定前的原始特征的值可以在bundle中识别,考虑到histogram-based算法将连续的值保存为离散的bins,我们可以使得不同特征的值分到bundle中的不同bin(箱子)中,这可以通过在特征值中加一个偏置常量来解决。
- 比如,我们要绑定两个特征A和B,A特征的原始取值为区间[0,10),B特征的原始取值为区间[0,20),我们可以在B特征的取值上加一个偏置常量10,将其取值范围变为[10,30),则绑定后的特征取值范围为 [0, 30)。
- 这样一来,在区间 [0,10) 的为A特征,在区间 [10,30) 的就为B特征了,这不就轻易从合并的特征中分离出来了。哈哈哈妙啊老哥~😄
3、LGB的工程优化
3.1、直接支持类别特征
实际上大多数机器学习工具都无法直接支持类别特征,一般需要把类别特征,通过 one-hot 编码,转化到多维的0/1特征,降低了空间和时间的效率。
🚀LightGBM是第一个直接支持类别特征的GBDT工具。
对于决策树来说,one-hot编码向量当维度比较高时,对决策树不友好:
- 样本切分不平衡问题。 意味着在每一个决策节点上只能使用one vs rest (many)。例如,动物类别切分后,会产生是否狗,是否猫等一系列特征。每次分裂带来的增益通常较少,因为每次仅仅从一大推信息中区分出那么一丁点信息;
- 影响决策树的学习。 因为就算可以对这个类别特征进行切分,独热编码也会把数据切分到很多零散的小空间上,如下图左边所示。而决策树学习时利用的是统计信息,在这些数据量小的空间上,统计信息不准确,学习效果会变差。会造成树的深度过大,导致过拟合。
LightGBM采用 many-vs-many 的切分方式,可以直接输入类别特征并实现最优切分。即将类别特征分为两个子集,实现类别特征的最优切分。
3.2、支持高效并行
3.2.1、特征并行
- 特征并行的主要思想是不同机器在不同的特征集合上分别寻找最优的分割点,然后在机器间同步最优的分割点。XGB使用的就是这种特征并行方法。缺点: 就是对数据进行垂直划分,每台机器所含数据不同,然后使用不同机器找到不同特征的最优分裂点,划分结果需要通过通信告知每台机器,增加了额外的复杂度。
- LGB则不进行数据垂直划分,而是在每台机器上保存全部训练数据,在得到最佳划分方案后可在本地执行划分而减少了不必要的通信。
3.2.2、数据并行
LightGBM在数据并行中使用分散规约 (Reduce scatter) 把直方图合并的任务分摊到不同的机器,降低通信和计算,并利用直方图做差,进一步减少了一半的通信量。具体过程如下图所示。
3.2.3、投票并行
投票并行进一步优化数据并行中的通信代价,本地找出 Top K 特征,并基于投票筛选出可能是最优分割点的特征;合并时只合并每个机器选出来的特征。
3.3、Cache命中率优化
Cache命中率是啥呢?我觉得是指,如果你访问缓存中的某部分数据时,不需要通过间接的方式(如保存好的索引表,根据索引来找),而是直接连续顺序的访问,那就非常快,也就是指Cache命中率高!
在计算增益时,会利用到梯度,所以需要访问
-
带大家回顾一下吧,在LGB中通过将特征连续值离散化,用直方图来统计,然后在直方图上遍历寻找最优的分割点。直方图中本就是按特征值大小进行统计的。所以每个样本的每个特征对梯度的访问,不需要排序,连续访问即可(如下图,第一行的特征那就访问第一行的存好的梯度)。当数据量非常大时,连续顺序访问的方式可以快四倍以上。
-
而XGB则在第1个循环应该是遍历所有特征。第2个循环则遍历这个特征所有的分裂方式(一开始会先对特征按特征值进行预排序,方便逐步枚举所有分裂情况),并分别记住每种分裂方式能够使目标函数减少多少 (score)。所以在预排序后,特征对梯度的访问是一种随机访问(即需要通过索引表访问),并且不同的特征访问的顺序不一样,无法对cache进行优化。同时,在每一层长树的时候,需要随机访问一个行(样本)索引到叶子索引的数组(这个数组是记录了划分情况的,即某某样本属于某某叶子节点),并且不同特征访问的顺序也不一样,也会造成较大的cache miss。
⭐注:回顾一下我在XGBoost中的推导:这个q其实就是指行(样本)索引到叶子索引的数组。
4、LGB代码实现方式
LGB可用来完成 分类or 回归 任务
两个库可用 LightGBM原生接口(import lightgbm as lgb) 和 scikit-learn接口(from lightgbm import LGBMClassifier),网上有很多例子了我懒得放
5、LGB面试高频提问与答案?
如果前面讲的都会了,大概率都能达上了,放心~
5.1、LGB的优点和缺点?
LGB相对于XGB的优点,从内存和速度两方面进行介绍。
优点:
速度更快
- 1、LGB采用了直方图算法将遍历样本转变为遍历直方图,极大的降低了时间复杂度;
- 2、LGB在训练过程中采用单边梯度算法过滤掉梯度小的样本,减少了大量的计算;
- 3、LGB 采用了基于 Leaf-wise 算法的增长策略构建树,减少了很多不必要的计算量;
- 4、LGB采用优化后的特征并行、数据并行方法加速计算,当数据量非常大的时候还可以采用投票并行的策略;
- 5、LGB对缓存也进行了优化,增加了缓存命中率;
内存更小
- 1、XGB使用预排序后需要记录特征值及其对应样本的统计值的索引,而 LGB 使用了直方图算法将特征值转变为 bin 值,且不需要记录特征到样本的索引,将空间复杂度从 O(2*#data) 降低为 O(#bin) ,极大的减少了内存消耗;
- 2、LGB 采用了直方图算法将存储特征值转变为存储 bin 值,降低了内存消耗;
- 3、LGB在训练过程中采用互斥特征捆绑算法减少了特征数量,降低了内存消耗。
缺点:
- 1、可能会长出比较深的决策树,产生过拟合。因此LGB在Leaf-wise之上增加了一个最大深度限制,在保证高效率的同时防止过拟合;
- 2、Boosting族是迭代算法,每一次迭代都根据上一次迭代的预测结果对样本进行权重调整,所以随着迭代不断进行,误差会越来越小,模型的偏差(bias)会不断降低。由于LGB是基于偏差的算法,所以会对噪点较为敏感;
- 3、在寻找最优解时,依据的是最优切分变量,没有将最优解是全部特征的综合这一理念考虑进去;
5.2、LGB与XGB的联系和区别有哪些?
(1)LGB使用了基于histogram的决策树算法,这一点不同于XGB中的贪心算法和近似算法,histogram算法在内存和计算代价上都有不小优势。
- 1)内存上优势:很明显,直方图算法的内存消耗为 O(#bin) (因为对特征分桶后只需保存特征离散化之后的值),而XGB的贪心算法内存消耗为:O(#data) ,因为XGB既要保存原始feature的值,也要保存这个值的顺序索引,这些值需要位的浮点数来保存。
- 2)计算上的优势:预排序算法在选择好分裂特征计算分裂收益时需要遍历所有样本的特征值,时间为O(#data * # feature),而直方图算法只需要遍历桶就行了,时间为 O(#bin * # feature)。
(2)XGB采用的是level-wise的分裂策略,而LGB采用了leaf-wise的策略,区别是XGB对每一层所有节点做无差别分裂,可能有些节点的增益非常小,对结果影响不大,但是XGB也进行了分裂,带来了不必要的开销。leaft-wise的做法是在当前所有叶子节点中选择分裂收益最大的节点进行分裂,如此递归进行,很明显leaf-wise这种做法容易过拟合,因为容易陷入比较高的深度中,因此需要对最大深度做限制,从而避免过拟合。
(3)XGB在每一层都动态构建直方图,因为XGB的直方图算法不是针对某个特定的特征,而是所有特征共享一个直方图(每个样本的权重是二阶导),所以每一层都要重新构建直方图,而LGB中对每个特征都有一个直方图,所以构建一次直方图就够了。
(4)LGB使用直方图做差加速,一个子节点的直方图可以通过父节点的直方图减去兄弟节点的直方图得到,从而加速计算。
(5)LGB支持类别特征,不需要进行独热编码处理。
(6)LGB优化了特征并行和数据并行算法,除此之外还添加了投票并行方案。
(7)LGB采用基于梯度的单边采样来减少训练样本并保持数据分布不变,减少模型因数据分布发生变化而造成的模型精度下降。
(8)特征捆绑转化为图着色问题,减少特征数量。