XGBoost stands for eXtreme Gradient Boosting.
XGBoost is an implementation of gradient boosted decision trees designed for speed and performance.
Speed: 并行计算每个样本的一、二次梯度,CART分裂时,也可并行计算不同分裂点,从中选择最优分裂点。
Performance: 修剪技术、正则项、考虑二次梯度。
目标函数:训练损失 + 正则项
目标函数由两部分组成:训练损失
L
(
θ
)
L(\theta)
L(θ)和正则项
Ω
(
θ
)
\Omega(\theta)
Ω(θ)。
obj
(
θ
)
=
L
(
θ
)
+
Ω
(
θ
)
\color{darkcyan} \text{obj}(\theta) = L(\theta) + \Omega(\theta)
obj(θ)=L(θ)+Ω(θ)
训练损失用于测量模型在训练集的预测性能,训练损失可以是 mean squared error:
L
(
θ
)
=
∑
i
(
y
i
−
y
^
i
)
2
\color{darkcyan} L(\theta)=\sum_i(y_i-\hat y_i)^2
L(θ)=i∑(yi−y^i)2
或者是 logistic loss:
L
(
θ
)
=
∑
i
[
y
i
ln
(
1
+
e
−
y
^
i
)
+
(
1
−
y
i
)
ln
(
1
+
e
y
^
i
)
]
\color{darkcyan} L(\theta) = \sum_i[ y_i\ln (1+e^{-\hat{y}_i}) + (1-y_i)\ln (1+e^{\hat{y}_i})]
L(θ)=i∑[yiln(1+e−y^i)+(1−yi)ln(1+ey^i)]
正则项控制模型复杂度,以避免overfitting,考虑下列图片中对同一数据的不同拟合:
The model marked in red visually seems a reasonable fit to you (bias-variance tradeoff).
Understanding the process in a formalized way also helps us to understand the objective that we are learning and the reason behind the heuristics such as pruning and smoothing
集成决策树
以下是使用CART决策树预测是否喜欢电脑游戏的例子:
家族成员被分配到不同叶子节点,CART中不同叶节点对应不同分数,使用分数可以使用统一的优化方法(后续章节介绍)。通常情况下,单棵树在实践中效果不好,可利用集成思想,即建立多棵树,并把这些树的结果组合起来作为最终输出:
以上集成两棵树,同一样本在两棵树中对应叶节点的分数值相加作为最终输出。从例中可看到,两棵树互补,从数学角度可解释为
y
^
i
=
∑
k
=
1
K
f
k
(
x
i
)
,
f
k
∈
F
\color{darkcyan} \hat{y}_i = \sum_{k=1}^K f_k(x_i), f_k \in \mathcal{F}
y^i=k=1∑Kfk(xi),fk∈F
其中 K \color{darkcyan}K K是树的数量, f \color{darkcyan}f f是函数空间 F \color{darkcyan}\mathcal F F中的函数, F \color{darkcyan}\mathcal F F是所有可能CARTs的集合。
目标函数的优化由下列公式给出:
obj
(
θ
)
=
∑
i
n
ℓ
(
y
i
,
y
^
i
)
+
∑
k
=
1
K
Ω
(
f
k
)
\color{darkcyan} \text{obj} (\theta) = \sum_i^n \ell(y_i, \hat{y}_i) + \sum_{k=1}^K \Omega(f_k)
obj(θ)=i∑nℓ(yi,y^i)+k=1∑KΩ(fk)
XGBoost 训练
模型参数是什么? 从目标函数可以看出,模型参数是多棵叶节点输出分数值的决策树。
树优化不能使用传统的欧式空间中的优化方法,因此我们采用累加策略:迭代学习每一棵子树用于修正当前损失(残差学习),并把所学子树组合到总体。若样本
(
x
i
,
y
i
)
\color{darkcyan}(x_i,y_i)
(xi,yi)在时间步
t
\color{darkcyan}t
t的预测为
y
^
i
\color{darkcyan}\hat y_i
y^i,则模型学习过程(逐步逼近)为
y
^
i
(
0
)
=
0
y
^
i
(
1
)
=
f
1
(
x
i
)
=
y
^
i
(
0
)
+
f
1
(
x
i
)
y
^
i
(
2
)
=
f
1
(
x
i
)
+
f
2
(
x
i
)
=
y
^
i
(
1
)
+
f
2
(
x
i
)
…
y
^
i
(
t
)
=
∑
k
=
1
t
f
k
(
x
i
)
=
y
^
i
(
t
−
1
)
+
f
t
(
x
i
)
\color{darkcyan} \begin{aligned}\hat{y}_i^{(0)} &= 0\\ \hat{y}_i^{(1)} &= f_1(x_i) = \hat{y}_i^{(0)} + f_1(x_i)\\ \hat{y}_i^{(2)} &= f_1(x_i) + f_2(x_i)= \hat{y}_i^{(1)} + f_2(x_i)\\ &\dots\\ \hat{y}_i^{(t)} &= \sum_{k=1}^t f_k(x_i)= \hat{y}_i^{(t-1)} + f_t(x_i) \end{aligned}
y^i(0)y^i(1)y^i(2)y^i(t)=0=f1(xi)=y^i(0)+f1(xi)=f1(xi)+f2(xi)=y^i(1)+f2(xi)…=k=1∑tfk(xi)=y^i(t−1)+ft(xi)
子树如何学习? 新的子树可降低总体损失,
t
\color{darkcyan}t
t时刻,我们定义目标函数为:
obj
(
t
)
=
∑
i
=
1
n
ℓ
(
y
i
,
y
^
i
(
t
)
)
+
∑
i
=
1
t
Ω
(
f
i
)
=
∑
i
=
1
n
ℓ
(
y
i
,
y
^
i
(
t
−
1
)
+
f
t
(
x
i
)
)
+
Ω
(
f
t
)
+
c
o
n
s
t
a
n
t
\color{darkcyan} \begin{aligned} \text{obj}^{(t)} &= \sum_{i=1}^n \ell(y_i, \hat{y}_i^{(t)}) + \sum_{i=1}^t\Omega(f_i)\\ &= \sum_{i=1}^n \ell(y_i, \hat{y}_i^{(t-1)} + f_t(x_i)) + \Omega(f_t) + \mathrm{constant} \end{aligned}
obj(t)=i=1∑nℓ(yi,y^i(t))+i=1∑tΩ(fi)=i=1∑nℓ(yi,y^i(t−1)+ft(xi))+Ω(ft)+constant
我们将
f
t
(
x
)
\color{darkcyan}f_t(x)
ft(x)作为增量,使用泰勒公式将损失函数
ℓ
\color{darkcyan}\ell
ℓ在
y
^
(
t
−
1
)
\color{darkcyan}\hat y^{(t-1)}
y^(t−1)处二次展开得
ℓ
(
y
,
y
^
(
t
−
1
)
+
f
t
(
x
)
)
=
ℓ
(
y
,
y
^
(
t
−
1
)
)
+
g
t
f
t
(
x
)
+
1
2
h
t
f
t
2
(
x
)
\color{darkcyan} \ell(y,\hat y^{(t-1)}+f_t(x))=\ell(y,\hat y^{(t-1)})+g_tf_t(x)+\frac{1}{2}h_tf_t^2(x)
ℓ(y,y^(t−1)+ft(x))=ℓ(y,y^(t−1))+gtft(x)+21htft2(x)
式中一阶导
g
t
=
∂
ℓ
/
∂
y
^
(
t
−
1
)
\color{darkcyan}g_t=\partial \ell/\partial \hat y^{(t-1)}
gt=∂ℓ/∂y^(t−1),二阶导
h
t
=
∂
ℓ
2
/
∂
2
y
^
(
t
−
1
)
\color{darkcyan}h_t=\partial \ell^2/\partial^2 \hat y^{(t-1)}
ht=∂ℓ2/∂2y^(t−1),
ℓ
(
y
,
y
^
(
t
−
1
)
)
\color{darkcyan}\ell(y,\hat y^{(t-1)})
ℓ(y,y^(t−1))为常数。
此时可以得到近似目标函数
obj
(
t
)
≈
∑
i
=
1
n
[
g
i
f
t
(
x
i
)
+
1
2
h
i
f
t
2
(
x
i
)
]
+
Ω
(
f
t
)
+
c
o
n
s
t
a
n
t
\color{darkcyan} \text{obj}^{(t)}\approx \sum_{i=1}^n [g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) + \mathrm{constant}
obj(t)≈i=1∑n[gift(xi)+21hift2(xi)]+Ω(ft)+constant
最小化目标函数求解第
t
\color{darkcyan}t
t步子树,第
t
\color{darkcyan}t
t步损失函数仅包含之前损失对样本上的一、二阶导,XGBoost支持自定义损失函数。
Taylor Expression
f ( x + Δ x ) = f ( x ) + f ′ ( x ) Δ x + 1 2 f ′ ′ ( x ) ( Δ x ) 2 f(x+\Delta x)=f(x)+f'(x)\Delta x+\frac{1}{2}f''(x)(\Delta x)^2 f(x+Δx)=f(x)+f′(x)Δx+21f′′(x)(Δx)2
令
q
(
x
)
\color{darkcyan}q(x)
q(x)表示样本
x
\color{darkcyan}x
x所属叶节点,
w
\color{darkcyan}w
w表示所有叶节点对应的分数向量,
T
\color{darkcyan}T
T表示叶节点数量,则决策树
f
t
(
x
)
\color{darkcyan}f_t(x)
ft(x)表示为
f
t
(
x
)
=
w
q
(
x
)
w
∈
R
T
,
q
:
R
d
→
{
1
,
2
,
⋯
,
T
}
.
\color{darkcyan} f_t(x)=w_{q(x)} \quad w∈R^T,q:R_d→\{1,2,⋯,T\}.
ft(x)=wq(x)w∈RT,q:Rd→{1,2,⋯,T}.
XGBoost的复杂度定义为
Ω
(
f
)
=
γ
T
+
1
2
λ
∑
j
=
1
T
w
j
2
\color{darkcyan} \Omega(f) = \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2
Ω(f)=γT+21λj=1∑Twj2
定义树复杂度的方式有很多,但是这种方式的定义在实践中表现很好。
现在我们将损失函数中对样本损失求和转化为对叶节点损失求和,我们这样做的原因是因为同一叶节点中的样本分数相同,且样本数一定小于节点数,这样可以简化计算量或支持并行计算,因此,目标函数在时间
t
\color{darkcyan}t
t的损失为
obj
(
t
)
≈
∑
i
=
1
n
[
g
i
w
q
(
x
i
)
+
1
2
h
i
w
q
(
x
i
)
2
]
+
γ
T
+
1
2
λ
∑
j
=
1
T
w
j
2
=
∑
j
=
1
T
[
w
j
∑
i
∈
I
j
g
i
+
1
2
w
j
2
(
λ
+
∑
i
∈
I
j
h
i
)
]
+
γ
T
\color{darkcyan} \begin{aligned} \text{obj}^{(t)} &\approx \sum_{i=1}^n [g_i w_{q(x_i)} + \frac{1}{2} h_i w_{q(x_i)}^2] + \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2\\ &= \sum^T_{j=1}\left[w_j \sum\nolimits_{i\in I_j}g_i+ \frac{1}{2} w_j^2\left(\lambda+\sum\nolimits_{i\in I_j}h_i\right)\right] + \gamma T \end{aligned}
obj(t)≈i=1∑n[giwq(xi)+21hiwq(xi)2]+γT+21λj=1∑Twj2=j=1∑T[wj∑i∈Ijgi+21wj2(λ+∑i∈Ijhi)]+γT
式中
I
j
=
{
i
∣
q
(
x
i
)
=
j
}
\color{darkcyan}I_j = \{i|q(x_i)=j\}
Ij={i∣q(xi)=j}是节点
j
\color{darkcyan}j
j中的样本集,令
G
j
=
∑
i
∈
I
j
g
i
,
H
j
=
∑
i
∈
I
j
h
i
\color{darkcyan}G_j = \sum_{i\in I_j} g_i,\ H_j = \sum_{i\in I_j} h_i
Gj=∑i∈Ijgi, Hj=∑i∈Ijhi,目标函数简化为
obj
(
t
)
=
∑
j
=
1
T
[
G
j
w
j
+
1
2
(
H
j
+
λ
)
w
j
2
]
+
γ
T
\color{darkcyan} \text{obj}^{(t)} = \sum^T_{j=1} [G_jw_j + \frac{1}{2} (H_j+\lambda) w_j^2] +\gamma T
obj(t)=j=1∑T[Gjwj+21(Hj+λ)wj2]+γT
其中
w
j
\color{darkcyan}w_j
wj相互独立,在特定树结构
q
(
x
)
\color{darkcyan}q(x)
q(x)中,最优叶节点输出值和目标函数为
w
j
∗
=
−
G
j
H
j
+
λ
obj
∗
=
−
1
2
∑
j
=
1
T
G
j
2
H
j
+
λ
+
γ
T
\color{darkcyan} \begin{aligned} w_j^\ast &= -\frac{G_j}{H_j+\lambda}\\ \text{obj}^\ast &= -\frac{1}{2} \sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T \end{aligned}
wj∗obj∗=−Hj+λGj=−21j=1∑THj+λGj2+γT
剩下最后一个问题:怎么学习子树的结构
q
(
x
)
\color{darkcyan}\mathbb q(x)
q(x)?
学习到的子树应能极小化目标函数,可枚举所有可能的子树,并选择使损失最小的子树,但计算量太大。因此,我们通过贪婪方式学习子树:每个节点的分裂均能极小化目标函数,直到达到分类终止条件,如达最大深度、或分裂之后损失不在下降。
我们首先可获得每个样本的
g
i
\color{darkcyan}g_i
gi和
h
i
\color{darkcyan}h_i
hi,在特定的树结构
q
(
x
)
\color{darkcyan}q(x)
q(x)下,可以很容易计算出每个节点的
G
j
\color{darkcyan}G_j
Gj和
H
j
\color{darkcyan}H_j
Hj。根据单节点二分裂前后对应的最优目标函数
obj
\text{obj}
obj,可以计算出分裂后降低的损失(增益) 为
G
a
i
n
=
1
2
[
G
L
2
H
L
+
λ
+
G
R
2
H
R
+
λ
−
(
G
L
+
G
R
)
2
H
L
+
H
R
+
λ
]
−
γ
\color{darkcyan} Gain = \frac{1}{2} \left[\frac{G_L^2}{H_L+\lambda}+\frac{G_R^2}{H_R+\lambda}-\frac{(G_L+G_R)^2}{H_L+H_R+\lambda}\right] - \gamma
Gain=21[HL+λGL2+HR+λGR2−HL+HR+λ(GL+GR)2]−γ
公式中四项的解释:划分后左子节点的损失、划分后右子节点的损失、原始节点的损失、叶节点上的正则项系数。
重要特性:额外增加的
γ
\color{darkcyan}\gamma
γ用于控制复杂度,若增益小于
γ
\color{darkcyan}\gamma
γ,则停止分裂,也称为决策树 “pruning (修剪)技术”。
如何选择分裂特征和分裂点?对于实值特征,将所有样本按此特征值顺序排列,从左到右扫描,可有效地找到最优分裂点:
对于N个样本M个实值特征(特征取值不重复)的训练集,需遍历M*(N-1)次,此外当数据无法全载入内存时,也不能使用这种方法。
由于模型整体是在不断迭代极小化损失,我们在学习子树时,也不一定需要找到最优子树,因此分裂点可采用等分位点方法,对于大数据集可显著降低计算量,如果子树较多,效果并不一定差。
缩减和随机特征采样
这里介绍两种额外的避免过拟合的技术。
第一种技术是Friedman提出的 “shrinkage (缩减)”,shrinkage是将所学新树乘以一个参数 η \color{darkcyan}\eta η之后再加到总体模型,shrinkage降低了单棵树对总体的影响,给出来子树学习提供了更多的空间。
第二种技术是 “column (feature) subsampling (随机特征)”,这种技术常用于随机森林(子树是在随机样本、随机特征中学习),column sub-sampling 比 “row sub-sampling (随机样本)” 更能防止过拟合。
XGBoost 糖尿病预测
# First XGBoost model for Pima Indians dataset
import numpy as np
import pandas as pd
from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold, cross_val_score, train_test_split
from xgboost import XGBClassifier
# 数据集下载地址:https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv
df = pd.read_csv('pima-indians-diabetes.data.txt', header=None)
df.columns = ['preg_times', 'glucose', 'blood_pressure', 'skin_thick', 'insulin', 'BMI',
'DPF', 'age', 'outcome']
print(df.describe())
print(df.shape)
print(df.head())
# glucose、blood_pressure、skin_thick、insulin、BMI 0值为缺失值,填充为均值
missing_columns = ['glucose', 'blood_pressure', 'skin_thick', 'insulin', 'BMI']
df[missing_columns] = df[missing_columns].replace(0, np.nan)
for column in missing_columns:
df.loc[df[column].isna(), column] = df[column].mean()
print('missing filled.')
print(df.describe())
# load data
# dataset = loadtxt('pima-indians-diabetes.data.txt', delimiter=",")
dataset = df.values
# split data into X and y
X = dataset[:, 0:8]
y = dataset[:, 8]
# split data into train and test sets
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=7, shuffle=True)
model = XGBClassifier(
eta=0.08, # 别名learning_rate,子树shrinkage
gamma=0.001, # 别名min_split_loss,分割损失小于改值时停止分割
max_depth=3, # 最大深度
min_child_weight=2, # 子节点样本权重和低于该值时停止分割
max_delta_step=0,
subsample=0.8, # 每次训练时样本采样率
sampling_method='uniform', # 训练样本采样方法
colsample_bytree=0.7, # 每棵树的特征采样比
colsample_bylevel=0.8, # 每层的特征采样比
colsample_bynode=0.7, # 每个节点的特征采样比,对防止过拟合非常重要
reg_alpha=0, # L1正则化系数
reg_lambda=1, # L2正则化系数
tree_method='auto', # 树构造算法
base_score=0.5,
booster='gbtree',
missing=None,
n_estimators=80,
n_jobs=4,
objective='binary:logistic',
random_state=2,
seed=6,
scale_pos_weight=1.0,
silent=True,
verbosity=2
)
model.fit(X_train, y_train)
# cv_result = cross_val_score(model, X, y, cv=KFold(
# n_splits=10, random_state=22, shuffle=True))
# print('Cross validation accuracy: %.2f%%' % (cv_result.mean() * 100))
# train predictions
acc_train = accuracy_score(y_train, model.predict(X_train))
print("Train accuracy: %.2f%%" % (acc_train * 100.0))
# evaluate predictions
acc_test = accuracy_score(y_test, model.predict(X_test))
print("Test accuracy: %.2f%%" % (acc_test * 100.0))
预测结果:
Train accuracy: 84.20%
Test accuracy: 83.12%
Reference
1. XGBoost Tutorials
2. XGBoost: A Scalable Tree Boosting System