xgboost原理
XGBoost其实是由一群训练出来的CART回归树集成出来的模型。
目标
目标其实就是训练一群回归树,使这树群的预测值尽量接近真实值,并且有尽可能强大的泛化能力。来看看我们的优化函数:
优化函数
i表示的是第i个样本,前一项是表示的是预测误差。后一项表示的是树的复杂度的函数,值越小表示复杂度越低,泛化能力越强。我们来看看后一项的表达式:
树的复杂度函数
其中T表示叶子节点的个数,w表示的是节点的预测值(回归树的节点才有预测值)。
我们要做的就是使预测误差尽量小,叶子节点数尽量少,预测值尽量不极端(什么叫预测值尽量不极端?举个栗子,一个人的真实年龄是4岁,有两个模型,第一个模型的第一颗回归树预测值是3岁,第二颗回归树预测值是1岁,第二个模型的第一颗回归树预测值是2岁,第二颗预测值也是2岁,那我们更倾向于选择第二个模型,因为第一个模型学习的太多,有过拟合的风险)
那么我们如何才能把优化函数和回归树的参数联系在一起呢?回归树的参数我们知道有两个:(1)选哪个feature进行分裂(2)如何求取节点的预测值,上述公式并没有很好地反映出这两个问题的答案,那么是如何解决上述两个问题的呢?
答案就是:贪心策略+最优化(二次最优化)
贪心策略
那么是如何运用贪心策略(眼前利益最大化,每个节点的预测值都选最优)的呢?假设我们有一堆样本,放在第一个节点,这时T=1,w是多少呢?暂时不知道,w是计算出来的,这时所有的样本的w都相等,将w和T代入优化函数中
第一步的损失函数
如果我们这里的l(w-y_i)损失函数使用的是平方损失函数,那么上式就变成了一个关于w的二次函数,最小的点就是极值点也就是该节点的预测值。
到这你可能发现了,这不就是二次函数的最优化问题吗?
那如果损失函数不是平方误差函数怎么办?我们采用的是泰勒展开的方式,任何函数总能用泰勒展开的方法表示,不是二次函数我们总能想办法让它变成二次函数。
那么我们再来看我们的两个问题:
(1)选哪个feature进行分裂?最粗暴的枚举法,用损失函数效果最好的那一个(粗暴枚举和XGBoost的并行化等我们在后面介绍)
(2)如何求取节点的预测值,对!就是我们刚刚说到的二次函数求最值(固定套路:二次函数求导为零的点就是最优值)!
那么步骤就是:枚举第一个feature,计算loss_function的最小值,枚举第二个feature,计算loss_function的最小......直到遍历完所有的feature,选择效果最好的feature,将feature的值分成大于w和小于w的两类,这不就把树给分成两叉了吗?
接下来继续分裂,在上一个分类的基础上,又形成一棵树,再形成一棵树,每次都是在最优的基础上进行分裂,不就是我们的贪心策略么。
但是一般这种循环迭代的方式都需要一个终止条件,总不能让它一直跑下去吧。
停止条件
停止条件大概有以下几种:
(1)当引入的分裂带来的增益(loss_function的降低量)小于一个阈值的时候,可以剪掉当前的分裂,所以并不是每一次分裂loss_function都会增加的。
(2)当树达到最大深度时,停止建树,因为树的深度太深容易出现过拟合,这里需要设置一个超参数max_depth。
(3)当样本权重和(跟AdaBoost一样,每个样本都有一个权重,这里的样本是指的节点的样本,不是全部的样本,所以权重相加不为1)小于某一个阈值时也停止建树,涉及到一个超参数:最小样本权重和,大意就是如果每个叶子节点包含的样本数量太少也停止,同样是过拟合的原因。
XGBoost的亮点
节点权值
XGBoost的权值是通过最优化二次函数(求导)求出来的,算是一种创新吧,和普通的求均值的或者其他什么规则不一样。
避免过拟合(正则化、shrinkage与采样技术)
正则化
一说起过拟合,我们的第一反应就是正则化。XGBoost也是这样做的。
我们在loss_function里看到了正则化项(树的复杂度函数),正则化的目的就是防止过拟合。我们再看看这个函数:
这里出现了γ和λ,这是XGBoost自己定义的,在使用XGBoost时,你可以设定它们的值,显然,γ越大,表示越希望获得结构简单的树,因为要整体最小化的话就要最小化T。λ越大也是越希望获得结构简单的树。
Shrinkage
除了使用正则化,我们还有shrinkage与采样技来避免过拟合的出现。
所谓的shrinkage就是在每次的迭代产生的树中,对每个叶子结点乘以一个缩减权重,主要的目的就是缩减该次迭代产生的树的影响力,留给后边迭代生成的树更多发挥的空间。
举个栗子:比如第一棵树预测值为3.3,label为4.0,第二棵树才学0.7,那后面的树就没啥可学的了,所以给他打个折扣,比如3折,那么第二棵树训练的残差为4.0-3.3*0.3=3.01,这就可以发挥了啦,以此类推,作用的话,就是防止过拟合。
采样技术
采样技术有两种,分别是行采样和列采样。
列采样效果比较好的是按层随机的方法:之前提到,每次分裂节点的时候我们都要遍历所有的特征和分割点,来确定最优分割点。如果加入列采样,我们会在同一层的结点分割前先随机选一部分特征,遍历的时候只用遍历这部分特征就行了。
行采样则是采用bagging的思想,每次只抽取部分样本进行训练,不使用全部的样本,可以增加树的多样性。
损失函数
这个点也是XGBoost比较bug的地方,因为XGBoost能够自定义损失函数,只要能够使用泰勒展开(能求一阶导和二阶导)的函数,都可以拿来做损失函数。你开心就好!
支持并行化