xgboost 一般多少棵树_XGBoost的树到底是怎么长出来的?分类和回归的区别在哪里...

之前肝了三篇XGBoost

涉及到的起源挂枝儿:从Boosting到BDT再到GBDT​zhuanlan.zhihu.com

到XGBoost的总体推到过程挂枝儿:再从GBDT到XGBoost!​zhuanlan.zhihu.com

再到代码层面的参数初步理解挂枝儿:XGBoost从原理到调参​zhuanlan.zhihu.com

现在基本能够大概讲清楚XGBoost到底是怎么一回事了,但是遇到具体的问题的时候对于XGBoost 到底怎么去兼顾分类和回归问题,其中有哪些区别感觉还是有些疑惑,我总结了一下,就是目前为止的对于算法的理解其实还缺了一个很大的部分,那就是:

形象的理解树 到底是怎么长的?

所以今天的这篇(应该是最后一篇了)就是想结合具体的案例,分别用XGBoost来进行回归和分类来彻底理解每棵树是怎么长的,个人过了一遍之后对于XGBoost正则化参数的理解感觉又比之前上了一个层次。

下面开始:

之前的原理篇有提到,XGBoost最厉害的地方是他是一个抽象的算法框架,无论我们是拿他来进行回归,我们都可以根据泰勒展开后的目标函数得到下式,下面简单再进行一下重新推导:

根据上式,我们需要最小化Ovalue,求导=0可得(<===重点1!!!)! !

再将求得的Ovalue最小值带回我们的目标函数,得到我们的叶子节点分数(<===重点2!!!)

注意到上式最后把1/2约去了,那是因为求最小值常量不碍事可直接约去

开始做分类与回归:根据上面的式子我们可以发现,无论是做分类还是回归,为了分裂节点建树,我们都需要:

1. 把最小化叶子节点分数得到,为了得到最小化的叶子节点分数,我们需要计算损失函数对于叶子节点的一阶到二阶导

2. 计算Similarity Score

3. 计算子节点的Similarity Score,与父节点相减得到gain(需要最大化gain)

4. 重复以上...

5. 确定树结构后通过lambda,gamma进行剪枝(对应下图计算梯度和分裂节点的部分)

XGBoost的回归树

我们先从回归树说起(更简单一些)

对于回归问题,一般来说我们最常用的损失函数就是

一阶导gi (用到链式法则),可以理解为残差

二阶导hi (求没了已经) 全部为1

写到这里,我们可以惊喜的发现,对于回归问题,XGBoost的最优化叶子节点式子就可以写成:

分子就是残差,分母就是叶子节点的样本数量+lambda

相应的目标函数就可以写为:

分子就是残差和的平方,分母就是叶子节点的样本数量+lambda

接下来我们通过boston放假数据集来验证我们的想法,作为样例:

from sklearn.datasets import load_iris, load_boston,load_iris

from xgboost import XGBClassifier,XGBRegressor

boston = load_boston()

train = pd.DataFrame(boston['data'])

label = pd.Series(boston['target'],name='label')

full = pd.concat((train,label),axis=1)

model = XGBRegressor(n_estimators=3,max_depth=1,reg_lambda=0,reg_alpha=0)

model.fit(train,label)

model.get_booster().trees_to_dataframe()

我们可以发现根节点的gain = 19339.546900

我们再通过手动计算一遍,看看手动的梯度计算,叶子节点的计算分数是否能和算法输出出来的数字一致:

full['g'] = full['label'] - 0.5

full['h'] = 1

root_score = full['g'].sum() ** 2 / full.shape[0]

left_df = full[full.iloc[:,5] < 6.9410]

right_df = full[full.iloc[:,5] >= 6.9410]

left_score = left_df['g'].sum() ** 2 / 430

right_score = right_df['g'].sum() ** 2 / 76

print('The Gain for Root is left node score{} + right node score{} - root score{} = {}' \

.format(left_score,right_score,root_score,right_score+left_score - root_score))

>>> The Gain for Root is left node score162397.88895348838 + right node score102576.61065789477 - root score245634.94458498035 =

>>> 19339.555026402784

可以发现我们的gain分数是完全对的上的!

刚刚为了方便计算,我们故意把lambda设成了0,这次我们设成1,再看看结果

train = pd.DataFrame(boston['data'])

label = pd.Series(boston['target'],name='label')

full = pd.concat((train,label),axis=1)

full['g'] = full['label'] - 0.5

full['h'] = 1

model = XGBRegressor(n_estimators=3,max_depth=1,reg_lambda=1,reg_alpha=0)

model.fit(train,label)

model.get_booster().trees_to_dataframe()

full['g'] = full['label'] - 0.5

full['h'] = 1

root_score = full['g'].sum() ** 2 / (506 + 1)

left_df = full[full.iloc[:,12] < 9.7250]

right_df = full[full.iloc[:,12] >= 9.7250]

left_score = left_df['g'].sum() ** 2 / (212 + 1)

right_score = right_df['g'].sum() ** 2 / (294 + 1)

print('The Gain for Root is left node score{} + right node score{} - root score{} = {}' \

.format(left_score,right_score,root_score,right_score+left_score - root_score))

>>> The Gain for Root is left node score180271.60356807514 + right node score83126.45423728814 - root score245150.457514793 =

18247.600290570263

可以发现第一棵树的数字被压缩了,这也正是正则化参数lambda存在的目的,其实从他在狮子中的位置也可以看得出来:lambda越大,节点少的叶子的权重会被稀释的越厉害,而叶子节点多的权重收到的影响会稍小一些。用高深点的话说:lambda>0时,会shrink我们的分数值,让输出的结果分更小。(我看到这里感觉有种他和学习率eta双重绑定,环环相扣的感觉)

在后剪枝的过程中,gamma会参与进来,所有分裂得分

分类树:

接下来再来啃一下有点劝退的分类树:

分类函数的损失一般用logloss

我们把logloss转换一下(先放一个p,log_odds,odds解释图,看不懂的话可以先理解一图里的式子)

所以最终我们的损失函数的等于:

一阶导gi (依旧是残差,只不过是概率

二阶导hi (一个很奇妙的相互乘的式子)

得到了这两个式子后,我们就可以得到分类树的叶子节点最优分:

分子依旧是残差,分母是前一项预测值交叉乘 + lambda

以及对应的结构分

同样的,我们来用一个例子验证一下我们的式子

iris = load_iris()

train = pd.DataFrame(iris['data'])

label = pd.Series(iris['target'],name='label')

full = pd.concat((train,label),axis=1)

full = full[full.label != 2]

train = full[[x for x in full.columns if x != 'label']]

label = full['label']

model = XGBClassifier(n_estimators=3,max_depth=1,reg_lambda=0,reg_alpha=0)

model.fit(train,label)

model.get_booster().trees_to_dataframe()

full['g'] = full['label'] - 0.5

full['h'] = 0.5 * (1-0.5)

root_score = full['g'].sum() ** 2 / full.shape[0]

left_df = full[full.iloc[:,2] < 2.45]

right_df = full[full.iloc[:,2] >= 2.45]

left_score = left_df['g'].sum() ** 2 / 12.5

right_score = right_df['g'].sum() ** 2 / 12.5

print('The Gain for Root is left node score{} + right node score{} - root score{} = {}' \

.format(left_score,right_score,root_score,right_score+left_score - root_score))

>>> The Gain for Root is left node score50.0 + right node score50.0 - root score0.0 =

100.0

可以发现也是对的上的。同时我们可以发现,xgboost中的一个minchildweight参数其实就是回归树中的分母的叶子数量,分类树中的概率交互乘项,可以发现在定义这个参数时分类与回归树的区别还是要注意,2个参数在不同的分类任务下的值域显然是不同的。

最后放一个分类回归分数计算和结果分数的式子对比,方便加深印象总结:分类树 回归树所用的算法思想是一致的,都是在XGBOOST的框架下,不停的去拟合上一轮的残差(都是0.5分开始)

两者的损失函数不同造成两者生长的策略有所不同,导致XGBOOST 的参数像是minchildweight设置的值域也不同.https://www.youtube.com/watch?v=ZVFeW798-2I

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值