树模型(七):LightGBM

1 XGB vs LGB

2 直方图算法

2.1 缘起

XGBoost的Exact greedy算法流程

  1. 对每个特征都按照特征值进行排序
  2. 在每个排好序的特征都寻找最优切分点
  3. 用最优切分点进行切分

这个算法比较精确,但是缺点明显:

  1. 空间消耗大。需要保存数据的特征值。XGBoost采用Block结构,存储指向样本的索引,需要消耗两倍的内存。
  2. 时间开销大。在寻找最优切分点时,要对每个特征都进行排序,还要对每个特征的每个值都进行了遍历,并计算增益。
  3. 对Cache不友好。使用Block块预排序后,特征对梯度的访问是按照索引来获取的,是一种随机访问,而不同特征访问顺序也不一样,容易照成命中率低的问题。同时,在每一层长树的时候,需要随机访问一个行索引到叶子索引的数组,并且不同特征访问的顺序也不一样,也会造成较大的Cache miss。

使用直方图算法进行划分点的查找可以很好的克服这些缺点

2.2 算法描述

直方图算法(Histogram algorithm)的做法是把连续的浮点特征值离散化为k个整数(其实又是分桶的思想,而这些桶称为bin)比如[0,0.1)→0, [0.1,0.3)→1。

关键点(个人理解)

  • pre-bin,先分箱后分裂
  • LightGBM采用的直方图方法与XGBoost的”加权分位图”类似,直方图的边界即分裂值

同时,将特征根据其所在的bin进行梯度累加。这样,遍历一次数据后,直方图累积了需要的梯度信息,然后可以直接根据直方图,寻找最优的切分点。

流程描述(似乎loss计算那块有些问题,不过不影响理解直方图)

结合伪代码一起看

仔细看上面的伪代码,相信你有几个问题:

  • 如何将特征映射到bin呢?即如何分桶?
  • 如何构建直方图?直方图算法累加的g是什么?
  • 构建完直方图如何找最优特征,有用到二阶信息么?

2.2.1 分桶(bin)策略

对于数值型特征

  • 如果特征取值数比max_bin数量少,直接取distinct_values的中点放置
  • 特征取值比max_bin来得大,说明几个特征取值要共用一个bin
    • 计算mean size for one bin
    • 标记一个特征取值数超过mean,因为这些特征需要单独一个bin
    • 剩下的特征取值中平均每个bin的取值个数

对于类别特征

  • 首先对特征取值按出现的次数排序(大到小)
  • 取前min(max_bin, distinct_values_int.size())中的每个特征做第3步(这样可能忽略一些出现次数很少的特征取值)
  • 然后用𝑏𝑖𝑛_2_𝑐𝑎𝑡𝑒𝑔𝑜𝑟𝑖𝑐𝑎𝑙_bin_2_categorical_(vector类型)记录b对应的特征取值,以及用𝑐𝑎𝑡𝑒𝑔𝑜𝑟𝑖𝑐𝑎𝑙_2_𝑏𝑖𝑛_categorical_2_bin_(unordered_map类型) 将特征取值到哪个bin和一一对应起来。这样,以后就能很方便的进行bin到特征取值和特征取值到bin的转化。

2.2.2 做差加速

直方图算法还可以进一步加速:一个叶子节点的直方图可以由它的父亲节点的直方图与其兄弟的直方图做差得到

原来构造直方图,需要遍历该叶子上的所有数据,但直方图做差仅需遍历直方图的#bin个桶。使用这个方法,构建完一个叶子的直方图后,可以用非常微小的代价得到它兄弟的直方图,相当于速度提升了一倍

2.3 小结

可以看出,直方图算法的有点有:

  • 可以减少内存占用,比如离散为256个Bin时,只需要用8位整形就可以保存一个样本被映射为哪个Bin(这个bin可以说就是转换后的特征),对比预排序的Exact greedy算法来说(用int_32来存储索引+ 用float_32保存特征值),可以节省7/8的空间。
  • 计算效率也得到提高,预排序的Exact greedy对每个特征都需要遍历一遍数据,并计算增益,复杂度为𝑂(#𝑓𝑒𝑎𝑡𝑢𝑟𝑒×#𝑑𝑎𝑡𝑎。而直方图算法在建立完直方图后,只需要对每个特征遍历直方图即可,复杂度为𝑂(#𝑓𝑒𝑎𝑡𝑢𝑟𝑒×#𝑏𝑖𝑛𝑠)
  • 提高缓存命中率,因为它访问梯度是连续的(直方图)。
  • 此外,在数据并行的时候,直方图算法可以大幅降低通信代价。(数据并行、特征并行在本文后面讲解)

当然也有不够精确的缺点:
由于特征被离散化后,找到的并不是很精确的分割点,所以会对结果产生影响。但在不同的数据集上的结果表明,离散化的分割点对最终的精度影响并不是很大,甚至有时候会更好一点。原因是决策树本来就是弱模型,分割点是不是精确并不是太重要;较粗的分割点也有正则化的效果,可以有效地防止过拟合;即使单棵树的训练误差比精确分割的算法稍大,但在梯度提升(Gradient Boosting)的框架下没有太大的影响。

3 直方图算法的改进

直方图算法仍有优化的空间,建立直方图的复杂度为𝑂(#𝑓𝑒𝑎𝑡𝑢𝑟𝑒×#𝑑𝑎𝑡𝑎)O(#feature×#data),如果能降低特征数或者降低样本数,训练的时间会大大减少。以往的降低样本数的方法中,要么不能直接用在GBDT上,要么会损失精度。而降低特征数的直接想法是去除弱的特征(通常用PCA完成),然而,这些方法往往都假设特征是有冗余的,然而通常特征是精心设计的,去除它们中的任何一个可能会影响训练精度。因此LightGBM提出了GOSS算法和EFB算法。

3.1 GOSS算法——(行)减少训练样本数

使用GOSS进行采样,使得训练算法更加的关注没有充分训练(under-trained)的样本,并且只会稍微的改变原有的数据分布。

3.1.1 直观理解

GOSS全称为Gradient-based One-Side Sampling,中文译为”基于梯度的单边采样”。具体采样方式是,对于权值大的样本全部进行保留,而对于权值小的样本,可以选择部分丢弃,是谓“单边采样”。但是这个权值为什么选择”梯度”呢?因为GOSS认为,一个样本如果其梯度值很小,则表明它的训练误差小,也就是说它很好地被训练了,这时再拟合它的意义已经不大了;并且在分裂点计算时,它对Loss下降的贡献也不大。因此,可以将梯度小的样本抛弃。

但是这样又带来一个问题,就是改变了原始样本的分布(准确来说是影响了梯度的分布),容易过拟合。为了弥补这个缺限,Goss会对抛弃的样本集合的梯度累计值进行一定程度的比例放大。

3.1.2 算法描述

GOSS的做法伪代码描述如下:

即:

  1. 根据梯度的绝对值将样本进行降序排序
  2. 选择前𝑎×100%的样本,这些称为样本A
  3. 剩下的数据(1−𝑎)×100%的数据中,随机抽取𝑏×100%的数据,这些称为样本B
  4. 在计算增益的时候,放大样本B中的梯度(1−𝑎)/𝑏倍

原有的在特征j值为d处分数据带来的增益可以定义为:

V j ∣ O ( d ) = 1 n O ( ( ∑ x i ∈ O : x i j ≤ d g i ) 2 n l ∣ O j ( d ) + ( ∑ x i ∈ O : x i j > d g i ) 2 n r ∣ O j ( d ) ) V_{j|O}(d) = \frac{1}{n_O}\left(\frac{(\sum_{x_i\in O:x_{ij} \le d}g_i)^2}{n_{l|O}^j(d)} + \frac{(\sum_{x_i\in O:x_{ij} \gt d}g_i)^2}{n_{r|O}^j(d)} \right) VjO(d)=nO1(nlOj(d)(xiO:xijdgi)2+nrOj(d)(xiO:xij>dgi)2)

其中:

  • O为在决策树待分裂节点的训练集
  • n o = ∑ I ( x i ∈ O ) n_o = \sum I(x_i \in O) no=I(xiO)
  • n l ∣ O j ( d ) = ∑ I [ x i ∈ O : x i j ≤ d ]   a n d   n r ∣ O j ( d ) = ∑ I [ x i ∈ O : x i j > d ] n_{l|O}^j(d) = \sum I[x_i \in O: x_{ij} \le d]\ and\ n_{r|O}^j(d) = \sum I[x_i \in O: x_{ij} \gt d] nlOj(d)=I[xiO:xijd] and nrOj(d)=I[xiO:xij>d]

而使用GOSS后,增益定义为:
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|O}(d) = \frac{1}{n_O}\left(\frac{(\sum_{x_i\in A_l} g_i + \frac{1-a}{b} \sum_{x_i\in B_l} g_i)^2 }{n_{l}^j(d)} + \frac{(\sum_{x_i\in A_r} g_i + \frac{1-a}{b} \sum_{x_i\in B_l} g_r)^2 }{n_{r}^j(d)} \right) VjO(d)=nO1(nlj(d)(xiAlgi+b1axiBlgi)2+nrj(d)(xiArgi+b1axiBlgr)2)

其中:

  • A l = { x i ∈ A : x i j ≤ d } , A r = { x i ∈ A : x i j > d } A_l = \{x_i \in A: x_{ij} \le d\}, A_r = \{x_i \in A: x_{ij} \gt d\} Al={xiA:xijd},Ar={xiA:xij>d}
  • B l = { x i ∈ B : x i j ≤ d } , B r = { x i ∈ B : x i j > d } B_l = \{x_i \in B: x_{ij} \le d\}, B_r = \{x_i \in B: x_{ij} \gt d\} Bl={xiB:xijd},Br={xiB:xij>d}

3.1.3 示例

定义
N N N表示总样本数
a a a表示大梯度样本比例值
b b b表示小梯度样本的采样比值(很多文章理解从剩下的小梯度样本中采样,这是错误的,实际这里的百分比是相对于全部样本而言的

举例
有100个样本(N=100),其中大梯度样本20个,小梯度样本80个。此时 a = 0.2 a=0.2 a=0.2,因为20/100 = 0.2。假设从小梯度样本中采样30%,则 b = 0.3 b=0.3 b=0.3,即30个,因为100*0.3=30。为了保证采样前后的分布保持一致,最后小样本数据计算梯度时需要乘以(1−𝑎)/𝑏 = (1-0.2)/0.3 = 8/3。

验证一下: 20:80 = 20:30(8/3)*

3.2 EFB——(列)减少训练特征

高维数据通常是非常稀疏的,而且很多特征是互斥的(即两个或多个特征列不会同时为0),lightGBM对这类数据采用了名为EFB(exclusive feature bundling)的优化策略,将这些互斥特征分组合并为#bundle个维度。通过这种方式,可以将特征的维度降下来,相应的,构建histogram所耗费的时间复杂度也从O(#data ×× #feature)变为O(#data ×× #bundle),其中#feature << #bundle。方法说起来虽然简单,但是实现起来将面临两大难点

  1. 哪些特征可以bundle在一起
  2. 如何构建bundle,实现特征降维

针对这两个问题,paper里面提到了两种算法:Greedy Bundling和Merge Exclusive feature。

3.2.1 Greedy Bundling

可以简单理解为"互斥特征绑定"

对于第一个问题,将特征划分为最少数量的Bundle本质上属于NP-hard problem。原理与图着色相同,给定一个图G,定点为V,表示特征,边为E,表示特征之间的互斥关系,接着采用贪心算法对图进行着色,以此来生成bundle。不过论文中指出,对于特征值得互斥在一定程度上是可以容忍的,具体的读者可以参考下原paper。具体的算法流程如Algorithm 3所示。

  1. 首先构建一张带权重的图,权重为特征间的总冲突数;
  2. 对特征按照在图内的度进行降序排序;
  3. 检查排好序的特征,并将其划分到一个冲突较小的bundle里,如果没有就创建一个bundle。

采用这种方法对于特征数目不大的数据,还算OK,但是对于超大规模的特征将会出现性能瓶颈。一个优化的方向就是:采用非0值得个数作为排序的值,因为非零值越多通常冲突就越大。

3.2.2 Merge Exclusive feature

可以简单理解为"互斥特征合并"

对于第二个问题:应该如何如何构建bundle?关键在于构建前的特征的值在构建后的bundle中能够识别。由于基于histogram的方法存储的是离散的bin而不是连续的数值,因此我们可以将不同特征的bin值设定为不同的区间即可。例如,特征A的bin值为[0,10),特征B的bin值为[0,20),要想将两个特征bin合并,我们可以将特征B的特征bin的值加上10,其取值区间将变为[0,30)。整个方法描述如下图所示。

不用担心特征值还原的问题,因为EBF合并特征只为生成更简单的直方图,进而生成树,以后在预测时,相关特征依然会进行同样的偏移,只要能得到最终正确的回归值即可。

通过MEF算法,将许多互斥的稀疏特征转化为稠密的特征,降低了特征的数量,提高了建直方图的效率。

4 树的生成策略

Level-wise过一次数据可以同时分裂同一层的叶子,容易进行多线程优化,也好控制模型复杂度,不容易过拟合。但实际上Level-wise是一种低效的算法,因为它不加区分的对待同一层的叶子,带来了很多没必要的开销,因为实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。

Leaf-wise则是一种更为高效的策略,每次从当前所有叶子中,找到分裂增益最大的一个叶子,然后分裂,如此循环。因此同Level-wise相比,在分裂次数相同的情况下,Leaf-wise可以降低更多的误差,得到更好的精度。Leaf-wise的缺点是可能会长出比较深的决策树,产生过拟合。因此LightGBM在Leaf-wise之上增加了一个最大深度的限制,在保证高效率的同时防止过拟合。

5 支持类别特征

传统的机器学习工具一般,不能直接输入类别特征,需要预先做离散化,抓换为很多,多维的0,1特征,这样的做法无论在时间上还是空间上,效率都不高。

LightGBM通过更改决策树算法的决策规则,直接原生支持类别特征,不需要额外的离散化。并且通过一些实验,MRSA研究人员验证了直接使用离散特征可以比使用0-1离散化后的特征,速度快到8倍以上 。

6 Increase cache hit chance

6.1 Pre-sorted算法

Pre-sorted 的算法当中,有两个操作频繁的地方,会造成cache-miss。

  • 对梯度的访问,在计算gain的时候,需要利用梯度,但不同的feature访问梯度顺序都是不一样的,而且是随机的。

  • 对于索引表的访问,pre-sorted使用一个行号和叶子节点号的索引表,防止数据切分的时候,对所有的feature进行切分。对访问梯度一样,所有的feature都要通过访问这个索引表,所以,都是随机的访问,这个时候,会带了非常大的系统性能的下降。

6.2 直方图算法

LightGBM使用直方图算法则是天然的cache friendly,首先,对梯度的访问,因为不需要对feature进行排序,同时,所有的feature都采用同样的方式进行访问,所以只需要对梯度访问的顺序进行一个重新的排序,所有的feature都能连续地访问梯度。

此外,直方图算法不需要数据id到叶子id的一个索引表,没有这样一个cache-miss的问题。事实上,在cache-miss这样一个方面,对速度的影响是很大的,尤其在数据量很大的时候,MRSA研究人员进行过测试,在数据量很多的时候,相比于随机访问,顺序访问的速度可以快4倍以上,这其中速度的差异基本上就是由cache-miss而带来的。

7 并行计算

7.1 特征并行

特征并行主要是并行化决策树中寻找最优划分点(“Find Best Split”)的过程,因为这部分最为耗时。

7.1.1 传统特征并行

  1. 垂直划分数据(对特征划分),不同的worker有不同的特征集
  2. 每个workers找到局部最佳的切分点{feature, threshold}
  3. workers使用点对点通信,找到全局最佳切分点
  4. 具有全局最佳切分点的worker进行节点分裂,然后广播切分后的结果(左右子树的instance indices)
  5. 其它worker根据收到的instance indices也进行划分

传统算法的缺点是:

  1. 无法加速split的过程,该过程复杂度为O(#data),当数据量大的时候效率不高
  2. 需要广播划分的结果(左右子树的instance indices),1条数据1bit的话,大约需要花费O(#data/8)

7.1.2 LGB特征并行

每个worker保存所有的数据集,这样找到全局最佳切分点后各个worker都可以自行划分,就不用进行广播划分结果,减小了网络通信量。过程如下:

  1. 每个workers找到局部最佳的切分点{feature, threshold}
  2. workers使用点对点通信,找到全局最佳切分点
  3. 每个worker根据全局全局最佳切分点进行节点分裂

但是这样仍然有缺点:

  1. split过程的复杂度仍是O(#data),当数据量大的时候效率不高
  2. 每个worker保存所有数据,存储代价高

7.2 数据并行

7.2.1 传统数据并行

数据并行目标是并行化整个决策学习的过程:

  1. 水平切分数据,不同的worker拥有部分数据
  2. 每个worker根据本地数据构建局部直方图
  3. 合并所有的局部直方图得到全部直方图
  4. 根据全局直方图找到最优切分点并进行分裂

在第3步中,有两种合并的方式:

  • 采用点对点方式(point-to-point communication algorithm)进行通讯,每个worker通讯量为O(#machine*#feature*#bin)
  • 采用collective communication algorithm(如“All Reduce”)进行通讯(相当于有一个中心节点,通讯后在返回结果),每个worker的通讯量为O(2*#feature*#bin)

可以看出通信的代价是很高的,这也是数据并行的缺点。

7.2.2 LGB数据并行

  1. 使用“Reduce Scatter”将不同worker的不同特征的直方图合并,然后workers在局部合并的直方图中找到局部最优划分,最后同步全局最优划分。
  2. 前面提到过,可以通过直方图作差法得到兄弟节点的直方图,因此只需要通信一个节点的直方图。

通过上述两点做法,通信开销降为O(0.5∗#feature∗#bin)

7.3 Voting Parallel

LightGBM采用一种称为PV-Tree的算法进行投票并行(Voting Parallel),其实这本质上也是一种数据并行

PV-Tree和普通的决策树差不多,只是在寻找最优切分点上有所不同。

其算法伪代码描述如下:

  1. 水平切分数据,不同的worker拥有部分数据。
  2. Local voting: 每个worker构建直方图,找到top-k个最优的本地划分特征
  3. Global voting: 中心节点聚合得到最优的top-2k个全局划分特征(top-2k是看对各个worker选择特征的个数进行计数,取最多的2k个)
  4. Best Attribute Identification: 中心节点向worker收集这top-2k个特征的直方图,并进行合并,然后计算得到全局的最优划分
  5. 中心节点将全局最优划分广播给所有的worker,worker进行本地划分。

可以看出,PV-tree将原本需要#feature×#bin 变为了2k×#bin,通信开销得到降低。此外,可以证明,当每个worker的数据足够多的时候,top-2k个中包含全局最佳切分点的概率非常高。

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值