从GBDT到XGBoost
XGBoost相对GBDT的优化
- 算法:在算法的弱学习器模型选择上,GBDT只支持CART回归树,XGBoost还可以使用很多其他的弱学习器。在算法的优化方式上,GBDT的损失函数只对误差部分做负梯度(一阶泰勒)展开,而XGBoost损失函数对误差部分做二阶泰勒展开,更加准确。
- 算法运行效率:对每个弱学习器(如决策树)建立过程中做并行选择,找到合适的子树分裂特征和特征值。在并行选择之前,先对所有的特征的值进行排序分组,方便并行选择。对分组的特征,选择合适的分组大小,使用CPU缓存进行读取加速。将各个分组保存到多个硬盘以提高IO速度。
- 算法健壮性:对于缺失值的特征,通过枚举所有缺失值在当前节点是进入左子树还是右子树来决定缺失值的处理方式。算法本身加入了L1和L2正则化项,可以防止过拟合,泛化能力更强。
XGBoost损失函数
GBDT的回归算法
- 得到负梯度(残差),或者是泰勒展开式的一阶导数。
- 第一个优化求解:基于残差拟合一颗CART回归树,得到J个叶子节点区域。
- 第二个优化求解:在第二步优化求解的结果上,对每个节点区域做一次线性搜索,得到每个叶子节点区域的最优取值。
- 最终得到当前轮的强学习器。
求解当前决策树最优的所有J个叶子节点区域和每个叶子节点区域的最优解????????????。
GBDT分两步走,先求出最优的所有J个叶子节点区域,再求出每个叶子节点区域的最优解。
XGBoost则一次求解出决策树最优的所有J个叶子节点区域和每个叶子节点区域的最优解????????????。
- 加入正则化项的XGBoost损失函数Ω(ht)=γJ+2λj=1∑Jwtj2????是叶子节点的个数,而wtj是第j个叶子节点的最优值(ctj)Lt=i=1∑mL(yi,ft−1(xi)+ht(xi))+γJ+2λj=1∑Jwtj2≈i=1∑m(L(yi,ft−1(xi))+∂ft−1(xi)∂L(yi,ft−1(xi))ht(xi)+21∂ft−12(xi)∂2L(yi,ft−1(xi))ht2(xi))+γJ+2λj=1∑Jwtj2把第i个样本在第t个弱学习器的一阶和二阶导数分别记为gti=∂ft−1(xi)∂L(yi,ft−1(xi),hti=∂ft−12(xi)∂2L(yi,ft−1(xi)Lt≈i=1∑m(L(yi,ft−1(xi))+gtiht(xi)+21htiht2(xi))+γJ+2λj=1∑Jwtj2
- 损失函数里面L(yi,ft−1(xi))是常数,对最小化无影响可以去掉,同时由于每个决策树的第j个叶子节点的取值最终会是同一个值wtj,因此损失函数继续化简Lt≈i=1∑mgtiht(xi)+21htiht2(xi))+γJ+2λj=1∑Jwtj2=j=1∑J(xi∈Rtj∑gtiwtj+21xi∈Rtj∑htiwtj2)+γJ+2λj=1∑Jwtj2=j=1∑J[(xi∈Rtj∑gti)wtj+21(xi∈Rtj∑hti+λ)wtj2]+γJ把每个叶子节点区域样本的一阶和二阶导数的和单独表示Gtj=xi∈Rtj∑gti,Htj=xi∈Rtj∑htiLt=j=1∑J[Gtjwtj+21(Htj+λ)wtj2]+γJ
XGBoost损失函数的优化求解
- 一次求解出决策树最优的所有J个叶子节点区域和每个叶子节点区域的最优解wtj
- 如果已经求出了第t个决策树的J个最优的叶子节点区域,如何求出每个叶子节点区域的最优解wtj
- 对当前决策树做子树分裂决策时,应该如何选择哪个特征和特征值进行分裂,使最终的损失函数????????最小
-
对于第一个问题,直接基于损失函数对wtj求导并令导数为0。这样得到叶子节点区域的最优解????????????wtj=−Htj+λGtj
-
第二个问题:在GBDT里面是直接拟合的CART回归树,所以树节点分裂使用的是均方误差。XGBoost这里使用贪心法,即每次分裂都期望最小化损失函数的误差。
????????????取最优解时Lt=−21j=1∑JHtj+λGtj2+γJ
假设当前节点左右子树的一阶二阶导数和为GL,HL,GR,HL, 则我们期望最大化下式:−21HL+HR+λ(GL+GR)2+γJ−(−21HL+λGL2−21HR+λGR2+γ(J+1))使分裂后的损失函数误差尽可能小,即max21HL+λGL2+21HR+λGR2−21HL+HR+λ(GL+GR)2−γ举个简单的年龄特征的例子,假设选择年龄这个特征的值a作为决策树的分裂标准,则可以得到左子树2个人,右子树三个人,这样可以分别计算出左右子树的一阶和二阶导数和,进而求出最终的上式的值。
使用不同的划分标准,找出可以使上式最大的组合,以它对应的特征值来分裂子树。
XGBoost算法主流程
输入是训练集样本I={(x1,y1),(x2,y2),...(xm,ym)},最大迭代次数T,损失函数L, 正则化系数????, ????。
输出是强学习器f(x)
对迭代轮数 t=1,2,...T :
- 计算样本xi在当前轮损失函数L基于ft−1(xi)的一阶导数gti,二阶导数hti,计算所有样本的一阶导数和Gtj=xi∈Rtj∑gti以及二阶导数和Htj=xi∈Rtj∑hti
- 基于当前节点尝试分裂决策树,默认分数score=0,G和H为当前分裂节点的一阶二阶导数之和。
对特征序号 k=1,2...K:
- GL=0,HL=0
- 将样本按特征k从小到大排列,依次取出第i个样本,依次计算当前样本放入左子树后,左右子树一阶和二阶导数和:GL=GL+gti,GR=G−GLHL=HL+hti,HR=H−HL
- 更新最大的分数score=max(score,21HL+λGL2+21HR+λGR2−21HL+HR+λ(GL+GR)2−γ)
- 基于最大score对应的划分特征和特征值分裂子树。
- 如果最大score为0,则当前决策树建立完毕,计算所有叶子区域的wtj,得到弱学习器 ht(x),更新强学习器 ft(x),进入下一轮弱学习器迭代。如果最大score不是0,则转到第2步继续尝试分裂决策树。
XGBoost算法运行效率的优化
- Boosting算法的弱学习器不可以并行迭代,但是单个弱学习器里面最耗时的是决策树的分裂过程,XGBoost针对这个分裂做了比较大的并行优化。对于不同的特征的特征划分点,XGBoost分别在不同的线程中并行选择分裂的最大增益。
- 同时,对训练的每个特征排序并且以块的的结构存储在内存中,方便后面迭代重复使用,减少计算量。默认所有的样本都在右子树,然后从小到大迭代,依次放入左子树,并寻找最优的分裂点。这样做可以减少很多不必要的比较。
- 此外,通过设置合理的分块的大小,充分利用了CPU缓存进行读取加速(cache-aware access)。使得数据读取的速度更快。另外,通过将分块进行压缩(block compressoin)并存储到硬盘上,并且通过将分块分区到多个硬盘上实现了更大的IO。
XGBoost算法健壮性的优化
-
除了正则化项提高算法的泛化能力外,XGBoost还对特征的缺失值做了处理。
-
XGBoost没有假设缺失值一定进入左子树还是右子树,则是尝试通过枚举所有缺失值在当前节点是进入左子树,还是进入右子树更优来决定一个处理缺失值默认的方向,这样处理起来更加的灵活和合理。
算法流程中步骤2会执行2次,第一次假设特征k所有有缺失值的样本都走左子树,第二次假设特征k所有缺失值的样本都走右子树。然后每次都是针对没有缺失值的特征k的样本走上述流程,而不是所有的的样本。
如果是所有的缺失值走右子树,使用原步骤即可。如果是所有的样本走左子树,则步骤2要变成:GR=0,HR=0GR=GR+gti,GL=G−GRHR=HR+hti,HL=H−HR
XGBoost小结
陈天奇(师兄·。·)设计的XGBoost将GBDT的优化走向了一个极致,GBDT的优点及缺点都成为了XGBoost的优点。
详见XGboost论文及PPT。