上篇文章集成学习之梯度提升决策树(GBDT)已经介绍了集成学习中boosting系列的重要算法GBDT,这里的GBDT特指“Greedy Function Approximation:A Gradient Boosting Machine”里提出的算法,它只用了 一阶导数信息。
实际上GBDT泛指所有梯度提升树算法,包括XGBoost、LightGBM,它们是GBDT的变种。
1.Xgboost
GBDT在利用前向分布算法学习加法模型时,用到了基于一阶泰勒展开的梯度提升求解法;而XGBoost在函数空间中用牛顿法进行优化。
1.1模型函数形式
给定数据集D = {(Xi,Yi)},XGBoost进行additive training,学习 K棵树,采用以下函数对样本进行预测:
![](https://i-blog.csdnimg.cn/blog_migrate/5ffa2dfc0c3cadcce27a81d795d0b99b.png)
这里F是假设空间,f(x)是回归树(CART):
![](https://i-blog.csdnimg.cn/blog_migrate/e20abd6dac4077c22d456cfce6ebd05b.png)
q(x)表示将样本x分到了某个叶子节点上,W是叶子节点的分数(leaf score),所以 Wq(x) 表示回归树对样本的预测值。
- 例子:预测一个人是否喜欢电脑游戏
![](https://i-blog.csdnimg.cn/blog_migrate/0da0b41cc9c0e0805662cd9dda54f1a6.png)
回归树的预测输出是实数分数,可以用于回归、分类、排序等任务中。对于回归问题,可以直接作为目标值,对于分类问题,需要映射成概率,比如采用Sigmoid函数。
1.2目标函数
参数空间中的目标函数:
![](https://i-blog.csdnimg.cn/blog_migrate/d605b51de55a46333c1e5aca8ad43d40.png)
误差函数可以是square loss,logloss等,正则项可以是L1正则, L2正则等。
XGBoost的目标函数(函数空间)
![](https://i-blog.csdnimg.cn/blog_migrate/df070a6f19a60a4abf471f350ad83677.png)
正则项对每棵回归树的复杂度进行了惩罚
正则项
正则项的作用,可以从Bayes先验的角度去解释。从Bayes角度来看,正则相当于对模型参数引入先验分布:
![](https://i-blog.csdnimg.cn/blog_migrate/b9980ed7834bf4cfbc0b461bf735ca43.png)
L2正则,模型参数服从高斯分布 Θ \Theta Θ ~ N (0, σ 2 \sigma^2 σ2),对参数加了分布约束,大部分绝对值很小。
![](https://i-blog.csdnimg.cn/blog_migrate/e20745a1692ae1a023e7f18a297493a3.png)
L1正则,模型参数服从拉普拉斯分布,对参数加了分布约束,大部分取值为0。
Xgboost的正则项
相比原始的GBDT,XGBoost的目标函数多了正则项,使得学习出来的 模型更加不容易过拟合。
有哪些指标可以衡量树的复杂度? 树的深度,内部节点个数,叶子节点个数(T),叶节点分数(w)… XGBoost采用的:
![](https://i-blog.csdnimg.cn/blog_migrate/7579af74c351347fd82093df5302ac11.png)
对叶子节点个数进行惩罚,相当于在训练过程中做了剪枝。
误差函数的二阶泰勒展开
第t次迭代后,模型的预测等于前t-1次的模型预测加上第t棵树的预测:
![](https://i-blog.csdnimg.cn/blog_migrate/15b4336fa7d378066e2735200b6a3aa6.png)
此时目标函数可写作:
![](https://i-blog.csdnimg.cn/blog_migrate/07aa7d74d933ca39b81ad934e98f9720.png)
公式中 yi , yi(t-1) 都已知,模型要学习的只有第t棵树 ft。
将误差函数在 yi(t-1) 处进行二阶泰勒展开:
![](https://i-blog.csdnimg.cn/blog_migrate/62ec007352c2e2998e5eb2ec980b2d52.png)
上式中,
![](https://i-blog.csdnimg.cn/blog_migrate/bb691e0b80f16d5b1111b324ac06c3be.png)
将公式中的常数项去掉,得到:
![](https://i-blog.csdnimg.cn/blog_migrate/2e930576654d6fd71e3e438c9537c28f.png)
把 ft ,Ω( ft ) 写成树结构的形式,即把下式代入目标函数中:
![](https://i-blog.csdnimg.cn/blog_migrate/936cd8429ae453c4ee3fb63ccb9cda5e.png)
得到:
![](https://i-blog.csdnimg.cn/blog_migrate/6f1c5f4cfe10b271a3f747bdfba7300d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/62da566495085035bdd7b0608a7025fc.png)
怎么统一起来?
定义每个叶节点j上的样本集合为:
![](https://i-blog.csdnimg.cn/blog_migrate/0e8b1288d3fcac56fb3ee0c7962c6442.png)
则目标函数可以写成按叶节点累加的形式:
![](https://i-blog.csdnimg.cn/blog_migrate/45d7341415aefbfee84d41eec02609ba.png)
如果确定了树的结构(即q(x)确定),为了使目标函数最小,可以令其导数为0,解得每个叶节点的最优预测分数为:
![](https://i-blog.csdnimg.cn/blog_migrate/7d9783025cf0c968739ec51efedd0802.png)
代入目标函数,得到最小损失为:
![](https://i-blog.csdnimg.cn/blog_migrate/4dfebce3e533d78b4175e07ddea491d7.png)
1.3回归树的学习策略
当回归树的结构确定时,我们前面已经推导出其最优的叶节点分数以及 对应的最小损失值,问题是怎么确定树的结构?
暴力枚举所有可能的树结构,选择损失值最小的 - NP难问题
贪心法,每次尝试分裂一个叶节点,计算分裂前后的增益,选择增益最大的。
分裂前后的增益怎么计算?
ID3算法采用信息增益
C4.5算法采用信息增益比
CART采用Gini系数
XGBoost呢?
XGBoost的打分函数
![](https://i-blog.csdnimg.cn/blog_migrate/26417d859776765ccbb09809807e5003.png)
标红部分衡量了每个叶子节点对总体损失的的贡献,我们希望损失越小越好,则标红部分的值越大越好。
因此,对一个叶子节点进行分裂,分裂前后的增益定义为:
![](https://i-blog.csdnimg.cn/blog_migrate/195069d9e8cb52e6b4ab10344bcd60ed.png)
Gain的值越大,分裂后 L 减小越多。所以当对一个叶节点分割时,计算所 有候选(feature,value)对应的gain,选取gain最大的进行分割。
树节点分裂方法(Split Finding)
精确算法
遍历所有特征的所有可能的分割点,计算gain值,选取值最大的(feature,value)去分割
![](https://i-blog.csdnimg.cn/blog_migrate/a16f4b229c258eda214a26f261536495.png)
近似算法
对于每个特征,只考察分位点,减少计算复杂度
Global:学习每棵树前,提出候选切分点
Local:每次分裂前,重新提出候选切分点
![](https://i-blog.csdnimg.cn/blog_migrate/a6a6a918a5b4b9ae57da6af516c6398e.png)
近似算法举例:三分位数
![](https://i-blog.csdnimg.cn/blog_migrate/da9ea6ac38795c0f65ea50b134e386f6.png)
实际上XGBoost不是简单地按照样本个数进行分位,而是以二阶导数值作为权重(Weighted Quantile Sketch),比如:
![](https://i-blog.csdnimg.cn/blog_migrate/9d7f7bb844ea27a0905b613ae482ee3a.png)
为什么用hi加权?
把目标函数整理成以下形式,可以看出hi有对loss加权的作用
![](https://i-blog.csdnimg.cn/blog_migrate/5f4fb3aa6fb0d1bf4c66b77007c6bb55.png)
稀疏值处理
• 稀疏值: 缺失,类别one-hot编码,大量0值
• 当特征出现缺失值时,XGBoost 可以学习出默认的节点分裂方向
![](https://i-blog.csdnimg.cn/blog_migrate/9ed62e06ca9bb354eaf7847e004e297b.png)
XGBoost怎么处理缺失值?
这是XGBoost的一个优点。具体处理方法为:在某列特征上寻找分裂节点时,不会对缺失的样本进行遍历,只会对非缺失样本上的特征值进行遍历,这样减少了为稀疏离散特征寻找分裂节点的时间开销。另外,为了保证完备性,对于含有缺失值的样本,会分别把它分配到左叶子节点和右叶子节点,然后再选择分裂后增益最大的那个方向,作为预测时特征值缺失样本的默认分支方向。如果训练集中没有缺失值,但是测试集中有,那么默认将缺失值划分到右叶子节点方向。
XGBoost中的一棵树的停止生长条件
当树达到最大深度时,停止建树,因为树的深度太深容易出现过拟合,这里需要设置一个超参数max_depth。当新引入的一次分裂所带来的增益Gain<0时,放弃当前的分裂。这是训练损失和模型结构复杂度的博弈过程。当引入一次分裂后,重新计算新生成的左、右两个叶子结点的样本权重和。如果任一个叶子结点的样本权重低于某一个阈值,也会放弃此次分裂。这涉及到一个超参数:最小样本权重和,是指如果一个叶子节点包含的样本数量太少也会放弃分裂,防止树分的太细。
XGBoost可以做特征选择,它是如何评价特征重要性的?
XGBoost中有三个参数可以用于评估特征重要性:weight :该特征在所有树中被用作分割样本的总次数。gain :该特征在其出现过的所有树中产生的平均增益。cover :该特征在其出现过的所有树中的平均覆盖范围。覆盖范围这里指的是一个特征用作分割点后,其影响的样本数量,即有多少样本经过该特征分割到两个子节点。
1.4重点
w是叶子分数就是某一个数据的当前拟合值,是最优化求出来的,不是啥平均值或规则指定的,CART中用均值是对损失函数的负梯度令其为0得到的,这里xgboost也是用的目标函数的泰勒展开的负梯度令其为0得到的,这个算是一个思路上的新颖吧;
正则化防止过拟合的技术,上述看到了,直接loss function里面就有;支持自定义loss function,只要能泰勒展开(能求一阶导和二阶导,二次可微);
支持并行化
这是xgboost的闪光点,直接的效果是训练速度快,boosting技术中下一棵树依赖上述树的训练和预测,所以树与树之间应该是只能串行!那么大家想想,哪里可以并行?!
没错,在选择最佳分裂点,进行枚举的时候并行!(据说恰好这个也是树形成最耗时的阶段)
Attention:同层级节点可并行。具体的对于某个节点,节点内选择最佳分裂点,候选分裂点计算增益用多线程并行。
对于每个特征,需要遍历所有的特征值去选择最佳分割点。如果在训练之前就排好序并存储在内存block中,每一个特征对应一个block。这样在构建第k棵树的过程中,可以同时对所有的叶结点进行分割点的遍历,也可以同时对所有特征进行分割点的遍历,这样特征的选择和叶结点的分裂可以同时进行,达到并行化计算的目的,可以进行分布式或多线程计算,缩短训练时间。
较少的离散值作为分割点倒是很简单,比如“是否是单身”来分裂节点计算增益是很easy,但是“月收入”这种feature,取值很多,从5k~50k都有,总不可能每个分割点都来试一下计算分裂增益吧?(比如月收入feature有1000个取值,难道你把这1000个用作分割候选?缺点1:计算量,缺点2:出现叶子节点样本过少,过拟合)我们常用的习惯就是划分区间,那么问题来了,这个区间分割点如何确定(难道平均分割),作者是这么做的:
方法名字:Weighted Quantile Sketch
大家还记得每个样本在节点(将要分裂的节点)处的loss function一阶导数gi和二阶导数hi,衡量预测值变化带来的loss function变化,举例来说,将样本“月收入”进行升序排列,5k、5.2k、5.3k、…、52k,分割线为“收入1”、“收入2”、…、“收入j”,满足(每个间隔的样本的hi之和/总样本的hi之和)为某个百分比ϵ(我这个是近似的说法),那么可以一共分成大约1/ϵ个分裂点。
1.5工程优化
Column Block for Parallel Learning
总的来说:按列切开,升序存放;
方便并行,同时解决一次性样本读入炸内存的情况
由于将数据按列存储,可以同时访问所有列,那么可以对所有属性同时执行split finding算法,从而并行化split finding(切分点寻找)-特征间并行
可以用多个block(Multiple blocks)分别存储不同的样本集,多个block可以并行计算-特征内并行
Blocks for Out-of-core Computation
数据大时分成多个block存在磁盘上,在计算过程中,用另外的线程读取数据,但是由于磁盘IO速度太慢,通常更不上计算的速度,
将block按列压缩,对于行索引,只保存第一个索引值,然后只保存该数据与第一个索引值之差(offset),一共用16个bits来保存 offset,因此,一个block一般有216个样本。
1.6疑问及总结
Xgboost第一感觉就是防止过拟合+各种支持分布式/并行,所以一般传言这种大杀器效果好(集成学习的高配)+训练效率高(分布式),与深度学习相比,对样本量和特征数据类型要求没那么苛刻,适用范围广。
GBDT是一个梯度迭代树,使用梯度迭代下降法求解,认为每一棵迭代树都在学习前N-1棵树的负梯度。残差只是误差为均方误差的情况下推导出来的。
这里真心有个疑问:
Xgboost在下一棵树拟合的是残差还是负梯度,还是说是一阶导数+二阶导数,−gi(1+hi)? 应该是一阶导数除以二阶导数gi/hi。
Xgboost和深度学习的关系,陈天奇在Quora上的解答如下:
不同的机器学习模型适用于不同类型的任务。深度神经网络通过对时空位置建模,能够很好地捕获图像、语音、文本等高维数据。而基于树模型的XGBoost则能很好地处理表格数据,同时还拥有一些深度神经网络所没有的特性(如:模型的可解释性、输入数据的不变性、更易于调参等)。
这两类模型都很重要,并广泛用于数据科学竞赛和工业界。举例来说,几乎所有采用机器学习技术的公司都在使用tree boosting,同时XGBoost已经给业界带来了很大的影响。
2.LightGBM
2.1直方图算法
把连续的浮点特征值离散化成k个整数,同时构造一个宽度为k的直方图。在遍历数据的时候,根据离散化后的值作为索引在直方图中累积统计量,当遍历一次数据后,直方图累积了需要的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。
![](https://i-blog.csdnimg.cn/blog_migrate/5e4d4f94b40ea626566a0bb1c3deb104.png)
• 减小内存占用,比如离散为256个bin时,只需要8bit,节省7/8
• 减小了splitfinding时计算增益的计算量,从O(#data)降到O(#bins)
直方图差加速
一个叶子的直方图可以由它的父亲节点的直方图与它兄弟节点的直方图做差得到,提升一倍速度:
![](https://i-blog.csdnimg.cn/blog_migrate/5429652d45de83a89574ef3ea9da5ad5.png)
思考:选取哪个子节点统计直方图?
2.2建树过程的两种方法
Level-wise和Leaf-wise
![](https://i-blog.csdnimg.cn/blog_migrate/b26e8d82b4e75207544631392b18437f.png)
XGBoost同一层所有节点都做分裂,最后剪枝。
![](https://i-blog.csdnimg.cn/blog_migrate/65c68304c3a6a3f16a34af0b828f76d1.png)
LightGBM选取具有最大增益的节点分裂,容易过拟合,通过max_depth限制。
2.3并行优化(Optimization in parallel learning)
特征并行
传统的特征并行
• 垂直切分数据,每个worker只有部分特征;
• 每个worker找到局部最佳切分点(feature,threshold)
• worker之间互相通信,找到全局最佳切分点;
• 具有全局最佳切分特征的worker进行节点分裂,然后广播切分后左右子树的instance indices;
• 其他worker根据广播的instance indices进行节点分裂。
特征并行示意图
![](https://i-blog.csdnimg.cn/blog_migrate/e941112548a3e3c71fbf3f6f94632915.png)
缺点:
split finding计算复杂度O(#data),当数据量大时会比较慢。
网络通信代价大,需要广播instanceindices。
LightGBM的特征并行
每个worker保存所有数据集
• 每个worker在其特征子集上寻找最佳切分点
• worker之间互相通信,找到全局最佳切分点
• 每个worker根据全局最佳切分点进行节点分裂
优点:
避免广播instance indices,减小网络通信量。
缺点:
split finding计算复杂度没有减小且当数据量比较大时,单个worker存储所有数据代价高。
数据并行
传统的数据并行
水平切分数据,每个worker只有部分数据
• 每个worker根据本地数据统计局部直方图
• 合并所有局部直方图得到全局直方图
• 根据全局直方图进行节点分裂
数据并行示意图
![](https://i-blog.csdnimg.cn/blog_migrate/4a9453fc656aedccf816e241d7fdf400.png)
缺点:网络通信代价巨大
采用point-to-point communication algorithm,每个worker通信量 O(#machine * #feature * #bin)
采用collective communication algorithm , 每个worker通信量O(2 * #feature * #bin)
LightGBM的数据并行
• 不同的worker合并不同特征的局部直方图
• 采用直方图做差算法,只需要通信一个节点的直方图
通信量减小到 O(0.5 * #feature* #bin)
Voting parallel,参考论文“A Communication-Efficient Parallel Algorithm for Decision Tree”
其它
• Gradient-based One Side Sampling (GOSS)
在每一次迭代前,利用了GBDT中的样本梯度和误差的关系,对训练样本进行采样: 对误差大(梯度绝对值大)的数据保留;对误差小的数 据采样一个子集,但给这个子集的数据一个权重,让这个子集可以近 似到误差小的数据的全集。这么采样出来的数据,既不损失误差大的 样本,又在减少训练数据的同时不改变数据的分布,从而实现了在几 乎不影响精度的情况下加速了训练。
• Exclusive Feature Bundling (EFB)
在特征维度很大的数据上,特征空间一般是稀疏的。利用这个特征, 我们可以无损地降低GBDT算法中需要遍历的特征数量,更确切地说, 是降低构造特征直方图(训练GBDT的主要时间消耗)需要遍历的特征 数量。