本文根据多篇博文整理总结而成.
提升树是利用加法模型与前向分步算法实现学习的优化过程,它有一些高效实现,如XGBoost, pGBRT,GBDT等。其中GBDT采用负梯度作为划分的指标(信息增益),XGBoost则利用到二阶导数。它们共同的不足是计算信息增益需要扫描所有样本,从而找到最优划分点。在面对大量数据或者特征维度很高时,它们的效率和扩展性很难使人满意。
微软开源的LightGBM(基于GBDT的)则很好的解决这些问题,LightGBM是个快速的,分布式的,高性能的基于决策树算法的梯度提升框架。有以下优势:更快的训练效率;低内存使用;更高的准确率;支持并行化学习;可处理大规模数据;
XGBoost和LightGBM之间更加细致的性能对比如下所示
xgboost优缺点:
优点:XGB利用了二阶梯度来对节点进行划分,相对来说,精度更加高。XGB利用局部近似算法对分裂节点的贪心算法优化,取适当的eps时,可以保持算法的性能且提高算法的运算速度。在损失函数中加入了L1/L2项,控制模型的复杂度,提高模型的鲁棒性。提供并行计算能力,主要是在树节点求不同的候选的分裂点的Gain Infomation(分裂后,损失函数的差值),Tree Shrinkage,column subsampling等不同的处理细节。
缺点:每轮迭代时,都需要多次遍历整个训练数据。如果把整个训练数据装进内存则会限制训练数据的大小;如果不装进内存,反复地读写训练数据又会消耗非常大的时间。
预排序方法(pre-sorted):首先,空间消耗大。这样的算法需要保存数据的特征值,还保存了特征排序的结果(例如排序后的索引,为了后续快速的计算分割点),这里需要消耗训练数据两倍的内存。其次时间上也有较大的开销,在遍历每一个分割点的时候,都需要进行分裂增益的计算,消耗的代价大。
对cache优化不友好。在预排序后,特征对梯度的访问是一种随机访问,并且不同的特征访问的顺序不一样,无法对cache进行优化。同时,在每一层长树的时候,需要随机访问一个行索引到叶子索引的数组,并且不同特征访问的顺序也不一样,也会造成较大的cache miss。
LightGBM改进:
针对这些缺点LightGBM进行了相应的改进。LightGBM基于histogram算法代替pre-sorted所构建的数据结构,利用histogram做差,提高了cache命中率(主要是因为使用了leaf-wise)。
在机器学习当中,面对大数据量时候都会使用采样的方式(根据样本权值)来提高训练速度。又或者在训练的时候赋予样本权值来处理某一类样本(如Adaboost)。LightGBM利用了GOSS来做采样算法。
由于histogram算法对稀疏数据的处理时间复杂度没有pre-sorted好,因为histogram并不管特征值是否为0。因此采用了EFB来预处理稀疏数据。
Histogram算法:
直方图算法的基本思想:先把连续的浮点特征值离散化成k个整数,同时构造一个宽度为k的直方图。遍历数据时,根据离散化后的值作为索引在直方图中累积统计量,当遍历一次数据后,直方图累积了需要的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。
相对于pre-sorted算法,它的内存空间需要相对小很多。因为pre-sorted算法需要保存每一个特征的排序结构,所以其需要的内存大小是2 * #data * #feature * 4Bytes,而histogram只需保存离散值bin value而且不需要原始的feature value,所以占用的内存大小为:#data * # feature * 1Byte,因为离散值bin value使用uint8_t已经足够了。另外对于求子节点相应的feature histogram时,只需构造一个子节点的feature histogram,另外一个子节点的feature histogram可以用父节点的histogram减去刚构造出来的子节点的histogram便可,时间复杂度就压缩到O(k),k为histogram的桶数。这是一个很巧妙的做差法。
梯度单边采样Gradient-based One-Side Sampling(GOSS):
选取前a%个较大梯度的值作为大梯度值的训练样本;
从剩余的1 - a%个较小梯度的值中,我们随机选取其中的b%个作为小梯度值的训练样本;
对于较小梯度的样本,也就是b% * (1 - 1%) * #samples,我们在计算信息增益时将其放大(1 - a) / b倍;
总的来说就是a% * #samples + b% * (1 - a%) * #samples个样本作为训练样本。 而这样的构造是为了尽可能保持与总的数据分布一致,并且保证小梯度值的样本得到训练。
独立特征合并Exclusive Feature Bundling
使用这个算法的原因是要解决数据稀疏的问题。在很多时候,数据通常都是几千万维的稀疏数据。因此对不同维度的数据合并一齐使得一个稀疏矩阵变成一个稠密矩阵。这里就有两个问题:1. 如何确定哪些特征用于融合且效果为较好。2. 如何将这些特征合并到一齐。
对于第一个问题,这是一个NP-hard问题。把feature看作是图中的点(V),feature之间的总冲突看作是图中的边(E)。而寻找合并特征且使得合并的bundles个数最小,这是一个图着色问题。所以这个找出合并的特征且使得bundles个数最小的问题需要使用近似的贪心算法来完成。将问题一换成图着色算法去解决。构建一个以feature为图中的点(V),以feature之间的总冲突为图中的边(E)这里说的冲突值应该是feature之间cos夹角值,因为我们是尽可能保证feature之间非0元素不在同一个row上。首先按照度来对每个点(feature)做降序排序(度数越大与其他点的冲突越大),然后将特征合并到冲突数小于K的bundle或者新建另外一个bundle。算法的时间复杂度为O(#feature^2)。
第二问题是将这些bundles中的特征合并起来。由于每一个bundle当中,features的range都是不一样,所以我们需要重新构建合并后bundle feature的range。在第一个for循环当中,我们记录每个feature与之前features累积totalRange。在第二个for循环当中,根据之前的binRanges重新计算出新的bin value(F[j]bin[i] + binRanges[j])保证feature之间的值不会冲突。这是针对于稀疏矩阵进行优化。由于之前Greedy Bundling算法对features进行冲突检查确保bundle内特征冲突尽可能少,所以特征之间的非零元素不会有太多的冲突。
如此一来,数据的shape就变成了#samples * #bundles,且#bundles << #features。EFB降低了数据特征规模提高了模型的训练速度。
leaf-wise VS level-wise
LightGBM使用带深度限制的Leaf-wise的叶子生长策略.
Level-wise遍历一次数据可以同时分裂同一层的叶子,容易进行多线程优化,也好控制模型复杂度,不容易过拟合。但实际上Level-wise是一种低效算法,因为它不加区分的对待同一层的叶子,带来了很多没必要的开销,因为实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。
Leaf-wise则是一种更为高效的策略:每次从当前所有叶子中,找到分裂增益最大的一个叶子,然后分裂,如此循环。因此同Level-wise相比,在分裂次数相同的情况下,Leaf-wise可以降低更多的误差,得到更好的精度。
Leaf-wise的缺点:可能会长出比较深的决策树,产生过拟合。因此LightGBM在Leaf-wise之上增加了一个最大深度限制,在保证高效率的同时防止过拟合。
关于LightGBM更多内容请参考博文
LightGBM——提升机器算法(图解+理论+安装方法+python代码)