https://www.hrwhisper.me/machine-learning-xgboost/
https://mp.weixin.qq.com/s/_NCKAon-megJbxzV6w3aYg
CART Tree Ensemble:
与
最优化问题的解法:
1. 梯度下降法:
,
为残差项
- 我的理解:视
为
,
为
令为单位向量,
为学习率,当
很小的时候,
约等于0
将并入
中化简为最终表达式:
2. 牛顿法
泰勒公式在处二阶展:
因为最优点一阶导数为0,求关于的偏导:
令偏导为0,可得:
3. 在数值分析中的做法:
上面的做法是在最优化问题中的,求的是一阶导为0,即
而在数值分析中,想要求的是方程式的根,即,我们只需要进行一阶展开,并令其为0
得到迭代的公式:
XGBoost:
这里定义目标函数如下,损失函数,并带了关于分类器
的正则项
:
我们不能用诸如梯度下降的方法,因为是树,而非数值型的向量。所以我们需要用前向分步算法,即贪心法找局部最优解:
基于前向算法的目标函数:
因为
目标函数处在第t轮,等于每个样本的损失+t轮每棵树的正则项。而在第t轮,前面的t-1轮的正则项都相当于常数,可以不做考虑。
把视为x,
视为
,故可泰勒展开为:
其中损失函数一阶导为,二阶导为
,注意是对
求导
- 以平方损失函数为例:
则:( 我估计就在下面忽略了)
由于已知,所以
是一个常数,对其函数优化不会产生影响,目标函数可以写成:
- Xgboost 的基模型不仅支持决策树,还支持线性模型
目标函数的正则项:
模型复杂度由叶子节点数量T决定,此外叶节点的值也不应该太高。
- 完整的目标函数与化简:
定义函数将x映射到某个叶节点j上,则
,令
鉴于
可知是前面
轮得到的结果,其值已知可视为常数,对
这个变量求导等于0可进一步简化:
举个例子:求每个节点每个样本的一阶导数与二阶导数
,然后对所含样本求和得到
,最后得到目标函数。目标函数越小则越优。
-
都是调参的参数,预先已经设计好了
最优切分点划分算法:
- 贪心算法
1)从深度0开始对每个叶节点枚举所有的可用特征
2)对某一类特征的所有特征值进行升序排序,通过线性扫描的方式决定最佳分裂点,记录分裂收益
3)选择分裂收益最大的点作为分裂位置,分裂出左右两个最新的叶节点,并关联对应的样本集
4)重复以上步骤
- 所以怎么算分裂收益?
- 当数据量太大时则无法读入内存进行计算,近似算法主要针对贪婪算法这一缺点给出了近似最优解。
- 近似算法(Exact Greedy Algorithm)
举个例子:
关于分界点的选择,XGBoost有两种策略,全局策略(Global)和局部策略(Local)
- Global: 学习每棵树前, 提出候选切分点
- Local: 每次分裂前, 重新提出候选切分点
直观上来看,Local 策略需要更多的计算步骤,而 Global 策略因为节点没有划分所以需要更多的候选点。
我们可以看到 Global 策略在候选点数多时(eps 小)可以和 Local 策略在候选点少时(eps 大)具有相似的精度。此外我们还发现,在 eps 取值合理的情况下,分位数策略可以获得与贪婪算法相同的精度。局部切分点个数不需要那么多,因为每一次分裂都重新进行了选择。
- 那么,直接等分得到候选点么?
不是,使用分位数:是使用二阶梯度
候选点要求,即让相邻两个候选分裂点相差不超过某个值
,所以最后会得到
个分割点。
- 为什么要用二阶梯度加权?
下面是我们泰勒二阶展开后的目标函数:
(因为加入的是上一轮
的,所以是常数)
长得很像是标签为,权重为
的平方损失,因此用
加权
稀疏值处理:
在真实世界中,我们的特征往往是稀疏的,可能的原因有:
1)数据缺失值
2)大量的0值(比如统计出现的)
3)进行了One-hot 编码(又称为一位有效编码,主要是采用N位状态寄存器来对N个状态进行编码,每个状态都由他独立的寄存器位,并且在任意时候只有一位有效)
- 决策树模型不推荐对离散特征进行one-hot编码:
会产生样本不平衡问题,本来特征是:红的白的绿的,现在变为是否红的、是否白的、是否绿的。。只有少量样本为 1,大量样本为 0。这种特征的危害是,本来节点的划分增益还可以,但是拆分后的特征,占总样本的比例小的特征,所以无论增益多大,乘以该比例之后会很小,占比例大的特征其增益也几乎为 0,影响模型学习;决策树依赖的是数据的统计信息,one-hot 会把数据切分到零散的小空间上,在这些零散的小空间上,统计信息是不准确的,学习效果变差。
- 稀疏感知算法:
XGBoost 在构建树的节点过程中只考虑非缺失值的数据遍历,而为每个节点增加了一个缺省方向,当样本相应的特征值缺失时,可以被归类到缺省方向上,最优的缺省方向可以从数据中学到。至于如何学到缺省值的分支,其实很简单,分别枚举特征缺省的样本归为左右分支后的增益,选择增益最大的枚举项即为最优缺省方向。
先计算不含缺失值的样本得到分类的score,然后:
1)让特征k的所有缺失值(整体)的都到右子树,然后和之前的一样,枚举划分点,计算最大的gain
2)让特征k的所有缺失值的都到左子树,然后和之前的一样,枚举划分点,计算最大的gain
使用了该方法,相当于比传统方法多遍历了一次,但是它只在非缺失值的样本上进行迭代,因此其复杂度与非缺失值的样本成线性关系。在Allstate-10k数据集上,比传统方法快了50倍:
XGBOOST的加速
- 分块并行:
1)Block中的数据以稀疏格式CSC进行存储(增加一些"元信息"来描述矩阵中的非零元素存储的位置(基于列),然后结合非零元素的值来表示矩阵。这样在一些场景下可以减少矩阵存储的空间)
2)Block中的特征进行排序(不对缺失值排序)
3)Block 中特征还需存储指向样本的索引,这样才能根据特征的值来取梯度。
4)一个Block中存储一个或多个特征的值
只需在建树前排序一次,后面节点分裂时可以直接根据索引得到梯度信息。缺点是空间消耗大了一倍。
- 缓存优化
使用Block结构的一个缺点是取梯度的时候,是通过索引来获取的,而这些梯度的获取顺序是按照特征的大小顺序的。这将导致非连续的内存访问,可能使得CPU cache缓存命中率低,从而影响算法效率。
- CPU的Cache理论:
程序局部性原理,包括时间局部性和空间局部性。即最近被CPU访问的数据,短期内CPU 还要访问(时间);被 CPU 访问的数据附近的数据,CPU 短期内还要访问(空间)。因此如果将刚刚访问过的数据缓存在Cache中,那下次访问时,可以直接从Cache中取,其速度可以得到数量级的提高。
因此,对于exact greedy算法中, 使用缓存预取。具体来说,对每个线程分配一个连续的buffer,读取梯度信息并存入Buffer中(这样就实现了非连续到连续的转化),然后再统计梯度信息。该方式在训练样本数大的时候特别有用。
- Block大小设置:
在approximate 算法中,对Block的大小进行了合理的设置。定义Block的大小为Block中最多的样本数。设置合适的大小是很重要的,设置过大则容易导致命中率低,过小则容易导致并行化效率不高。经过实验,发现比较好。
Blocks for Out-of-core Computation
当数据量太大不能全部放入主内存的时候,为了使得out-of-core计算称为可能,将数据划分为多个Block并存放在磁盘上。
- 计算的时候,使用独立的线程预先将Block放入主内存,因此可以在计算的同时读取磁盘
- Block压缩,貌似采用的是近些年性能出色的LZ4 压缩算法,按列进行压缩,读取的时候用另外的线程解压。对于行索引,只保存第一个索引值,然后用16位的整数保存与该block第一个索引的差值。
- Block Sharding, 将数据划分到不同硬盘上,提高磁盘吞吐率
XGBoost为什么快
- 当数据集大的时候使用近似算法
- Block与并行
- CPU cache 命中优化
- Block预取、Block压缩、Block Sharding等
XGBoost与传统GBDT的不同
- 传统GBDT以CART作为基分类器,XGBoost还支持线性分类器,这个时候XGBoost相当于带L1和L2正则化项的Logistic回归(分类问题)或者线性回归(回归问题)。
- 传统的GBDT只用了一阶导数信息(使用牛顿法的除外),而XGBoost对损失函数做了二阶泰勒展开。并且XGBoost支持自定义损失函数,只要损失函数一阶、二阶可导。
- XGBoost的目标函数多了正则项, 相当于预剪枝,使得学习出来的模型更加不容易过拟合。
- XGBoost还有列抽样,进一步防止过拟合。
- 对缺失值的处理。对于特征的值有缺失的样本,XGBoost可以自动学习出它的分裂方向。
- XGBoost工具支持并行。当然这个并行是在特征的粒度上,而非tree粒度,因为本质还是boosting算法。
XGBOOST与RF(随机森林)的区别
- 数据敏感性:RF对异常值不敏感,而GBDT对异常值比较敏感
- 最终结果:RF最终是多棵树进行多数表决(回归问题是取平均),而GBDT是加权融合
比较LR和GBDT,说说什么情景下GBDT不如LR
- LR是线性模型,可解释性强,很容易并行化,但学习能力有限,需要大量的人工特征工程
- GBDT是非线性模型,具有天然的特征组合优势,特征表达能力强,但是树与树之间无法并行训练,而且树模型很容易过拟合;
当在高维稀疏特征的场景下,LR的效果一般会比GBDT好。原因如下:
一个二分类问题,label为0和1,特征有100维,如果有1w个样本,
但其中只有10个正样本1,而这些样本的特征 f1的值为全为1,
而其余9990条样本的f1特征都为0(在高维稀疏的情况下这种情况很常见)。
我们都知道在这种情况下,树模型很容易优化出一个使用f1特征作为重要
分裂节点的树,因为这个结点直接能够将训练数据划分的很好,
但是当测试的时候,却会发现效果很差,因为这个特征f1只是刚好偶然间
跟y拟合到了这个规律,这也是我们常说的过拟合。
那么这种情况下,如果采用LR的话,应该也会出现类似过拟合的情况呀:y = W1f1 + Wifi+….,其中 W1特别大以拟合这10个样本。为什么此时树模型就过拟合的更严重呢?
因为现在的模型普遍都会带着正则项,而 LR 等线性模型的正则项是对权重的惩罚,也就是 W1一旦过大,惩罚就会很大,进一步压缩 W1的值,使他不至于过大。但是,树模型则不一样,树模型的惩罚项通常为叶子节点数和深度等,而我们都知道,对于上面这种 case,树只需要一个节点就可以完美分割9990和10个样本,一个结点,最终产生的惩罚项极其之小,相当于没有正则化项也就没有过拟合的保护。
这也就是为什么在高维稀疏特征的时候,线性模型会比非线性模型好的原因了:带正则化的线性模型比较不容易对稀疏特征过拟合
防止过拟合的措施:
1)目标函数的正则项, 叶子节点数+叶子节点数输出分数的平方和
2)行抽样和列抽样:训练的时候只用一部分样本和一部分特征
可以设置树的最大深度
3)η: 可以叫学习率、步长或者shrinkage
4)Early stopping:使用的模型不一定是最终的ensemble,可以根据测试集的测试情况,选择使用前若干棵树
叶节点的权值是什么?
利用当前叶子上所有样本的一阶梯度和二阶梯度计算出来的
其中是第j个叶节点,
是第j个叶节点的第i个样本的一阶导,
则是二阶导,
是正则化系数