XGBoost算法分析与案例调参实例
XGBoost算法分析
XGBoost是陈天奇等人开发的一个开源机器学习项目,高效地实现了GBDT算法并进行了算法和工程上的许多改进,被广泛应用在Kaggle竞赛及其他许多机器学习竞赛中并取得了不错的成绩。
XGBoost本质上还是一个GBDT,但是力争把速度和效率发挥到极致,所以叫X (Extreme) GBoosted。
XGBoost是一个优化的分布式梯度增强库,旨在实现高效,灵活和便携。 它在Gradient Boosting框架下实现机器学习算法。 XGBoost提供了并行树提升(也称为GBDT,GBM),可以快速准确地解决许多数据科学问题。 相同的代码在主要的分布式环境(Hadoop,SGE,MPI)上运行,并且可以解决超过数十亿个样例的问题。XGBoost利用了核外计算并且能够使数据科学家在一个主机上处理数亿的样本数据。最终,将这些技术进行结合来做一个端到端的系统以最少的集群系统来扩展到更大的数据集上。Xgboost以CART决策树为子模型,通过Gradient Tree Boosting实现多棵CART树的集成学习,得到最终模型。
下面我们来看看XGBoost的最终模型构建:
引用陈天奇的论文,我们的数据为:
D
=
{
(
x
i
,
y
i
)
}
(
∣
D
∣
=
n
,
x
i
∈
R
m
,
y
i
∈
R
)
{D}=\left\{\left(\mathbf{x}_{i}, y_{i}\right)\right\}\left(|{D}|=n, \mathbf{x}_{i} \in \mathbb{R}^{m}, y_{i} \in \mathbb{R}\right)
D={(xi,yi)}(∣D∣=n,xi∈Rm,yi∈R)
- 构造目标函数:
假设有K棵树,则第i个样本的输出为 y ^ i = ϕ ( x i ) = ∑ k = 1 K f k ( x i ) , f k ∈ F \hat{y}_{i}=\phi\left(\mathrm{x}_{i}\right)=\sum_{k=1}^{K} f_{k}\left(\mathrm{x}_{i}\right), \quad f_{k} \in {F} y^i=ϕ(xi)=∑k=1Kfk(xi),fk∈F,其中, F = { f ( x ) = w q ( x ) } ( q : R m → T , w ∈ R T ) {F}=\left\{f(\mathbf{x})=w_{q(\mathbf{x})}\right\}\left(q: \mathbb{R}^{m} \rightarrow T, w \in \mathbb{R}^{T}\right) F={f(x)=wq(x)}(q:Rm→T,w∈RT)
因此,目标函数的构建为:
L ( ϕ ) = ∑ i l ( y ^ i , y i ) + ∑ k Ω ( f k ) {L}(\phi)=\sum_{i} l\left(\hat{y}_{i}, y_{i}\right)+\sum_{k} \Omega\left(f_{k}\right) L(ϕ)=i∑l(y^i,yi)+k∑Ω(fk)
其中, ∑ i l ( y ^ i , y i ) \sum_{i} l\left(\hat{y}_{i}, y_{i}\right) ∑il(y^i,yi)为loss function, ∑ k Ω ( f k ) \sum_{k} \Omega\left(f_{k}\right) ∑kΩ(fk)为正则化项。 - 叠加式的训练(Additive Training):
给定样本 x i x_i xi, y ^ i ( 0 ) = 0 \hat{y}_i^{(0)} = 0 y^i(0)=0(初始预测), y ^ i ( 1 ) = y ^ i ( 0 ) + f 1 ( x i ) \hat{y}_i^{(1)} = \hat{y}_i^{(0)} + f_1(x_i) y^i(1)=y^i(0)+f1(xi), y ^ i ( 2 ) = y ^ i ( 0 ) + f 1 ( x i ) + f 2 ( x i ) = y ^ i ( 1 ) + f 2 ( x i ) \hat{y}_i^{(2)} = \hat{y}_i^{(0)} + f_1(x_i) + f_2(x_i) = \hat{y}_i^{(1)} + f_2(x_i) y^i(2)=y^i(0)+f1(xi)+f2(xi)=y^i(1)+f2(xi)…以此类推,可以得到:$ \hat{y}_i^{(K)} = \hat{y}_i^{(K-1)} + f_K(x_i)$ ,其中,$ \hat{y}_i^{(K-1)} $ 为前K-1棵树的预测结果,$ f_K(x_i)$ 为第K棵树的预测结果。
因此,目标函数可以分解为:
L ( K ) = ∑ i = 1 n l ( y i , y ^ i ( K − 1 ) + f K ( x i ) ) + ∑ k Ω ( f k ) {L}^{(K)}=\sum_{i=1}^{n} l\left(y_{i}, \hat{y}_{i}^{(K-1)}+f_{K}\left(\mathrm{x}_{i}\right)\right)+\sum_{k} \Omega\left(f_{k}\right) L(K)=i=1∑nl(yi,y^i(K−1)+fK(xi))+k∑Ω(fk)
由于正则化项也可以分解为前K-1棵树的复杂度加第K棵树的复杂度,因此: L ( K ) = ∑ i = 1 n l ( y i , y ^ i ( K − 1 ) + f K ( x i ) ) + ∑ k = 1 K − 1 Ω ( f k ) + Ω ( f K ) {L}^{(K)}=\sum_{i=1}^{n} l\left(y_{i}, \hat{y}_{i}^{(K-1)}+f_{K}\left(\mathrm{x}_{i}\right)\right)+\sum_{k=1} ^{K-1}\Omega\left(f_{k}\right)+\Omega\left(f_{K}\right) L(K)=∑i=1nl(yi,y^i(K−1)+fK(xi))+∑k=1K−1Ω(fk)+Ω(fK),由于 ∑ k = 1 K − 1 Ω ( f k ) \sum_{k=1} ^{K-1}\Omega\left(f_{k}\right) ∑k=1K−1Ω(fk)在模型构建到第K棵树的时候已经固定,无法改变,因此是一个已知的常数,可以在最优化的时候省去,故:
L ( K ) = ∑ i = 1 n l ( y i , y ^ i ( K − 1 ) + f K ( x i ) ) + Ω ( f K ) {L}^{(K)}=\sum_{i=1}^{n} l\left(y_{i}, \hat{y}_{i}^{(K-1)}+f_{K}\left(\mathrm{x}_{i}\right)\right)+\Omega\left(f_{K}\right) L(K)=i=1∑nl(yi,y^i(K−1)+fK(xi))+Ω(fK) - 使用泰勒级数近似目标函数:
L ( K ) ≃ ∑ i = 1 n [ l ( y i , y ^ ( K − 1 ) ) + g i f K ( x i ) + 1 2 h i f K 2 ( x i ) ] + Ω ( f K ) {L}^{(K)} \simeq \sum_{i=1}^{n}\left[l\left(y_{i}, \hat{y}^{(K-1)}\right)+g_{i} f_{K}\left(\mathrm{x}_{i}\right)+\frac{1}{2} h_{i} f_{K}^{2}\left(\mathrm{x}_{i}\right)\right]+\Omega\left(f_{K}\right) L(K)≃i=1∑n[l(yi,y^(K−1))+gifK(xi)+21hifK2(xi)]+Ω(fK)
其中, g i = ∂ y ^ ( t − 1 ) l ( y i , y ^ ( t − 1 ) ) g_{i}=\partial_{\hat{y}(t-1)} l\left(y_{i}, \hat{y}^{(t-1)}\right) gi=∂y^(t−1)l(yi,y^(t−1))和 h i = ∂ y ^ ( t − 1 ) 2 l ( y i , y ^ ( t − 1 ) ) h_{i}=\partial_{\hat{y}^{(t-1)}}^{2} l\left(y_{i}, \hat{y}^{(t-1)}\right) hi=∂y^(t−1)2l(yi,y^(t−1))
在这里,我们补充下泰勒级数的相关知识:
在数学中,泰勒级数(英语:Taylor series)用无限项连加式——级数来表示一个函数,这些相加的项由函数在某一点的导数求得。具体的形式如下:
f ( x ) = f ( x 0 ) 0 ! + f ′ ( x 0 ) 1 ! ( x − x 0 ) + f ′ ′ ( x 0 ) 2 ! ( x − x 0 ) 2 + … + f ( n ) ( x 0 ) n ! ( x − x 0 ) n + . . . . . . f(x)=\frac{f\left(x_{0}\right)}{0 !}+\frac{f^{\prime}\left(x_{0}\right)}{1 !}\left(x-x_{0}\right)+\frac{f^{\prime \prime}\left(x_{0}\right)}{2 !}\left(x-x_{0}\right)^{2}+\ldots+\frac{f^{(n)}\left(x_{0}\right)}{n !}\left(x-x_{0}\right)^{n}+...... f(x)=0!f(x0)+1!f′(x0)(x−x0)+2!f′′(x0)(x−x0)2+…+n!f(n)(x0)(x−x0)n+......
由于 ∑ i = 1 n l ( y i , y ^ ( K − 1 ) ) \sum_{i=1}^{n}l\left(y_{i}, \hat{y}^{(K-1)}\right) ∑i=1nl(yi,y^(K−1))在模型构建到第K棵树的时候已经固定,无法改变,因此是一个已知的常数,可以在最优化的时候省去,故:
L ~ ( K ) = ∑ i = 1 n [ g i f K ( x i ) + 1 2 h i f K 2 ( x i ) ] + Ω ( f K ) \tilde{{L}}^{(K)}=\sum_{i=1}^{n}\left[g_{i} f_{K}\left(\mathbf{x}_{i}\right)+\frac{1}{2} h_{i} f_{K}^{2}\left(\mathbf{x}_{i}\right)\right]+\Omega\left(f_{K}\right) L~(K)=i=1∑n[gifK(xi)+21hifK2(xi)]+Ω(fK) - 如何定义一棵树:
为了说明如何定义一棵树的问题,我们需要定义几个概念:第一个概念是样本所在的节点位置 q ( x ) q(x) q(x),第二个概念是有哪些样本落在节点j上 I j = { i ∣ q ( x i ) = j } I_{j}=\left\{i \mid q\left(\mathbf{x}_{i}\right)=j\right\} Ij={i∣q(xi)=j},第三个概念是每个结点的预测值 w q ( x ) w_{q(x)} wq(x),第四个概念是模型复杂度 Ω ( f K ) \Omega\left(f_{K}\right) Ω(fK),它可以由叶子节点的个数以及节点函数值来构建,则: Ω ( f K ) = γ T + 1 2 λ ∑ j = 1 T w j 2 \Omega\left(f_{K}\right) = \gamma T+\frac{1}{2} \lambda \sum_{j=1}^{T} w_{j}^{2} Ω(fK)=γT+21λ∑j=1Twj2。
q ( x 1 ) = 1 , q ( x 2 ) = 3 , q ( x 3 ) = 1 , q ( x 4 ) = 2 , q ( x 5 ) = 3 q(x_1) = 1,q(x_2) = 3,q(x_3) = 1,q(x_4) = 2,q(x_5) = 3 q(x1)=1,q(x2)=3,q(x3)=1,q(x4)=2,q(x5)=3, I 1 = { 1 , 3 } , I 2 = { 4 } , I 3 = { 2 , 5 } I_1 = \{1,3\},I_2 = \{4\},I_3 = \{2,5\} I1={1,3},I2={4},I3={2,5}, w = ( 15 , 12 , 20 ) w = (15,12,20) w=(15,12,20)
因此,目标函数用以上符号替代后:
L ~ ( K ) = ∑ i = 1 n [ g i f K ( x i ) + 1 2 h i f K 2 ( x i ) ] + γ T + 1 2 λ ∑ j = 1 T w j 2 = ∑ j = 1 T [ ( ∑ i ∈ I j g i ) w j + 1 2 ( ∑ i ∈ I j h i + λ ) w j 2 ] + γ T \begin{aligned} \tilde{{L}}^{(K)} &=\sum_{i=1}^{n}\left[g_{i} f_{K}\left(\mathrm{x}_{i}\right)+\frac{1}{2} h_{i} f_{K}^{2}\left(\mathrm{x}_{i}\right)\right]+\gamma T+\frac{1}{2} \lambda \sum_{j=1}^{T} w_{j}^{2} \\ &=\sum_{j=1}^{T}\left[\left(\sum_{i \in I_{j}} g_{i}\right) w_{j}+\frac{1}{2}\left(\sum_{i \in I_{j}} h_{i}+\lambda\right) w_{j}^{2}\right]+\gamma T \end{aligned} L~(K)=i=1∑n[gifK(xi)+21hifK2(xi)]+γT+21λj=1∑Twj2=j=1∑T⎣⎡⎝⎛i∈Ij∑gi⎠⎞wj+21⎝⎛i∈Ij∑hi+λ⎠⎞wj2⎦⎤+γT
由于我们的目标就是最小化目标函数,现在的目标函数化简为一个关于w的二次函数: L ~ ( K ) = ∑ j = 1 T [ ( ∑ i ∈ I j g i ) w j + 1 2 ( ∑ i ∈ I j h i + λ ) w j 2 ] + γ T \tilde{{L}}^{(K)}=\sum_{j=1}^{T}\left[\left(\sum_{i \in I_{j}} g_{i}\right) w_{j}+\frac{1}{2}\left(\sum_{i \in I_{j}} h_{i}+\lambda\right) w_{j}^{2}\right]+\gamma T L~(K)=∑j=1T[(∑i∈Ijgi)wj+21(∑i∈Ijhi+λ)wj2]+γT,根据二次函数求极值的公式: y = a x 2 b x c y=ax^2 bx c y=ax2bxc求极值,对称轴在 x = − b 2 a x=-\frac{b}{2 a} x=−2ab,极值为 y = 4 a c − b 2 4 a y=\frac{4 a c-b^{2}}{4 a} y=4a4ac−b2,因此:
w j ∗ = − ∑ i ∈ I j g i ∑ i ∈ I j h i + λ w_{j}^{*}=-\frac{\sum_{i \in I_{j}} g_{i}}{\sum_{i \in I_{j}} h_{i}+\lambda} wj∗=−∑i∈Ijhi+λ∑i∈Ijgi
以及
L ~ ( K ) ( q ) = − 1 2 ∑ j = 1 T ( ∑ i ∈ I j g i ) 2 ∑ i ∈ I j h i + λ + γ T \tilde{{L}}^{(K)}(q)=-\frac{1}{2} \sum_{j=1}^{T} \frac{\left(\sum_{i \in I_{j}} g_{i}\right)^{2}}{\sum_{i \in I_{j}} h_{i}+\lambda}+\gamma T L~(K)(q)=−21j=1∑T∑i∈Ijhi+λ(∑i∈Ijgi)2+γT - 如何寻找树的形状:
不难发现,刚刚的讨论都是基于树的形状已经确定了计算 w w w和 L L L,但是实际上我们需要像学习决策树一样找到树的形状。因此,我们借助决策树学习的方式,使用目标函数的变化来作为分裂节点的标准。
因此,从上面的例子看出:分割节点的标准为 m a x { L ~ ( o l d ) − L ~ ( n e w ) } max\{\tilde{{L}}^{(old)} - \tilde{{L}}^{(new)} \} max{L~(old)−L~(new)},即:
L split = 1 2 [ ( ∑ i ∈ I L g i ) 2 ∑ i ∈ I L h i + λ + ( ∑ i ∈ I R g i ) 2 ∑ i ∈ I R h i + λ − ( ∑ i ∈ I g i ) 2 ∑ i ∈ I h i + λ ] − γ {L}_{\text {split }}=\frac{1}{2}\left[\frac{\left(\sum_{i \in I_{L}} g_{i}\right)^{2}}{\sum_{i \in I_{L}} h_{i}+\lambda}+\frac{\left(\sum_{i \in I_{R}} g_{i}\right)^{2}}{\sum_{i \in I_{R}} h_{i}+\lambda}-\frac{\left(\sum_{i \in I} g_{i}\right)^{2}}{\sum_{i \in I} h_{i}+\lambda}\right]-\gamma Lsplit =21[∑i∈ILhi+λ(∑i∈ILgi)2+∑i∈IRhi+λ(∑i∈IRgi)2−∑i∈Ihi+λ(∑i∈Igi)2]−γ -
-
精确贪心分裂算法
XGBoost在生成新树的过程中,最基本的操作是节点分裂。节点分裂中最重 要的环节是找到最优特征及最优切分点, 然后将叶子节点按照最优特征和最优切 分点进行分裂。选取最优特征和最优切分点的一种思路如下:首先找到所有的候 选特征及所有的候选切分点, 一一求得其 L split {L}_{\text {split }} Lsplit , 然后选择 L s p l i t {L}_{\mathrm{split}} Lsplit 最大的特征及 对应切分点作为最优特征和最优切分点。我们称此种方法为精确贪心算法。该算法是一种启发式算法, 因为在节点分裂时只选择当前最优的分裂策略, 而非全局最优的分裂策略。
2.基于直方图的近似算法
精确贪心算法在选择最优特征和最优切分点时是一种十分有效的方法。它计算了所有特征、所有切分点的收益, 并从中选择了最优的, 从而保证模型能比较好地拟合了训练数据。但是当数据不能完全加载到内存时,精确贪心算法会变得 非常低效,算法在计算过程中需要不断在内存与磁盘之间进行数据交换,这是个非常耗时的过程, 并且在分布式环境中面临同样的问题。为了能够更高效地选 择最优特征及切分点, XGBoost提出一种近似算法来解决该问题。 基于直方图的近似算法的主要思想是:对某一特征寻找最优切分点时,首先对该特征的所有切分点按分位数 (如百分位) 分桶, 得到一个候选切分点集。特征的每一个切分点都可以分到对应的分桶; 然后,对每个桶计算特征统计G和H得到直方图, G为该桶内所有样本一阶特征统计g之和, H为该桶内所有样本二阶特征统计h之和; 最后,选择所有候选特征及候选切分点中对应桶的特征统计收益最大的作为最优特征及最优切分点。基于直方图的近似算法的计算过程如下所示:- 对于每个特征 k = 1 , 2 , ⋯ , m , k=1,2, \cdots, m, k=1,2,⋯,m, 按分位数对特征 k k k 分桶 Θ , \Theta, Θ, 可得候选切分点, S k = { S k 1 , S k 2 , ⋯ , S k l } 1 S_{k}=\left\{S_{k 1}, S_{k 2}, \cdots, S_{k l}\right\}^{1} Sk={Sk1,Sk2,⋯,Skl}1
- 对于每个特征
k
=
1
,
2
,
⋯
,
m
,
k=1,2, \cdots, m,
k=1,2,⋯,m, 有:
G k v ← = ∑ j ∈ { j ∣ s k , v ≥ x j k > s k , v − 1 } g j H k v ← = ∑ j ∈ { j ∣ s k , v ≥ x j k > s k , v − 1 } h j \begin{array}{l} G_{k v} \leftarrow=\sum_{j \in\left\{j \mid s_{k, v} \geq \mathbf{x}_{j k}>s_{k, v-1\;}\right\}} g_{j} \\ H_{k v} \leftarrow=\sum_{j \in\left\{j \mid s_{k, v} \geq \mathbf{x}_{j k}>s_{k, v-1\;}\right\}} h_{j} \end{array} Gkv←=∑j∈{j∣sk,v≥xjk>sk,v−1}gjHkv←=∑j∈{j∣sk,v≥xjk>sk,v−1}hj - 类似精确贪心算法,依据梯度统计找到最大增益的候选切分点。
近似算法实现了两种候选切分点的构建策略:全局策略和本地策略。全局策略是在树构建的初始阶段对每一个特征确定一个候选切分点的集合, 并在该树每一层的节点分裂中均采用此集合计算收益, 整个过程候选切分点集合不改变。本地策略则是在每一次节点分裂时均重新确定候选切分点。全局策略需要更细的分桶才能达到本地策略的精确度, 但全局策略在选取候选切分点集合时比本地策略更简单。
-
在XGBoost系统中, 用户可以根据需求自由选择使用精确贪心算法、近似算法全局策略、近似算法本地策略, 算法均可通过参数进行配置。
XGBoost算法案例
分类案例
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn import datasets
from xgboost import plot_importance
from matplotlib import pyplot as plt
# 使用鸢尾花分类数据集
iris = datasets.load_iris()
x_train, x_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.2, random_state=1)
params = {
'booster': 'gbtree',
'objective': 'multi:softmax',
'num_class': 3,
'gamma': 0.1,
'max_depth': 6,
'lambda': 2,
'subsample': 0.7,
'colsample_bytree': 0.75,
'min_child_weight': 3,
'silent': 0,
'eta': 0.1,
'seed': 1,
'nthread': 4,
}
plst = list(params.items())
# 生成数据集格式
dtrain = xgb.DMatrix(x_train, y_train)
num_rounds = 500
# 训练模型
model = xgb.train(plst, dtrain, num_rounds)
d_test = xgb.DMatrix(x_test)
# 预测
y_pred = model.predict(d_test)
# 计算准确率
accuracy = accuracy_score(y_test, y_pred)
print("Accuracy: {:.2f}%".format(accuracy * 100))
# 显示重要特征
plot_importance(model)
plt.show()
运行结果
Accuracy: 96.67%
回归案例
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn import datasets
from xgboost import plot_importance
from matplotlib import pyplot as plt
# 加载波士顿房价数据集
boston = datasets.load_boston()
X,y = boston.data,boston.target
# 划分训练集测试集
x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1)
params = {
'booster': 'gbtree',
'objective': 'reg:squarederror',
'gamma': 0.1,
'max_depth': 6,
'lambda': 3,
'subsample': 0.7,
'colsample_bytree': 0.7,
'min_child_weight': 3,
'silent': 1,
'eta': 0.1,
'seed': 2000,
'nthread': 4,
}
dtrain = xgb.DMatrix(x_train, y_train)
plst = list(params.items())
num_rounds = 300
model = xgb.train(plst, dtrain, num_rounds)
# 对测试集进行预测
dtest = xgb.DMatrix(x_test)
ans = model.predict(dtest)
# 显示重要特征
plot_importance(model)
plt.show()
运行结果