1. LightGBM
1.1 介绍
LightGBM 是微软开发的 boosting 集成模型,和 XGBoost 一样是对 GBDT 的优化和高效实现,原理有一些相似之处,但它很多方面比 XGBoost 有着更为优秀的表现。官方给出的这个工具库模型的优势如下:
- 更快的训练效率
- 低内存使用
- 更高的准确率
- 支持并行化学习
- 可处理大规模数据
- 支持直接使用 category 特征
GBDT 在每一次迭代的时候,都需要遍历整个训练数据多次。
- 如果把整个训练数据一次性装进内存,会明显限制训练数据的大小。
- 如果不装进内存,反复地读写训练数据又会消耗非常大的时间。
面对工业级海量的数据,普通的 GBDT 算法无法满足需求。 LightGBM 提出的主要原因之一,就是为了解决上述大数据量级下的 GBDT 训练问题,以便工业实践中能支撑大数据量并保证效率。
1.2 XGBoost 优缺点
XGB优缺点归纳如下:
- 1、精确贪心算法
多轮迭代时,都需要遍历整个训练数据多次。如果把整个训练数据装进内存则会限制训练数据的大小;如果不装进内存,反复地读写训练数据又会消耗非常大的时间。
-
优点:可以找到精确的划分条件。
-
缺点:计算量巨大、内存占用巨大、易产生过拟合。
-
2、Level-wise生长方式
XGBoost 采用 Level-wise 的增长策略:基于层进行生长,直到达到停止条件。这种增长策略方便并行计算每一层的分裂节点,提高了训练速度,但同时也因为节点增益过小增加了很多不必要的分裂,增加了计算量
- 优点:可以使用多线程、可以加速精确贪心算法。
- 缺点:效率低下,可能产生不必要的叶结点。
1.3 LightGBM优势
- 基于 Histogram 的决策树算法
- 带深度限制的 Leaf-wise 的叶子生长策略
- 直方图做差加速
- 直接支持类别特征(Categorical Feature)
- Cache命中率优化
- 基于直方图的稀疏特征优化
- 多线程优化
1.4 LightGBM:直方图算法
(1)直方图算法
LightGBM 使用的是直方图算法,算法思想:
- 将连续的浮点特征离散成 k个离散值,并构造宽度为 k 的 histogram 。
- 遍历训练数据,统计每个离散值在直方图中的累计统计量。
- 在进行特征选择时,只需要根据直方图的离散值,遍历寻找最优的分割点。
优点:内存占用更小、计算代价更小。
当然,Histogram算法并不是完美的。由于特征被离散化后,找到的并不是很精确的分割点,所以会对结果产生影响。但在不同的数据集上的结果表明,离散化的分割点对最终的精度影响并不是很大,甚至有时候会更好一点。原因是决策树本来就是弱模型,分割点是不是精确并不是太重要;较粗的分割点也有正则化的效果,可以有效地防止过拟合; 即使单棵树的训练误差比精确分割的算法稍大,但在梯度提升(Gradient Boosting)的框架下没有太大的影响。
(2)直方图做差加速
LightGBM另一个优化是Histogram(直方图)做差加速。一个叶子的直方图可以由它的父亲节点的直方图与它兄弟的直方图做差得到,在速度上可以提升一倍。通常构造直方图时,需要遍历该叶子上的所有数据,但直方图做差仅需遍历直方图的k个桶。在实际构建树的过程中,LightGBM还可以先计算直方图小的叶子节点,然后利用直方图做差来获得直方图大的叶子节点,这样就可以用非常微小的代价得到它兄弟叶子的直方图。
注意: XGBoost 在进行预排序时只考虑非零值进行加速,而 LightGBM 也采用类似策略:只用非零特征构建直方图。
Lightgbm里面的直方图算法就是为了减少分裂点的数量, Lightgbm里面的单边梯度抽样算法就是为了减少样本的数量, 而Lightgbm里面的互斥特征捆绑算法就是为了减少特征的数量。并且后面两个是Lightgbm的亮点所在。
- 直方图这个思路在xgboost里面也体现过,不算是lightgbm的亮点了, 这个是会有一些效果,可以减少点计算,但是比较微妙,lightgbm直方图算法进行了更好的优化(具体的下面说), 比xgboost的这个还要快很多,并且XGB虽然每次只需要遍历几个可能的分裂节点,然后比较每个分裂节点的信息增益,选择最大的那个进行分割,但比较时需要考虑所有样本带来的信息增益,这样还是比较费劲。所以基于xgboost寻找最优分裂点的复杂度,总结下面三点:
- 特征数量
- 分裂点的数量
- 样本的数量
Lightgbm里面的直方图算法就是为了减少分裂点的数量, Lightgbm里面的单边梯度抽样算法就是为了减少样本的数量, 而Lightgbm里面的互斥特征捆绑算法就是为了减少特征的数量。并且后面两个是Lightgbm的亮点所在。
(3)与XGBOOST在寻找分裂点时的对比
这样在遍历到该特征的时候,只需要根据直方图的离散值,遍历寻找最优的分割点即可,由于bins的数量是远小于样本不同取值的数量的,所以分桶之后要遍历的分裂点的个数会少了很多,这样就可以减少计算量。基于上面的这个方式,如果是把所有特征放到一块的话,应该是下面的这种感觉:
- xgboost不是后面的近似分割算法也进行了分桶吗?为啥会比lightgbm的直方图算法慢这么多呢?
- 答:xgboost那里的分桶是基于Weight Quantile Sketch算法,考虑的是对loss的影响权值,用的每个样本的hi来表示的(如果忘了,可以去看看我写的xgboost),相当于基于hi的分布去找候选分割点,这样带来的一个问题就是每一层划分完了之后,下一次依然需要构建这样的直方图,毕竟样本被划分到了不同的节点中,二阶导分布也就变了。 所以xgboost在每一层都得动态构建直方图, 因为它这个直方图算法不是针对某个特定的feature的,而是所有feature共享一个直方图(每个样本权重的二阶导)。而lightgbm对每个特征都有一个直方图,这样构建一次就OK, 并且分裂的时候还能通过直方图作差进行加速。故xgboost的直方图算法是不如lightgbm的直方图算法快的。
1.5 LightGBM的两大先进技术(GOSS & EFB)
下面的这两大技术是Lightgbm相对于xgboost独有的, 分别是单边梯度抽样算法(GOSS)和互斥特征捆绑算法(EFB), 我们上面说到,GOSS可以减少样本的数量,而EFB可以减少特征的数量,这样就能降低模型分裂过程中的复杂度。
1.5.1 单边梯度抽样算法(GOSS)
单边梯度抽样算法(Gradient-based One-Side Sampling)是从减少样本的角度出发, 排除大部分权重小的样本,仅用剩下的样本计算信息增益,它是一种在减少数据和保证精度上平衡的算法。
-
众所周知,GBDT中没有原始样本的权重,既然Lightgbm是GBDT的变种,应该也没有原始样本的权重,这里怎么排除大部分权重小的样本?
-
答:我们知道在AdaBoost中,会给每个样本一个权重,然后每一轮之后调大错误样本的权重,让后面的模型更加关注前面错误区分的样本,这时候样本权重是数据重要性的标志,到了GBDT中, 确实没有一个像Adaboost里面这样的样本权重,理论上说是不能应用权重进行采样的,但是GBDT中每个数据都会有不同的梯度值, 这个对采样是十分有用的, 即梯度小的样本,训练误差也比较小,说明数据已经被模型学习的很好了,因为GBDT不是聚焦残差吗?在训练新模型的过程中,梯度比较小的样本对于降低残差的作用效果不是太大,所以我们可以关注梯度高的样本,这样不就减少计算量了吗?
-
样本的梯度应该越大越好,所以这就是为啥梯度小的样本对于降低残差的效果不大。也是为啥样本的梯度大小可以反映样本权重的原因。但是要是盲目的直接去掉这些梯度小的数据,这样就会改变数据的分布了啊,所Lightgbm才提出了单边梯度抽样算法,根据样本的权重信息对样本进行抽样,减少了大量梯度小的样本,但是还能不会过多的改变数据集的分布,这就比较牛了。怎么做的呢?
-
GOSS 算法保留了梯度大的样本,并对梯度小的样本进行随机抽样,为了不改变样本的数据分布,在计算增益时为梯度小的样本引入一个常数进行平衡。首先将要进行分裂的特征的所有取值按照绝对值大小降序排序(xgboost也进行了排序,但是LightGBM不用保存排序后的结果),然后拿到前N的梯度大的样本,和剩下样本。在计算增益时,后面的这通过乘上来放大梯度小的样本的权重。一方面算法将更多的注意力放在训练不足的样本上,另一方面通过乘上权重来防止采样对原始数据分布造成太大的影响。
通过上面,我们就通过采样的方式,选出了我们的样本,两个梯度大的6号和7号,然后又从剩下的样本里面随机选了2个梯度小的,4号和2号,这时候我们重点看看基于采样样本的估计直方图长什么样子,毕竟我们从8个里面选出了四个,如果直接把另外四个给删掉的话,这时候会改变数据的分布,但应该怎么做呢?也就是乘上来放大梯度小的样本的权重到底是怎么算的?看下图:
梯度小的样本乘上相应的权重之后,我们在基于采样样本的估计直方图中可以发现Ni的总个数依然是8个, 虽然6个梯度小的样本中去掉了4个,留下了两个。但是这2个样本在梯度上和个数上都进行了3倍的放大,所以可以防止采样对原数数据分布造成太大的影响, 这也就是论文里面说的将更多的注意力放在训练不足的样本上的原因。
单边梯度抽样算法基本上就理清楚了,Lightgbm正是通过这样的方式,在不降低太多精度的同时,减少了样本数量,使得训练速度加快。
1.5.2 互斥特征捆绑算法(EFB)
高维度的数据往往是稀疏的,这种稀疏性启发我们设计一种无损的方法来减少特征的维度。通常被捆绑的特征都是互斥的(即特征不会同时为非零值,像one-hot),这样两个特征捆绑起来才不会丢失信息。如果两个特征并不是完全互斥(部分情况下两个特征都是非零值),可以用一个指标对特征不互斥程度进行衡量,称之为冲突比率,当这个值较小时,我们可以选择把不完全互斥的两个特征捆绑,而不影响最后的精度。
举例:看到上面的这些特征够稀疏了吧(大部分都是0),而每一个特征都只有一个训练样本是非0且都不是同一个训练样本,这样的话特征之间也没有冲突了。这样的情况就可以把这四个特征捆绑成一个,这样是不是维度就减少了啊。
所以互斥特征捆绑算法(Exclusive Feature Bundling)是从减少特征的角度去帮助Lightgbm更快, 它指出如果将一些特征进行融合绑定,则可以降低特征数量。
但是针对这个特征捆绑融合,有两个问题需要解决, 毕竟像上面举得那种极端的例子除了OneHot之后的编码,其实很少见。
- 怎么判定哪些特征应该绑在一起?
- EFB 算法利用特征和特征间的关系构造一个加权无向图,并将其转换为图着色的问题来求解,求解过程中采用的贪心策略。感觉这里如果说成图着色问题的话反而有点难理解了,毕竟这里是加权无向图,而图着色问题可以去百度一下到底是怎么回事,反正觉得还不如直接说过程好理解,所以直接看过程反而简单一些。
- 其实说白了,捆绑特征就是在干这样的一件事:
- 首先将所有的特征看成图的各个顶点,将不相互独立的特征用一条边连起来,边的权重就是两个相连接的特征的总冲突值(也就是这两个特征上不同时为0的样本个数)。
- 然后按照节点的度对特征降序排序, 度越大,说明与其他特征的冲突越大
- 对于每一个特征, 遍历已有的特征簇,如果发现该特征加入到特征簇中的矛盾数不超过某一个阈值,则将该特征加入到该簇中。如果该特征不能加入任何一个已有的特征簇,则新建一个簇,将该特征加入到新建的簇中。
- 上面这个过程因为要遍历特征,每个特征还要遍历所有的簇, 在特征不多的情况下还行,但是如果特征维度很大,就不好使了。所以为了改善效率,可以不建立图,而是将特征按照非零值个数进行排序,因为更多的非零值的特征会导致更多的冲突,所以跳过了上面的第一步,直接排序然后第三步分簇。
- 特征绑在一起之后,特征值应该如何确定呢?
- 这里面的一个关键就是原始特征能从合并的特征中分离出来, 这是什么意思?绑定几个特征在同一个bundle里需要保证绑定前的原始特征的值可以在bundle里面进行识别,考虑到直方图算法将连续的值保存为离散的bins,我们可以使得不同特征的值分到簇中的不同bins里面去,这可以通过在特征值中加入一个偏置常量来解决。
- 比如,我们把特征A和B绑定到了同一个bundle里面, A特征的原始取值区间[0,10), B特征原始取值区间[0,20), 这样如果直接绑定,那么会发现我从bundle里面取出一个值5, 就分不出这个5到底是来自特征A还是特征B了。所以我们可以再B特征的取值上加一个常量10转换为[10, 30),这样绑定好的特征取值就是[0,30), 我如果再从bundle里面取出5, 就一下子知道这个是来自特征A的。这样就可以放心的融合特征A和特征B了。
- 看下图:
- 特征捆绑算法到这里也就基本上差不多了, 通过EFB,许多排他的特征就被捆绑成了更少的密集特征,这个大大减少的特征的数量,对训练速度又带来很大的提高。利用这种思路,可以通过对某些特征的取值重新编码,将多个这样互斥的特征捆绑成为一个新的特征。有趣的是,对于类别特征,如果转换成onehot编码,则这些onehot编码后的多个特征相互之间是互斥的,从而可以被捆绑成为一个特征。因此,对于指定为类别型的特征,LightGBM可以直接将每个类别取值和一个bin关联,从而自动地处理它们,而无需预处理成onehot编码多此一举。
1.6 LightGBM的生长策略(Leaf-wise)
lightgbm除了在寻找最优分裂点过程中进行了优化,其实在树的生成过程中也进行了优化, 它抛弃了xgboost里面的按层生长(level-wise), 而是使用了带有深度限制的按叶子生长(leaf-wise)。
-
XGBoost 在树的生成过程中采用 Level-wise的增长策略,该策略遍历一次数据可以同时分裂同一层的叶子,容易进行多线程优化,也好控制模型复杂度,不容易过拟合。但实际上Level-wise是一种低效的算法,因为它不加区分的对待同一层的叶子,实际上很多叶子的分裂增益较低,没必要进行搜索和分裂,因此带来了很多没必要的计算开销。
-
Leaf-wise 则是一种更为高效的策略,每次从当前所有叶子中,找到分裂增益最大的一个叶子,然后分裂,如此循环。因此同 Level-wise 相比,在分裂次数相同的情况下,Leaf-wise 可以降低更多的误差,得到更好的精度。 Leaf-wise 的缺点是可能会长出比较深的决策树,产生过拟合。因此 LightGBM 在 Leaf-wise 之上增加了一个最大深度的限制,在保证高效率的同时防止过拟合。
-
Level-wise的做法会产生一些低信息增益的节点,浪费运算资源,但是这个对于防止过拟合挺有用。而Leaf-wise能够追求更好的精度,降低误差,但是会带来过拟合问题。过拟合这个问题挺严重但是人家能提高精度,并且作者也使用了max_depth来控制树的高度。其实敢用Leaf-wise还有一个原因就是Lightgbm在做数据合并,直方图和GOSS等各个操作的时候,其实都有天然正则化的作用,所以作者感觉在这里使用Leaf-wise追求高精度是一个不错的选择。
1.7 LightGBM的工程优化
工程优化这部分主要涉及到了三个点:
- 类别特征的支持(这个不算是工程)
- 高效并行
- Cache命中率优化
1.7.1 支持类别特征
LightGBM是第一个直接支持类别特征的GBDT工具。 我们知道大多数机器学习工具都无法直接支持类别特征,一般需要把类别特征,通过one-hot 编码,转化到多维的0/1特征,降低了空间和时间的效率。但对于决策树来说,其实并不推荐使用独热编码,尤其是特征中类别很多,会存在以下问题:
-
会产生样本切分不平衡问题,切分增益会非常小。 如,国籍切分后,会产生是否中国,是否美国等一系列特征,这一系列特征上只有少量样本为1,大量样本为0。这种划分的增益非常小:较小的那个拆分样本集,它占总样本的比例太小。无论增益多大,乘以该比例之后几乎可以忽略;较大的那个拆分样本集,它几乎就是原始的样本集,增益几乎为零;
-
影响决策树学习:决策树依赖的是数据的统计信息,而独热码编码会把数据切分到零散的小空间上。在这些零散的小空间上统计信息不准确的,学习效果变差。本质是因为独热码编码之后的特征的表达能力较差的,特征的预测能力被人为的拆分成多份,每一份与其他特征竞争最优划分点都失败,最终该特征得到的重要性会比实际值低。
-
LightGBM 原生支持类别特征,采用 many-vs-many 的切分方式将类别特征分为两个子集,实现类别特征的最优切分。
上图为左边为基于 one-hot 编码进行分裂,后图为 LightGBM 基于 many-vs-many 进行分裂,右边叶子节点的含义是或者放到左孩子,其余放到右孩子, 右边的切分方法,数据会被切分到两个比较大的空间,进一步的学习也会更好。
其基本思想在于每次分组时都会根据训练目标对类别特征进行分类,在枚举分割点之前,先把直方图按照每个类别对应的label均值进行排序;然后按照排序的结果依次枚举最优分割点。看下面这个图:
从上面可以看到,为类别的均值。当然,这个方法很容易过拟合,所以LightGBM里面还增加了很多对于这个方法的约束和正则化。实验结果证明,这个方法可以使训练速度加速8倍。
1.7.2 支持高效并行
我们知道,并行计算可以使得速度更快, lightgbm支持三个角度的并行:特征并行,数据并行和投票并行。下面我们一一来看看:
- 特征并行:特征并行的主要思想是不同机器在不同的特征集合上分别寻找最优的分割点,然后在机器间同步最优的分割点。XGBoost使用的就是这种特征并行方法。这种特征并行方法有个很大的缺点:就是对数据进行垂直划分,每台机器所含数据不同,然后使用不同机器找到不同特征的最优分裂点,划分结果需要通过通信告知每台机器,增加了额外的复杂度。
LightGBM 则不进行数据垂直划分,而是在每台机器上保存全部训练数据,在得到最佳划分方案后可在本地执行划分而减少了不必要的通信。具体过程如下图所示。
- 数据并行 传统的数据并行策略主要为水平划分数据,让不同的机器先在本地构造直方图,然后进行全局的合并,最后在合并的直方图上面寻找最优分割点。这种数据划分有一个很大的缺点:通讯开销过大。如果使用点对点通信,一台机器的通讯开销大约为 ;如果使用集成的通信,则通讯开销为 。
LightGBM在数据并行中使用分散规约 (Reduce scatter) 把直方图合并的任务分摊到不同的机器,降低通信和计算,并利用直方图做差,进一步减少了一半的通信量。具体过程如下图所示。
- 投票并行:基于投票的数据并行则进一步优化数据并行中的通信代价,使通信代价变成常数级别。在数据量很大的时候,使用投票并行的方式只合并部分特征的直方图从而达到降低通信量的目的,可以得到非常好的加速效果。具体过程如下图所示。大致步骤为两步:
-
本地找出 Top K 特征,并基于投票筛选出可能是最优分割点的特征;
-
合并时只合并每个机器选出来的特征。
1.7.3 Cache命中率优化
cache是一种快速小型的内存,用以存储最近访问内存位置。
XGBoost对cache优化不友好,如下图所示。在预排序后,特征对梯度的访问是一种随机访问,并且不同的特征访问的顺序不一样,无法对cache进行优化。同时,在每一层长树的时候,需要随机访问一个行索引到叶子索引的数组,并且不同特征访问的顺序也不一样,也会造成较大的cache miss。为了解决缓存命中率低的问题,XGBoost 提出了缓存访问算法进行改进。
而 LightGBM 所使用直方图算法对 Cache 天生友好:
- 首先,所有的特征都采用相同的方式获得梯度(区别于XGBoost的不同特征通过不同的索引获得梯度),只需要对梯度进行排序并可实现连续访问,大大提高了缓存命中率;
- 其次,因为不需要存储行索引到叶子索引的数组,降低了存储消耗,而且也不存在 Cache Miss的问题。
2. CatBoost
CatBoost是Yandex开发的一种基于梯度提升决策树的机器学习算法。它特别擅长处理具有分类特征的数据集,并且在许多基准测试中表现出色。
优点:
无需手动处理分类特征,CatBoost能够自动处理。
使用了组合类别特征(Categorical Features)技术,提高了模型的性能。
采用了排序提升(Ordered Boosting)技术,减少了过拟合的风险。
缺点:
CatBoost在某些情况下可能比其他算法需要更多的计算资源。
对于非分类特征的数据集,CatBoost可能不是最佳选择。
与XGBoost、LightGBM相比,CatBoost的创新点有:
- 嵌入了自动将类别型特征处理为数值型特征的创新算法。首先对categorical
features做一些统计,计算某个类别特征(category)出现的频率,之后加上超参数,生成新的数值型特征(numerical features)。 - Catboost还使用了组合类别特征,可以利用到特征之间的联系,这极大的丰富了特征维度。
- 采用排序提升的方法对抗训练集中的噪声点,从而避免梯度估计的偏差,进而解决预测偏移的问题。 采用了完全对称树作为基模型。
2.1 类别型特征
2.1.1 类别型特征的相关工作
所谓类别型特征,即这类特征不是数值型特征,而是离散的集合,比如省份名(山东、山西、河北等),城市名(北京、上海、深圳等),学历(本科、硕士、博士等)。在梯度提升算法中,最常用的是将这些类别型特征转为数值型来处理,一般类别型特征会转化为一个或多个数值型特征。
如果某个类别型特征基数比较低(low-cardinality features),即该特征的所有值去重后构成的集合元素个数比较少,一般利用One-hot编码方法将特征转为数值型。One-hot编码可以在数据预处理时完成,也可以在模型训练的时候完成,从训练时间的角度,后一种方法的实现更为高效,CatBoost对于基数较低的类别型特征也是采用后一种实现。
显然,在高基数类别型特征(high cardinality features)当中,比如 user ID,这种编码方式会产生大量新的特征,造成维度灾难。 一种折中的办法是可以将类别分组成有限个的群体再进行One-hot编码。一种常被使用的方法是根据目标变量统计(Target Statistics,以下简称TS)进行分组,目标变量统计用于估算每个类别的目标变量期望值。 甚至有人直接用TS作为一个新的数值型变量来代替原来的类别型变量。重要的是,可以通过对TS数值型特征的阈值设置,基于对数损失、基尼系数或者均方差,得到一个对于训练集而言将类别一分为二的所有可能划分当中最优的那个。在LightGBM当中,类别型特征用每一步梯度提升时的梯度统计(Gradient Statistics,以下简称GS)来表示。虽然为建树提供了重要的信息,但是这种方法有以下两个缺点:
- 增加计算时间,因为需要对每一个类别型特征,在迭代的每一步,都需要对GS进行计算;
- 增加存储需求,对于一个类别型变量,需要存储每一次分离每个节点的类别;
为了克服这些缺点,LightGBM以损失部分信息为代价将所有的长尾类别归为一类,作者声称这样处理高基数类别型特征时比One-hot编码还是好不少。不过如果采用TS特征,那么对于每个类别只需要计算和存储一个数字。
因此,采用TS作为一个新的数值型特征是最有效、信息损失最小的处理类别型特征的方法。TS也被广泛应用在点击预测任务当中,这个场景当中的类别型特征有用户、地区、广告、广告发布者等。接下来我们着重讨论TS,暂时将One-hot编码和GS放一边。
2.1.2 目标变量统计(Target Statistics)
CatBoost算法的设计初衷是为了更好的处理GBDT特征中的categorical features。在处理 GBDT特征中的categorical features的时候,最简单的方法是用 categorical feature 对应的标签的平均值来替换。在决策树中,标签平均值将作为节点分裂的标准。这种方法被称为 Greedy Target-based Statistics , 简称 Greedy TS,用公式来表达就是:
这种方法有一个显而易见的缺陷,就是通常特征比标签包含更多的信息,如果强行用标签的平均值来表示特征的话,当训练数据集和测试数据集数据结构和分布不一样的时候会出条件偏移问题。
一个标准的改进 Greedy TS的方式是添加先验分布项,这样可以减少噪声和低频率类别型数据对于数据分布的影响:
2.1.3 特征组合
值得注意的是几个类别型特征的任意组合都可视为新的特征。例如,在音乐推荐应用中,我们有两个类别型特征:用户ID和音乐流派。如果有些用户更喜欢摇滚乐,将用户ID和音乐流派转换为数字特征时,根据上述这些信息就会丢失。结合这两个特征就可以解决这个问题,并且可以得到一个新的强大的特征。 然而,组合的数量会随着数据集中类别型特征的数量成指数增长,因此不可能在算法中考虑所有组合。为当前树构造新的分割点时,CatBoost会采用贪婪的策略考虑组合。对于树的第一次分割,不考虑任何组合。对于下一个分割,CatBoost将当前树的所有组合、类别型特征与数据集中的所有类别型特征相结合,并将新的组合类别型特征动态地转换为数值型特征。CatBoost还通过以下方式生成数值型特征和类别型特征的组合:树中选定的所有分割点都被视为具有两个值的类别型特征,并像类别型特征一样被进行组合考虑。
- 举例:
好的,为了更好地理解 CatBoost 中特征组合的过程,我们继续以音乐推荐的例子来详细说明:
假设我们有一个音乐推荐数据集,包含以下几个类别型特征:
- 用户 ID(有用户 A、用户 B、用户 C 等)
- 音乐流派(有摇滚乐、流行乐、古典乐等)
- 播放设备(有手机、电脑、平板等)
初始状态下,数据集中有这三个类别型特征。
-
第一次分割:
在构建树的第一次分割时,CatBoost 不考虑任何特征组合,只基于单个特征进行分割。例如,可能选择“音乐流派”这个特征进行第一次分割,将数据集按照不同的音乐流派(摇滚乐、流行乐、古典乐)分成不同的子集。 -
第二次分割:
此时,CatBoost 会采用贪婪策略考虑特征组合。- 首先,它会将当前树已经存在的分割(这里是基于“音乐流派”的分割)所形成的特征,与数据集中原本的其他类别型特征(“用户 ID”和“播放设备”)进行结合。
- 例如,结合“音乐流派”和“用户 ID”:会生成新的组合特征,如“用户 A - 摇滚乐”“用户 B - 流行乐”“用户 C - 古典乐”等。这些新的组合特征会被动态地转换为数值型特征。转换方式可能是给每个不同的组合分配一个唯一的数值编号。
- 同样地,还会结合“音乐流派”和“播放设备”,生成如“摇滚乐 - 手机”“流行乐 - 电脑”“古典乐 - 平板”等新的组合特征,并转换为数值型特征。
- 另外,对于树中选定的分割点(这里基于“音乐流派”的分割点),会被当作具有两个值的类别型特征(比如对于“摇滚乐”这个分割点,可以看作是“属于摇滚乐”和“不属于摇滚乐”这两个值),然后这个特殊的“类别型特征”会和其他类别型特征(“用户 ID”和“播放设备”)进行组合。
- 例如,生成“用户 A - 属于摇滚乐”“用户 B - 不属于摇滚乐”“手机 - 属于摇滚乐”“电脑 - 不属于摇滚乐”等新的组合特征,也将它们转换为数值型特征。
- 通过这样的方式,在第二次分割时,CatBoost 就利用特征组合生成了许多新的特征,这些新特征可以帮助模型更细致地捕捉数据中的信息和模式,比如不同用户在不同设备上对不同音乐流派的偏好,从而更好地进行音乐推荐。
- 随着树的进一步构建,后续的分割也会按照类似的方式,不断地生成和利用新的特征组合,逐步完善模型对数据的理解和预测能力。
2.1.4 catboost类别特征的处理(ordered ts)
对比:
lgb:直方图
catboost:主要使用统计特征对类别进行编码
下面主要介绍catboost的一种ts编码转换方式——buckets (简记为:加权累加和混合编码)
首先,按行将数据随机shuffle,并且标签也跟着一起shuffle的;
(需要特别提到的是,catboost每次生成新树的时候都会shuffle一下训练数据然后重新计算类别特征的编码值)
one_hot_max_size 限制独热编码
one_hot_max_size 来决定是否对类别特征进行编码操作,当类别数量超过one_hot_max_size之后才进行编码,否则默认是2以上不再onehot;
自带特征组合,如颜色+种类组合成 “blue+dog”
CatBoost只考虑一部分combinations。在选择第一个节点时,只考虑选择一个特征,例如A。在生成第二个节点时,考虑A和任意一个categorical
feature的组合,选择其中最好的。就这样使用贪心算法生成combinations。
2.2 内部细节
2.2.1 打分函数和损失函数
打分函数是xgboost对损失函数的处理在树的分裂过程中的表现,这也是后xgboost时代,gbdt算法的一个比较大的改变,基树不再是单纯的cart树使用gini进行分裂而是使用上面的梯度信息进行分裂的指导。
打分函数:catboost实现了XGBoost的打分函数的形式,也实现了原始的打分函数,同时也创造了另外四种的启发式的打分函数。
2.2.2 缺失值处理
catboost处理缺失值的方法很kaggle,其实就和平常比赛的时候把缺失值替换为9999,-9999一个意思,这里简单使用max和min其实并不是很好,你在训练集指定的max和min可能在测试集存在更大的max值或者更小的min值。
2.2.3 防止过拟合
Catoost除了增加了正则项来防止过拟合,还支持行列采样的方式来防止过拟合。
- 列采样: 正常的采样,参数为colsample_bylevel
- 行采样:贝叶斯采样:每一个样本的权重按照[0,
1]区间内随机生成一个数字a,然后权重w = a t a^t at,这里t越大则每一个样本的权重越小,相当于xgboost的subsample的程度越深。 - 随机赋权模式:这里catboost是针对每一个样本进行随机赋权;
2.2.4 Catboost的基学习器
catboost与xgboost的level-wise以及lightgbm的leaf-wise不同,catboost的基学习器使用的是完全对称二叉树(实际上catboost实现了多种树的生长方式):
- 完全对称二叉树:逐level构建树,直到达到指定的深度;在每次迭代中,选取每层中最优的一种分裂方式,以相同的该种方式进行分裂树的所有叶子结点,保持完全二叉树的结构。
- 深度(同xgb的level-wise):逐级构建,直至最大深度;在每次迭代中,将拆分树的所有非终端的叶子,每片叶子按条件进行分割,损失改善最佳
- 损失指南(lossguide,同lgb的leaf-wise):逐叶构建一棵树,直到达到指定的最大叶数;在每次迭代中**,将损失最佳的非终端叶子结点进行拆分。**
对比xbg、lgb
xgb:按层分裂,待分裂层的所有节点各自按照自己的最大增益进行分裂,不统一
lgb:按叶子分裂,计算所有待分裂的叶子节点,选取增益最大的一个进行分裂
catboost:计算所有待分裂的叶子节点,选取增益最大的一个,叶子结点层统一进行分裂
2.3 梯度偏差的优化 (gradient bias)—— ordered boosting
原始的gbdt无行列采样,所有的base tree都在同一个完整的训练数据集上拟合,然而和rf不一样的是,gbdt中的tree都不是独立训练的,而是用上一轮的tree计算得到的负梯度为标签继续训练,这就导致了如果上一轮的tree计算产生了偏差,这个偏差会继续在下一轮累计,那么假设训练集和测试集的分布差异较大,则偏差会在base trees中疯狂累计,从而使得整个gbdt过度拟合训练集。
通过行列采样可以在一定程度上缓解这个问题,catboost不仅仅学习xgboost引入行列采样(子采样)、引入树的正则化这种从超参数层面控制过拟合程度的方面开发,更是在tree训练的过程中采用ordered boosting的方式来缓解这种过拟合。
catboost实际上是支持两种boosting模式的:
- 排序:根据这里的描述,在小型的数据集上能够提供更好的结果,但是耗时。不支持GPU。(官网给的建议是样本不超过5万可以考虑ordered的boosting type。)
- 白板:plain表示常规的gbdt的boosting模式,支持GPU
2.3.1 ordered boosting详细说明
- 在CatBoost中,我们生成训练数据集的s个随机排列。采用多个随机排列是为了增强算法的鲁棒性
- 在Odered TS中,针对每一个随机排列,计算得到其梯度,为了与Ordered TS保持一致,这里ordered boosting的排列与用于计算Ordered TS时的排列相同。(也就是说,ordered ts和ordered boosting是使用的同样的shuffle)
2.3.2 boosting计算流程图举例:(迭代中加入上一轮的负梯度+真实值,拟合残差)
- catboost的ordered boosint type在一些地方被称为引入了online learning的思路,啥意思呢,假设我们要计算x5的负梯度,则我们使用x1,x2,x3,x4训练一棵树,用这棵树predict出x5的预测值和真实值带入负梯度计算公式算出负梯度,从而作为x5的负梯度g作为下一轮的标签。 如果要计算x6的负梯度,则使用x1,x2,x3,x4,x5按照上面的方法如法炮制。
- 显然,这种计算方式将导致非常大的计算量,因此catboost内部做了一些改进,默认情况下,catboost只训练log(num_of_datapoints)个模型,而不是为每个数据点训练不同的模型。
2.3.3 训练log个模型的例子
- 在第一个数据点上训练的模型用于计算第二个数据点的残差。
- 在前两个数据点上经过训练的另一个模型用于计算第三和第四数据点的残差
- 在前四个数据点上经过训练的另一个模型用于计算第5,6,7,8个数据点的残差 依次类推
2.4 调参
- cat_features:传入这个参数中的分类特征才能被CatBoost用他那迷人的方式处理,这个参数为空的话CatBoost和其他数据就没区别了,所以是最重要的特征!
- one_hot_max_size:catboost将会对所有unique值<=one_hot_max_size的特征进行独热处理。这个参数的调整因人而异
- learning_rate & n_estimators:这个和其他gbdt算法一样,学习率越小需要的学习器就越多。
- max_depth深度,这个没啥好多说
- subsample 子采样参数,在Bayesian Boosting Type不可用
- colsample_bylevel,colsample_bytree,colsample_bynode 列采样,不多说
- l2_leaf_reg: l2正则化系数
- random_strength:每一次分裂都会有一个分数,这个参数会给分数+一个随机性,来进一步抵抗过拟合的问题。
3 XGBoost、LightGBM、CatBoost的详细比较
算法 | 优点 | 缺点 | 改进点 | 创新点 |
---|---|---|---|---|
GBDT(Gradient Boosting Decision Tree) | 1. 预测精度高,能处理非线性关系,对数据分布和特征尺度不敏感。 2. 可以处理多种类型的数据,包括连续型和离散型。 3. 可以通过特征重要性分析来解释模型。 | 1. 训练速度慢,尤其是在处理大规模数据集时。 2. 容易过拟合,需要仔细调整参数。 3. 对于高维稀疏数据的处理能力较弱。 | 1. 通过正则化方法(如 L1 和 L2 正则化)来减少过拟合的风险。 2. 采用随机采样(如随机森林中的 bagging 思想)来提高模型的泛化能力。 | 1. 首次提出梯度提升的框架,通过迭代地训练弱分类器来构建强分类器。 2. 使用负梯度作为残差的近似,使得模型能够更快地收敛。 |
XGBoost(eXtreme Gradient Boosting) | 1. 高效的计算性能,支持并行计算,能充分利用多核 CPU 的资源。 2. 具有良好的可扩展性,可以处理大规模数据集。 3. 内置了正则化项,能有效防止过拟合。 4. 提供了丰富的调参选项,能更好地优化模型性能。 | 1. 对于类别型特征的处理不够直接,需要进行额外的编码。 2. 模型复杂度较高,调参难度较大。 | 1. 在目标函数中加入了二阶导数信息,使得模型的收敛速度更快,精度更高。 2. 采用了稀疏感知算法,能有效处理稀疏数据。 3. 支持列抽样,进一步减少过拟合的风险。 | 1. 提出了一种新的树结构学习算法,能够在大规模数据集上高效地构建决策树。 2. 实现了分布式计算,提高了模型的训练效率。 |
LightGBM(Light Gradient Boosting Machine) | 1. 训练速度极快,内存占用少,适合处理大规模数据集。 2. 采用了直方图算法,将连续特征离散化,减少了计算量。 3. 支持类别型特征的直接输入,无需额外编码。 4. 可以进行大规模并行训练。 | 1. 在某些小数据集上可能不如其他算法表现好。 2. 对参数的设置比较敏感,需要进行一定的调优。 | 1. 引入了 Leaf-wise 的树生长策略,能够更高效地找到最优解。 2. 采用了特征并行和数据并行的方式,进一步提高了训练速度。 3. 实现了基于直方图的近似算法,减少了内存占用。 | 1. 提出了直方图算法,将连续特征离散化,大大减少了计算量。 2. 引入了 Exclusive Feature Bundling(EFB)技术,能够有效地处理高维稀疏数据。 |
CatBoost(Categorical Boosting) | 1. 能够自动处理类别型特征,无需手动进行特征编码。 2. 具有较好的泛化能力,在处理类别型数据时表现出色。 3. 训练过程稳定,不易过拟合。 4. 提供了简单易用的 API,方便用户使用。 | 1. 训练速度相对较慢,尤其是在处理大规模数据集时。 2. 模型的可解释性相对较弱。 | 1. 采用了 Ordered Target Statistics 算法来处理类别型特征,避免了数据泄露的问题。 2. 在训练过程中使用了随机排序,提高了模型的稳定性。 | 1. 提出了一种新的类别型特征处理方法,能够自动学习类别型特征之间的关系。 2. 引入了 Ordered Boosting 技术,提高了模型的泛化能力。 |
以下详细介绍CatBoost、GBDT、XGBoost、LightGBM的树结构特点:
CatBoost
- 树结构特点:采用对称树(也称为完全对称决策树)。对称树意味着在树的每一层,所有的节点都会按照相同的特征和分裂点进行分裂。也就是说,树的结构是左右完全对称的,无论从哪个路径到达某一层的节点,该层节点的分裂规则都是一样的。
- 优势:这种结构使得模型的训练和预测过程更加稳定和可解释,并且在处理类别型特征时能够更好地利用特征之间的关系。同时,对称树的结构相对简单,减少了过拟合的风险。
GBDT(Gradient Boosting Decision Tree)
- 树结构特点:通常使用传统的决策树(非对称树)作为弱学习器。在构建决策树时,它采用贪心算法,每次选择一个最优的特征和分裂点来最大化信息增益或最小化损失函数,不要求树的结构对称。决策树会根据数据的分布和特征的重要性进行灵活的分裂,不同分支的深度和分裂方式可能差异很大。
- 优势:非对称树能够更精细地拟合数据,针对不同的数据分布和特征组合进行灵活调整,在处理复杂数据时具有很强的表达能力。
为了更直观了解 GBDT、XGBoost、LightGBM 和 CatBoost
所使用的树的样子,下面结合不同树结构特点进行描述,并给出简单示例。
GBDT 和 XGBoost 的非对称树
GBDT 和 XGBoost 通常使用非对称的决策树。这种树在生长过程中,会根据数据的特征和分布,在每个节点选择最优的特征和分裂点进行分裂,不同分支的深度和分裂情况差异较大。
示例结构
假设我们有一个关于是否购买某商品的数据集,包含特征“年龄”“收入”和“性别”。生成的非对称决策树可能如下:
根节点:年龄 < 30?
|-- 是:收入 < 5000?
| |-- 是:预测不购买
| |-- 否:预测购买
|-- 否:性别 = 男?
|-- 是:预测购买
|-- 否:收入 < 8000?
|-- 是:预测不购买
|-- 否:预测购买
在这个例子中,树的不同分支深度不同,且每个节点的分裂特征也不同,这体现了非对称树的灵活性,可以根据数据的实际情况进行复杂的决策。
LightGBM 的 Leaf - wise 非对称树
LightGBM 默认采用 Leaf - wise 的非对称树生长策略。它会优先选择能带来最大增益的叶子节点进行分裂,所以树可能会在某些区域长得很深,以快速降低损失函数。
示例结构
同样以购买商品数据集为例,Leaf - wise 生长的非对称树可能如下:
根节点:收入 < 6000?
|-- 是:年龄 < 25?
| |-- 是:性别 = 女?
| |-- 是:预测不购买
| |-- 否:预测购买
| |-- 否:预测购买
|-- 否:年龄 < 40?
|-- 是:预测购买
|-- 否:预测不购买
可以看到,树在某些分支上生长得较深,通过不断对叶子节点进行分裂来优化模型性能。
CatBoost 的对称树
CatBoost 使用对称树,在树的每一层,所有节点都会按照相同的特征和分裂点进行分裂,树的结构左右对称。
示例结构
还是以购买商品数据集为例,对称树可能如下:
根节点:年龄 < 35?
|-- 左子树:
| |-- 第二层:收入 < 6500?
| |-- 第三层:预测购买或不购买(根据数据统计确定)
|-- 右子树:
| |-- 第二层:收入 < 6500?
| |-- 第三层:预测购买或不购买(根据数据统计确定)
在这个对称树中,左右子树在同一层的分裂规则是相同的,都是根据“收入 < 6500?”进行分裂,保证了树结构的对称性。
XGBoost
- 树结构特点:使用的也是非对称树。和GBDT类似,XGBoost在构建树的过程中,基于目标函数(包含损失函数和正则化项)来选择最优的分裂特征和分裂点,不限制树的对称性。它会根据数据的具体情况动态地决定每个节点的分裂方式,以达到更好的性能。
- 优势:非对称树结构使得XGBoost可以更精确地捕捉数据中的复杂模式和关系,尤其是在处理高维、复杂的数据时表现出色。同时,结合其高效的优化算法和正则化机制,能够在保证拟合能力的同时避免过拟合。
LightGBM
- 树结构特点:默认采用 Leaf - wise(按叶子生长)的非对称树生长策略。它在每次迭代时,会选择具有最大增益的叶子节点进行分裂,而不是像 Level - wise(按层生长)那样逐层进行分裂。这种方式可以更快地降低损失函数,生成的树往往是深度较深、叶子节点数量较少的非对称树。
- 优势:Leaf - wise的非对称树生长策略使得LightGBM在训练效率上有显著提升,能够更快地收敛到较好的结果,尤其适合处理大规模数据集。
4 Boost在新闻推荐系统中排序模块的应用
以天池新闻推荐项目为例:
A. 数据处理 / 特征工程
添加以下字符段用于特征工程:
i. 新闻特征
- 新闻字数
- 新闻创建时间
- 新闻被阅读数量
ii. 用户特征
- 用户点击新闻的创建时间差的平均值
- 用户点击新闻的点击时间差的平均值
- 用户点击新闻的点击-创建时间差的统计值:mean,std
- 用户点击新闻的点击时长统计值
- 用户点击新闻的字数统计值
- 用户点击新闻的创建时间统计值
- 用户点击新闻的点击时间统计值
- 用户新闻阅读数量
- 用户某种类新闻阅读数量
iii. 用户-新闻交互特征
- 待预测新闻和用户所有历史点击新闻相似度按次序加权求和
- 待预测新闻和用户最近一次点击新闻相似度
B. 模型训练 / 模型融合
- 训练数据是29个特征+一个标签(1/0代表点击/未点击),需要完成的任务是对于多路召回阶段返回文章进行点击概率排序任务。
- 所以可以将排序问题转换为二分类问题(点击/未点击),根据文章的点击概率进行排序。
参考:
【白话机器学习】算法理论+实战之LightGBM算法
万字详解:LightGBM 原理、代码最全解读!
深入理解CatBoost
初学CatBoost模型——特性、原理、目标编码、调参