全程eXtreme Gradient Boosting ,极限梯度提升算法。致力于让提升树突破自身计算极限,以实现快速、性能优秀的工程目标
XGBoost库,名为xgboost。xgboost是一个独立的,开源的,专门提供梯度提升树以及XGBoost算法应用的算法库。建模流程如下:
其中parms可选参数如下:
如此多的参数所以xgboost库中必须先使用字典设定参数集。
如果使用sklearn中惯例的实例化、fit、predict来运行并使用其中的API,其中参数列表如下:
虽然sklearn里面API和xgboost库中参数看起来很不一样,但是它们只是写法不同,功能是相同的。此外,使用xgboost中设定的建模流程建模和使用sklearnAPI的类来建模相比,前者的运算速度和调参手段都要简单一些
XGBoost本身的核心是基于梯度提升树实现的集成算法,其三个核心分别是:集成算法本身,用于集成的弱评估器,以及应用中的其他过程。
通过在数据上构建多个弱评估器(预测概率高于50%的任意模型),汇总所有弱评估器的建模结果,获取比单个模型更好的回归或分类表现。其中集成不同弱评估器的方法有很多中,如一次性建立多个平行独立的弱评估器的装袋法和经过多次迭代逐渐积累多个弱评估器的提升法。形象的Boosting算法工作流程如下:
即先建立一颗数,然后逐次迭代,每次迭代过程中都增加一棵树,逐渐形成众多树模型集成的强评估器。树中可以有回归树或分类树,两者都以CART树算法作为主流,XGBoost背后也是CART树,即XBGoost中所有书都是二叉的。
回归树上的每个样本预测结果表示为加权求和:
y ^ i ( k ) = ∑ k K γ k h k ( x i ) \hat y_i^{(k)}=\sum_{k}^{K}\gamma_kh_k(x_i) y^i(k)=k∑Kγkhk(xi)
GBDT中预测值是由所有弱分类器上的预测结果的加权求和,其中每个样本上的预测结果就是样本所在的叶子节点的均值。而XGBT中的预测值是所有弱分类器上的叶子权重直接求和得到,计算叶子权重是一个复杂的过程。
参数含义 | xgb.train() | xgb.XGBRegressor() |
---|---|---|
集成中弱评估器的数量 | num_round,默认10 | n_estimators,默认100 |
训练中是否打印每次训练的结果 | slient,默认False | slient,默认True |
第一步:导入库和数据建模
from xgboost import XGBRegressor as XGBR
from sklearn.ensemble import RandomForestRegressor as RFR
from sklearn.linear_model import LinearRegression as linearR
from sklearn.datasets import load_boston
from sklearn.model_selection import KFold, cross_val_score as CVS ,train_test_split as TTS
from sklearn.metrics import mean_squared_error as MSE
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from time import time
import datetime
data = load_boston()
X = data.data
y = data.target
Xtrain, Xtest,Ytrain,Ytest = TTS(X,y,test_size=0.3,random_state=420)
reg = XGBR(n_estimators=100).fit(Xtrain,Ytrain)
reg.predict(Xtest)
reg.score(Xtest,Ytest)
MSE(Ytest,reg.predict(Xtest))
reg.feature_importances_
reg = XGBR(n_estimators=100)
CVS(reg,Xtrain,Ytrain,cv=5).mean()
CVS(reg,Xtrain,Ytrain,cv=5,scoring="neg_mean_squared_error").mean()
rfr = RFR(n_estimators=100)
CVS(rfr,Xtrain,Ytrain,cv=5).mean()
CVS(rfr,Xtrain,Ytrain,cv=5,scoring="neg_mean_squared_error").mean()
lr = linearR()
CVS(lr,Xtrain,Ytrain,cv=5).mean()
CVS(lr,Xtrain,Ytrain,cv=5,scoring="neg_mean_squared_error").mean()
reg = XGBR(n_estimator=10,silent=False)
CVS(reg,Xtrain,Ytrain,cv=5,scoring="neg_mean_squared_error").mean()
第五步:定义绘制学习曲线函数观察XGB在该数据集上的潜力
def plot_learning_curve(estimator,title,X,y,
ax=None,
ylim=None,
cv=None,
n_jobs=None):
from sklearn.model_selection import learning_curve
import matplotlib.pyplot as plt
import numpy as np
train_sizes,train_scores,test_scores = learning_curve(estimator,X,y,
shuffle=True,
cv=cv,
n_jobs=n_jobs)
if ax == None:
ax=plt.gca()
else:
ax=plt.figure()
ax.set_title(title)
if ylim is not None:
ax.set_ylim(*ylim)
ax.set_xlabel("Training examples")
ax.set_ylabel("Score")
ax.grid()
ax.plot(train_sizes,np.mean(train_scores,axis=1),'o-',color="r",label="Training score")
ax.plot(train_sizes,np.mean(test_scores,axis=1),'o-',color="g",label="Test score")
ax.legend(loc="best")
return ax
cv = KFold(n_splits=5,shuffle=True,random_state=42)
plot_learning_curve(XGBR(n_estimators=100,random_state=420),"XGB",Xtrain,Ytrain,ax=None,cv=cv)
plt.show()
训练集表现模型的学习能力,测试集表现模型的泛化能力。可以看到待调整的空间比较大,可以尝试对参数进行调整
第六步:使用参数学习曲线观察n_estimators对模型的影响
axisx = range(10,1010,50)
rs = []
for i in axisx:
reg = XGBR(n_estimators=i,random_state=420)
rs.append(CVS(reg,Xtrain,Ytrain,cv=cv).mean())
print(axisx[rs.index(max(rs))],max(rs))
plt.figure(figsize=(20,5))
plt.plot(axisx,rs,c="red",label="XGB")
plt.legend()
plt.show()
随机森林中有过方差-偏差困境这个概念。衡量未知数据准确率的指标叫做泛化误差,集成模型中泛化误差 E ( f ; D ) E(f;D) E(f;D)由方差,偏差,和噪声共同决定。泛化误差越小,模型越理想。公式如下
E ( f ; D ) = b i a s 2 + v a r + ϵ 2 E(f;D)=bias^2+var+\epsilon^2 E(f;D)=bias2+var+ϵ2
第七步:学习曲线找适合的n_estimators
axisx = range(50,1050,50)
rs = []
var = []
ge = []
for i in axisx:
reg = XGBR(n_estimators=i,random_state=420)
cvresult = CVS(reg,Xtrain,Ytrain,cv=cv)
#记录1-偏差
rs.append(cvresult.mean())
#记录方差
var.append(cvresult.var())
#计算泛化误差的可控部分
ge.append((1 - cvresult.mean())**2+cvresult.var())
#打印R2最高所对应的参数取值,并打印这个参数下的方差
print(axisx[rs.index(max(rs))],max(rs),var[rs.index(max(rs))])
#打印方差最低时对应的参数取值,并打印这个参数下的R2
print(axisx[var.index(min(var))],rs[var.index(min(var))],min(var))
#打印泛化误差可控部分的参数取值,并打印这个参数下的R2,方差以及泛化误差的可控部分
print(axisx[ge.index(min(ge))],rs[ge.index(min(ge))],var[ge.index(min(ge))],min(ge))
plt.figure(figsize=(20,5))
plt.plot(axisx,rs,c="red",label="XGB")
plt.legend()
plt.show()
第七步:细化学习曲线找出使泛化误差最小的n_estimators
axisx = range(100,210,10)
rs = []
var = []
ge = []
for i in axisx:
reg = XGBR(n_estimators=i,random_state=420)
cvresult = CVS(reg,Xtrain,Ytrain,cv=cv)
rs.append(cvresult.mean())
var.append(cvresult.var())
ge.append((1 - cvresult.mean())**2+cvresult.var())
print(axisx[rs.index(max(rs))],max(rs),var[rs.index(max(rs))])
print(axisx[var.index(min(var))],rs[var.index(min(var))],min(var))
print(axisx[ge.index(min(ge))],rs[ge.index(min(ge))],var[ge.index(min(ge))],min(ge))
rs = np.array(rs)
var = np.array(var)*0.01
plt.figure(figsize=(20,5))
plt.plot(axisx,rs,c="black",label="XGB")
#添加方差线
plt.plot(axisx,rs+var,c="red",linestyle='-.')
plt.plot(axisx,rs-var,c="red",linestyle='-.')
plt.legend()
plt.show()
plt.figure(figsize=(20,5))
plt.plot(axisx,ge,c="gray",linestyle='-.')
plt.show()
time0 = time()
print(XGBR(n_estimators=100,random_state=420).fit(Xtrain,Ytrain).score(Xtest,Ytest))
print(time()-time0)
time0 = time()
print(XGBR(n_estimators=160,random_state=420).fit(Xtrain,Ytrain).score(Xtest,Ytest))
print(time()-time0)
一般数据量大的时候使用,如果样本量本来就小,那会使过拟合现象更加严重
参数含义 | xgb.train() | xgb.XGBRegressor() |
---|---|---|
随机抽样的时候抽取的样本比例,范围(0,1] | subsample,默认1 | subsample,默认1 |
对于随机抽样是因为大多数时候,数据量太过巨大,而树模型是天生过拟合的模型,计算会非常缓慢,所以需要进行有放回的抽样。
在无论是装袋还是提升的集成算法中,有放回抽样都是我们防止过拟合,让单一弱分类器变得更轻量的必要操作。这样做还可以保证集成算法中的每个弱分类器(每棵树)都是不同的模型,基于不同的数据建立的自然是不同的模型,而集成一系列一模一样的弱分类器是没有意义的。梯度提升树中,每次都会将之前判错的样本加入,来保证每次集成效果增加,形象图如下:
简单说就是让树倾向于这些权重更大的,容易被判错的样本。
axisx = np.linspace(0,1,20)
rs = []
for i in axisx:
reg = XGBR(n_estimators=100,subsample=i,random_state=420)
rs.append(CVS(reg,Xtrain,Ytrain,cv=cv).mean())
print(axisx[rs.index(max(rs))],max(rs))
plt.figure(figsize=(20,5))
plt.plot(axisx,rs,c="green",label="XGB")
plt.legend()
plt.show()
axisx = np.linspace(0.05,1,20)
rs = []
var = []
ge = []
for i in axisx:
reg = XGBR(n_estimators=180,subsample=i,random_state=420)
cvresult = CVS(reg,Xtrain,Ytrain,cv=cv)
rs.append(cvresult.mean())
var.append(cvresult.var())
ge.append((1 - cvresult.mean())**2+cvresult.var())
print(axisx[rs.index(max(rs))],max(rs),var[rs.index(max(rs))])
print(axisx[var.index(min(var))],rs[var.index(min(var))],min(var))
print(axisx[ge.index(min(ge))],rs[ge.index(min(ge))],var[ge.index(min(ge))],min(ge))
rs = np.array(rs)
var = np.array(var)
plt.figure(figsize=(20,5))
plt.plot(axisx,rs,c="black",label="XGB")
plt.plot(axisx,rs+var,c="red",linestyle='-.')
plt.plot(axisx,rs-var,c="red",linestyle='-.')
plt.legend()
plt.show()
结果为1也印证了数据量小的导致过拟合的问题。总体来说,这个参数对于该数据集影响不大
让迭代效果每次都变好,除了让模型倾向困难样本的方向的同时,还必须控制新的弱分类器的生成,必须保证每次添加的树一定是对于这个数据集预测效果最优的一棵树。
在逻辑回归中,有方程如下:
y ( x ) = 1 1 + e − θ T x y(x)=\frac{1}{1+e^{-\theta^Tx}} y(x)=1+e−θTx1
首先找出逻辑回归的损失函数 J ( θ ) J(\theta) J(θ),这个函数通过代入 θ \theta θ来衡量逻辑回归在训练集上的拟合效果。然后通过地图下降法迭代:
θ K + 1 = θ k α ∗ d k i \theta_{K+1}=\theta_k\alpha*d_{ki} θK+1=θkα∗dki
其实梯度提升树与逻辑回归中做的事情很相似
集成算法有:
y ^ ( k + 1 ) = y ^ ( k ) + f k + 1 ( x i ) \hat y^{(k+1)}=\hat y^{(k)}+f_{k+1}(x_i) y^(k+1)=y^(k)+fk+1(xi)
前k棵树的集成结果是 y ^ i ( k ) = ∑ k K γ k h k ( x i ) \hat y_i^{(k)}=\sum_{k}^{K}\gamma_kh_k(x_i) y^i(k)=∑kKγkhk(xi)。加上新的叶子权重然后迭代一直持续找到让损失函数最小的 y ^ \hat y y^,然后作为模型的预测结果。在XBG中可以决定决策时的步长(学习率)来控制结果的精确性,形象解释如下:
参数含义 | xgb.train() | xgb.XGBRegressor() |
---|---|---|
集成中的学习率,又称为步长 以控制迭代速率,常用于防止过拟合 |
eta,默认0.3 取值范围[0,1] |
learning_rate,默认0.1 取值范围[0,1] |
取零,就是一棵树罢了
def regassess(reg,Xtrain,Ytrain,cv,scoring = ["r2"],show=True):
score = []
for i in range(len(scoring)):
if show:
print("{}:{:.2f}".format(scoring[i]
,CVS(reg,
Xtrain,Ytrain,cv=cv,
scoring=scoring[i]).mean()))
score.append(CVS(reg,Xtrain,Ytrain,cv=cv,scoring=scoring[i]).mean())
return score
regassess(reg,Xtrain,Ytrain,cv,scoring=["r2","neg_mean_squared_error"])
import pytz
for i in [0,0.2,0.5,1]:
time0=time()
reg = XGBR(n_estimators=180,random_state=420,learning_rate=i)
print("learning_rate = {}".format(i))
regassess(reg,Xtrain,Ytrain,cv,scoring = ["r2","neg_mean_squared_error"])
print(datetime.datetime.fromtimestamp(time()-time0,pytz.timezone('UTC')).strftime("%M:%S:%f"))
print("\t")
axisx = np.arange(0.05,1,0.05)
rs = []
te = []
for i in axisx:
reg = XGBR(n_estimators=180,random_state=420,learning_rate=i)
score = regassess(reg,Xtrain,Ytrain,cv,scoring = ["r2","neg_mean_squared_error"])
test = reg.fit(Xtrain,Ytrain).score(Xtest,Ytest)
rs.append(score[0])
te.append(test)
print(axisx[rs.index(max(rs))],max(rs))
plt.figure(figsize=(20,5))
plt.plot(axisx,te,c="gray",label="test")
plt.plot(axisx,rs,c="green",label="train")
plt.legend()
plt.show()
可以看到默认0.1在这个数据集上的表现就已经达到了最好。一般调整在[0.01,0.2]之间变动,模型效果更多是从树本身的角度来剪枝,而不是调整eta。
三个重要部分:
XGBoost是在梯度提升树的这三个核心要素上运行,它重新定义了损失函数和弱评估器,并且对提升算法的集成手段进行了改进,实现了运算速度和模型效果的高度平衡。XGBoost将原本的梯度提升树拓展,不再是单纯树的集成模型,也不只是单单的回归模型。通过调整参数可以选择任何希望的集成算法实现任何功能