XGBoost学习-分裂点的选择
分裂依据
由上一篇博客我们推倒出Xgboost 目标函数(损失函数)的表达形式为
其实际意义表示按照特定分裂点(即按照某种树的结构)分裂后产生的损失值
其中
- 表示被分到编号为j的个叶子节点的样本
这样,我们可以得到样本群依据某个特征值分裂后的损失函数的减小值,将其作为分裂时的依据。
其中:
- 表示分裂后形成的两拨样本
有了节点分裂的依据,那么我们在基学习器中就可以生成树的结构,也就如何选择分裂点。
论文中共介绍了三种分裂点选择方法,
- 第一种“Exact Greedy Algorithm for Split Finding”,是一种利用穷举法选择最佳的分裂节点
- 第二种“Approximate Algorithm for Split Finding”,通过加权分位数方法近似选择最佳的分裂节点
- 第三种“Sparsity-aware Split Finding”,针对稀疏特征的分裂点选择法
(一)Exact Greedy Algorithm for Split Finding 精确贪心算法
算法如上图所示,核心思想如下
- 两个for循环,第一个for遍历所有特征,第二个for找出最佳的特征值作为分裂点
- 选分裂点的依据score为分裂前后损失函数的减少量
- 第二个for循环中,先对数据按照特征值进行排序,这样做的目的为了后面一次遍历就能求出所有分裂点的score值。只需要在当前的基础上进行加减,不需要再扫描所有数据
- 贪心算法体现在,当前分裂点的选择只考虑能使得当前损失函数减少量最大
(二)Approximate Algorithm for Split Finding 近似分割算法
精确贪心算法虽然很强大,但是当数据无法完全加载到内存中或者在分布式的条件下,这种基于穷举的分裂点寻找方法效率就会非常低下。于是作者提出了一种近似分割的算法,这种算法首先通过加权分位数的算法选出了一些可能的分裂点,然后再遍历这些较少的分裂点来找到最佳分裂点。
加权分位数算法
近似分割算法的核心是加权分位数算法,首先介绍分位数点。比如有下面10个样本数据,已经排好序了
分位数点
value: 1 1 2 3 4 5 5 6 7 8
rank : 1 2 3 4 5 6 7 8 9 10
那么0.3分数点的值是多少呢,对应的rank为10*0.3 =3,对应的值为2。这就是分位数点的基本思想,我们只要选出一些合适的分位数点然后遍历然后取遍历他们,就能达到和穷举法相同的精确程度。
加权分位数
因为我们要均分的是loss,而不是样本的数量,而每个样本对loss的贡献可能是不一样的,按样本均分会导致loss分布不均匀,取到的分位点会有偏差。所以要在每个样本前面加个权重。而目标函数又可以化简为如下的形式,所谓我们可以将二阶偏导作为权重。
样本的权重定义好之后,我们就能定义rank函数
表示第k个特征中特征值小于z的样本所占的加权比例。
举个例子来理解加权分位点:
99对应的分位点就是(0.1 * 6+0.4+0.2)/(0.1 * 6+0.4+0.2+0.6) = 2/3
这样我们就能选出一些候选分割点,并让他们满足下面的条件:
这样,我们就选出了大约候选分割点,对这些分割点进行枚举遍历,选出最佳的分割点即可。
需要注意是:引入的分割不一定会使得情况变好,因为在引入分割的同时也引入新叶子的惩罚项。所以通常需要设定一个阈值,如果引入的分割带来的增益小于一个阀值的时候,我们可以剪掉这个分割。此外在XGBoost的具体实践中,通常会设置树的深度来控制树的复杂度,避免单个树过于复杂带来的过拟合问题。
关于上述两个算法的小结
QA:为什么近似分割算法比精确贪心算法要快?
首先我们得捋一下这两个寻找最佳分裂点的时候都有哪些公共的时间开销
- 预排序:每个特征都是按照排序好的顺序存储的,这一部分在存储的时候就已经完成了
- 计算所有样本的一阶导G和二阶导L,这个过程只需要进行一次
- 对样本的G和L累加求和,这个过程也只需要进行一次,为了后续做差加速
- 精确贪心算法中是将所有样本G、L累加
- 近似分割算法中是按桶累加
- 算法中的两个for循环,第一个循环是遍历所有特征,这一步两个算法相同
不同的开销在于,两个算法中第二个for循环中:
- 精确贪心算法遍历的所有样本 O(#feature x # samples)
- 近似贪心算法遍历了所有桶 : O(#feature x # bins )
因为桶的数目远小于样本数,所以得以加速
(三)Sparsity-aware Split Finding 稀疏特征处理
Xgboost 在处理带缺失值的特征时,先对非缺失的样本进行排序,对该特征缺失的样本先不处理,然后在遍历每个分裂点时,将这些缺失样本分别划入左子树和右子树来计算损失然后求最优。
如果训练样本中没有缺失值,而预测过程中出现了缺失值,那么样本会被默认分到右子树。