集成学习机器学习,集成机器学习是什么
终极管理员 知识笔记 112阅读
文章目录 集成学习BoostingAdaBoost梯度提升树GBDTXGBoostxgboost库sklearn APIxgboost库xgboost应用
集成学习

集成学习ensemble learning的算法主要包括三大类装袋法Bagging
提升法Boosting
和stacking
。
在博客【机器学习】集成学习(以随机森林为例)里面主要写到了什么是集成学习以及以随机森林为代表的bagging算法感兴趣的可以事先阅读一下。

Boosting 的过程很类似于人类学习的过程我们学习新知识的过程往往是迭代式的第一遍学习的时候我们会记住一部分知识但往往也会犯一些错误对于这些错误我们的印象会很深。第二遍学习的时候就会针对犯过错误的知识加强学习以减少类似的错误发生。不断循环往复直到犯错误的次数减少到很低的程度 。
Boosting算法的工作机制
首先从训练集用初始权重训练出一个弱学习器1根据弱学习的学习误差率表现来更新训练样本的权重使得之前弱学习器1学习误差率高的训练样本点的权重变高使得这些误差率高的点在后面的弱学习器2中得到更多的重视。
然后基于调整权重后的训练集来训练弱学习器2.如此重复进行直到弱学习器数达到事先指定的数目N最终将这N个弱学习器通过集合策略进行整合得到最终的强学习器。
Boosting的重点在于取新模型之长补旧模型之短来降低偏差bias尽可能获得无偏估计。
AdaBoostAdaBoost是Adaptive Boosting的缩写即自适应提升法。
算法步骤
Step1: Initialise the dataset and assign equal weight to each of the data point.Step2: Provide this as input to the model and identify the wrongly classified data points.Step3: Increase the weight of the wrongly classified data points.Step4: if (got required results) Goto step 5 else Goto step 2 Step5: End
初始的时候赋予每个训练样本相同的权重然后每次迭代后增加分类错误样本的权重数据集还是原来的数据集只不过各个样本的权重变了使得下一轮迭代时更加关注这些样本。
最终我们需要将这些基础学习器集成在一起自然就是哪个学习器学习性能好哪一个系数大一些啦。这里的学习性能就要通过误分类率来评定了误分类率小的学习器我们就用的多一些误分类率大的学习器我们就少用一些这就是加权多数表决策略最后得到最终学习器。
AdaBoost一般选用决策树桩decision stump作为弱学习器。所谓stump是指由一个决策节点和两个叶子节点组成的二叉树
树桩可以理解为在某一处“划一刀”左边为一类右边为一类可以看下面那个例子。
以二分类问题的学习过程示例包含了三轮迭代
具体算法描述及示例
输入 训练集数据集 x 1 , y 1 , . . . , x m , y m x_1,y_1,...,x_m,y_m x1,y1,...,xm,ym共m个样本 x i x_i xi属性 y i y_i yi标签取值是-1或1.
初始化 D i 1 / m D_i1/m Di1/m初始状态所有样本的权重相同 D i D_i Di表示第 i i i个样本的权重。
迭代循环
1使用权值分布为 D t D_t Dt的数据集训练得到一个弱分类器 h t h_t ht.
2计算 h t h_t ht在训练数据上面的误差 ε t ε_t εt.
3根据训练误差 ε t ε_t εt计算出权重调整系数 α t α_t αt
4更新所有训练样本的权重用于下一次迭代.
5当训练误差比较低(比如错误率为0或者弱分类器数目达到预先的设置值(超参控制)就停止循环
最终的分类器就是各个各个弱分类器的加权求和如果是分类问题就再加上符号函数sign
(输入数据大于0输出1等于0输出0小于0输出-1) .
训练误差 ε t ε_t εt
其中 I ( y i ≠ h t ( x i ) I(y_i\neq h_t(x_i) I(yiht(xi))的含义是如果 y i ≠ h t ( x i ) y_i\neq h_t(x_i) yiht(xi) 成立则返回1否则返回0。
权重一般是做过归一化的所以分母里面的所有权重之和其实为1而且分子中分类正确的样本的 I I I值为0也就是上面的公式可以简化为
调整系数 α t α_t αt
α t 1 2 ln 1 − ε t ε t α_t \frac{1}{2} \ln \frac{1-ε_t}{ε_t} αt21lnεt1−εt
当弱分类器的准确率为0.5时其权重为0准确率大于0.5即错误率小于0.5时权重大于0准确率小于0.5即错误率大于0.5时权重小于0。错误率 ε ε ε越小 α α α越大即当前模型的表现越好在最终的生成器中占的权重就越大。
样本权重更新:
分析
当弱分类器在某个样本上分类正确的时候该样本的权重会降低否则就会提升符合理论预期。
增加了预测错误的样本的权重是为了在下一轮迭代中更关注他们主要有两种方式来实现
1N个样本的数据集每一轮选择本轮使用的mm < N个根据权重随机采样这样权重大的样本就更容易被选中甚至选中多次了。
2原来有N个样本每次选N个样本进行弱学习器的训练。第一轮大家权重一样第二轮的时候根据权重先将原数据集划分“Bucket”权重高的样本会占据多个bucket。每个bucket被选中的几率是一样的所以权重高的样本就可能被多选几次。
每轮迭代改变了样本的权重后对下一轮的影响是体现在挑选数据集的时候。当数据集选好后这些数据集又会被赋予相同的权重开始新一轮的迭代。后续不断按此方式迭代直到错误率降到某个阈值或者达到预设的迭代次数。
梯度提升树GBDTAdaboost是利用前一轮迭代弱学习器的误差率来更新训练集的权重这样一轮轮的迭代下去。GBDT也是迭代使用了前向分布算法但是弱学习器限定了只能使用CART
回归树模型同时迭代思路和Adaboost也有所不同。
GB里面的重点在于每次迭代的时候是拟合残差。所谓残差residual就是真实值和预测值的差值 r e s i d u a l i y i − f ( x i ) residual_iy_i−f(xi_) residualiyi−f(xi)它表示了数据模型中所不可能刻画的部分。所以GB的思想大致可以描述如下
1)初始先有一个初始的弱学习器然后用这个学习器去训练样本上面进行预测得到每个样本的残差。
2)第一轮用训练样本作为属性第一轮得到的残差作为target而不是原始样本的target训练得到第2个弱学习器。然后用这个弱学习器对样本进行预测得到预测值并计算与真实值的残差作为一下轮样本的target。
3)第N轮重复前面的步骤。每次变化的是使用上一轮计算的残差作为本轮样本的target。
4)N次迭代后得到最终的强学习器。
弱学习器CART
回归树模型是回归模型的我们一般使用均方误差作为损失函数即
L ( θ ) 1 2 ∑ i 0 n ( f ( x i ) − y i ) L(\theta) \frac{1}{2}\sum_{i0}^n(f(x_i)-y_i) L(θ)21i0∑n(f(xi)−yi)
采用梯度下降方式求解最最佳的模型
中间的 ( f ( x i ) − y i ) (f(x_i)-y_i) (f(xi)−yi)加个负号就是残差对残差的拟合其实就是对负梯度的拟合根据残差来更新集成后的模型实际就是根据负梯度来更新。
尽管这里残差和负梯度的值完全一样但二者代表的含义却是不一样的负梯度指向的是单个模型参数更新的方向残差即梯度提升则表示了集成模型下一个模型的拟合目标。梯度的不断下降可以让模型的参数逐渐收敛到最优参数上而残差的不断拟合则让集成之后的模型越来越解决真实数据的生成机制。
梯度下降是参数空间的优化针对单个模型的参数优化找到最优的一组参数
梯度提升是函数空间的优化用多个模型的组合来逼近问题的解每个模型都是一个函数
可以从损失函数的负梯度角度去构造更丰富的提升方法即构造更多的损失函数。因为基于残差的损失函数有一个明显的缺点就是对异常值比较敏感。对于异常值后续模型会对这个值关注过多这样并不好所以一般回归类的损失函数会使用绝对损失absolute loss或者huber损失函数huber loss 来代替平方损失函数
梯度提升中则将权重换成了梯度.
具体算法流程
完整的迭代公式
XGBoosteXtreme Gradient Boosting是对GBDT的优化和工程化的实现。优化可分为算法优化和工程实现方面的优化
1.Algorithmic Enhancements: 算法改进a)Regularization: It penalizes more complex models through both LASSO (L1) and Ridge (L2) regularization to prevent overfitting.正则化 它通过 LASSOL1和 RidgeL2正则化对更复杂的模型进行惩罚以防止过度拟合。b)Sparsity Awareness: XGBoost naturally admits sparse features for inputs by automatically ‘learning’ best missing value depending on training loss and handles different types of sparsity patterns in the data more efficiently.稀疏意识 XGBoost 可根据训练损失自动 学习 最佳缺失值从而自然地接受稀疏特征输入并更高效地处理数据中不同类型的稀疏模式。c)Weighted Quantile Sketch: XGBoost employs the distributed weighted Quantile Sketch algorithm to effectively find the optimal split points among weighted datasets.加权量子草图 XGBoost 采用分布式加权 Quantile Sketch 算法能有效地在加权数据集中找到最佳分割点。d)Cross-validation: The algorithm comes with built-in cross-validation method at each iteration, taking away the need to explicitly program this search and to specify the exact number of boosting iterations required in a single run.交叉验证 该算法在每次迭代时都内置了交叉验证方法因此无需对这一搜索进行明确编程也无需指定单次运行中所需的精确提升迭代次数。 2.System Optimization 系统优化a)Parallelization: XGBoost approaches the process of sequential tree building using parallelized implementation. This is possible due to the interchangeable nature of loops used for building base learners; the outer loop that enumerates the leaf nodes of a tree, and the second inner loop that calculates the features. This nesting of loops limits parallelization because without completing the inner loop (more computationally demanding of the two), the outer loop cannot be started. Therefore, to improve run time, the order of loops is interchanged using initialization through a global scan of all instances and sorting using parallel threads. This switch improves algorithmic performance by offsetting any parallelization overheads in computation.并行化 XGBoost 采用并行化实现方式来处理顺序树构建过程。之所以能做到这一点是因为用于构建基础学习器的循环具有可互换性外循环用于枚举树的叶节点第二个内循环用于计算特征。循环的这种嵌套限制了并行化因为如果不完成内循环这两个循环对计算要求更高就无法启动外循环。因此为了缩短运行时间我们通过对所有实例进行全局扫描和使用并行线程进行排序的初始化方法来交换循环的顺序。这种切换可以抵消计算中的并行化开销从而提高算法性能。b)Tree Pruning: The stopping criterion for tree splitting within GBM framework is greedy in nature and depends on the negative loss criterion at the point of split. XGBoost uses ‘max_depth’ parameter as specified instead of criterion first, and starts pruning trees backward. This ‘depth-first’ approach improves computational performance significantly.树形修剪 在 GBM 框架内树分割的停止准则本质上是贪婪的取决于分割点的负损失准则。XGBoost 使用指定的 max_depth 参数而不是准则优先并开始向后修剪树。这种 深度优先 的方法大大提高了计算性能。c)Hardware Optimization: This algorithm has been designed to make efficient use of hardware resources. This is accomplished by cache awareness by allocating internal buffers in each thread to store gradient statistics. Further enhancements such as ‘out-of-core’ computing optimize available disk space while handling big data-frames that do not fit into memory.硬件优化 该算法旨在有效利用硬件资源。通过在每个线程中分配内部缓冲区来存储梯度统计数据从而实现缓存感知。进一步的增强功能如 核外 计算可优化可用磁盘空间同时处理内存中无法容纳的大数据帧。
XGBoost算法主要是对目标函数两个方面的优化1.加了一个正则项2.使用泰勒公式展开
其中 T T T为树f的叶节点个数 ω ω ω为所有叶节点输出回归值构成的向量 γ , λ γ,λ γ,λ为超参数。添加正则项可以防止过拟合、降低方差获取更好的泛化效果。
泰勒公式
pip install xgboost
XGBoost本身的核心是基于梯度提升树实现的集成算法整体来说可以有三个核心部分集成算法本身用于集成的弱评估器以及应用中的其他过程。三个部分中前两个部分包含了XGBoost的核心原理以及数学过程最后的部分主要是在XGBoost应用中占有一席之地。
使用XGboost我们通常有两个库一个是xgboost库一个是sklearn里面封装的XGboost类。
使用xgboost中设定的建模流程来建模和使用sklearnAPI中的类来建模模型效果是比较相似的但是xgboost库本身的运算速度尤其是交叉验证以及调参手段比sklearn要简单。
XGBoost是基于梯度提升树算法。
集成算法通过在数据上构建多个弱评估器汇总所有弱评估器的建模结果以获取比单个模型更好的回归或分类表现。
弱评估器被定义为是表现至少比随机猜测更好的模型即预测准确率不低于50%的任意模型。
集成不同弱评估器的方法有很多种。
随机森林是一次性建立多个平行独立的弱评估器的装袋法。
提升法是逐一构建弱评估器经过多次迭代逐渐累积多个弱评估器的方法。提升法的中最著名的算法包括Adaboost和梯度提升树XGBoost就是由梯度提升树发展而来的。梯度提升树中可以有回归树也可以有分类树两者都以CART树算法作为主流XGBoost背后也是CART树这意味着XGBoost中所有的树都是二叉的。
以梯度提升回归树为例
梯度提升回归树是专注于回归的树模型的提升集成模型其建模过程大致如下最开始先建立一棵树然后逐渐迭代每次迭代过程中都增加一棵树逐渐形成众多树模型集成的强评估器。
对于决策树而言每个被放入模型的任意样本 i i i最终一个都会落到一个叶子节点上。而对于回归树每个叶子节点上的值是这个叶子节点上所有样本的均值。
对于梯度提升回归树来说每个样本的预测结果可以表示为所有树上的结果的加权求和
其中 K K K是树的总数量 k k k代表第 k k k棵树 γ k \gamma_k γk 是这棵树的权重 h k h_k hk表示这棵树上的预测结果。
XGB作为GBDT的改进在预测 y ^ \hat y y^上却有所不同。对于XGB来说每个叶子节点上会有一个预测分数prediction score也被称为叶子权重。这个叶子权重就是所有在这个叶子节点上的样本在这一棵树上的回归取值用 f k ( x i ) f_k(x_i) fk(xi) 或者 w w w来表示其中 f k f_k fk表示第 k k k棵决策树 x i x_i xi表示样本 i i i对应的特征向量。
当只有一棵树的时候 f 1 ( x i ) f_1(x_i) f1(xi)就是提升集成算法返回的结果但这个结果往往非常糟糕。当有多棵树的时候集成模型的回归结果就是所有树的预测分数之和假设这个集成模型中总共有 K K K棵决策树则整个模型在这个样本 i i i上给出的预测结果为
求解预测值 y ^ \hat y y^的方式不同 是XGBosst与GBDT核心区别
GBDT中预测值是由所有弱分类器上的预测结果的加权求和其中每个样本上的预测结果就是样本所在的叶子节点的均值。而XGBT中的预测值是所有弱分类器上的叶子权重直接求和得到计算叶子权重是一个复杂的过程。
在集成中我们需要的考虑的第一件事是我们的超参数K究竟要建多少棵树
n_estimators
越大模型的学习能力就会越强模型也越容易过拟合。
class xgboost.XGBRegressor (max_depth3, learning_rate0.1, n_estimators100, silentTrue,objectivereg:linear, boostergbtree, n_jobs1, nthreadNone, gamma0, min_child_weight1, max_delta_step0,subsample1, colsample_bytree1, colsample_bylevel1, reg_alpha0, reg_lambda1, scale_pos_weight1,base_score0.5, random_state0, seedNone, missingNone, importance_typegain, **kwargs)
导入的库
from xgboost import XGBRegressor as XGBR # sklearn apifrom sklearn.datasets import fetch_california_housing # 加利福尼亚房价数据集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 pdimport numpy as npimport matplotlib.pyplot as pltfrom time import timeimport datetime
数据集处理
housing_california fetch_california_housing()X housing_california.data # datay housing_california.target # labelXtrain,Xtest,Ytrain,Ytest TTS(X,y,test_size0.3,random_state420)
模型
reg XGBR(n_estimators100).fit(Xtrain,Ytrain)
reg.predict(Xtest) #传统接口predict 返回预测值
评估
reg.score(Xtest,Ytest) #评估指标 r^2
MSE(Ytest,reg.predict(Xtest)) # 评估指标 均方误差
reg XGBR(n_estimators100) # 交叉验证导入没有经过训练的模型CVS(reg,Xtrain,Ytrain,cv5).mean() # 评估指标 与模型的score接口返回相同的指标这里r平方
CVS(reg,Xtrain,Ytrain,cv5,scoringneg_mean_squared_error).mean() # 指定其他评价指标 neg_mean_squared_error 负均方误差
属性
eg.feature_importances_# 树模型的优势之一能够查看模型的重要性分数# xgboost可以使用嵌入法(SelectFromModel)进行特征选择
重要参数n_estimators
画学习曲线来找出最佳的n_estimators
参数
1定义绘制以训练样本数为横坐标的学习曲线的函数
def plot_learning_curve(estimator, # 模型 title, # 图标题 X, # 训练集 y, # 标签 axNone, #选择子图 ylimNone, #设置纵坐标的取值范围 cvNone, #交叉验证 n_jobsNone #设定索要使用的线程 ): 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 ,shuffleTrue ,cvcv ,random_state420 ,n_jobsn_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, axis1), o- , colorr,labelTraining score) ax.plot(train_sizes, np.mean(test_scores, axis1), o- , colorg,labelTest score) ax.legend(locbest) return ax
2交叉验证画图
cv KFold(n_splits5, shuffle True, random_state42) # 5折 交叉验证 打乱顺序plot_learning_curve(XGBR(n_estimators100,random_state420) ,XGB,Xtrain,Ytrain,axNone,cvcv)plt.show() # 过拟合 训练集表现很好 测试集不太好
训练集的效果要比测试集好很多模型过拟合探讨一下效果最好的学习曲线是什么样的
第3个图是最想达到的但现实数据集很难做到往往还是通过来降低训练集的效果提高测试的效果逼近两者如图2所示。
3大范围调参
axisx range(10,1010,50)rs []for i in axisx: reg XGBR(n_estimatorsi,random_state420) rs.append(CVS(reg,Xtrain,Ytrain,cvcv).mean())print(axisx[rs.index(max(rs))],max(rs))plt.figure(figsize(20,5))plt.plot(axisx,rs,cred,labelXGB)plt.legend()plt.show()
参数应该再210附近缩小范围继续调参。
4增加考虑因素
在机器学习中我们用来衡量模型在未知数据上的准确率的指标叫做泛化误差Genelization error。
一个集成模型(f)在未知数据集(D)上的泛化误差 E ( f ; D ) E(f;D) E(f;D)由 方差(var)偏差(bais)和噪声(ε) 共同决定。其中偏差就是训练集上的拟合程度决定方差是模型的稳定性决定噪音是不可控的。而泛化误差越小模型就越理想。
要考虑泛化误差中我们可控的部分:
a)学习曲线获得的分数的最高点即考虑偏差最小的点.
b)模型会相对不稳定因此我们应当将方差也纳入考虑的范围.
不仅要考虑偏差的大小还要考虑方差的大小
axisx range(50,1050,50)rs []var []ge []for i in axisx: reg XGBR(n_estimatorsi,random_state420) cvresult CVS(reg,Xtrain,Ytrain,cvcv) #记录1-偏差 rs.append(cvresult.mean()) #记录方差 var.append(cvresult.var()) #计算泛化误差的可控部分 ge.append((1 - cvresult.mean())**2cvresult.var())#打印R2最高所对应的参数取值并打印这个参数下的方差print(axisx[rs.index(max(rs))],max(rs),var[rs.index(max(rs))])#打印方差最低时对应的参数取值并打印这个参数下的R2print(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,cred,labelXGB)plt.legend()plt.show()
可以看得在200附近缩小范围继续调参。【方差非常非常小其实可以不考虑这个因素】
【偏差-模型的准确率方差-模型的稳定性】
5缩小范围
axisx range(100,300,10)rs []var []ge []for i in axisx: reg XGBR(n_estimatorsi,random_state420) cvresult CVS(reg,Xtrain,Ytrain,cvcv) rs.append(cvresult.mean()) var.append(cvresult.var()) ge.append((1 - cvresult.mean())**2cvresult.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.01plt.figure(figsize(20,5))plt.plot(axisx,rs,cblue,labelXGB)#添加方差线plt.plot(axisx,rsvar,cred,linestyle-.)plt.plot(axisx,rs-var,cred,linestyle-.)plt.legend()plt.show()
三条线重合方差真的没什么影响选定n_estimators190
单独查看一下可控误差的曲线图
#看看泛化误差的可控部分如何plt.figure(figsize(20,5))plt.plot(axisx,ge,cgray,linestyle-.)plt.show()
确定190的时候最低
6设定调好的参数 检测模型效果
time0 time()print(XGBR(n_estimators190,random_state420).fit(Xtrain,Ytrain).score(Xtest,Ytest))print(time()-time0)
准确率确实提升了。
7总结
aXGBoost中的树的数量决定了模型的学习能力树的数量越多模型的学习能力越强。只要XGB中树的数量足够了即便只有很少的数据 模型也能够学到训练数据100%的信息所以XGB也是天生过拟合的模型。但在这种情况下模型会变得非常不稳定。
bXGB中树的数量很少的时候对模型的影响较大当树的数量已经很多的时候对模型的影响比较小只能有微弱的变化。当数据本身就处于过拟合的时候再使用过多的树能达到的效果甚微反而浪费计算资源。当唯一指标或者准确率给出的n_estimators
看起来不太可靠的时候我们可以改造学习曲线来帮助我们。
c树的数量提升对模型的影响有极限最开始模型的表现会随着XGB的树的数量一起提升但到达某个点之后树的数量越多模型的效果会逐步下降这也说明了暴力增加n_estimators
不一定有效果。
这些都和随机森林中的参数n_estimators表现出一致的状态。在随机森林中我们总是先调整n_estimators当n_estimators的极限已达到我们才考虑其他参数但XGB中的状况明显更加复杂当数据集不太寻常的时候会更加复杂。
建议优先调整n_estimators
一般都不会建议一个太大的数目300以下为佳。
subsample
【有放回随机抽样】 我们都知道树模型是天生过拟合的模型并且如果数据量太过巨大树模型的计算会非常缓慢因此我们要对我们的原始数据集进行有放回抽样bootstrap。有放回的抽样每次只能抽取一个样本若我们需要总共N个样本就需要抽取N次。每次抽取一个样本的过程是独立的这一次被抽到的样本会被放回数据集中下一次还可能被抽到因此抽出的数据集中可能有一些重复的数据。
有放回抽样都是我们防止过拟合让单一弱分类器变得更轻量的必要操作。实际应用中每次抽取50%左右的数据就能够有不错的效果了。
在梯度提升树中我们每一次迭代都要建立一棵新的树因此我们每次迭代中都要有放回抽取一个新的训练样本。不过这并不能保证每次建新树后集成的效果都比之前要好。因此我们规定在梯度提升树中每构建一个评估器都让模型更加集中于数据集中容易被判错的那些样本。来看看下面的这个过程。
在建第一棵树时我们对数据进行初次又放回抽样然后建模。建模完毕后我们对模型进行一个评估然后将模型预测错误的样本反馈给我们的数据集一次迭代就算完成。紧接着我们要建立第二棵决策树于是开始进行第二次又放回抽样。但这次有放回抽样和初次的随机有放回抽样就不同了在这次的抽样中我们加大了被第一棵树判断错误的样本的权重。也就是说被第一棵树判断错误的样本更有可能被我们抽中。
基于这个有权重的训练集来建模我们新建的决策树就会更加倾向于这些权重更大的很容易被判错的样本。不停的迭代这个过程
在sklearn中我们使用参数subsample
来控制我们的随机抽样。在xgb和sklearn中这个参数都默认为1且不能取到0这说明我们无法控制模型是否进行随机有放回抽样只能控制抽样抽出来的样本量大概是多少。
采样会减少样本数量而从学习曲线来看样本数量越少模型的过拟合会越严重因为对模型来说数据量越少模型学习越容易学到的规则也会越具体越不适用于测试样本。所以subsample
参数通常是在样本量本身很大的时候来调整和使用。
代码测试
axisx np.linspace(0,1,20)rs []for i in axisx: reg XGBR(n_estimators190,subsamplei,random_state420) rs.append(CVS(reg,Xtrain,Ytrain,cvcv).mean())print(axisx[rs.index(max(rs))],max(rs))plt.figure(figsize(20,5))plt.plot(axisx,rs,cgreen,labelXGB)plt.legend()plt.show()
其实添加了这个参数模型的效果还变差了一点数据量不多直接用全部数据当然还可以细化一下参数再看看
#细化学习曲线axisx np.linspace(0.05,1,20)rs []var []ge []for i in axisx: reg XGBR(n_estimators190,subsamplei,random_state420) cvresult CVS(reg,Xtrain,Ytrain,cvcv) rs.append(cvresult.mean()) var.append(cvresult.var()) ge.append((1 - cvresult.mean())**2cvresult.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,cblack,labelXGB)plt.plot(axisx,rsvar,cred,linestyle-.)plt.plot(axisx,rs-var,cred,linestyle-.)plt.legend()plt.show()
确实是全数据效果好一点。
重要参数eta
learning_rate
【迭代决策树】在XGB中我们完整的迭代决策树的公式应该写作
我们让 k k k棵树的集成结果 y ^ i k \hat y^{k}_i y^ik加上我们新建的树上的叶子权重 f k 1 ( x i ) f_{k1}(x_i) fk1(xi)就可以得到第 k 1 k1 k1次迭代后总共 k 1 k1 k1棵树的预测结果 y ^ i k 1 \hat y^{k1}_i y^ik1了。其中 η \eta η 读作eta是迭代决策树时的步长shrinkage又叫做学习率learning rate。
η \eta η越大迭代的速度越快算法的极限很快被达到有可能无法收敛到真正的最佳。 η \eta η越小越有可能找到更精确的最佳值更多的空间被留给了后面建立的树但迭代速度会比较缓慢。
sklearn代码
# 首先我们先来定义一个评分函数这个评分函数能够帮助我们直接打印Xtrain上的交叉验证结果def regassess(reg,Xtrain,Ytrain,cv,scoring [r2],showTrue): score [] for i in range(len(scoring)): if show: print({}:{:.2f}.format(scoring[i] ,CVS(reg ,Xtrain,Ytrain ,cvcv,scoringscoring[i]).mean())) score.append(CVS(reg,Xtrain,Ytrain,cvcv,scoringscoring[i]).mean()) return score
# 运行一下函数来看看效果regassess(reg,Xtrain,Ytrain,cv,scoring [r2,neg_mean_squared_error])
#关闭打印功能regassess(reg,Xtrain,Ytrain,cv,scoring [r2,neg_mean_squared_error],showFalse)
#观察一下eta如何影响我们的模型from time import timeimport datetimefor i in [0,0.2,0.5,1]: time0time() reg XGBR(n_estimators190,random_state420,learning_ratei) print(learning_rate {}.format(i)) regassess(reg,Xtrain,Ytrain,cv,scoring [r2,neg_mean_squared_error]) print(datetime.datetime.fromtimestamp(time()-time0).strftime(%M:%S:%f)) print(\t)
axisx np.arange(0.05,1,0.05)rs []te []for i in axisx: reg XGBR(n_estimators190,random_state420,learning_ratei) score regassess(reg,Xtrain,Ytrain,cv,scoring [r2,neg_mean_squared_error],showFalse) 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,cgray,labelXGB)plt.plot(axisx,rs,cgreen,labelXGB)plt.legend()plt.show()
虽然从图上来说默认的0.1看起来是一个比较理想的情况并且看起来更小的步长更利于现在的数据但我们也无法确定对于其他数据会有怎么样的效果。所以通常我们不调整 η \eta η 即便调整一般它也会在[0.01,0.2]之间变动。如果我们希望模型的效果更好更多的可能是从树本身的角度来说对树进行剪枝而不会寄希望于调整 η \eta η。
booster
梯度提升算法中不只有梯度提升树XGB作为梯度提升算法的进化自然也不只有树模型一种弱评估器。在XGB中除了树模型我们还可以选用线性模型比如线性回归来进行集成。虽然主流的XGB依然是树模型但我们也可以使用其他的模型。基于XGB的这种性质我们有参数“booster来控制我们究竟使用怎样的弱评估器。
for booster in [gbtree,gblinear,dart]: reg XGBR(n_estimators190 ,learning_rate0.1 ,random_state420 ,boosterbooster).fit(Xtrain,Ytrain) print(booster) print(reg.score(Xtest,Ytest))
重要参数objective
【目标函数】 不同于逻辑回归和SVM等算法中固定的损失函数写法集成算法中的损失函数是可选的要选用什么损失函数取决于我们希望解决什么问题以及希望使用怎样的模型。
如果我们的目标是进行回归预测那我们可以选择调节后的均方误差RMSE作为我们的损失函数。如果我们是进行分类预测那我们可以选择错误率error或者对数损失log_loss。只要我们选出的函数是一个可微的能够代表某种损失的函数它就可
以是我们XGB中的损失函数。
损失函数的核心是衡量模型的泛化能力即模型在未知数据上的预测的准确与否。XGB引入了模型复杂度来衡量算法的运算效率。XGB的目标函数被写作传统损失函数 模型复杂度。【XGB的是实现了模型表现和运算速度的平衡的算法。】
其中 i i i代表数据集中的第 i i i个样本 m m m表示导入第 k k k棵树的数据总量 K K K代表建立的所有树(n_estimators)当只建立了 t t t棵树的时候式子应当为 ∑ k 1 t Ω ( f k ) \sum_{k1}^t \Omega (f_k) ∑k1tΩ(fk)。第一项代表传统的损失函数衡量真实标签 y i y_i yi 与预测值 y ^ i \hat y_i y^i之间的差异通常是RMSE调节后的均方误差。第二项代表模型的复杂度使用树模型的某种变换 Ω \Omega Ω 表示这个变化代表了一个从树的结构来衡量树模型的复杂度的式子可以有多种定义。注意我们的第二项中没有特征矩阵 x i x_i xi的介入。我们在迭代每一棵树的过程中都最小化 O b j Obj Obj来力求获取最优的 y ^ \hat y y^因此我们同时最小化了模型的错误率和模型的复杂度。
与其他算法一样我们最小化目标函数以求模型效果最佳并且我们可以通过限制n_estimators来限制我们的迭代次数因此我们可以看到生成的每棵树所对应的目标函数取值。目标函数中的第二项看起来是与 棵树都相关但我们的第一个式子看起来却只和样本量相关其实不是第一项传统损失函数也是与已经建好的所有树相关的相关在这里
y ^ i ( t ) \hat y^{(t)}_i y^i(t) 中已经包含了所有树的迭代结果因此整个目标函数都与 棵树相关。
从另一个角度去理解我们的目标函数就是的方差-偏差
方差和偏差是此消彼长的并且模型的复杂度越高方差越大偏差越小。方差可以被简单地解释为模型在不同数据集上表现出来地稳定性而偏差是模型预测的准确度。那方差-偏差困境就可以对应到我们的 O b j Obj Obj中了
第一项是衡量我们的偏差模型越不准确第一项就会越大。第二项是衡量我们的方差模型越复杂模型的学习就会越具体到不同数据集上的表现就会差异巨大方差就会越大。
所以我们求解 O b j Obj Obj的最小值其实是在求解方差与偏差的平衡点以求模型的泛化误差最小运行速度最快。
我们知道树模型和树的集成模型都是学习天才是天生过拟合的模型因此大多数树模型最初都会出现在图像的右上方我们必须通过剪枝来控制模型不要过拟合。现在XGBoost的损失函数中自带限制方差变大的部分也就是说XGBoost会比其他的树模型更加聪明不会轻易落到图像的右上方。
在应用中我们使用参数“objective
来确定我们目标函数的第一部分中的 l ( y i , y ^ i ) l(y_i,\hat y_i) l(yi,y^i)也就是衡量损失的部分。
xgboost库和sklearn API
还可以选择自定义损失函数。比如说我们可以选择输入平方损失 l ( y i , y ^ i ) ( y i − y ^ i ) 2 l(y_i,\hat y_i) (y_i-\hat y_i)^2 l(yi,y^i)(yi−y^i)2此时我们的XGBoost其实就是算法梯度提升机器gradient boosted machine。在xgboost中我们被允许自定义损失函数但通常我们还是使用类已经为我们设置好的损失函数。我们的回归类中本来使用的就是reg:linear因此在这里无需做任何调整。注意分类型的目标函数导入回归类中会直接报错。
xgboost库和sklearn API底层的实现是有差异的一般来说xgboost库是要优于sklearn API的推荐使用xgboost库上面的直接替换成xgboost需要的参数即可。
xgboost库建模流程
其中最核心的是DMtarix
这个读取数据的类以及train()
这个用于训练的类。
与sklearn把所有的参数都写在类中的方式不同xgboost库中必须先使用字典设定参数集再使用train来将参数及输入然后进行训练。
用字典设定参数集是因为XGB所涉及到的参数实在太多全部写在xgb.train()中太长也容易出错。
params
{eta, gamma, max_depth, min_child_weight, max_delta_step, subsample, colsample_bytree,colsample_bylevel, colsample_bynode, lambda, alpha, tree_method string, sketch_eps, scale_pos_weight, updater,refresh_leaf, process_type, grow_policy, max_leaves, max_bin, predictor, num_parallel_tree}
xgboost.train
(params, dtrain, num_boost_round10, evals(), objNone, fevalNone, maximizeFalse,
early_stopping_roundsNone, evals_resultNone, verbose_evalTrue, xgb_modelNone, callbacksNone,learning_ratesNone)
大致看一下参数后面进行详解其实这些参数只是写法不同功能与sklearn API是相同的。
简单看一下代码建模流程
#xgb实现法import xgboost as xgb#使用类Dmatrix读取数据dtrain xgb.DMatrix(Xtrain,Ytrain) # 特征矩阵和标签dtest xgb.DMatrix(Xtest,Ytest)# 写明参数param {verbosity: 0 ,objective:reg:squarederror ,eta:0.1}num_round 190 # n_estimators#类train可以直接导入的参数是训练数据树的数量其他参数都需要通过params来导入bst xgb.train(param, dtrain, num_round)#predict接口preds bst.predict(dtest) #接口predictfrom sklearn.metrics import r2_scorer2_score(Ytest,bst.predict(dtest))MSE(Ytest,bst.predict(dtest))
效果是明显比sklearn API要好的。
怎么去求解XGB的目标函数
求解目标函数的目的为了求得在第 t t t次迭代中最优的树 f t f_t ft。
在求解XGB的目标函数的过程中我们考虑的是如何能够将目标函数转化成更简单的与树的结构直接相关的写法以此来建立树的结构与模型的效果包括泛化能力与运行速度 之间的直接联系。也因为这种联系的存在XGB的目标函数又被称为“结构分数”。
第t次迭代的结果是前面t-1次加上第k次的结果
前面已经提到了xgboost主要优化是1.添加了正则项。2.泰勒展开。
其中 g i g_i gi 和 h i h_i hi分别是在损失函数 l ( y i t , y ^ i ( t − 1 ) ) l(y^t_i,\hat y^{(t-1)}_i) l(yit,y^i(t−1))上对 所求的一阶导数和二阶导数他们被统称为每个样本的梯度统计量gradient statisticts。在许多算法的解法推导中我们求解导数都是为了让一阶导数等于0来求解极值而现在我们求解导数只是为了配合泰勒展开中的形式仅仅是简化公式的目的罢了。所以GBDT和XGB的区别之中GBDT求一阶导数XGB求二阶导数这两个过程根本是不可类比的。XGB在求解极值为目标的求导中也是求解一阶导数。
关于上面泰勒公式详解
1常规泰勒展开
其中 f ′ ( c ) f(c) f′(c)表示 f ( x ) f(x) f(x)上对x求导后令x的值等于c所取得的值。
2)假设c与x非常接近 非常接近0于是我们可以将式子改写成
只取前两项,取约等于
如刚才所说 x − c x-c x−c需要很小与 x x x相比起来越小越好那在我们的式子中需要很小的这部分就是 f t ( x i ) f_t(x_i) ft(xi)。这其实很好理解对于一个集成算法来说每次增加的一棵树对模型的影响其实非常小尤其是当我们有许多树的时候——比如n_estimators500的情况 f t ( x i ) f_t(x_i) ft(xi) 与 x x x 相比总是非常小的因此这个条件可以被满足泰勒展开可以被使用。如此我们的目标函数可以被顺利转化成【去掉常数项】
这个式子中 g i g_i gi和 h i h_i hi 只与传统损失函数相关核心的部分是我们需要决定的树 f t f_t ft。
参数化决策树 f k ( x ) f_k(x) fk(x) 参数alphalambda对于决策树而言每个被放入模型的任意样本 i i i最终一个都会落到一个叶子节点上。对于回归树通常来说每个叶子节点上的预测值是这个叶子节点上所有样本的标签的均值。但值得注意的是XGB作为普通回归树的改进算法在 y ^ \hat y y^上却有所不同
对于XGB来说每个叶子节点上会有一个预测分数predictionscore也被称为叶子权重。这个叶子权重就是所有在这个叶子节点上的样本在这一棵树上的回归取值用 f k ( x i ) f_k(x_i) fk(xi)或者 w w w来表示。
当有多棵树的时候集成模型的回归结果就是所有树的预测分数之和假设这个集成模型中总共有 棵决策树则整个模型在这个样本 上给出的预测结果为:
基于这个理解我们来考虑每一棵树。对每一棵树它都有自己独特的结构这个结构即是指叶子节点的数量树的深度叶子的位置等等所形成的一个可以定义唯一模型的树结构。在这个结构中我们使用 q ( x i ) q(x_i) q(xi)表示样本 x i x_i xi所在的叶子节点并且使用 w q ( x i ) w_{q(x_i)} wq(xi)来表示这个样本落到第 t t t棵树上的第 q ( x i ) q(x_i) q(xi) 个叶子节点中所获得的分数于是有
这是对于每一个样本而言的叶子权重然而在一个叶子节点上的所有样本所对应的叶子权重是相同的。设一棵树上总共包含了 T T T个叶子节点其中每个叶子节点的索引为 j j j则这个叶子节点上的样本权重是 w j w_j wj 。依据这个我们定义模型的复杂度 为 Ω ( f ) \Omega (f) Ω(f)注意这不是唯一可能的定义我们当然还可以使用其他的定义只要满足叶子越多/深度越大复杂度越大的理论我们可以自己决定我们的 Ω ( f ) \Omega (f) Ω(f) 要是一个怎样的式子
这个结构中有两部分内容一部分是控制树结构的 γ T \gamma T γT另一部分则是我们的正则项。叶子数量 T T T可以代表整个树结构这是因为在XGBoost中所有的树都是CART树二叉树所以我们可以根据叶子的数量 T T T判断出树的深度而 γ \gamma γ是我们自定的控制叶子数量的参数。
至于第二部分正则项参数 λ \lambda λ 和 α \alpha α 的作用其实非常容易理解他们都是控制正则化强度的参数我们可以二选一使用也可以一起使用加大正则化的力度。当 λ \lambda λ 和 α \alpha α 都为0的时候目标函数就是普通的梯度提升树的目标函数。
正则项的存在是XGB vs GBDT 核心区别之一
代码参数
当 λ \lambda λ 和 α \alpha α 越大惩罚越重正则项所占的比例就越大在尽全力最小化目标函数的最优化方向下叶子节点数量就会被压制模型的复杂度就越来越低所以对于天生过拟合的XGB来说正则化可以一定程度上提升模型效果。
网格搜索:
#使用网格搜索来查找最佳的参数组合from sklearn.model_selection import GridSearchCVparam {reg_alpha:np.arange(0,5,0.05),reg_lambda:np.arange(0,2,0.05)}gscv GridSearchCV(reg,param_grid param,scoring neg_mean_squared_error,cvcv)#【TIME WARNING10~20 mins】#time0time()gscv.fit(Xtrain,Ytrain)print(datetime.datetime.fromtimestamp(time()-time0).strftime(%M:%S:%f))gscv.best_params_gscv.best_score_preds gscv.predict(Xtest)from sklearn.metrics import r2_scoremean_squared_error as MSEr2_score(Ytest,preds)MSE(Ytest,preds)
在实际应用中正则化参数往往不是我们调参的最优选择如果真的希望控制模型复杂度我们会调整 γ \gamma γ 而不是调整这两个正则化参数因此大家不必过于在意这两个参数最终如何影响了我们的模型效果。对于树模型来说还是剪枝参数地位更高更优先。
寻找最佳树结构求解 ω \omega ω与 T T T
定义了树和树的复杂度的表达式树我们使用叶子节点上的预测分数来表达而树的复杂度则是叶子数目加上正则项
假设现在第 t t t棵树的结构已经被确定为 q q q我们可以将树的结构带入我们的损失函数来继续转化我们的目标函数。转化目标函数的目的是建立树的结构叶子节点的数量与目标函数的大小之间的直接联系以求出在第 t t t次迭代中需要求解的最优的树 f t f_t ft。注意我们假设我们使用的是L2正则化这也是参数lambda和alpha的默认设置lambda为1alpha为0因此接下来的推导也会根据L2正则化来进行:
上式的简单推导
根据同一片叶子有相同的权重可以推理出
其中每个 j j j取值下都是一个以 为自变量的二次函数 F ∗ F^* F∗我们的目标是追求让 O b j Obj Obj 最小只要单独的每一个叶子 j j j取值下的二次函数都最小那他们的加和必然也会最小。于是我们在 F ∗ F^* F∗上对 w j w_j wj求导让一阶导数等于0以求极值可得
上式代入目标函数可得
原来是损失函数 复杂度现在式我们的样本量 i i i已经被归结到了每个叶子当中去我们的目标函数是基于每个叶子节点也就是树的结构来计算。所以我们的目标函数又叫做“结构分数”structure score分数越低树整体的结构越好。如此我们就建立了树的结构叶子和模型效果的直接联系。
例子
在XGB的运行过程中我们会根据 O b j Obj Obj的表达式直接探索最好的树结构也就是说找寻最佳的树。从式子中可以看出 λ \lambda λ 和 γ \gamma γ 是我们设定好的超参数 G j G_j Gj和 H j H_j Hj 是由损失函数和这个特定结构下树的预测结果 y ^ i ( t − 1 ) \hat y^{(t-1)}_i y^i(t−1)共同决定而 T T T只由我们的树结构决定。则我们通过最小化 O b j Obj Obj所求解出的其实是 T T T叶子的数量。所以本质也就是求解树的结构了。
待我们选定了最佳的树结构最佳的 之后我们使用这种树结构下计算出来的 G j G_j Gj和 H j H_j Hj就可以求解出每个叶子上的权重 如此就找到我们的最佳树结构完成了这次迭代。
疑惑点
G j G_j Gj和 H j H_j Hj的本质其实是损失函数上的一阶导数 g i g_i gi 和二阶导数 h i h_i hi之和而一阶和二阶导数本质是
y i y_i yi是已知的标签但我们有预测值的求解公式
G j G_j Gj和 H j H_j Hj的计算中带有 ω \omega ω那先确定最佳的 T T T 再求出 G j G_j Gj和 H j H_j Hj结合 λ \lambda λ求出叶子权重 ω j \omega_j ωj 的思路不觉得有些问题么仿佛在带入 ω \omega ω 求解 ω \omega ω
请注意我们的 y ^ i ( t − 1 ) \hat y^{(t-1)}_i y^i(t−1)与我们现在要求解的 w j w_j wj 其实不是在同一棵树上的。别忘记我们是在一直迭代的我们现在求解的 w j w_j wj 是第 t t t棵树上的结果而 y ^ i ( t − 1 ) \hat y^{(t-1)}_i y^i(t−1)是前面的 ( t − 1 ) (t-1) (t−1)棵树的累积 w w w是在前面所有的迭代中已经求解出来的已知的部分。
求解第一棵树时没有“前面已经迭代完毕的部分”怎么办?
那我们如何求解第一棵树在样本 i i i下的预测值 y ^ i 1 \hat y^{1}_i y^i1呢在建立第一棵树时并不存在任何前面的迭代已经计算出来的信息但根据公式我们需要使用如下式子来求解 f 1 ( x i ) f_1(x_i) f1(xi)并且我们在求解过程中还需要对前面所有树的结果进行求导。
这时候我们假设 y ^ i ( 0 ) 0 \hat y^{(0)}_i 0 y^i(0)0来解决我们的问题事实是由于前面没有已经测出来的树的结果整个集成算法的结果现在也的确为0。
第0棵树的预测值假设为0那求解第一棵树的 和 的过程是在对0求导
我们其实不是在对0求导而是对一个变量 y ^ i ( t − 1 ) \hat y^{(t-1)}_i y^i(t−1)求导。只是除了求导之外我们还需要在求导后的结果中带入这个变量此时此刻的取值而这个取值在求解第一棵树时刚好等于0而已。更具体地可以看看下面对0求导和对变量求导后变量取值为0的区别
为了找出最小的 O b j Obj Obj枚举所有可能的树结构浪费资源又浪费时间。【采用贪婪算法寻找最佳结构】
寻找最佳分枝结构分数之差贪婪算法
贪婪算法指的是控制局部最优来达到全局最优的算法决策树算法本身就是一种使用贪婪算法的方法。XGB作为树的集成模型自然也想到采用这样的方法来进行计算所以我们认为如果每片叶子都是最优则整体生成的树结构就是最优如此就可以避免去枚举所有可能的树结构。
决策树中我们使用基尼系数或信息熵来衡量分枝之后叶子节点的不纯度分枝前的信息熵与分治后的信息熵之差叫做信息增益信息增益最大的特征上的分枝就被我们选中当信息增益低于某个阈值
时就让树停止生长。
XGB中我们使用的方式是类似的我们首先使用目标函数来衡量树的结构的优劣然后让树从深度0开始生长每进行一次分枝我们就计算目标函数减少了多少当目标函数的降低低于我们设定的某个阈值时就让树停止生长。
具体计算例子
对于中间节点这一个叶子节点而言我们的 T 1 T1 T1 则这个节点上的结构分数为
则分枝后的结构分数之差为
CART树全部是二叉树因此这个式子是可以推广的。从这个式子我们可以总结出其实分枝后的结构分数之差为
其中 G L G_L GL 和 H L H_L HL 从左节点弟弟节点上计算得出 G R G_R GR和 H R H_R HR 从有节点妹妹节点上计算得出而 ( G L G R ) (G_LG_R) (GLGR) 和 ( H L H R ) (H_LH_R) (HLHR)从中间节点上计算得出。对于任意分枝我们都可以这样来进行计算。在现实中我们会对所有特征的所有分枝点进行如上计算然后选出让目标函数下降最快的节点来进行分枝。对每一棵树的每一层我们都进行这样的计算比起原始的梯度下降实践证明这样的求解最佳树结构的方法运算更快并且在大型数据下也能够表现不错。
重要参数 γ \gamma γ让树停止生长
前面的式子都涉及到这个参数 γ \gamma γ从目标函数和结构分数之差 G a i n Gain Gain 的式子中来看 γ \gamma γ 是我们每增加一片叶子就会被剪去的惩罚项。增加的叶子越多结构分数之差 G a i n Gain Gain 会被惩罚越重所以 γ \gamma γ又被称之为是“复杂性控制”complexity control所以 是我们用来防止过拟合的重要参数。
实践证明 γ \gamma γ是对梯度提升树影响最大的参数之一其效果丝毫不逊色于n_estimators和防止过拟合的神器max_depth。同时 γ \gamma γ 还是我们让树停止生长的重要参数。
在逻辑回归中我们使用参数 t o l tol tol来设定阈值并规定如果梯度下降时损失函数减小量小于 t o l tol tol下降就会停止。在XGB中我们规定只要结构分数之差 G a i n Gain Gain 是大于0的即只要目标函数还能够继续减小我们就允许树继续进行分枝。
也就是说我们对于目标函数减小量的要求是
如此我们可以直接通过设定 γ \gamma γ的大小来让XGB中的树停止生长。 γ \gamma γ因此被定义为在树的叶节点上进行进一步分枝所需的最小目标函数减少量在决策树和随机森林中也有类似的参数min_split_lossmin_samples_split。 γ \gamma γ设定越大算法就越保守树的叶子数量就越少模型的复杂度就越低。
可以看到我们完全无法从中看出什么趋势偏差时高时低方差时大时小参数 γ \gamma γ引起的波动远远超过其他参数其他参数至少还有一个先上升再平稳的过程而 γ \gamma γ则是仿佛完全无规律。在sklearn下XGBoost太不稳定如果这样来调整参数的话效果就很难保证。因此为了调整 γ \gamma γ 我们需要来引入新的工具xgboost库中的类xgboost.cv
。
来看用于回归和分类的评估指标都有哪些
import xgboost as xgb#为了便捷使用全数据dfull xgb.DMatrix(X,y)#设定参数param1 {verbosity:0,objective:reg:linear,gamma:0}num_round 180n_fold5#使用类xgb.cvcvresult1 xgb.cv(param1, dfull, num_round, n_fold)plt.figure(figsize(20,5))plt.grid()plt.plot(range(1,181),cvresult1.iloc[:,0],cred,labeltrain,gamma0)plt.plot(range(1,181),cvresult1.iloc[:,2],corange,labeltest,gamma0)plt.legend()plt.show()
cvresult1
变量设置的180棵树会迭代180次返回了每一次的训练集和测试集的均方误差的均值和方差。
画图的结果
随着树的增多均方误差都急剧下降后趋于平稳平稳的空间就是过拟合的区间。
修改eval_metric
:mae
模型评估指标
调整 γ \gamma γ【控制训练集上的训练–降低训练集上的表现】
param1 {verbosity:0,objective:reg:linear,gamma:0}param2 {verbosity:0,objective:reg:linear,gamma:10}num_round 180n_fold5cvresult1 xgb.cv(param1, dfull, num_round,n_fold)cvresult2 xgb.cv(param2, dfull, num_round,n_fold)plt.figure(figsize(20,5))plt.grid()plt.plot(range(1,181),cvresult1.iloc[:,0],cred,labeltrain,gamma0)plt.plot(range(1,181),cvresult1.iloc[:,2],corange,labeltest,gamma0)plt.plot(range(1,181),cvresult2.iloc[:,0],cgreen,labeltrain,gamma10)plt.plot(range(1,181),cvresult2.iloc[:,2],cblue,labeltest,gamma10)plt.legend()plt.show()
调整 γ \gamma γ后过拟合确实有缓解但是。。无论是训练集还是测试集都比之前的低。
分类的例子
from sklearn.datasets import load_breast_cancerdata2 load_breast_cancer()x2 data2.datay2 data2.targetdfull2 xgb.DMatrix(x2,y2)param1 {verbosity:0,objective:binary:logistic,gamma:0, nfold:5}param2 {verbosity:0,objective:binary:logistic,gamma:0.5, nfold:5}num_round 100cvresult1 xgb.cv(param1, dfull2, num_round,metrics(error))cvresult2 xgb.cv(param2, dfull2, num_round,metrics(error)) plt.figure(figsize(20,5))plt.grid()plt.plot(range(1,101),cvresult1.iloc[:,0],cred,labeltrain,gamma0)plt.plot(range(1,101),cvresult1.iloc[:,2],corange,labeltest,gamma0)plt.plot(range(1,101),cvresult2.iloc[:,0],cgreen,labeltrain,gamma2)plt.plot(range(1,101),cvresult2.iloc[:,2],cblue,labeltest,gamma2)plt.legend()plt.show()
有缓解一点点过拟合。可以让我们直接看到参数如何影响了模型的泛化能力。
xgboost应用 过拟合剪枝参数与回归模型调参作为天生过拟合的模型XGBoost应用的核心之一就是减轻过拟合带来的影响。作为树模型减轻过拟合的方式主要是靠对决策树剪枝来降低模型的复杂度以求降低方差。
可以用来防止过拟合的参数:
所有的这些参数都可以用来减轻过拟合。
除此之外我们还有几个影响重大的专用于剪枝的参数
树的最大深度是是最常用的剪枝参数不过在XGBoost中最大深度的功能与参数 γ \gamma γ 相似这两个参数中我们只使用一个进行调参即可。
三个随机抽样特征的参数中前两个比较常用。在建立树时对特征进行抽样其实是决策树和随机森林中比较常见的一种方法但是在XGBoost之前这种方法并没有被使用到boosting算法当中过。Boosting算法一直以抽取样本横向抽样来调整模型过拟合的程度而实践证明其实纵向抽样抽取特征更能够防止过拟合。
参数min_child_weight
不太常用它是一篇叶子上的二阶导数 h i h_i hi之和当样本所对应的二阶导数很小时比如说为0.01min_child_weight若设定为1则说明一片叶子上至少需要100个样本。本质上来说这个参数其实是在控制叶子上所需的最小样本量因此对于样本量很大的数据会比较有效。如果样本量很小比如我们现在使用的波士顿房价数据集则这个参数效用不大。就剪枝的效果来说这个参数的功能也被 替代了一部分通常来说我们会试试看这个参数但这个参数不是我的优先选择。
获得了一个数据集后我们先使用网格搜索找出比较合适的n_estimators
和eta
组合然后使用gamma
或者max_depth
观察模型处于什么样的状态过拟合还是欠拟合处于方差-偏差图像的左边还是右边最后再决定是否要进行剪枝。通常来说对于XGB模型大多数时候都是需要剪枝的。接下来我们就来看看使用xgb.cv这个类
来进行剪枝调参以调整出一组泛化能力很强的参数。
设定默认参数开始先观察一下默认参数下我们的交叉验证曲线长什么样
housing_california fetch_california_housing()X housing_california.data # datay housing_california.target # labelimport xgboost as xgbdfull xgb.DMatrix(X,y)param1 {verbosity:0 ,objective:reg:linear #并非默认 ,subsample:1 ,max_depth:6 ,eta:0.3 ,gamma:0 ,lambda:1 ,alpha:0 ,colsample_bytree:1 ,colsample_bylevel:1 ,colsample_bynode:1 ,nfold:5}num_round 200cvresult1 xgb.cv(param1, dfull, num_round)fig,ax plt.subplots(1,figsize(15,8))#ax.set_ylim(top5)ax.grid()ax.plot(range(1,201),cvresult1.iloc[:,0],cred,labeltrain,original)ax.plot(range(1,201),cvresult1.iloc[:,2],corange,labeltest,original)ax.legend(fontsizexx-large)plt.show()
过拟合有点严重接下来开始进行剪枝。我们的目标是训练集和测试集的结果尽量接近如果测试集上的结果不能上升那训练集上的结果降下来也是不错的选择让模型不那么具体到训练数据增加泛化能力。
使用三组曲线
第一组原始参数第二组调节某个参数在结果比第一组好的情况下第三组对第二组的参数进行微调如果第三组参数能够调的比第二组好更新第二组参数为第三组的参数第三组继续微调如果怎么调也调不出比第二组好的参数那么最佳参数就是第二组的参数。
调节最大深度max_depth
【已经调好了的】
num_round 200# 第一组 原始参数param1 {verbosity:0 ,objective:reg:linear ,subsample:1 ,max_depth:6 ,eta:0.3 ,gamma:0 ,lambda:1 ,alpha:0 ,colsample_bytree:1 ,colsample_bylevel:1 ,colsample_bynode:1 ,nfold:5}cvresult1 xgb.cv(param1, dfull, num_round)
# 第二组调节了max_depth# 第二组调节了max_depthparam2 {verbosity:0 ,objective:reg:linear ,max_depth:3 ,nfold:5}param3 {verbosity:0 ,objective:reg:linear ,max_depth:4 ,nfold:5}cvresult2 xgb.cv(param2, dfull, num_round)cvresult3 xgb.cv(param3, dfull, num_round)
fig,ax plt.subplots(1,figsize(15,8))ax.grid()ax.plot(range(1,201),cvresult1.iloc[:,0],cred,labeltrain,original)ax.plot(range(1,201),cvresult1.iloc[:,2],corange,labeltest,original)ax.plot(range(1,201),cvresult2.iloc[:,0],cgreen,labeltrain,last)ax.plot(range(1,201),cvresult2.iloc[:,2],cblue,labeltest,last)ax.plot(range(1,201),cvresult3.iloc[:,0],cgray,labeltrain,this)ax.plot(range(1,201),cvresult3.iloc[:,2],cpink,labeltest,this)ax.legend(fontsizexx-large)plt.show()
三组测试集误差几乎重叠选与训练集间隔小的即泛化能力强的所以选择max_depth:3
按照上面继续调节 e t a eta eta和 g a m m a gamma gamma最后调出的最佳
param3 {verbosity:0 ,objective:reg:linear ,max_depth:3 ,eta:0.3 ,gamma:0 ,lambda:1 ,alpha:0 ,colsample_bytree:1 ,colsample_bylevel:1 ,colsample_bynode:1 ,nfold:5}
XGBoost模型的保存和调用 1使用Pickle保存和调用模型
a保存
# 导入库import pickledtrain xgb.DMatrix(Xtrain,Ytrain)#设定参数对模型进行训练param {silent:True ,obj:reg:linear ,subsample:1 ,eta:0.05 ,gamma:20 ,lambda:3.5 ,alpha:0.2 ,max_depth:4 ,colsample_bytree:0.4 ,colsample_bylevel:0.6 ,colsample_bynode:1}num_round 180bst xgb.train(param, dtrain, num_round)#保存模型pickle.dump(bst, open(xgboostonboston.dat,wb))#注意open中我们往往使用w或者r作为读取的模式但其实w与r只能用于文本文件当我们希望导入的不是文本文件而是模型本身的时候我们使用wb和rb作为读取的模式。其中wb表示以二进制写入rb表示以二进制读入
b) 调用
#注意如果我们保存的模型是xgboost库中建立的模型则导入的数据类型也必须是xgboost库中的数据类型dtest xgb.DMatrix(Xtest,Ytest)#导入模型loaded_model pickle.load(open(xgboostonboston.dat, rb))print(Loaded model from: xgboostonboston.dat)#做预测ypreds loaded_model.predict(dtest)from sklearn.metrics import mean_squared_error as MSE, r2_scoreMSE(Ytest,ypreds)r2_score(Ytest,ypreds)
2使用Joblib保存和调用模型
bst xgb.train(param, dtrain, num_round)import joblib#同样可以看看模型被保存到了哪里joblib.dump(bst,xgboost-boston.dat)loaded_model joblib.load(xgboost-boston.dat)ypreds loaded_model.predict(dtest)MSE(Ytest, ypreds)r2_score(Ytest,ypreds)
参考