gbdt和xgboost是非常非常常用的数据挖掘算法,常用到什么程度呢,你要是去参加面试,肯定有人会问你用过xgboost吗?能说出来gbdt、xgboost、lightgbm的区别吗?当然很多从事数据挖掘工作的小伙伴肯定不能认怂,要说用过,但是转念一想你们是不是只调用过gbdt和xgboost的包,对于他们怎么计算出最终结果却一无所知,当然嘛工作中效果好就行了吗,管那么多原理,但真正理解原理才能知道怎么去优化这个模型。
今天就用通俗易懂的语言带大家去彻底了解这两个模型,因为这两个模型有一个递进的关系,xgboost实际是对gbdt的优化,先从gbdt说起,然后再说xgboost。
1 gbdt模型结果的计算
gbdt的基分类器是cart,因此可以用于回归和分类问题,如果你愿意还可以拟合一个模型树出来。
1.1 回归问题
如果你工作中更多面对的是回归任务,那么你应该庆幸,因为回归问题的损失函数表示简单,要么是绝对误差,要么是均方损失误差,要么是二者的结合,采用梯度下降的时候求导都特别简单,尤其是最常用的均方损失误差,采用梯度拟合实际就是对残差进行拟合,理解起来毫不费力。
以预测年龄为例先说一下gbdt做回归的流程:
(1)明确回归的指标和目标,指标是你现有的特征集合,目标是要拟合的数值,这个数值是连续的,此处年龄这个变量就是连续的,假设有三个样本,年龄分别为25、26、27,你现在的目标就是尽可能准确的预测出来这三个人的岁数。
(2)确定损失函数,这个地方我认为是非常重要的,有人对损失函数的理解可能不够深,损失函数最大的作用是确定梯度下降时的梯度方向,回归问题一般采用MSE(均方误差损失),它求完导梯度实际就是残差。
(3)拟合一颗cart树,怎么拟合呢,我们不是有三个数据:
[特征1,特征2,…,特征n,25]、
[特征1,特征2,…,特征n,26]、
[特征1,特征2,…,特征n,27]。
就拿这三个数据根据特征,遍历数据特征取值去划分cart树,由于是回归问题,划分标准为所有样本的均方误差损失最小(这部分不懂去看看cart树吧,原理很简单),每一轮选择一个特征进行划分,知道所有样本都落入叶子节点,我们假设25在一个叶子节点,26、27在一个叶子节点上,取均值就是这个叶子节点的得分值。
(4)计算当前预测值,gbdt是残差拟合,这一轮得到的预测值是之前所有树的加和,有了这个加和就可以计算损失函数了。实际上这里还有一个学习率,这个学习率就是控制迭代速度的,太大了容易错过最优点,太小了就导致收敛速度慢。
(5)根据损失函数的形式计算梯度方向,采用MSE梯度方向就是偏差,就是说下次用于预测的数据变成了,
[特征1,特征2,…,特征n,0]、
[特征1,特征2,…,特征n,-0.5]、
[特征1,特征2,…,特征n,0.5]。
(6)重复3-5,直到达到预先设计的最大的基分类器数目或者cart树已经满足收敛了就跳出来。
可以看出来,在回归问题中叶子节点的得分值就是所有位于该叶子节点的样本均值,最后的预测结果怎么得到呢,就是某个样本X在每颗cart树上对应叶子节点的加和值,我好像一个公式都没放,这里放一个吧:
就说了不爱放公式,还得多余解释,这个m=1:M就是M个基分类器求和,讲j=1:J是第m颗上的叶子节点编号,这个I就是单位矩阵,啥意思这个样本在叶子节点上就取1,不在就取0呗,cmj更简单了,就是上面说的叶子节点得分值。
简单的理解,gbdt回归问题就是一个累积计算的过程,没一点技术含量,会加法就能算,估计小学数学水平就够了。
1.2 分类问题
分类问题比较有意思,也是我们工作中最长遇到的场景,比如识别异常用户等,这个分类又包括了二分类和多分类问题,二分类比较简单,关于多分类有很多处理方法,什么一对一、一对多策略等,说白了就是要么转换成多个二分类问题,要么直接用多分类的方法去解决。二分类简单,先说二分类吧,再说多分类。
(1)二分类问题
为啥说二分类问题简单,因为二分类问题,类似于逻辑回归,直接用sigmoid函数就可以得到最终的类别归属概率。分类问题的损失函数形式很多的,根据目标变量表示的不同(0,1表示或-1,1表示)还有不同的形式,最常用的形式包括对数和指数以及二者之间的结合等,还有人把回归的MSE拿到分类问题来一样可以用。为啥强调损失函数呢,因为在gbdt中残差拟合,梯度就是下一代要拟合的值,所以重要。
接下来,放个图,这个图是大招,讲的就是gbdt分类的流程,但是是一个多分类问题的图,本来放到下面讲的,但是这里为了说明方便先拿出来。二分类是多分类的一个特例,所以这个图也适用。
基本你看一篇gbdt算法的文章就有这个伪代码,但是没人愿意给你解释,我一行行说吧:
row1:K分类个数,K=2就是二分类问题。
row2:M基分类器个数,就是你要训练几颗树出来
row3:计算属于每一类的概率,softmax函数
row4:对于多分类问题,每次迭代都训练和类别相同的树,但是对于二分类问题就不用这么麻烦了,多分类问题会形成M*K颗树。所以这里再嵌套一层循环。
row5:计算残差了
row6:拿特征值和当前残差值用cart拟合树了
row7:根据损失函数计算当前叶子节点的得分值,观察一下这个应该是交叉熵损失函数的导数计算而来的,应该是用了二阶泰勒展开。具体是啥不重要,重要的是你要会套公式。虽然我不关注咋算的,但是你们可以看看这篇文章了解一下:公式推导,数学不好的慎点。
row8:更新预测值,也是累加,没啥好说的。
好勒,这里可以说下二分类问题的流程了:
1)明确分类的指标和目标,假设有三个样本,要预测是不是喜欢看电影,用0-1表示,你现在的目标就是尽可能准确的预测出来这三个人到底爱不爱看电影。
2)确定损失函数,二分类的损失函数一般是交叉熵损失,写起来就是下面这样:
这个损失函数咋来的去看逻辑回归说的比较清楚,我就不啰嗦了,有人嫌他麻烦,给简化了:
3)拟合一颗cart树,这里有一个最重要的问题就是初始的F值怎么计算,大神用0,我们直接用类别占比。接下来和回归树一样,我们不是有三个数据:
[特征1,特征2,…,特征n,1]、
[特征1,特征2,…,特征n,0]、
[特征1,特征2,…,特征n,0]。
就拿这三个数据根据特征,遍历数据特征取值去划分cart树,这里树的分裂准则,分类问题啊一般是用GINI系数的,但是考虑到MSE比较好理解,也可以用MSE,推荐用MSE,因为简单。每一轮选择一个特征进行划分,知道所有样本都落入叶子节点。我们假设样本1、2在一个叶子节点上,样本3在一个叶子节点上,你本来可以取均值获得这个叶子节点的得分值的,但是遗憾的是你的损失函数是交叉熵损失函数,你严格按照公式算吧,记得要先算梯度。公式是这个东西:
4)计算当前预测值,gbdt是残差拟合,这一轮得到的预测值是之前所有树的加和,用这个公式算。
5)根据当前预测值和损失函数的形式计算梯度方向,得到梯度值,更新用于训练样本的目标值,具体就不算了,反正你知道这个值更新了就好了。
6)重复3-5,直到达到预先设计的最大的基分类器数目或者cart树已经满足收敛了就跳出来。
然而,跳出来是跳出来了,你现在只有一个得分值,还没转化为分类概率呢。这个别慌,二分类嘛,用sigmoid函数带入最终累积的函数得分值就是该样本为类别1的概率,你再设置一个阈值,分类一下就做好了。
总结一下,根据损失函数的不同,gbdt分类的样本得分值是不同的,梯度方向也是不同的,这两部分你了解了是咋回事,二分类问题就明明白白了。
此处是不是想有个例子,但是我懒啊,我只讲原理,不过这位大哥不懒你们参考我说的去看他的案例:gbdt二分类案例。
(2)多分类问题
上面我说了多分类的原理了,二分类实际是一种特殊的多分类问题,多分类和二分类不同之处主要有2点:
1)训练树的颗数不同,二分类每一代你训练一颗树就行了,但是多分类不行,每一代针对每一个类别都要训练一棵树,最终得到了M*K颗树,这里有一个疑问是怎么根据类别训练树。这个就是目标函数的不同了,采用0-1编码表示样本属于哪一个类别,在训练属于这个类别的树时,目标函数值就是1,不属于这颗树的时候,目标函数值就是0。怎么训练树和二分类一样,用cart就行。
2)最后确定分类的公式不一样了,回忆二分类问题,每一个样本在每一棵树上都有一个得分值,计算公式上边也说了,也不废话了,最后完成多轮迭代后,我们有了一个总计的最终得分值,现在划分类别了,用softmax函数,计算出概率值后,选最大的那个作为预测分类,当然了每一代的预测分类都是根据累计目标函数计算的。
很遗憾,多分类暂时没有案例给你们看。
2 xgboost模型结果的计算
这部分今天太累了不更新了。
----2019.8.7更新----
拖了好久了,今天来更新。
2.1 回归问题
回归问题永远是最好理解的问题,和GBDT一样,这个也是残差预测,每一代叶子节点的得分值是当前的残差,如果要算当前的预测值得话就拿前一代累积得分值加上当前的得分值,就是最终的预测结果,当然如果你设置了得分率,需要乘上得分率。我们来看公式:
如果有学习率,把学习率乘到那两个求和符号前边。
至于这个c怎么计算的,xgboost和gbdt不一样的,xgboost怎么算的,你过去看看我这篇文章xgboost原理,其实就是参数的计算方法,一系列推导展开的。回归问题都这样,就不多说了。
2.2 分类问题
在说分类问题之前,给大家一个提示。
xgboost这个包有原生和sklearn两个接口,具体怎么区分呢:
原生接口:
举个例子
import xgboost as xgb
params = {
'eta':0.1,
'max_depth':2,
'min_child_weight':3,
'gamma':0,
'subsample':.8,
'colsample_bytree':.7,
'reg_alpha':1,
'objective':'reg:linear'
}
dtrain = xgb.DMatrix(xtrain,ytrain)
dtest = xgb.DMatrix(xtest,ytest)
watchlist1 = [(dtrain,'train'),(dtest,'test')]
model1 = xgb.train(params=params,dtrain=dtrain,num_boost_round=100,early_stopping_rounds=10,evals=watchlist1)
这是原生的,数据需要整理成它要求的样子,直接调用包的train函数进行训练。
sklearn接口:
from xgboost.sklearn import XGBRegressor
model2 = XGBRegressor(
learn_rate = 0.1,
max_depth = 2,
min_child_weight = 3,
gamma = 0,
subsample = 0.8,
colsample_bytree = 0.7,
reg_alpha = 1,
objective = 'reg:linear',
n_estimators = 100
)
watchlist2 = [(xtrain,ytrain),(xtest,ytest)]
model2.fit(xtrain,ytrain,eval_set=watchlist2,early_stopping_rounds=10)
sklearn接口就简单多了,跟你平时用sklearn的其他算法是一样的。
为什么说这个,因为二者在结果输出是有区别的。
不管是多分类还是二分类任务,我直接说了。
- 原生接口,返回的是概率值,这个概率值是每一个样本在每一棵树上得分加种后,采用softmax归一化的值。
- sklearn,返回的事类别,它直接以0.5为阈值进行划分,当然为了给你创造空间,还提供了predict_proba 函数返回预测概率值。