XGBoost

https://www.hrwhisper.me/machine-learning-xgboost/


XGBoost_第1张图片
image.png

CART Tree Ensemble:

XGBoost_第2张图片
image.png

与最优化问题的解法:

1. 梯度下降法:

,为残差项

  • 我的理解:视为,为

令为单位向量,为学习率,当很小的时候,约等于0

将并入中化简为最终表达式:


2. 牛顿法

泰勒公式在处二阶展:

因为最优点一阶导数为0,求关于的偏导:

令偏导为0,可得:


3. 在数值分析中的做法:

上面的做法是在最优化问题中的,求的是一阶导为0,即

而在数值分析中,想要求的是方程式的根,即,我们只需要进行一阶展开,并令其为0

得到迭代的公式:


XGBoost:

这里定义目标函数如下,损失函数,并带了关于分类器的正则项:

我们不能用诸如梯度下降的方法,因为是树,而非数值型的向量。所以我们需要用前向分步算法,即贪心法找局部最优解:

基于前向算法的目标函数:

因为

目标函数处在第t轮,等于每个样本的损失+t轮每棵树的正则项。而在第t轮,前面的t-1轮的正则项都相当于常数,可以不做考虑。

把视为x,视为,故可泰勒展开为:

其中损失函数一阶导为,二阶导为,注意是对求导

  • 以平方损失函数为例:

则:( 我估计就在下面忽略了)

由于已知,所以是一个常数,对其函数优化不会产生影响,目标函数可以写成:

  • Xgboost 的基模型不仅支持决策树,还支持线性模型

目标函数的正则项:

模型复杂度由叶子节点数量T决定,此外叶节点的值也不应该太高。

  • 完整的目标函数与化简:

Obj^{\ (t)} = \underbrace{\sum_{i=1}^N \left(g_if_t({\bf x_i}) + \frac{1}{2} h_if_t^2({\bf x_i})\right)}_{对样本累加} +\gamma T +\frac{1}{2}\lambda \underbrace{\sum_{j=1}^{T} w_j^2}_{对叶结点累加}

定义函数将x映射到某个叶节点j上,则,令







鉴于


可知是前面轮得到的结果,其值已知可视为常数,对这个变量求导等于0可进一步简化:

XGBoost_第3张图片
image.png

举个例子:求每个节点每个样本的一阶导数与二阶导数,然后对所含样本求和得到,最后得到目标函数。目标函数越小则越优。

  • 都是调参的参数,预先已经设计好了

最优切分点划分算法:

  1. 贪心算法
    1)从深度0开始对每个叶节点枚举所有的可用特征
    2)对某一类特征的所有特征值进行升序排序,通过线性扫描的方式决定最佳分裂点,记录分裂收益
    3)选择分裂收益最大的点作为分裂位置,分裂出左右两个最新的叶节点,并关联对应的样本集
    4)重复以上步骤
  • 所以怎么算分裂收益?

obj_{final}= Obj_{before}-Obj_{after} = \frac{1}{2}[\underbrace{\frac{G_L^2}{H_L+\lambda}}_{左子树分数} + \underbrace{\frac{G_R^2}{H_R+\lambda}}_{右子树分数} – \underbrace{\frac{(G_L+G_R)^2}{H_L+H_R+\lambda}}_{分裂前分数}] – \underbrace{\gamma}_{新叶节点复杂度}

  • 当数据量太大时则无法读入内存进行计算,近似算法主要针对贪婪算法这一缺点给出了近似最优解。
XGBoost_第4张图片
image.png
  1. 近似算法(Exact Greedy Algorithm)

举个例子:

XGBoost_第5张图片
image.png

关于分界点的选择,XGBoost有两种策略,全局策略(Global)和局部策略(Local)

  • Global: 学习每棵树前, 提出候选切分点
  • Local: 每次分裂前, 重新提出候选切分点

直观上来看,Local 策略需要更多的计算步骤,而 Global 策略因为节点没有划分所以需要更多的候选点。

XGBoost_第6张图片
准确度.png

我们可以看到 Global 策略在候选点数多时(eps 小)可以和 Local 策略在候选点少时(eps 大)具有相似的精度。此外我们还发现,在 eps 取值合理的情况下,分位数策略可以获得与贪婪算法相同的精度。局部切分点个数不需要那么多,因为每一次分裂都重新进行了选择。

  • 那么,直接等分得到候选点么?

不是,使用分位数:是使用二阶梯度

候选点要求,即让相邻两个候选分裂点相差不超过某个值,所以最后会得到个分割点。

XGBoost_第7张图片
image.png
  • 为什么要用二阶梯度加权?

下面是我们泰勒二阶展开后的目标函数:




(因为加入的是上一轮的,所以是常数)

长得很像是标签为,权重为的平方损失,因此用加权


稀疏值处理:

在真实世界中,我们的特征往往是稀疏的,可能的原因有:

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_第8张图片
image.png

XGBOOST的加速

  1. 分块并行:

1)Block中的数据以稀疏格式CSC进行存储(增加一些"元信息"来描述矩阵中的非零元素存储的位置(基于列),然后结合非零元素的值来表示矩阵。这样在一些场景下可以减少矩阵存储的空间)
2)Block中的特征进行排序(不对缺失值排序)
3)Block 中特征还需存储指向样本的索引,这样才能根据特征的值来取梯度。
4)一个Block中存储一个或多个特征的值

XGBoost_第9张图片
image.png

只需在建树前排序一次,后面节点分裂时可以直接根据索引得到梯度信息。缺点是空间消耗大了一倍。

  1. 缓存优化

使用Block结构的一个缺点是取梯度的时候,是通过索引来获取的,而这些梯度的获取顺序是按照特征的大小顺序的。这将导致非连续的内存访问,可能使得CPU cache缓存命中率低,从而影响算法效率。

  • CPU的Cache理论:

程序局部性原理,包括时间局部性和空间局部性。即最近被CPU访问的数据,短期内CPU 还要访问(时间);被 CPU 访问的数据附近的数据,CPU 短期内还要访问(空间)。因此如果将刚刚访问过的数据缓存在Cache中,那下次访问时,可以直接从Cache中取,其速度可以得到数量级的提高。

因此,对于exact greedy算法中, 使用缓存预取。具体来说,对每个线程分配一个连续的buffer,读取梯度信息并存入Buffer中(这样就实现了非连续到连续的转化),然后再统计梯度信息。该方式在训练样本数大的时候特别有用。

XGBoost_第10张图片
image.png
  • Block大小设置:

在approximate 算法中,对Block的大小进行了合理的设置。定义Block的大小为Block中最多的样本数。设置合适的大小是很重要的,设置过大则容易导致命中率低,过小则容易导致并行化效率不高。经过实验,发现比较好。

XGBoost_第11张图片
image.png

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的不同

  1. 传统GBDT以CART作为基分类器,XGBoost还支持线性分类器,这个时候XGBoost相当于带L1和L2正则化项的Logistic回归(分类问题)或者线性回归(回归问题)。
  2. 传统的GBDT只用了一阶导数信息(使用牛顿法的除外),而XGBoost对损失函数做了二阶泰勒展开。并且XGBoost支持自定义损失函数,只要损失函数一阶、二阶可导。
  3. XGBoost的目标函数多了正则项, 相当于预剪枝,使得学习出来的模型更加不容易过拟合。
  4. XGBoost还有列抽样,进一步防止过拟合。
  5. 对缺失值的处理。对于特征的值有缺失的样本,XGBoost可以自动学习出它的分裂方向。
  6. XGBoost工具支持并行。当然这个并行是在特征的粒度上,而非tree粒度,因为本质还是boosting算法。

防止过拟合的措施:

1)目标函数的正则项, 叶子节点数+叶子节点数输出分数的平方和
2)行抽样和列抽样:训练的时候只用一部分样本和一部分特征
可以设置树的最大深度
3)η: 可以叫学习率、步长或者shrinkage
4)Early stopping:使用的模型不一定是最终的ensemble,可以根据测试集的测试情况,选择使用前若干棵树​

你可能感兴趣的:(XGBoost)