根据菜菜的课程进行整理,方便记忆理解
代码位置如下:
XGBoost的智慧
class xgboost.XGBRegressor (kwargs,max_depth=3, learning_rate=0.1, n_estimators=100, silent=True, objective='reg:linear', booster='gbtree', n_jobs=1, nthread=None, gamma=0, min_child_weight=1, max_delta_step=0, subsample=1, colsample_bytree=1, colsample_bylevel=1, reg_alpha=0, reg_lambda=1, scale_pos_weight=1, base_score=0.5, random_state=0, seed=None, missing=None, importance_type='gain')
选择弱评估器:booster
梯度提升算法中不只有梯度提升树,XGB作为梯度提升算法的进化,自然也不只有树模型一种弱评估器。在XGB中,除了树模型,我们还可以选用线性模型,比如线性回归,来进行集成。虽然主流的XGB依然是树模型,但我们也可以使用其他的模型。基于XGB的这种性质,我们有参数“booster"来控制我们究竟使用怎样的弱评估器。
| xgb.train() & params | xgb.XGBRegressor() |
|---|---|
| xgb_model | booster |
| 使用哪种弱评估器。可以输入gbtree, gblinear或dart。输入的评估器不同,使用 的params参数也不同,每种评估器都有自 己的params列表。评估器必须于param参 数相匹配,否则报错。 | 使用哪种弱评估器。可以输入gbtree,gblinear或dart。 gbtree代表梯度提升树,dart是Dropouts meet Multiple Additive Regression Trees,可译为抛弃提升树,在建树的过 程中会抛弃一部分树,比梯度提升树有更好的防过拟合功能。 输入gblinear使用线性模型。 |
使用哪种弱评估器。可以输入gbtree,gblinear或dart。gbtree代表梯度提升树,dart是Dropouts meet Multiple Additive Regression Trees,可译为抛弃提升树,在建树的过程中会抛弃一部分树,比梯度提升树有更好的防过拟合功能。输入gblinear使用线性模型。
for booster in ["gbtree","gblinear","dart"]:
reg = XGBR(n_estimators=180
,learning_rate=0.1
,random_state=420
,booster=booster).fit(Xtrain,Ytrain)
print(booster)
print(reg.score(Xtest,Ytest))
"""
gbtree
0.9260984369386971
gblinear
0.6480662894674278
dart
0.9260984459922119
"""
XGB的目标函数:objective
梯度提升算法中都存在着损失函数。不同于逻辑回归和SVM等算法中固定的损失函数写法,集成算法中的损失函数是可选的,要选用什么损失函数取决于我们希望解决什么问题,以及希望使用怎样的模型。比如说,如果我们的目标是进行回归预测,那我们可以选择调节后的均方误差RMSE作为我们的损失函数。如果我们是进行分类预测,那我们可以选择错误率error或者对数损失log_loss。只要我们选出的函数是一个可微的,能够代表某种损失的函数,它就可以是我们XGB中的损失函数。
在众多机器学习算法中,损失函数的核心是衡量模型的泛化能力,即模型在未知数据上的预测的准确与否,我们训练模型的核心目标也是希望模型能够预测准确。在XGB中,预测准确自然是非常重要的因素,但我们之前提到过,XGB的是实现了模型表现和运算速度的平衡的算法。普通的损失函数,比如错误率,均方误差等,都只能够衡量模型的表现,无法衡量模型的运算速度。回忆一下,我们曾在许多模型中使用空间复杂度和时间复杂度来衡量模型的运算效率。XGB因此引入了模型复杂度来衡量算法的运算效率。因此XGB的目标函数被写作:传统损失函数 + 模型复杂度。
其中i代表数据集中的第i个样本, m表示导入第k棵树的数据总量, 代表建立的所有树(n_estimators),当只建立了t棵树的时候,式子应当为。第一项代表传统的损失函数,衡量真实标签与预测值之间的差异,通常是RMSE,调节后的均方误差。第二项代表模型的复杂度,使用树模型的某种变换表示,这个变化代表了一个从树的结构来衡量树模型的复杂度的式子,可以有多种定义。注意,我们的第二项中没有特征矩阵的介入。我们在迭代每一棵树的过程中,都最小化来力求获取最优的,因此我们同时最小化了模型的错误率和模型的复杂度,这种设计目标函数的方法不得不说实在是非常巧妙和聪明。
目标函数:可能的困惑
与其他算法一样,我们最小化目标函数以求模型效果最佳,并且我们可以通过限制n_estimators来限制我们的迭代次数,因此我们可以看到生成的每棵树所对应的目标函数取值。目标函数中的第二项看起来是与棵树都相关,但我们的第一个式子看起来却只和样本量相关,仿佛只与当前迭代到的这棵树相关,这不是很奇怪么?
其实并非如此,我们的第一项传统损失函数也是与已经建好的所有树相关的,相关在这里:
我们的中已经包含了所有树的迭代结果,因此整个目标函数都与K棵树相关。
我们还可以从另一个角度去理解我们的目标函数。回忆一下我们曾经在随机森林中讲解过的方差-偏差困境。在机器学习中,我们用来衡量模型在未知数据上的准确率的指标,叫做泛化误差(Genelization error)。一个集成模型(f)在未知数据集(D)上的泛化误差,由方差(var),偏差(bais)和噪声(ε)共同决定,而泛化误差越小,模型就越理想。从下面的图可以看出来,方差和偏差是此消彼长的,并且模型的复杂度越高,方差越大,偏差越小
方差可以被简单地解释为模型在不同数据集上表现出来地稳定性,而偏差是模型预测的准确度。那方差-偏差困境就可以对应到我们的中了:
第一项是衡量我们的偏差,模型越不准确,第一项就会越大。第二项是衡量我们的方差,模型越复杂,模型的学习就会越具体,到不同数据集上的表现就会差异巨大,方差就会越大。所以我们求解的最小值,其实是在求解方差与偏差的平衡点,以求模型的泛化误差最小,运行速度最快。我们知道树模型和树的集成模型都是学习天才,是天生过拟合的模型,因此大多数树模型最初都会出现在图像的右上方,我们必须通过剪枝来控制模型不要过拟合。现在XGBoost的损失函数中自带限制方差变大的部分,也就是说XGBoost会比其他的树模型更加聪明,不会轻易落到图像的右上方。可见,这个模型在设计的时候的确是考虑了方方面面,无怪XGBoost会如此强大了。
在应用中,我们使用参数“objective"来确定我们目标函数的第一部分中的,也就是衡量损失的部分。
| xgb.train() | xgb.XGBRegressor() | xgb.XGBClassifier() |
|---|---|---|
| obj:默认binary:logistic | objective:默认reg:linear | objective:默认binary:logistic |
常用的选择有:
| 输入 | 选用的损失函数 |
|---|---|
| reg:linear | 使用线性回归的损失函数,均方误差,回归时使用 |
| binary:logistic | 使用逻辑回归的损失函数,对数损失log_loss,二分类时使用 |
| binary:hinge | 使用支持向量机的损失函数,Hinge Loss,二分类时使用 |
| multi:softmax | 使用softmax损失函数,多分类时使用 |
我们还可以选择自定义损失函数。比如说,我们可以选择输入平方损失,此时我们的XGBoost其实就是算法梯度提升机器(gradient boosted machine)。在xgboost中,我们被允许自定义损失函数,但通常我们还是使用类已经为我们设置好的损失函数。我们的回归类中本来使用的就是reg:linear,因此在这里无需做任何调整。注意:分类型的目标函数导入回归类中会直接报错。现在来试试看xgb自身的调用方式。
由于xgb中所有的参数都需要自己的输入,并且objective参数的默认值是二分类,因此我们必须手动调节。试试看在其他参数相同的情况下,我们xgboost库本身和sklearn比起来,效果如何:
#默认reg:linear
reg = XGBR(n_estimators=180,random_state=420).fit(Xtrain,Ytrain)
reg.score(Xtest, Ytest)
# 0.9050526026617368
MSE(Ytest,reg.predict(Xtest))
# 8.835224196909236
#xgb实现法
import xgboost as xgb
#使用类DMatrix读取数据
dtrain = xgb.DMatrix(Xtrain,Ytrain) #特征矩阵和标签都进行一个传入
dtest = xgb.DMatrix(Xtest,Ytest)
#非常遗憾无法打开来查看,所以通常都是先读到pandas里面查看之后再放到DMatrix中
dtrain
"""
<xgboost.core.DMatrix at 0x2d42d2241d0>
"""
import pandas as pd
pd.DataFrame(Xtrain)
#写明参数
param = {'silent':True #默认为False,通常要手动把它关闭掉
,'objective':'reg:linear'
,"eta":0.1}
num_round = 180 #n_estimators
#类train,可以直接导入的参数是训练数据,树的数量,其他参数都需要通过params来导入
bst = xgb.train(param, dtrain, num_round)
#接口predict
preds = bst.predict(dtest)
preds
"""
array([ 6.4613175, 22.123888 , 30.755163 , 13.424353 , 8.378563 ,
23.608473 , 14.2151165, 16.0265 , 15.498961 , 14.106488 ,
24.030863 , 34.36362 , 21.461111 , 28.839497 , 19.568035 ,
10.18866 , 19.42369 , 23.539951 , 22.850523 , 23.19871 ,
17.82486 , 16.072205 , 27.602034 , 20.773046 , 20.868807 ,
15.865789 , 22.076588 , 29.292158 , 22.841051 , 15.770392 ,
36.680496 , 21.057947 , 20.137005 , 23.777853 , 22.70615 ,
23.863268 , 15.595315 , 24.565872 , 17.720552 , 33.951115 ,
18.784288 , 20.483376 , 37.106678 , 18.068266 , 12.73839 ,
31.186407 , 45.895035 , 12.696718 , 10.773068 , 36.064293 ,
26.262571 , 19.908836 , 20.715096 , 48.814903 , 27.550056 ])
"""
from sklearn.metrics import r2_score
r2_score(Ytest,preds)
# 0.9260984369386971
MSE(Ytest,preds)
# 6.876827553497432
看得出来,无论是从还是从MSE的角度来看,都是xgb库本身表现更优秀,这也许是由于底层的代码是由不同团队创造的缘故。随着样本量的逐渐上升,sklearnAPI中调用的结果与xgboost中直接训练的结果会比较相似,如果希望的话可以分别训练,然后选取泛化误差较小的库。如果可以的话,建议脱离sklearnAPI直接调用xgboost库,因为xgboost库本身的调参要方便许多。