0%

机器学习|XGBoost模型原理详解与实战

XGBoost模型是集成学习中最为著名的一个模型。不用我说,我相信只要做ML/DM/DL领域的筒子们,都会听说过这个模型。这篇博客将详细地讲解XGBoost模型的原理,并且使用XGBoost库来实践。注意:在看XGBoost之前,请务必要读懂CART与GBDT,具体可参看我之前的文章:CARTGBDT

XGBoost模型介绍

XGBoost模型是对GBDT很好地工程化的实现。所以,XGBoost模型也是加法模型,对于给定的样本$x_i$,我们使用XGBoost对其分数$\hat y_i$进行预测,可以表示为:

其中,$K$表示总共有$K$个决策树,$f_k$表示第$k$个决策树,在XGBoost中使用CART树;$\cal F$表示假设假设空间,即所有决策树的集合。那么,接下来,我们需要给出XGBoost模型的损失函数(严格来讲,叫做 cost function),以便求出所有的$f_k$。那么,如下:

其中,$\sum_{i}l(\hat y_i,y_i)$是损失函数,表示的是模型对于训练集的拟合程度,$\sum_k \Omega(f_k)$表示正则项,也叫做惩罚项,XGBoost中对每一个回归树进行惩罚,让模型的复杂度不过高,从而不会轻易过拟合。而衡量树的复杂度的指标有很多,譬如:树的深度、叶子结点的数目、内部节点数目等等。在XGBoost中,采用了叶子结点的数目来衡量树的复杂度。其中,$T$表示的是叶子结点的数目,$w$表示的是叶子结点的分数,也就是回归树预测的值。实际上,这个正则化项也相当于剪枝了。

cost function的推导

接下来,重点来了,我们需要对损失函数这一块进行泰勒展开!怎么做呢?始终要记住一句话:XGBoost模型是对GBDT模型很好地工程化的实现。所以,XGBoost模型仍然是前向分步算法。我们需要对损失函数进行二阶泰勒展开。如下:

所以,通过泰勒展开和化简,我们得到最优的cost function如下:

所以,从上式可以看出,只要我们确定了回归树的结构,那么我们就能得到最小的损失。那么,怎么去确定回归树的结构呢?

确定回归树的结构

关于回归树的学习,一个很直接的想法,就是遍历所有特征的所有切分点,找到最佳切分变量与切分点,从而确定回归树的结构。那么,关键是:我们使用什么样的评价准则来评判切分的质量好坏?在XGBoost中,我们使用如下式子来评判分裂的好坏:

当$\cal L_{split}$越大,那么说明叶子结点分裂的质量越好,最终的损失函数的值也会越小。所以,我们就可以依次所有特征的所有切分点,从中找到最大的$\cal L_{split}$的切分点和所对应的值。这种方法叫做:精确贪心算法

但是,这种分裂算法,在特征数目以及其对应的取值太多的时候,效率会非常的慢,所以,在XGBoost中,还提出的近似贪心算法。核心思想是:我们不需要依次遍历所有特征值的所有切分点,我们只需要找到并遍历有可能的切分点,从中找到$\cal L_{split}$的值最大的切分变量与切分点就可以了。

不过,在近似算法中,需要注意的是,我们并不是根据样本数目来进行分位,而是以每一个样本的二阶导数作为权重,以此排序,来进行分位。那么,为什么可以采用二阶导数来做权重呢?推导如下(参看链接:xgboost):

稀疏值的处理

稀疏值有三种:大量的0值、大量的类别one-hot编码、缺失值。XGBoost可以自动学习出稀疏值的节点分裂方向。如下:

  • 首先需要注意到输入的两个集合:一个是$I$,其包含所有的样本(包括含空缺值的样本);另一个是$I_k$,是不包含空缺值样本的集合。在计算总的$G$和$H$的时候,用的是$I$。
  • 此外,可以看到内层循环里面有两个for。第一个for是从把特征取值从小到大排序。这个时候,在计算$G_R$的时候,是用总的$G$减去$G_L$,计算$H_R$也是使用$H$减去$H_L$。这就相当于将稀疏值归到了右节点。
  • 第二个for是从把特征取值从大到小排序。这个时候,在计算$G_L$ 的时候,是用总的$G$减去$G_R$,计算$H_L$也是使用$H$减去$H_R$。这就相当于将稀疏值归到了左节点。
  • 我们只要比较最大的增益出现在第一个for,还是第二个for,我们就能知道稀疏值的分裂方向是在左还是右。这就是XGBoost模型处理稀疏值的方法。

two tricks

在XGBoost模型中,除了通过添加正则化项来防止过拟合之外,还用了另外两个用来防止过拟合的trick:shrinkage(缩减)、column subsampling(列抽样)。我们一一介绍。shrinkage。所谓的shrinkage,就是将学习速率减小,增加迭代次数,从而起到正则化的作用。column subsampling。这个和RF中是一样的,没什么可说的。除此之外,XGBoost模型还有一些工程化的东西,像cache的设计等等。这些感兴趣的筒子们,可以去看原始paper🎉~

XGBoost模型实战

当然了,我们并不会从头去去实现一个XGBoost模型,那样十分复杂,而且个人觉得意义不是很大哈。感兴趣的通知可以去直接去阅读源码细节,这样反而更好些🎉在这里,我将使用xgb库来对XGBoost模型进行实践。(夜深了,明天写吧🥱)

附上代码👇不过说实话,XGBoost的参数真的多😣。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# coding:utf-8
# Author:codewithzichao
# E-mail:lizichao@pku.edu.cn

'''

数据集:Mnist
准确率:1.0
时间:53.53627586364746

'''

import pandas as pd

print(pd.__version__)
import numpy as np
from sklearn.model_selection import train_test_split
import time
import xgboost as xgb
from sklearn.metrics import accuracy_score
import os

path = os.getcwd()

start = time.time()

train = pd.read_csv(path + "/MnistData/mnist_train.csv", names=list(i for i in range(784)))
test = pd.read_csv(path + "/MnistData/mnist_test.csv", names=list(i for i in range(784)))

train_data = train.iloc[:, 1:]
train_label = train.iloc[:, 0]
print(train_data.shape)
print(train_label.shape)
test_data = test.iloc[:, 1:]
test_label = test.iloc[:, 0]

params = {
'booster': 'gbtree',
'objective': 'multi:softmax', # 多分类的问题
'num_class': 10, # 类别数,与 multisoftmax 并用
'gamma': 0.1, # 用于控制是否后剪枝的参数,越大越保守,一般0.1、0.2这样子。
'max_depth': 12, # 构建树的深度,越大越容易过拟合
'lambda': 2, # 控制模型复杂度的权重值的L2正则化项参数,参数越大,模型越不容易过拟合。
'subsample': 0.7, # 随机采样训练样本
'colsample_bytree': 0.7, # 生成树时进行的列采样
'min_child_weight': 3,
# 这个参数默认是 1,是每个叶子里面 h 的和至少是多少,对正负样本不均衡时的 0-1 分类而言
# ,假设 h 在 0.01 附近,min_child_weight 为 1 意味着叶子节点中最少需要包含 100 个样本。
# 这个参数非常影响结果,控制叶子节点中二阶导的和的最小值,该参数值越小,越容易 overfitting。
'silent': 0, # 设置成1则没有运行信息输出,最好是设置为0.
'eta': 0.007, # 如同学习率
'seed': 1000,
'nthread': 7, # cpu 线程数
# 'eval_metric': 'auc'
}

num_rounds = 5000 # 迭代次数

X_train, X_validation, Y_train, Y_validation = train_test_split(train_data, train_label, test_size=0.3, random_state=1)
# random_state is of big influence for val-auc


xgb_val = xgb.DMatrix(X_validation, label=Y_validation)
xgb_train = xgb.DMatrix(X_train, label=Y_train)
xgb_test = xgb.DMatrix(test_data)

watchlist = [(xgb_train, 'train'), (xgb_val, 'val')] # 允许查看train set与dev set的误差表现
# training model
# early_stopping_rounds 当设置的迭代次数较大时,early_stopping_rounds 可在一定的迭代次数内准确率没有提升就停止训练
model = xgb.train(params, xgb_train, num_rounds, watchlist, early_stopping_rounds=30)

model.save_model(path + "/xgboost/xgb.model") # 用于存储训练出的模型
print("best best_ntree_limit", model.best_ntree_limit)

print("跑到这里了model.predict")
preds = model.predict(xgb_test, ntree_limit=model.best_ntree_limit)

accuracy_test = accuracy_score(preds, test_label)
print(f"the accuracy is {accuracy_test}.")

end = time.time()
# 输出运行时长

print(f"xgboost success! cost time:{end - start}(s).")

参考文献

1 《XGBoost: A Scalable Tree Boosting System》

2 https://www.jianshu.com/p/ac1c12f3fba1

3 https://zhuanlan.zhihu.com/p/86816771

Would you like to buy me a cup of coffee☕️~