最近开始入手学习python处理数据,在看代码的时候发现了xgboost,并且作为第三方库在pycharm中无法导入,很是一筹莫展,那就从博客上搜索开始入手,首先了解这个库到底是完成了什么事情呢。。。
一、简介
下面开始据此对XGBoost的原理做简单的介绍…
XGBoost[1]是2014年2月诞生的专注于梯度提升算法的机器学习函数库,此函数库因其优良的学习效果以及高效的训练速度而获得广泛的关注。仅在2015年,在Kaggle[2]竞赛中获胜的29个算法中,有17个使用了XGBoost库,而作为对比,近年大热的深度神经网络方法,这一数据则是11个。在KDDCup 2015 [3]竞赛中,排名前十的队伍全部使用了XGBoost库。
XGBoost不仅学习效果很好,而且速度也很快,相比梯度提升算法在另一个常用机器学习库scikit-learn中的实现,XGBoost的性能经常有十倍以上的提升。
在今年的KDD会议上,XGBoost的作者陈天奇将这一库函数所涉及到的理论推导和加速方法整理为论文发表出来[4],本文据此对其原理做简单的介绍。尽管这是一个机器学习方面的函数库,但其中有大量通用的加速方法,也值得我们学习。
1. GBDT算法原理
XGBoost实现的是一种通用的Tree Boosting算法,此算法的一个代表为梯度提升决策树(Gradient Boosting Decision Tree, GBDT),又名MART(Multiple Additive Regression Tree)。
GBDT的原理是,首先使用训练集和样本真值(即标准答案)训练一棵树,然后使用这棵树预测训练集,得到每个样本的预测值,由于预测值与真值存在偏差,所以二者相减可以得到“残差”。接下来训练第二棵树,此时不再使用真值,而是使用残差作为标准答案。两棵树训练完成后,可以再次得到每个样本的残差,然后进一步训练第三棵树,以此类推。树的总棵数可以人为指定,也可以监控某些指标(例如验证集上的误差)来停止训练。
在预测新样本时,每棵树都会有一个输出值,将这些输出值相加,即得到样本最终的预测值。
使用两棵树来预测一个人是否喜欢电脑游戏的示意图如下,小男孩和老人的预测值为两棵树预测值的加和。
- XGBoost所做的改进
2.1. 损失函数从平方损失推广到二阶可导的损失
GBDT的核心在于后面的树拟合的是前面预测值的残差,这样可以一步步逼近真值。然而,之所以拟合残差可以逼近到真值,是因为使用了平方损失作为损失函数,公式如下
如果换成是其他损失函数,使用残差将不再能够保证逼近真值。XGBoost的方法是,将损失函数做泰勒展开到第二阶,使用前两阶作为改进的残差。可以证明,传统GBDT使用的残差是泰勒展开到一阶的结果,因此,GBDT是XGBoost的一个特例。注意:此处省略了严格的推导,详情请参阅陈天奇的论文。
2.2. 加入了正则化项
正则化方法是数学中用来解决不适定问题的一种方法,后来被引入机器学习领域。通俗的讲,正则化是为了限制模型的复杂度的。模型越复杂,就越有可能“记住”训练数据,导致训练误差达到很低,而测试误差却很高,也就是发生了“过拟合”。在机器学习领域,正则化项大多以惩罚函数的形式存在于目标函数中,也就是在训练时,不仅只顾最小化误差,同时模型复杂度也不能太高。
在决策树中,模型复杂度体现在树的深度上。XGBoost使用了一种替代指标,即叶子节点的个数。此外,与许多其他机器学习模型一样,XGBoost也加入了L2正则项,来平滑各叶子节点的预测值。
2.3. 支持列抽样
列抽样是指,训练每棵树时,不是使用所有特征,而是从中抽取一部分来训练这棵树。这种方法原本是用在随机森林中的,经过试验,使用在GBDT中同样有助于效果的提升。
3.为什么XGBoost效果这么好
XGBoost是boosting算法中的一种,其他的还包括AdaBoost等。Boosting方法是目前最好的机器学习方法之一,关于其优良的学习效果,已有理论解释包括偏差-方差分解和Margin理论,但都不完美。下面结合个人理解做一些通俗的讨论。
机器学习就是模型对数据的拟合。对于一组数据,使用过于复杂的模型去拟合,往往会发生过拟合,这时就需要引入正则化项来限制模型复杂度,然而正则化项的选取、正则化系数的设定都是比较随意的,也比较难做到最佳。而如果使用过于简单的模型,由于模型能力有限,很难把握数据中蕴含的规律,导致效果不佳。
Boosting算法比较巧妙,首先使用简单的模型去拟合数据,得到一个比较一般的结果,然后不断向模型中添加简单模型(多数情况下为层数较浅决策树),随着树的增多,整个boosting模型的复杂度逐渐变高,直到接近数据本身的复杂度,此时训练达到最佳水平。
因此,boosting算法要取得良好效果,要求每棵树都足够“弱”,使得每次增加的复杂度都不大,同时树的总数目要足够多。XGBoost中,对每棵树的叶子节点数做了惩罚,从而限制了叶子节点的增长,使得每棵树都是“弱”的,同时还引入了学习速率,进一步降低了每棵树的影响。这样做的代价是,数的总数目会多一些,但从其取得的效果上看,这样做是值得的。
4. 为什么XGBoost运行这么快
4.1. 连续型特征的处理
决策树在训练时需要进行分叉,对于连续型特征,枚举所有可能分叉点将会十分耗时。一种近似方法是只枚举若干个分位点,例如将所有样本根据此特征进行排序,然后均分10份,两份之间断开的数值即为分位点,枚举所有9个分位点后,使用降低损失最多的那个作为分叉点。
4.2. 利用数据稀疏性
数据稀疏有三个原因:缺失数据;某些特征本身就含有大量的0;对离散特征做了one-hot处理。无论是哪种,都会导致样本中出现大量的0。通常,利用稀疏性可以提高运算效率。XGBoost的方法是,每次分叉时,都指定一条默认分支,如果样本的这个特征为0,就走这个默认分支。这样,训练时不必考虑这些0元素,大大提高了运算速度。陈天奇的实验表明,此方法在稀疏数据上可以提高50倍。
4.3. 数据的预排序和分块存储
分叉的时候为了判断分叉点,需要对每个特征进行排序。这个步骤是要重复多次的,因此XGBoost在训练之前预先对数据进行每一列做了排序,并按列存储到内存中。在分布式环境下,可以进行分块存储。
4.4. 减少读写相关,提高Cache命中率
由于预排序的数据是按列存储的,但训练时并不总是按列读取和写回,在需要按行读写的时候,将需要的行预先收集到一块连续内存上,再进行计算。这样由于是连续内存地址,可以提高Cache命中率,从而提高了运算速度。
4.5. 数据量大时,提高硬盘吞吐率
当数据量很大,不能装入内存时,需要将一部分数据放在硬盘里。然而硬盘读写速度慢,会严重影响计算效率。XGBoost使用了两种方法提高吞吐率,一个是对存储的内容进行压缩,读取时再进行解压,这相当于在读取代价和解压代价之间做了一个权衡。另一个方法是做数据分片,即在多块硬盘上存储数据,然后同时读写,从而提高读写速度。
5. 结语
XGBoost综合了前人关于梯度提升算法的众多工作,并在工程实现上做了大量优化,是目前最成功的机器学习算法之一。目前,XGBoost已被工业界广泛使用,而GBDT及其改进算法也几乎成为了数据挖掘面试的必考题目。本文只是对其进行了走马观花式的梳理,对于它更深入的数学原理和优化细节,还请参看陈天奇在KDD’16上的原始论文[4]。
参考文献
[1] https://github.com/dmlc/XGBoost
[3] http://kddcup2015.com/submission-rank.html
[4] Chen T, Guestrin C. XGBoost: A Scalable Tree Boosting System[C]. Knowledge discovery and data mining, 2016.