对于XGBoost算法原理看陈天奇的PPT和一份算法实战指导文档就够了
目录:
一、XGBoost算法原理:
1,CART树
2,XGBoost算法与GBDT
3,一个实例
4,XGB的优缺点
二、RF,GBDT与XGB比较
1),GBDT与XGB
2),GBDT与RF区别
3),RF的优缺点
三、XGBoost算法的python实现
XGB相关知识模块:算法原理,损失函数,分裂结点算法,正则化,对缺失值处理。
一、XGBoost算法原理:
1,CART树:
CART - Classification and Regression Trees
分类与回归树,是二叉树,可以用于分类,也可以用于回归问题,最先由 Breiman 等提出。
分类树的输出是样本的类别,回归树的输出是一个实数。
CART算法有两步:
决策树生成和剪枝。
决策树生成:递归地构建二叉决策树的过程,基于训练数据集生成决策树,生成的决策树要尽量大;
自上而下从根开始建立节点,在每个节点处要选择一个最好的属性来分裂,使得子节点中的训练集尽量的纯。
不同的算法使用不同的指标来定义"最好":
分类问题,可以选择GINI,双化或有序双化;回归问题,可以使用最小二乘偏差(LSD)或最小绝对偏差(LAD)。
决策树剪枝:用验证数据集对已生成的树进行剪枝并选择最优子树,这时损失函数最小作为剪枝的标准。
这里用代价复杂度剪枝 Cost-Complexity Pruning(CCP)
2,XGBoost算法与GBDT:
GBDT无论在理论推导还是在应用场景实践都是相当完美的,但有一个问题:第n颗树训练时,需要用到第n-1颗树的(近似)残差。从这个角度来看,GBDT比较难以实现分布式(ps:虽然难,依然是可以的,换个角度思考就行)。
简单讲,XGB(extremegradient boosting)是GBDT的一种工业实现,也是通过不断增加新树,拟合伪残差去降低损失函数。其拟合过程是使用的损失函数的二阶泰勒展开,这是和GBDT的一个区别。
xgboost使用CART树而不是用普通的决策树。对于分类问题,由于CART树的叶子节点对应的值是一个实际的分数,而非一个确定的类别,这将有利于实现高效的优化算法。xgboost出名的原因一是准,二是快,之所以快,其中就有选用CART树的一份功劳。
不同于传统的gbdt方式 ,只利用了一阶的导数信息,xgboost对loss func 做了二阶的泰勒展开,并在目标函数之外加入了正则项整体求最优解,用以权衡目标函数的下降和模型的复杂程度,避免过拟合。具体推导详见陈天奇的ppt(文末有网盘链接),这里给出简要的摘注,一些变量的称谓沿用陈天奇ppt 里的叫法,和前述 friedman 的版本里不一致,请注意。
利用泰勒展开三项,做一个近似,我们可以很清晰地看到,最终的目标函数只依赖于每个数据点的在误差函数上的一阶导数和二阶导数。
3,一个实例:
1,XGBoost:
算法思想就是不断地添加树,不断地进行特征分裂来生长一棵树,每次添加一个树,其实是学习一个新函数,去拟合上次预测的残差。当我们训练完成得到k棵树,我们要预测一个样本的分数,其实就是根据这个样本的特征,在每棵树中会落到对应的一个叶子节点,每个叶子节点就对应一个分数,最后只需要将每棵树对应的分数加起来就是该样本的预测值。
注:w_q(x)为叶子节点q的分数,f(x)为其中一棵回归树。
如下图例子,训练出了2棵决策树,小孩的预测分数就是两棵树中小孩所落到的结点的分数相加。爷爷的预测分数同理。
4,XGB的优缺点:
xgBoosting在传统Boosting的基础上,利用cpu的多线程,引入正则化项,加入剪枝,控制了模型的复杂度。
与GBDT相比,xgBoosting有以下进步:
GBDT以传统CART作为基分类器,而xgBoosting支持线性分类器,相当于引入L1和L2正则化项的逻辑回归(分类问题)和线性回归(回归问题);
GBDT在优化时只用到一阶导数,xgBoosting对代价函数做了二阶Talor展开,引入了一阶导数和二阶导数;
当样本存在缺失值是,xgBoosting能自动学习分裂方向;
xgBoosting借鉴RF的做法,支持列抽样,这样不仅能防止过拟合,还能降低计算;
xgBoosting的代价函数引入正则化项,控制了模型的复杂度,正则化项包含全部叶子节点的个数,每个叶子节点输出的score的L2模的平方和。从贝叶斯方差角度考虑,正则项降低了模型的方差,防止模型过拟合;
xgBoosting在每次迭代之后,为叶子结点分配学习速率,降低每棵树的权重,减少每棵树的影响,为后面提供更好的学习空间;
xgBoosting工具支持并行,但并不是tree粒度上的,而是特征粒度,决策树最耗时的步骤是对特征的值排序,xgBoosting在迭代之前,先进行预排序,存为block结构,每次迭代,重复使用该结构,降低了模型的计算;block结构也为模型提供了并行可能,在进行结点的分裂时,计算每个特征的增益,选增益最大的特征进行下一步分裂,那么各个特征的增益可以开多线程进行;
可并行的近似直方图算法,树结点在进行分裂时,需要计算每个节点的增益,若数据量较大,对所有节点的特征进行排序,遍历的得到最优分割点,这种贪心法异常耗时,这时引进近似直方图算法,用于生成高效的分割点,即用分裂后的某种值减去分裂前的某种值,获得增益,为了限制树的增长,引入阈值,当增益大于阈值时,进行分裂;
然而,与LightGBM相比,又表现出了明显的不足:
xgBoosting采用预排序,在迭代之前,对结点的特征做预排序,遍历选择最优分割点,数据量大时,贪心法耗时,LightGBM方法采用histogram算法,占用的内存低,数据分割的复杂度更低;
xgBoosting采用level-wise生成决策树,同时分裂同一层的叶子,从而进行多线程优化,不容易过拟合,但很多叶子节点的分裂增益较低,没必要进行跟进一步的分裂,这就带来了不必要的开销;LightGBM采用深度优化,leaf-wise生长策略,每次从当前叶子中选择增益最大的结点进行分裂,循环迭代,但会生长出更深的决策树,产生过拟合,因此引入了一个阈值进行限制,防止过拟合.
二、RF,GBDT与XGB比较:
1),GBDT与XGB:
1. 传统GBDT以CART作为基分类器,xgboost还支持线性分类器(gblinear),这个时候xgboost相当于带L1和L2正则化项的逻辑斯蒂回归(分类问题)或者线性回归(回归问题)
2. 传统GBDT在优化时只用到一阶导数信息,xgboost则对代价函数进行了二阶泰勒展开,同时用到了一阶和二阶导数。顺便提一下,xgboost工具支持自定义代价函数,只要函数可一阶和二阶求导
3. xgboost在代价函数里加入了正则项,用于控制模型的复杂度。正则项里包含了树的叶子节点个数、每个叶子节点上输出的score的L2模的平方和。从Bias-variance tradeoff角度来讲,正则项降低了模型的variance,使学习出来的模型更加简单,防止过拟合,这也是xgboost优于传统GBDT的一个特性
4. Shrinkage(缩减),相当于学习速率(xgboost中的eta)。xgboost在进行完一次迭代后,会将叶子节点的权重乘上该系数,主要是为了削弱每棵树的影响,让后面有更大的学习空间。实际应用中,一般把eta设置得小一点,然后迭代次数设置得大一点。(补充:传统GBDT的实现也有学习速率)
5. 列抽样(column subsampling)。xgboost借鉴了随机森林的做法,支持列抽样,不仅能降低过拟合,还能减少计算,这也是xgboost异于传统gbdt的一个特性
6. 对缺失值的处理。对于特征的值有缺失的样本,xgboost可以自动学习出它的分裂方向。
7. xgboost工具支持并行。boosting不是一种串行的结构吗?怎么并行的?注意xgboost的并行不是tree粒度的并行,xgboost也是一次迭代完才能进行下一次迭代的(第t次迭代的代价函数里包含了前面t-1次迭代的预测值)。xgboost的并行是在特征粒度上的。我们知道,决策树的学习最耗时的一个步骤就是对特征的值进行排序(因为要确定最佳分割点),xgboost在训练之前,预先对数据进行了排序,然后保存为block结构,后面的迭代中重复地使用这个结构,大大减小计算量。这个block结构也使得并行成为了可能,在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行。
8. 可并行的近似直方图算法。树节点在进行分裂时,我们需要计算每个特征的每个分割点对应的增益,即用贪心法枚举所有可能的分割点。当数据无法一次载入内存或者在分布式情况下,贪心算法效率就会变得很低,所以xgboost还提出了一种可并行的近似直方图算法,用于高效地生成候选的分割点。大致的思想是根据百分位法列举几个可能成为分割点的候选者,然后从候选者中根据上面求分割点的公式计算找出最佳的分割点.
9. 在XGBoost里,对于稀疏性的离散特征,在寻找split point的时候,不会对该特征为missing的样本进行遍历统计,只对该列特征值为non-missing的样本上对应的特征值进行遍历,通过这个工程trick来减少了为稀疏离散特征寻找split point的时间开销。在逻辑实现上,为了保证完备性,会分别处理将missing该特征值的样本分配到左叶子结点和右叶子结点的两种情形。
2),GBDT与RF区别:
1、组成随机森林的树可以是分类树,也可以是回归树;而GBDT只由回归树组成,GBDT的会累加所有树的结果,而这种累加是无法通过分类完成的,因此GBDT的树都是CART回归树,而不是分类树(尽管GBDT调整后也可以用于分类但不代表GBDT的树为分类树)
2、组成随机森林的树可以并行生成;而GBDT只能是串行生成
3、对于最终的输出结果而言,随机森林采用多数投票等;而GBDT则是将所有结果累加起来,或者加权累加起来
4、随机森林对异常值不敏感,GBDT对异常值非常敏感
5、随机森林对训练集一视同仁,GBDT是基于权值的弱分类器的集成
6、随机森林是通过减少模型方差提高性能,GBDT是通过减少模型偏差提高性能
3),RF的优缺点:
1、容易理解和解释,树可以被可视化。
2、不需要太多的数据预处理工作,即不需要进行数据归一化,创造哑变量等操作。
3、隐含地创造了多个联合特征,并能够解决非线性问题。
4、和决策树模型,GBDT模型相比,随机森林模型不容易过拟合。
5、自带out-of-bag (oob)错误评估功能。 RF的重要特性是不用对其进行交叉验证或者使用一个独立的测试集获得无偏估计,它可以在内部进行评估,也就是说在生成的过程中可以对误差进行无偏估计,由于每个基学习器只使用了训练集中约63.2%的样本,剩下约36.8%的样本可用做验证集来对其泛化性能进行‘包外估计’。
6、易于并行化。
RF和Bagging对比:RF的起始性能较差,特别当只有一个基学习器时,随着学习器数目增多,随机森林通常会收敛到更低的泛化误差。随机森林的训练效率也会高于Bagging,因为在单个决策树的构建中,Bagging使用的是‘确定性’决策树,在选择特征划分结点时,要对所有的特征进行考虑,而随机森林使用的是‘随机性’特征数,只需考虑特征的子集
RF的缺点
随机森林的缺点:
1、不适合小样本,只适合大样本。
2、大多数情况下,RF模型的精度略低于GBDT模型的精度。
3、适合决策边界是矩形的,不适合对角线型的。
预剪枝方法:
a.节点达到完全纯度
b.树的深度达到用户所要的深度
c.节点中样本个数少于用户指定个数
d.不纯度指标下降的最大幅度小于用户指定的幅度
后剪枝方法:
CART:Cost-Complexity Pruning(代价-复杂度剪枝法)
XGB对特征重要性的评价:
XGBoost的特征重要性是如何得到的?某个特征的重要性(feature score),等于它被选中为树节点分裂特征的次数的和,比如特征A在第一次迭代中(即第一棵树)被选中了1次去分裂树节点,在第二次迭代被选中2次 ….. 那么最终特征A的featurescore就是 1+2+… 。
三、XGBoost算法的python实现
共分成5步:1, 加载数据;2,实例化xgb分类器对象,并训练模型;3,预测;4,网格调参;5,XGBoost的核心思想。
from sklearn import datasets
import numpy as np
from xgboost.sklearn import XGBClassifier
from xgboost.sklearn import XGBRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 1, 加载数据:
myDatas = datasets.load_iris()
X_train,X_test,y_train,y_test = train_test_split( #划分训练集、测试集
myDatas.data,myDatas.target, #load_iris的原始数据集
test_size = 0.3,
random_state = 7
)
# 2,实例化xgb分类器对象,并训练模型:
clfXgb = XGBClassifier(n_estimators=10,max_depth=3,learning_rate=0.1) #learning_rate = 0.1 根据经验是最合适的学习率,精确度0.93,改成0.5,精确度降为0.91了。
# 若将xgb用于回归,与分类类似,只需实例化模型:rXgb = XGBRegressor(n_estimators=10,max_depth=3)
clfXgb.fit(X_train,y_train) #训练分类
# 3,预测
clfXgbPred = clfXgb.predict(X_test)
acc = accuracy_score(y_test,clfXgbPred) #评估得分
print("预测精确度:",acc)
# 4,网格调参:
#模型建好以后要选一些合适的参数,让模型最优(损失最小)才是目的,
#然后把这些参数应用到模型,重新建模保存,服务于更多任务的测试工作==
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import StratifiedKFold
myMode = XGBClassifier()#正常这里是不会人为揸定参数,要求最合适参数,上面实例只是根据经验传了固定参数展示分类实现,
#以学习率为例,找一个最合适的学习率
#设几个不同学习率的列表,后面来遍历它,看哪个学习率下分类精确度最高,就用哪个学习率代回模型重新建模
learning_rate=[0.0001,0.001,0.1,0.2,0.3]
#这次使用交叉验证(交替充份使用有限数据)划分数据集
#实例化交叉验证类
kfold = StratifiedKFold(n_splits=2,shuffle=True,random_state=7)
#n_splits分成几组测试验证对
#实例化网格调参类(传入交叉验实例对象及XGB分类对象)
grid_search = GridSearchCV(myMode,#传入XGB分类对象
dict(learning_rate=learning_rate),#这里要字典格式打包传参
scoring = 'neg_log_loss',#评估损失函数选择
n_jobs = 1,#当前所有空闲CPU都去跑这个模型
cv = kfold#指定交叉验证实例对象
)
#用最终结合好的对象fit原始数据即可自动完成交叉验证并调参
gridRs = grid_search.fit(myDatas.data,myDatas.target)
#打印最优学习率和其得分
print("最优学习率: %s ,得分:%f " % (gridRs.best_params_,gridRs.best_score_))
means = gridRs.cv_results_['mean_test_score']
params = gridRs.cv_results_['params']
#打印平均分
print("每次迭代的平均值:",means)
print("对应的本次迭代学习率参数:",params)
'''
#以上调参完即可确定最优的learning_rate在模型中使用了,代回去再次建模,
#才可得到开篇中的最精确的预测值。
#试下把开篇learning_rate = 0.1 改成0.5,精确度从0.93降为0.91了。
#此模型才可用于其它同类任务的预测工作,总的流程是这样的。
#此处只调了一个参数举例,其它参数必要时也要调
'''
# 5,XGBoost的核心思想
# 下面通过每一步的test预测值,看是否XGBoost每加一棵树都会让集成学习效果优化提升(这是XGBoost的核心思想)。
eval_set = [(X_test,y_test)] # 构造一个测试集
clfXgb.fit(X_train,y_train,early_stopping_rounds=3,
eval_metric='mlogloss',eval_set = eval_set,verbose = True)
#参数:模型饱和后再加3次停止该模型
#指定mlogloss为损失函数,用来做模型优化标准,使logloss最小。
#测试值
clfXgbPred_2 = clfXgb.predict(X_test)
#把预测值装进预测值列表
predictions = [round(v) for v in clfXgbPred_2]
#遍历预测结果评估
acc_2 = accuracy_score(y_test,predictions)#每个测试结果和它对应的所有预测值比较分别评估
print("\n预测精确度:",acc_2)
#由结果可见XGB的确是在每步加入新村时使得集成学习向优化提升(损失越来越小,预测越来越接近真实值)
#另外上面设了early_stopping_rounds 为3 说明从底下往上数3个0.56时模型就已经是饱和状态了。
#==下面看一个xgboost的功能
#plot_importantce,
#可以查看特征重要性==
from xgboost import plot_importance
from matplotlib import pyplot
model = XGBClassifier() #实例化分类器对象
model.fit(myDatas.data,myDatas.target) #对象fit传原始数据集即可
rs = plot_importance(model) #算特征重要性
pyplot.show(rs) # 可视化
#图上列出了4个特征重要性