树模型系列之LightGBM

树模型系列之LightGBM

LightGBM模型

性能优化思想

LightGBM在XGBoost上主要有3方面的优化:

  • Histogram算法:直方图算法。
  • GOSS算法:基于梯度的单边采样算法。
  • EFB算法:互斥特征捆绑算法。

可以用如下一个简单公式来说明LightGBM和XGBoost的关系:
LightGBM = XGBoost + Histogram + GOSS + EFB

Histogram算法,GOSS算法,和EFB算法分别从什么角度对XGBoost进行性能优化呢?

先概括性地从全局进行分析,然后再逐个加以介绍


XGBoost模型训练的总体的复杂度可以粗略估计为:
训练复杂度 = 树的棵数✖️每棵树上叶子的数量✖️生成每片叶子的复杂度

由于XGBoost采用的基模型是二叉树,因此生成每片叶子需要分裂一次。而每次分裂,需要遍历所有特征上所有候选分裂点位,计算按照这些候选分裂点位分裂后的全部样本的目标函数增益,找到最大的那个增益对应的特征和候选分裂点位,从而生成一片新叶子


生成一片叶子的复杂度可以粗略估计为:
生成一片叶子的复杂度 = 特征数量✖️候选分裂点数量✖️样本的数量

而Hitogram算法的主要作用是减少候选分裂点数量,GOSS算法的作用是减少样本的数量,EFB算法的作用是减少特征的数量


通过这3个算法的引入,LightGBM生成一片叶子需要的复杂度大大降低了,从而极大节约了计算时间;同时Histogram算法还将特征由浮点数转换成0~255位的整数进行存储,从而极大节约了内存存储。

另外,在工程上面,LightGBM还在并行计算方面做了诸多的优化,支持特征并行和数据并行,并针对各自的并行方式做了优化,减少通信量。

优化策略

Histogram直方图算法

Histogram VS Pre-sorted

  • Pre-sorted
  • 在XGBoost当中的精确搜索算法(Exact Greedy Algorithm)在寻找分裂点时就是采用Pre-sorted的思想。
  • 预排序还是有一定优点的,如果不用预排序的话,在分裂节点的时候,选中某一个特征后,需要对A按特征值大小进行排序,然后计算每个阈值的增益,这个过程需要花费很多时间。
  • 预排序算法在计算最优分裂时,各个特征的增益可以并行计算,并且能精确地找到分割点。但是预排序后需要保存特征值及排序后的索引,因此需要消耗两倍于训练数据的内存,时间消耗大。另外预排序后,特征对梯度的访问是一种随机访问,并且不同的特征访问的顺序不一样,无法对cache进行优化,时间消耗也大。最后,在每一层,需要随机访问一个行索引到叶子索引的数组,并且不同特征访问的顺序也不一样

Historgram
首先需要指出的是,XGBoost在寻找树的分裂节点的也是支持直方图算法的,就是论文中提到的近视搜索算法(Approximate Algorithm)。只是,无论特征值是否为0,直方图算法都需要对特征的分箱值进行索引,因此对于大部分实际应用场景当中的稀疏数据优化不足。

回过头来,为了能够发挥直方图算法的优化威力,LightGBM提出了另外两个新技术:单边梯度采样(Gradient-based One-Side Sampling)和互斥特征合并(Exclusive Feature Bundling),在减少维度和下采样上面做了优化以后才能够将直方图算法发挥得淋漓尽致。下面依次介绍直方图算法、GOSS和EFB。

直方图算法
直方图算法的基本思想是先把连续的浮点特征值离散化成k个整数,同时构造一个宽度为k的直方图。在遍历数据的时候,根据离散化后的值作为索引在直方图中累积统计量,当遍历一次数据后,直方图累积了需要的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。(做法较为粗糙)。具体算法描述如下:
在这里插入图片描述
在这里插入图片描述
具体例子:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

直方图算法有如下优点:

  • 内存消耗降低。预排序算法需要的内存约是训练数据的两倍(2x样本数x维度x4Bytes),它需要用32位浮点来保存特征值,并且对每一列特征,都需要一个额外的排好序的索引,这也需要32位的存储空间。对于 直方图算法,则只需要(1x样本数x维度x1Bytes)的内存消耗,仅为预排序算法的1/8。因为直方图算法仅需要存储特征的 bin 值(离散化后的数值),不需要原始的特征值,也不用排序,而bin值用8位整型存储就足够了。

  • 算法时间复杂度大大降低。决策树算法在节点分裂时有两个主要操作组成,一个是“寻找分割点”,另一个是“数据分割”。从算法时间复杂度来看,在“寻找分割点”时,预排序算法对于深度为k的树的时间复杂度:对特征所有取值的排序为O(NlogN),N为样本点数目,若有D维特征,则O(kDNlogN),而直方图算法需要O(kD×bin)(bin是histogram 的横轴的数量,一般远小于样本数量N)。

  • 再举个例子说明上述两点的优化,假设数据集A的某个特征的特征值有(二叉树存储):{1.2,1.3,2.2,2.3,3.1,3.3},预排序算法要全部遍历一遍,需要切分大约5次。进行离散化后,只需要切分2次 {{1},{2,3}} 和 {{1,2},{3}},除了切分次数减少,内存消耗也大大降低。

  • 直方图算法还可以进一步加速。一个容易观察到的现象:一个叶子节点的直方图可以直接由父节点的直方图和兄弟节点的直方图做差得到。通常构造直方图,需要遍历该叶子上的所有数据,但直方图做差仅需遍历直方图的k个bin。利用这个方法,LightGBM可以在构造一个叶子的直方图后,可以用非常微小的代价得到它兄弟叶子的直方图,在速度上可以提升一倍。
    在这里插入图片描述
    当然,直方图算法并不是完美的。由于特征被离散化后,找到的并不是很精确的分割点,所以会对结果产生影响。但在不同的数据集上的结果表明,离散化的分割点对最终的精度影响并不是很大,甚至有时候会更好一点。原因是决策树本来就是弱模型,分割点是不是精确并不是太重要;较粗的分割点也有正则化的效果,可以有效地防止过拟合;即使单棵树的训练误差比精确分割的算法稍大,但在梯度提升(GradientBoosting)的框架下没有太大的影响。

GOSS基于梯度的单边采样算法(Gradient-based One-Side Sampling)

单边梯度采样是一种在减少数据量和保证精度上平衡的算法。在GBDT中, 我们对损失函数的负梯度进行拟合,样本误差越大,梯度的绝对值越大,这说明模型对该样本的学习还不足够,相反如果越小则表示模型对该样本的学习已经很充分。因此,我们可以这样推论:梯度的绝对值越大,样本重要性越高。单边梯度采样正是以样本的梯度作为样本的权重进行采样。

单边梯度采样保留所有的梯度较大的样本,在梯度小的实例上使用随机采样。为了抵消对数据分布的影响,计算信息增益的时候,单边梯度采样对小梯度的数据引入常量乘数。单边梯度采样首先根据数据的梯度绝对值排序,选取top a个样本。然后在剩余的数据中随机采样b个样本。接着计算信息增益时为采样出的小梯度数据乘以 (1−a )/b ,这样算法就会更关注训练不足的样本例,而不会过多改变原数据集的分布。正是单边梯度采样减少了数据量,让直方图算法发挥了更大的作用。单边梯度采样具体算法见下图:(a、b 应该是百分比)
在这里插入图片描述
具体例子:
在这里插入图片描述

EFB互斥特征捆绑算法

高维的数据通常是稀疏的,这种特征空间的稀疏性给我们提供了一种设计一种接近无损地降维的可能性。特别的,在稀疏特征空间中,许多特征是互斥的,换句话说,大部分特征不会同时取非0值,例如One-hot之后的类别特征他们从不同时为非0值。我们可以合并互斥的特征为单一特征(我们将这个过程称为Exclusive Feature Bundle).通过仔细设计特征扫描算法,我们从合并特征中构建出与单个特征相同的特征直方图。通过这种方式,直方图构建算法的时间复杂度从O(ND),降到O(N×bundle),其中N为样本点数目,D为特征维度。通常,bundle << D,我们能够在不损失精度的情况下极大地加速GBDT的训练。

那么接下来有两个问题需要处理:

  • 需要合并哪些特征
  • 如何合并这些特征
Greedy Bundling

找出最优的 bundle 组合数是一个 NP 问题,LightGBM通过将原问题转化为”图着色”问题进行贪心近似解决。

首先创建一个图G(V,E),其中V就是特征,为图中的节点,E为G中的边,将不是相互独立的特征用一条边连接起来,边的权重就是两个相连接的特征的总冲突值,这样需要绑定的特征就是在图着色问题中要涂上同一种颜色的那些点(特征)。具体算法过程如下:
在这里插入图片描述

Merge Exclusive Features

该过程主要是关键在于原始特征值可以从bundle中区分出来,即绑定几个特征在同一个bundle里需要保证绑定前的原始特征的值在bundle中能够被识别,考虑到直方图算法将连续的值保存为离散的bin,我们可以使得不同特征的值分到bundle中的不同bin中,这可以通过在特征值中加一个偏置常量来解决,比如,我们在bundle中绑定了两个特征A和B,A特征的原始取值为区间[0,10),B特征的原始取值为区间[0,20),我们可以在B特征的取值上加一个偏置常量10,将其取值范围变为[10,30),这样就可以放心的融合特征A和B了,因为在树模型中对于每一个特征都会计算分裂节点的,也就是通过将他们的取值范围限定在不同的bin中,在分裂时可以将不同特征很好的分裂到树的不同分支中去。具体算法如下:
在这里插入图片描述

Leaf-wise VS Level-wise
Level-wise

大多数GBDT框架使用的按层生长 (level-wise)
的决策树生长策略,Level-wise遍历一次数据可以同时分裂同一层的叶子,容易进行多线程优化,也好控制模型复杂度,不容易过拟合。但实际上Level-wise是一种低效的算法,因为它不加区分的对待同一层的叶子,带来了很多没必要的开销,因为实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。
在这里插入图片描述

Leaf-wise

LightGBM在直方图算法之上,对于树的生长策略做了进一步优化,抛弃了Level-wise策略,使用了带有深度限制的按叶子生长 (leaf-wise)算法。Leaf-wise则是一种更为高效的策略,每次从当前所有叶子中,找到分裂增益最大的一个叶子,然后分裂,如此循环。因此同Level-wise相比,在分裂次数相同的情况下,Leaf-wise可以降低更多的误差,得到更好的精度。Leaf-wise的缺点是可能会长出比较深的决策树,产生过拟合。因此LightGBM在Leaf-wise之上增加了一个最大深度的限制,在保证高效率的同时防止过拟合。
在这里插入图片描述

工程计算优化

特征并行和数据并行
特征并行

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

传统特征并行做法如下:

  • 垂直划分数据(对特征划分),不同的worker有不同的特征集
  • 每个workers找到局部最佳的切分点{feature, threshold}
  • workers使用点对点通信,找到全局最佳切分点
  • 具有全局最佳切分点的worker进行节点分裂,然后广播切分后的结果(左右子树的instance indices)
  • 其它worker根据收到的instance indices也进行划分
    在这里插入图片描述
    主要有以下缺点:
  • 无法加速分裂的过程,该过程的时间复杂度为O(N),当数据量大的时候效率不高
  • 需要广播划分的结果(左右子树的instance indices),1条数据1bit的话,通信花费大约需要O(N/8)

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

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

这样虽然不用进行广播划分结果,减小了网络通信量。但是也有缺点:

  • 分裂过程的复杂度保持不变
  • 每个worker保存所有数据,存储代价高
数据并行

数据并行的目标是并行化整个决策学习的过程,传统算法的做法如下:

  • 水平切分数据,不同的worker拥有部分数据
  • 每个worker根据本地数据构建局部直方图
  • 合并所有的局部直方图得到全部直方图
  • 根据全局直方图找到最优切分点并进行分裂
    在这里插入图片描述
    在第3步当中有两种合并方式:
  • 采用点对点方式(point-to-point communication algorithm)进行通讯,每个worker通讯量为𝑂(𝑚𝑎𝑐ℎ𝑖𝑛𝑒×𝑓𝑒𝑎𝑡𝑢𝑟𝑒×𝑏𝑖𝑛)
  • 采用collective communication algorithm(如“All Reduce”)进行通讯(相当于有一个中心节点,通讯后在返回结果),每个worker的通讯量为O(2×𝑓𝑒𝑎𝑡𝑢𝑟𝑒×𝑏𝑖𝑛)
    不难发现,通信的代价也是很高的,这也是数据并行的缺点。

LightGBM的数据并行主要做了以下两点优化:

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

通过上述两点做法,通信开销降为𝑂(0.5×𝑓𝑒𝑎𝑡𝑢𝑟𝑒×𝑏𝑖𝑛)。

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

PV-Tree和普通的决策树差不多,只是在寻找最优切分点上有所不同。具体算法如下:
在这里插入图片描述
主要思路如下:

  • 水平切分数据,不同的worker拥有部分数据
  • Local voting: 每个worker构建直方图,找到top−k个最优的本地划分特征
  • Global voting: 中心节点聚合得到最优的top−2k个全局划分特征(top−2k是看对各个worker选择特征的个数进行计数,取最多的2k个)
  • Best Attribute Identification: 中心节点向worker收集这top−2k个特征的直方图,并进行合并,然后计算得到全局的最优划分
  • 中心节点将全局最优划分广播给所有的worker,worker进行本地划分
    在这里插入图片描述
    可以看出,PV-tree将原本需要O(𝑓𝑒𝑎𝑡𝑢𝑟𝑒×𝑏𝑖𝑛) 变为了O(2k×𝑏𝑖𝑛),通信开销得到降低。此外,可以证明,当每个worker的数据足够多的时候,top−2k个中包含全局最佳切分点的概率非常高。
顺序访问梯度

Cache(高速缓存)作为内存局部区域的副本,用来存放当前活跃的程序和数据,它利用程序运行的局部性,把局部范围的数据从内存复制到Cache中,使CPU直接高速从Cache中读取程序和数据,从而解决CPU速度和内存速度不匹配的问题。高速缓冲存储器最重要的技术指标是它的命中率。CPU在Cache中找到有用的数据被称为命中,当Cache中没有CPU所需的数据时(这时称为未命中),CPU才访问内存。

预排序算法中有两个频繁的操作会导致cache-miss,也就是缓存消失(对速度的影响很大,特别是数据量很大的时候,顺序访问比随机访问的速度快4倍以上 )。

  • 对梯度的访问:在计算增益的时候需要利用梯度,假设梯度信息存在一个数组g[i]中,在对特征A进行增益时,需要根据特征A排序后的索引找到g[i]中对应的梯度信息。特征值A1对应的样本行号可能是3,对应的梯度信息在g[3],而特征值A2对应的样本行号可能是9999,对应的梯度信息在g[9999],即对于不同的特征,访问梯度的顺序是不一样的,并且是随机的
  • 对于索引表的访问:预排序算法使用了行号和叶子节点号的索引表,防止数据切分的时候对所有的特征进行切分。同访问梯度一样,所有的特征都要通过访问这个索引表来索引
    这两个操作都是随机的访问,会给系统性能带来非常大的下降。

LightGBM使用的直方图算法能很好的解决这类问题。首先。对梯度的访问,因为不用对特征进行排序,同时,所有的特征都用同样的方式来访问,所以只需要对梯度访问的顺序进行重新排序,所有的特征都能连续的访问梯度。并且直方图算法不需要把数据id到叶子节点号上(不需要这个索引表,没有这个缓存消失问题),大大提高cache的命中率,减少cache-miss出现的概率。

支持类别特征

实际上大多数机器学习工具都无法直接支持类别特征,一般需要把类别特征,转化one-hot特征,降低了空间和时间的效率。而类别特征的使用是在实践中很常用的。基于这个考虑,LightGBM优化了对类别特征的支持,可以直接输入类别特征,不需要额外的0/1展开。并在决策树算法上增加了类别特征的决策规则,直接原生支持类别特征,不需要转化,提高了近8倍的速度
在这里插入图片描述

LightGBM与XGBoost对比

LightGBM可以看成是XGBoost的升级加强版本,和XGBoost相比,LightGBM在大规模数据集上跑起来更加轻盈。

  • 模型精度:XGBoost和LightGBM相当。
  • 训练速度:LightGBM远快于XGBoost。
  • 内存消耗:LightGBM远小于XGBoost。
  • 缺失值特征:XGBoost和LightGBM都可以自动处理特征缺失值。
  • 分类特征:XGBoost不支持类别特征,需要OneHot编码预处理。LightGBM直接支持类别特征。

参考文献

LightGBM算法梳理
30分钟学会LightGBM
机器学习算法之LightGBM
LightGBM 中文文档

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值