基于xgboost的分类预测

news/2024/10/10 21:15:50/

一、xgboost的介绍

xgboost以及后续的lightGBM等在我的认知中算是目前数据挖掘中最常用的预测算法,在各种数据挖掘比赛中获得了Top的名次。如果想从事数据挖掘相关的岗位,该算法是一定要好好掌握的。

xgboost其实可以认为是GBDT算法的工程实现,GBDT的全名是Gradient Boosting Decision Tree,就是梯度提升树。该算法的逻辑是:首先训练一颗树,然后计算出每个样本的误差(也叫梯度),通过去拟合误差训练下一颗树,一直拟合直到训练结果符合要求。下图是GBDT算法的步骤图。

xgboost虽然大家都说是GBDT的工程实现,但是在很多地方都是做了不少优化的:

1、目标函数的二阶泰勒展开

xgboost与GBDT都是每一步都只优化当前模型,然后将所有模型加起来:

F_m(x_i)=F_{m-1}(x_i)+f_m(x_i)

目标函数为:

Obj=\sum_{i=1}^n L[F_m(x_i),y_i]+\sum_{j=1}^m {\Omega} (f_j)

其中𝐿为损失函数,  $F_m(x_i)$  是所有树的累加结果。  ${\Omega} (f_j)$  是每棵树的正则项(xgb引入了正则项,因此目标函数加上了结构化风险)。


与GBDT的一阶不同,xgboost采用了二阶展开,这其实就是优化算法的改进,同时拟合一阶误差和二阶误差有利于提升精度。
二阶展开:


$f(x_0+\Delta x)\approx f(x_0)+f'(x_0)\Delta x+\frac{f''(x_0)}{2}\Delta(\Delta x)^2$

其中可以将  $F_{m-1}(x_i)$  的结果视为 $x_0$$f_m(x_i)$  视为  $\Delta x$, $L(\widehat y_i,y_i)$ 为 $\widehat y_i$ 的函数。我们将 $F_m$   展开,重新得到我们的目标函数:

Obj=\sum_{i=1}^N[L[F_{m-1}(x_i),y_i]+\frac{\partial L}{\partial F_{m-1}(x_i )}f_m(x_i)]+\frac{1}{2}\frac{\partial^2 L}{\partial^2 F_{m-1}(x_i )}f^2_m(x_i)]+\sum_{j=1}^m {\Omega} (f_j)

在每次更新的时候,前𝑚−1个模型已经是确定了的,因此上面的式子中只有  $f_m(x_i)$  是未知的(其实也就是我们即将生成的𝑚棵树)。我们将常数部分稍微处理以下:

$Obj=\sum_{i=1}^N[g_if_m(x_i)+\frac{1}{2}h_if_m^2(x_i)]+\Omega (f_m)$

其中:


g_i=\frac{\partial L}{\partial F_m-1(x_i)},h_i=\frac{\partial^2 L}{\partial^2 F_m-1(x_i)}

也就是每个样本的前𝑚−1棵树与最终结果之间的一阶导数和二阶导数。在第𝑚−1树已经确定了的情况下,是很容易计算出来的。


2、xgboost的正则化

上文中我们提到了xgboost与GBDT相比还有一个正则项。其中:

\Omega (f_m)=\gamma T+\frac{1}{2}\lambda \sum_{j=1}^T w^2_j

其中𝑇为树𝑓的叶节点的个数,  $w_j$  为第𝑗个叶节点的权重。很容易理解叶节点越少越不容易过拟合,但是为什么𝑤叶节点的取值也可以作为正则项呢? 我的理解是:

xgboost是一个加法模型,每个样本最后的取值是每个树的值加起来。比如某个样本在n棵树中处于叶节点的取值分别为:  $w_i$  。那么该样本最后的取值为: $\sum w_i$   (这么一看的话和逻辑输入特征为0-1的逻辑回归还是很像的)。我们会倾向于该样本在每个树中的取值尽可能的小一些,那样样本最后的取值受某一个树的影响就会变小,更不容易过拟合。

我们将正则化的式子代入到上面的目标函数中:

Obj=\sum_{i=1}^N[g_if_m(x_i)+\frac{1}{2}h_if_m^2(x_i)]+\gamma T+\frac{1}{2}\lambda \sum_{j=1}^T w^2_j

我们定义,处于叶节点𝑗上的样本集合为𝐼(𝑗),这样我们可以将所有的样本拆分为𝐽份,映射到各个叶节点中:
其中在各个叶节点中    $f_m(x_i)=w_j$  。

Obj=\sum_{i=1}^T[(\sum_{i\in I(j)}g_i)w_j+\frac{1}{2}(\sum_{i\in I(j)}h_i+\lambda )w_j^2]+\gamma T

同样我们可以进一步将式子简化:

$Obj=\sum_{i=1}^T[(G_j)w_j+\frac{1}{2}(H_j+\lambda )w_j^2]+\gamma T$

其中:

$G_j=(\sum_{i\in I(j)}g_i),H_j=\sum_{i\in I(j)}h_i$

上述所有的变量中,只有  $w_j$  是未知量,因此我们很容易根据求解一元二次方程,当函数取得最小值时:

w^* = \frac{G_j}{H_j+\lambda }

将其代入函数我们有:

Obj^*=\sum_{j=1}^T(-\frac{1}{2} \frac{G_j^2}{H_j+\lambda}+\gamma)

以上就是xgboost模型的推导了。下面我们来介绍该模型的优化方法。

3、算法优化

对于树模型来说,算法优化其实就是指导树的分裂。xgb算法的思路与CART一样,都是遍历所有特征,选择收益最高的分裂点进行分裂。假设我们有  $I=I_L \cup I_R$$I_L,I_R$  为集合𝐼分裂之后的左右集合。那么在分裂后目标函数的减少为:

L_{split}=\frac{1}{2}[\frac{G^2_L}{H_L+\lambda }+ \frac{G^2_R}{H_R+\lambda }- \frac{(G^2_L+G^2_R)}{H_L+H_R+\lambda }]

(1):精确贪婪算法

这种算法就是详细地遍历每个特征,然后遍历每个特征可能的取值,搜索出最优的分裂特征以及该特征的分裂值

采用这种精确贪婪算法的算法步骤如下:

(2):近似算法

在很多种情况下,每个特征的取值(离散的取值)可能有成千上万个,遍历每个取值的话时间开销会非常的大。很多时候,特征分裂的取值稍微差了一点也不会有太大的影响。为了对整个分裂过程进行加速,我们可以对特征值进行分箱。

比如将样本种某个特征是连续值,取值有几万个,我们可以将其分箱为几十个,然后遍历每个特征的取值的时候,我们只需要遍历每个箱。这样的话极快地减少了计算的次数。

关于分箱的方法在xgb的论文种提及了两种:global和local两种。global是在所有树生成开始之前,将需要的特征分好箱。整个训练过程每个特征都只需要分一次。local是在每次进行特征值遍历的时候进行分箱,一个训练过程会分裂很多很多次。

该算法的步骤为:

下图中是xgboost作者关于global和local还有精确贪婪的一个结果对比:

1/eps就是分箱的个数,可以看到使用global,local,还有精确的贪婪搜索都可以达到差不多的结果。同时使用global的话需要相比local分的更细,当然它也只需要分一次。

(3):加权分位数

这一步是对上一步近似算法补充,上一步我们说到对特征分箱,但是怎么对特征分箱?常用的方法是按照样本分布,比如100个样本我要分5箱,那么我可能会使得每箱中有20个样本,这样我就可以将样本排序后,在第20,40,60,80处对样本进行分箱。但是在xgboost中采用一种加权分为的分箱方法。
如下图所示,将$h_i$作为权重,每个区间中$h_i$的权重相等。

为什么要采用这种方法呢?理由就是每个样本对最终目标函数的贡献是不一样的,而它们与目标函数的贡献是与  h_i  相关的。具体的推导如下:

$Obj=\sum_{i=1}^N[g_if_m(x_i)+\frac{1}{2}h_if_m^2(x_i)]+\Omega (f_m)$

我们将这个函数稍微处理下:

$\sum \frac{1}{2}h_i[f_m(x_i)-(-\frac{g_i}{h_i})]^2+\Omega (f_m)+C$

我们知道当  $f^*_m(x_i)=-\frac{g_i}{h_i}$  时,目标函数取得最优值,代入上式得话可以发现最后我们的目标函数确实是与 h_i   成正相关的。

4、其余细节的优化方法

(1):列采用

列采样是随机森林中的做法,具体的操作细节就是每次分裂的时候并不选取全部的可以进行分裂的特征,而是选择其中一个子集,这样一是防止过拟合,二是减少运行时间。在随机森林中还有每次不选取全部训练样本而是选取部分样本进行训练的操作, xgb中并没有介绍到使用了。

(2):学习率

学习率也叫做步长,是在每个子模型前(即在每个叶节点的回归值上)乘上该系数,削弱每颗树的影响,使得迭代更稳定。与随机梯度下降中的学习率类似

(3):稀疏感知

在数据中经常会出现这些现象:1:缺失值,2:为0的值太多,3:特征one-hot(其实也是导致为0的值太多)

xgb采用稀疏感知策略来同时处理这类问题,将缺失值和稀疏0值等同视作缺失值,"绑定"在一起,节点分裂的时候不会遍历这些节点。然后将这类值分裂到某个节点。下面是该算法的步骤:

可以看到并没有说放置在左节点还是右节点中,这个需要进行判断。

(4):特征并行优化

分裂之前提前将将所有特征进行并行地分箱,然后计算每个箱里面地g_jh_j,然后并行地计算每个特征地分裂收益。

(5):其余工程优化

xgb还有一些工程上的优化,比如Cache-aware Access(缓存访问),Blocks for Out-of-core Computation(核外计算模块),感兴趣的可以了解一下。

三、xgb实战

上面我们已经介绍了XGB的理论基础,了解它以后我们需要更好地应用它。下面我将来使用一个case来详细地介绍一下怎么使用xgb算法(也就是怎么优雅地调包)。我们采用五折交叉验证法进行训练。

import numpy as np
from sklearn import datasets
import pandas as pd
from sklearn.model_selection import train_test_split
X=datasets.load_breast_cancer()['data']
Y=datasets.load_breast_cancer()['target']
fea=datasets.load_breast_cancer()['feature_names']data=pd.DataFrame(X,columns=fea)
data['target']=Y
x_train=data[fea]
y_train=data['target']
data['target'].value_counts()'''
1    357
0    212
Name: target, dtype: int64
'''

xgb的包有两种,一般我们也会看到两种形式的xgb算法。一种是xgb原生的,一种是采用了sklearn包装之后的。

首先我们使用xgb的原生包进行训练:

from sklearn.model_selection import KFold,StratifiedKFold
import xgboost as xgb
import datetime
import sklearn
folds = KFold(n_splits=5, shuffle=True, random_state=1996)params = {'learning_rate': 0.05,'max_depth': 8,  'eval_metric': 'auc','objective': 'binary:logistic',
}import warnings 
warnings.filterwarnings('ignore')oof=np.zeros(x_train.shape[0]) 
for fold_, (train_index, test_index) in enumerate(folds.split(x_train, y_train)):print("第{}折".format(fold_))train_x, test_x, train_y, test_y = x_train.iloc[train_index], x_train.iloc[test_index], y_train.iloc[train_index], y_train.iloc[test_index]   num_round=5dtrain = xgb.DMatrix(train_x, train_y)dval = xgb.DMatrix(test_x, test_y)xgb_model = xgb.train(params,dtrain,num_boost_round=10000,evals=[(dtrain, 'Train'), (dval, 'Valid')],early_stopping_rounds=30,verbose_eval=50,)val_train=xgb_model.predict(dval)oof[test_index]=val_train
print("最终auc为:",sklearn.metrics.roc_auc_score(y_train,oof))'''
第0折
[0]	Train-auc:0.989447	Valid-auc:0.987137
Multiple eval metrics have been passed: 'Valid-auc' will be used for early stopping.Will train until Valid-auc hasn't improved in 30 rounds.
[50]	Train-auc:0.999918	Valid-auc:0.997661
Stopping. Best iteration:
[44]	Train-auc:0.999918	Valid-auc:0.997995第1折
[0]	Train-auc:0.998555	Valid-auc:0.951389
Multiple eval metrics have been passed: 'Valid-auc' will be used for early stopping.Will train until Valid-auc hasn't improved in 30 rounds.
Stopping. Best iteration:
[16]	Train-auc:0.999959	Valid-auc:0.963624第2折
[0]	Train-auc:0.98871	Valid-auc:0.984702
Multiple eval metrics have been passed: 'Valid-auc' will be used for early stopping.Will train until Valid-auc hasn't improved in 30 rounds.
Stopping. Best iteration:
[7]	Train-auc:0.998815	Valid-auc:0.999034第3折
[0]	Train-auc:0.994967	Valid-auc:0.968919
Multiple eval metrics have been passed: 'Valid-auc' will be used for early stopping.Will train until Valid-auc hasn't improved in 30 rounds.
[50]	Train-auc:0.999836	Valid-auc:0.990878
[100]	Train-auc:1	Valid-auc:0.99527
Stopping. Best iteration:
[93]	Train-auc:1	Valid-auc:0.99527第4折
[0]	Train-auc:0.99598	Valid-auc:0.929513
Multiple eval metrics have been passed: 'Valid-auc' will be used for early stopping.Will train until Valid-auc hasn't improved in 30 rounds.
[50]	Train-auc:0.999917	Valid-auc:0.974802
Stopping. Best iteration:
[33]	Train-auc:0.999711	Valid-auc:0.97859最终auc为: 0.9856178320384757
'''

下面我们采用sklearn包装后的包进行训练。sklearn包装的xgb的参数比较多,常见的有:
objective="binary:logistic" 目标函数,代表是二分类,损失函数是logistic
max_depth=6 树的深度
learning_rate=0.05 学习率
n_estimators=1000 树的数目
verbosity=10 每n次显示结果
booster="gbtree" xgb算法,默认就是GBDT
min_child_weight 样本权重,其实就是每个节点最少需要多少样本
subsample 样本采样
colsample_bytree="0.9",colsample_bynode="0.85" 特征采样
scale_pos_weight 处理样本不平衡的,scale_pos_weight的值为负样本数量除以正样本数量
random_state 随机种子,主要是复复现

oof=np.zeros(x_train.shape[0]) 
for fold_, (train_index, test_index) in enumerate(folds.split(x_train, y_train)):print("第{}折".format(fold_))train_x, test_x, train_y, test_y = x_train.iloc[train_index], x_train.iloc[test_index], y_train.iloc[train_index], y_train.iloc[test_index]   model=xgb.XGBClassifier(objective="binary:logistic",booster="gbtree",max_depth=6, learning_rate=0.05, n_estimators=1000,n_jobs=1, gamma=1,
#                  min_child_weight=1, subsample=0.9,
#                  colsample_bytree=0.9, 
#                  colsample_bynode=0.85, reg_alpha=1, reg_lambda=1,scale_pos_weight=5, random_state=2022)model.fit(train_x,train_y,sample_weight=None,eval_set=[(train_x,train_y),(test_x,test_y)],eval_metric="auc",early_stopping_rounds=50,verbose=20,)val_train=model.predict_proba(test_x)[:,1]oof[test_index]=val_train
print("最终auc为:",sklearn.metrics.roc_auc_score(y_train,oof))'''
第0折
[0]	validation_0-auc:0.963656	validation_1-auc:0.950551
Multiple eval metrics have been passed: 'validation_1-auc' will be used for early stopping.Will train until validation_1-auc hasn't improved in 50 rounds.
[20]	validation_0-auc:0.996221	validation_1-auc:0.997995
[40]	validation_0-auc:0.996396	validation_1-auc:0.997327
[60]	validation_0-auc:0.999341	validation_1-auc:0.998329
Stopping. Best iteration:
[22]	validation_0-auc:0.996324	validation_1-auc:0.999332第1折
[0]	validation_0-auc:0.984334	validation_1-auc:0.922288
Multiple eval metrics have been passed: 'validation_1-auc' will be used for early stopping.Will train until validation_1-auc hasn't improved in 50 rounds.
[20]	validation_0-auc:0.999856	validation_1-auc:0.955026
[40]	validation_0-auc:1	validation_1-auc:0.953704
[60]	validation_0-auc:1	validation_1-auc:0.965939
[80]	validation_0-auc:1	validation_1-auc:0.964616
[100]	validation_0-auc:1	validation_1-auc:0.96379
Stopping. Best iteration:
[61]	validation_0-auc:1	validation_1-auc:0.96627第2折
[0]	validation_0-auc:0.962804	validation_1-auc:0.943639
Multiple eval metrics have been passed: 'validation_1-auc' will be used for early stopping.Will train until validation_1-auc hasn't improved in 50 rounds.
[20]	validation_0-auc:0.99316	validation_1-auc:0.998068
[40]	validation_0-auc:0.999771	validation_1-auc:0.999356
[60]	validation_0-auc:1	validation_1-auc:0.999356
[80]	validation_0-auc:1	validation_1-auc:0.999034
Stopping. Best iteration:
[48]	validation_0-auc:0.999875	validation_1-auc:0.999678第3折
[0]	validation_0-auc:0.970119	validation_1-auc:0.916216
Multiple eval metrics have been passed: 'validation_1-auc' will be used for early stopping.Will train until validation_1-auc hasn't improved in 50 rounds.
[20]	validation_0-auc:0.992656	validation_1-auc:0.959628
[40]	validation_0-auc:0.99624	validation_1-auc:0.983446
[60]	validation_0-auc:0.998562	validation_1-auc:0.9875
[80]	validation_0-auc:0.999322	validation_1-auc:0.991554
[100]	validation_0-auc:0.999938	validation_1-auc:0.993919
[120]	validation_0-auc:1	validation_1-auc:0.995946
[140]	validation_0-auc:1	validation_1-auc:0.995946
[160]	validation_0-auc:1	validation_1-auc:0.995946
[180]	validation_0-auc:1	validation_1-auc:0.995946
Stopping. Best iteration:
[143]	validation_0-auc:1	validation_1-auc:0.996284第4折
[0]	validation_0-auc:0.986824	validation_1-auc:0.965744
Multiple eval metrics have been passed: 'validation_1-auc' will be used for early stopping.Will train until validation_1-auc hasn't improved in 50 rounds.
[20]	validation_0-auc:0.996517	validation_1-auc:0.962615
[40]	validation_0-auc:0.999308	validation_1-auc:0.967227
[60]	validation_0-auc:0.999762	validation_1-auc:0.96805
[80]	validation_0-auc:0.999938	validation_1-auc:0.966733
[100]	validation_0-auc:1	validation_1-auc:0.972167
[120]	validation_0-auc:1	validation_1-auc:0.973155
[140]	validation_0-auc:1	validation_1-auc:0.974144
[160]	validation_0-auc:1	validation_1-auc:0.972167
[180]	validation_0-auc:1	validation_1-auc:0.972167
Stopping. Best iteration:
[130]	validation_0-auc:1	validation_1-auc:0.975461最终auc为: 0.9810461920617303
'''

两种接口最后的结果其实也差不多,其实也就看大家习惯使用哪个。其实我比较倾向于第二种采用sklearn接口的,主要是参数较多,比如"样本权重"在原生接口中我没有看到过。

以上就是关于xgb的所有内容,再一次感叹xgb的强大,时至今日,很多还是应用于各种场景中,据我了解,很多非互联网场景的数据预测挖掘中还是工业界的首选。关于实战的部分可能较少,是因为我想把多一些的实战内容放到下一节LightGBM中。


http://www.ppmy.cn/news/1537122.html

相关文章

AI时代互联网信息的几点隐忧

笔者最近一年关注的AI技术比关注的安全技术还多,AI从事实上的对内容生产工作者带来工作效率的提高,年初的时候比较关注的是如何在安全行业使用AI进行赋能,领导也给了类似的命题,让我思考如何让AI在安全行业发力。 目前已经看到的是…

为什么在摄像头领域播放声音需要拉功放?

目录 一、声音的基础知识二、功放的基本原理三、摄像头领域为什么需要拉功放?四、实际应用场景分析五、结论 摄像头在现代安防、监控、智能家居等场景中发挥着重要作用。随着技术的进步,摄像头不仅具备了图像采集功能,还集成了语音传输和播放…

PHP中的HTTP请求:简化你的网络通信

在当今的网络应用开发中,PHP作为一种流行的服务器端脚本语言,经常需要与外部服务进行通信。这通常涉及到发送HTTP请求来获取或提交数据。幸运的是,PHP提供了多种方式来简化HTTP请求的过程,使得网络通信变得轻而易举。 PHP中的HTTP…

甲虫身体图像分割系统源码&数据集分享

甲虫身体图像分割系统源码&数据集分享 [yolov8-seg-EfficientRepBiPAN&yolov8-seg-C2f-FocusedLinearAttention等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challen…

毕业设计 大数据电影数据分析与可视化系统

文章目录 0 简介1 课题背景2 效果实现3 爬虫及实现4 Flask框架5 Ajax技术6 Echarts7 最后 0 简介 今天学长向大家介绍一个机器视觉的毕设项目 🚩基于大数据的电影数据分析与可视化系统 项目运行效果(视频): 毕业设计 大数据电影评论情感分析 &#x1…

令牌主动失效机制范例(利用redis)注释分析

介绍该机制 令牌生成 在需要限流的场景中,系统会根据一定的速率生成令牌,存储在 Redis 中。可以设定每秒生成的令牌数量。 令牌获取 当用户请求时,系统会从 Redis 中获取令牌。可以使用原子性操作(如 DECR)来确保令牌…

view deign 和 vue2 合并单元格的方法

1.vue版本和view design 版本 {"vue": "^2.6.11","view-design": "^4.7.0", }2.Data中定义数据 spanArr: [], // 某一列下需要合并的行数 pos: 0// 索引// 注意点: 在获取列表前,需要重置 this.spanArr [] 注…

## jupyter_server

$ conda install -c conda-forge jupyter_server 查看配置文件路径 $ jupyter --pathsconfig:/home/musk/.jupyter/home/musk/anaconda3/etc/jupyter/usr/local/etc/jupyter/etc/jupyter data:/home/musk/.local/share/jupyter/home/musk/anaconda3/share/jupyter/usr/local/s…