之前在讲xgboost
的时候,详细介绍了陈天奇等人于2014年发布的XGBoost的内在原理,同时阐述了其特有的几大优点。然而时代变化之迅速,新技术如春笋般应运而生,与日俱进。继xgboost之后,2016年微软进一步发布了GBST的另一个实现:lightgbm
。据悉,与xgboost
相比,在相同的运行时间下能够得到更好的预测性能。同时,在multi-class、classification、click prediction和排序(lerning to rank)都有很好的效果。本文将基于lightgbm
的原始paper,对其原理进行归纳总结。
为了确保文章的连续性,读者需对Boosting
系列有一定的理解,在阅读本文核心内容之前,还读者预先在心里回答下下面几个问题:
XGBoost的目标函数表达式?
O b j ( t ) = ∑ i = 1 n l ( y i , y ^ i ( t ) ) + ∑ i = 1 t Ω ( f i ) O b j^{(t)}=\sum_{i=1}^{n} l\left(y_{i}, \hat{y}_{i}^{(t)}\right)+\sum_{i=1}^{t} \Omega\left(f_{i}\right) Obj(t)=i=1∑nl(yi,y^i(t))+i=1∑tΩ(fi)
XGBoost在GBDT的基础上做了哪些优化?
XGBoost的exact greedy Algorithm for split finding是什么?
XGBoost寻找分裂点的增益计算方法?
XGBoost的近似算法原理?
解释下Weighted Quantile Sketch原理?
如果对上面的问题模棱两可的,可前往上一篇文章,回顾一下。接下来将会针对XGBoost
的不足进一步探讨lightGBM
模型,相对于GBDT
,lightGBM
的精度与它相差不大,但是速度可以提升20倍。By the way,在学习lightGBM
的时候,可以思考下下面几个问题(本文不会直接给出结论,读完之后,读者自然就明白了):
不管是XGBoost
还是lightGBM
,模型的优化方向上必不可少的就是决策树的分裂上。下面,将重点介绍lightGBM
算法在寻找最佳切分点上所做出的努力。
LigthGBM是一款常用的GBDT工具包,速度比XGBoost快,精度也还可以。它的设计理念是:
所以其使用分布式的GBDT,选择了基于直方图的决策树算法。
lightGBM
引入的核心思想包括两个方面:
Histogram
: 基于特征的数值进行分bin
,然后基于bin
的值寻找最佳分裂的bin
值。GOSS
(Gradient based One Side Sampling): 移除小梯度样本,使用余留的样本做增益计算;EFB
(exclusive Feature Bundling):bundle不会同时为零的特征(互斥),达到减少特征数量的目的。GBDT
是以决策树为基学习器的ensemble
模型,在每次迭代中,GBDT
都通过拟合负梯度来学习决策树,其中代价最大最耗时的就是寻找最佳切分点过程。
一种方法是采用了预排序的算法,然后枚举所有可能的切分点,再寻找到增益最大的分裂点。比如Xgboost。
XGBoost中的Exact greedy算法:
优点是比较精确,缺点是空间消耗比较大,时间开销大和对内存不友好,使用直方图算法进行划分点的查找可以克服这些缺点。
另一中方法是基于histogram
的算法。
直方图算法(Histogram algorithm)把连续的浮点特征值离散化为k
个整数(也就是分桶bins的思想),比如[0,0.1)—> 0,[0.1,0.3)—>1.并根据特征所在的bin对其进行梯度累加和个数统计,然后根据直方图,寻找最优的切分点。
histogram
算法并不是在预排序的特征值当中寻找最佳切分点,而是将连续的特征值进行离散化bin并放入不同的bucket,在训练的时候基于这些bin来构建特征histogram
。这种做法效率更高,速度更快。
如Algorithm 1所示,构建histogram
的时间复杂度为O(#data × #feature)
,寻找最佳分裂点的时间复杂度O(#bin × #feature)
。
如何分桶bins?数值型特征和类别特征采用的方法是不同
max_bin
(设置的超参数,最大有几个分桶)和distinct_value.size()
(数据去重后值的类别)中的较小值作为bins_num
(分桶个数)bins
中的平均样本个数mean_bin_size
,若某个distinct_value
的count
大于mean_bin_size
,则该特征值作为bins
的上界,小于该特征值的第一个distinct value
作为下界;若某个distinct_value
的count
小于mean_bin_size
,则要进行累计后再分组。min(max_bin,distinct_values_int.size())
中的每个特征做第3步(忽略一些出现次数很少的特征取值)bin_2_categorical_bin_2_categorical_(vector类型)
和categorical_2_bin_categorical_2_bin_(unordered_map类型)
将特征取值和bin一一对应起来,这样就可以方便进行两者之间的转换了。如何构建直方图
给定一个特征值,我们可以转化为对应的bin了,就要构建直方图了,直方图中累加了一阶梯度和二阶梯度,并统计了取值的个数。
一个叶子节点的直方图可以由它的父亲节点的直方图与其兄弟的直方图做差得到。使用这个方法,构建完一个叶子节点的直方图后,就可以用较小的代价得到兄弟节点的直方图,相当于速度提升了一倍。
直方图算法仍有优化的空间,建立直方图的复杂度为O(#feature * #data),如果能降低特征数或者降低样本数,训练的时间会大大减少。假如特征存在冗余时,可以使用PCA
等算法进行降维,但特征通常是精心设计的,去除它们中的任何一个可能会影响训练精度。因此LightGbm提出了GOSS
算法和EFB
算法。
样本的梯度越小,则样本的训练误差越小,表示样本已经训练的很好了。最直接的做法就是丢掉这部分样本,然而直接扔掉会影响数据的分布,因此lightGBM
采用了one-side
采样的方式来适配:GOSS(Gradient-based One-Side Sampleing)
采样策略,它保留了所有的大梯度样本,对小梯度样本进行随机采样,来划分最优节点。
同时为了保证分布的一致性,在计算信息增益的时候,将采样的小梯度样本乘以一个常量: 1 − a b \frac{1-a}{b} b1−a, a a a表示Top a×100%的大梯度样本
比例值, b b b表示小梯度样本
的采样比值(很多文章里面理解成从省下的小梯度样本中采样b%的比例,其实是有误解的,这里的百分比是相对于全部样本而言的,即b%×N)。
例如:100个样本中,大梯度样本有20个,小梯度样本80个,小梯度样本量是大梯度样本数据量的4倍,则大样本采样比率 a a a等于0.2,假设小梯度样本的采样率为30%,则 b b b=0.3,那么小梯度样本的采样数目等于 b b b×100=30个,为了保证采样前后样本的分布保持一致,最后小梯度样本采样得到的数据在计算梯度时需要乘以 1 − a b = 1 − 0.2 0.3 = 8 3 \frac{1-a}{b}=\frac{1-0.2}{0.3}=\frac{8}{3} b1−a=0.31−0.2=38(解释一下,乘以1− a a a是因为大梯度样本采样的整体是整个样本集N,小梯度样本采样的候选样本集为 ( 1 − a ) N (1−a)N (1−a)N,除以 b b b是因为采样导致小梯度样本的整体分布减少,为此需要将权重放大1/b倍)。整个过程如下图Algorithm 2所示。
通过这个过程,减少了我们在寻找最优分割点时候的计算量。
原始直方图算法下,在第 j j j个特征,值为 d d d处进行分裂带来的增益可以定义为:
V j ∣ O ( d ) = 1 n O ( ( ∑ x i ∈ O x i , S d g i ) 2 n l ∣ 0 j ( d ) + ( ∑ x i ∈ O x i > d g i ) 2 n r ∣ O j ( d ) ) V_{j \mid O}(d)=\frac{1}{n_{O}}\left(\frac{\left(\sum_{x_{i} \in O_{x_{i}, S d}} g_{i}\right)^{2}}{n_{l \mid 0}^{j}(d)}+\frac{\left(\sum_{x_{i} \in O_{x_{i}>d}} g_{i}\right)^{2}}{n_{r \mid O}^{j}(d)}\right) Vj∣O(d)=nO1⎝⎛nl∣0j(d)(∑xi∈Oxi,Sdgi)2+nr∣Oj(d)(∑xi∈Oxi>dgi)2⎠⎞ | (1) |
---|
原始计算是拿梯度的平方/子节点的个数
其中 O O O为在决策树待分裂节点的训练集, n o = ∑ I ( x i ∈ O ) , n l ∣ O j ( d ) = ∑ I [ x i ∈ O : x i j ≤ d ] n_{o}=\sum I\left(x_{i} \in O\right), n_{l \mid O}^{j}(d)=\sum I\left[x_{i} \in O: x_{i j} \leq d\right] no=∑I(xi∈O),nl∣Oj(d)=∑I[xi∈O:xij≤d] 并且 n r ∣ O j ( d ) = ∑ I [ x i ∈ O : x i j > d ] n_{r \mid O}^{j}(d)=\sum I\left[x_{i} \in O: x_{i j}>d\right] nr∣Oj(d)=∑I[xi∈O:xij>d].
采用GOSS
之后,在第 j j j个特征,值为 d d d处进行分裂带来的增益可以定义为:
V j ∣ O ( d ) = 1 n O ( ( ∑ x i ∈ A l g i + 1 − a b ∑ x i ∈ B l g i ) 2 n l j ( d ) + ( ∑ x i ∈ A r g i + 1 − a b ∑ x i ∈ B l g r ) 2 n r j ( d ) ) V_{j \mid O}(d)=\frac{1}{n_{O}}\left(\frac{\left(\sum_{x_{i} \in A_{l}} g_{i}+\frac{1-a}{b} \sum_{x_{i} \in B_{l}} g_{i}\right)^{2}}{n_{l}^{j}(d)}+\frac{\left(\sum_{x_{i} \in A_{r}} g_{i}+\frac{1-a}{b} \sum_{x_{i} \in B_{l}} g_{r}\right)^{2}}{n_{r}^{j}(d)}\right) Vj∣O(d)=nO1(nlj(d)(∑xi∈Algi+b1−a∑xi∈Blgi)2+nrj(d)(∑xi∈Argi+b1−a∑xi∈Blgr)2) | (2) |
---|
其中, A l = x i ∈ A : x i j ≤ d , A r = x i ∈ A : x i j > d , B l = x i ∈ B : x i j ≤ d , B r = x i ∈ B : x i j > d A_{l}=x_{i} \in A: x_{i j} \leq d, A_{r}=x_{i} \in A: x_{i j}>d, B_{l}=x_{i} \in B: x_{i j} \leq d, B_{r}=x_{i} \in B: x_{i j}>d Al=xi∈A:xij≤d,Ar=xi∈A:xij>d,Bl=xi∈B:xij≤d,Br=xi∈B:xij>d
计算量大大减少,本来是全部样本,现在只需要
算 A l A_{l} Al+ B l B_{l} Bl的计算量
其中,A表示大梯度样本集,而B表示小梯度样本中随机采样的结果。
接下来我们来看;另一种算法
高维数据通常是非常稀疏的,而且很多特征是互斥的(即两个或多个特征列不会同时为0),lightGBM
对这类数据采用了名为EFB(exclusive feature bundling)
的优化策略,将这些互斥特征进行合并,能够合并的特征为一个#bundle。通过这种方式,可以将特征的维度降下来,相应的,构建histogram
所耗费的时间复杂度也从O(#data × #feature)
变为O(#data × #bundle)
,其中#feature << #bundle
。方法说起来虽然简单,但是实现起来将面临两大难点:
Greedy bundle
Merge Exclusive Features
针对这两个问题,paper里面提到了两种算法:Greedy Bundling
和Merge Exclusive feature
。
对于第一个问题,将特征划分为最少数量的Bundle本质上属于NP-hard problem
(非确定性多项式)。
Greedy bundled的原理与图着色相同,给定一个图G
,定点为V
,表示特征,边为E
,表示特征之间的互斥关系,接着采用贪心算法对图进行着色,以此来生成bundle。
不过论文中指出,对于特征值的互斥在一定程度上是可以容忍的,具体的算法流程如Algorithm 3所示。
该算法复杂度为O(#feature^2),采用这种方法对于特征数目不大的数据,还算OK,但是对于超大规模的特征,效率不高,这时可以不建立图,采用特征中非零值的个数作为排序的值,因为非零值越多通常冲突就越大。
其中 F F F表示特征的个数, k k k表示互斥的个数
对于第二个问题:应该如何如何构建bundle。
MEF(Merge Exclusive Features)将bundle中的特征合并为新的特征,合并的关键在于构建前的特征的值在构建后的bundle中能够识别。由于基于histogram
的方法存储的是离散的bin
而不是连续的数值,因此可以通过添加偏移的方法将不同特征的bin
值设定为不同的区间。
例如,特征A的bin值为[0,10),特征B的bin值为[0,20),要想将两个特征bin合并,我们可以将特征B的特征bin的值加上10,其取值区间将变为[0,30)。整个方法描述如下图所示。
另外,在树的生成方式上,lightGBM
与XGBoost
也是有区别的。
XGBoost
中决策树的生长策略是level-wise
,按层生长,同一层的所有节点都做分裂,最后剪枝,它不加区分的对待同一层的叶子,带来了很多没必要的开销,因为实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。
对比之下,lightGBM
的生长策略是leaf-wise
,维持的是一颗平衡树,leaf-wise
策略以降低模型损失最大化为目的,对当前所有叶子节点中切分增益最大的leaf
节点进行切分。优点是生长出来的树一定是最优的。不过leaf-wise
存在一个弊端,就是最后会得到一颗非常深的决策树,为了防止过拟合,可以在模型参数中设置决策树的深度。
这里我们主要介绍下LightGBM中的并行计算优化方法,在本小节中,工作的节点称为worker,LightGBM具有支持高效并行的特点,原生支持并行学习,目前支持:
特征并行是并行化决策树中寻找最优划分点的过程。特征并行是将对特征进行划分,每个worker找到局部的最佳切分点,使用点对点通信找到全局的最佳切分点。
数据并行
的目标是并行化整个决策学习的过程。每个worker中拥有部分数据,独立的构建局部直方图,合并后得到全局直方图,在全局直方图中寻找最优切分点进行分裂。
LightGBM采用一种称为PV-Tree
的算法进行投票并行(Voting Parallel),其实这本质上也是一种数据并行,PV-Tree
和普通的决策树差不多,只是在寻找最优切分点上有所不同。
每个worker拥有部分数据,独自构建直方图并找到top-K最优的划分特征,中心worker聚合得到最优的2K个全局划分特征,再向每个worker手机top-2k特征的直方图,并进行合并得到最优划分,广播给所有worker进行本地划分。
lightGBM
主要提出了两个新颖的方法:GOSS
和EFB
。两者都对算法性能的提升有着重要的贡献,其中GOSS
是针对分裂时样本的数目进行采样优化(行优化),EFB
是针对特征进行合并,达到特征减少的目的(列优化)。
实际上,XGBoost
和lightGBM
都属于GBDT
的一种实现,旨在优化算法的性能,提升算法的训练速度,与XGBoost
相比,lightGBM
更适应于数据量更大的场景。
从GBDT
->XGBoost
->lightGBM
,在模型训练阶段,是不能百分百地断定lightGBM
就比GBDT
和XGBoost
好,因为数据量的大小也决定了模型的可行性。所以实际场景中,还是建议一一尝试之后再做抉择,因为训练一个XGBoost
或lightGBM
,都是非常简单的事情。