梯度提升树(GBDT)原理小结
本博客中使用到的完整代码请移步至: 我的github:https://github.com/qingyujean/Magic-NLPer,求赞求星求鼓励~~~
集成学习系列文章:
集成学习原理小结(AdaBoost & lightGBM demo)
梯度提升树(GBDT)原理小结
XGBoost使用
随机森林(Random Forest)原理小结
接着之前的 决策树 章节和 随机森林 章节,接下来还会继续介绍一些基于决策树的,具有代表性的集成模型,如GBDT,XGBoost以及lightGBM等。
本章主要介绍 提升树(Boosting Tree) 和 梯度提升(Gradient Boosting)树(简写GBDT) ,GBDT是boosting集成模型的扩展变体,所以前面会简要介绍一些关于boosting的内容,后面会有专门的 “集成学习” 的章节来重点介绍boosting相关内容。
1. boosting
Boosting是一族可将弱学习器
提升为强学习器
的算法,其著名代表是AdaBoost
。这里不会具体介绍AdaBoost的细节,会在集成学习里重点说明。
Boosting算法要求基学习器能对特定的数据分布进行学习,这可通过”重赋权法“(re-weighting)
实施;对于无法接受带权样本的基学习算法,则可通过”重采样法“(re-sampling)
来处理。
2. 提升树(boosting tree)
2.1 提升树模型
提升方法实际采用加法模型(即基函数的线性组合)
与前向分步
算法。而提升树是以决策树(二叉分类树或二叉回归树)
为基函数的提升方法。提升树模型可表示为:
f
M
(
x
)
=
∑
m
=
1
M
T
(
x
;
Θ
m
)
f_M(x)=\sum\limits_{m=1}^MT(x;\Theta_m)
fM(x)=m=1∑MT(x;Θm)
其中
T
(
x
;
Θ
m
)
T(x;\Theta_m)
T(x;Θm)表示决策树,
Θ
m
\Theta_m
Θm表示决策树的参数,
M
M
M 表示决策树的个数。
2.2 提升树算法:前向分步算法
f
m
(
x
)
=
f
m
−
1
(
x
)
+
T
(
x
;
Θ
m
)
f_m(x)=f_{m-1}(x)+T(x;\Theta_m)
fm(x)=fm−1(x)+T(x;Θm)
其中初始提升树
f
0
(
x
)
=
0
f_0(x)=0
f0(x)=0,
f
m
−
1
(
x
)
f_{m-1}(x)
fm−1(x)为当前模型,通过经验风险极小化
(模型f(X)关于训练数据集的平均损失成为 经验风险
)确定下一棵决策树的参数
Θ
m
\Theta_m
Θm:
Θ
^
m
=
arg
min
Θ
m
∑
i
=
1
N
L
(
y
i
,
f
m
(
x
i
)
)
=
arg
min
Θ
m
∑
i
=
1
N
L
(
y
i
,
f
m
−
1
(
x
i
)
+
T
(
x
i
;
Θ
m
)
)
\begin{array}{ll} &\hat{\Theta}_{m}=\arg\min_{\Theta_m} \sum\limits_{i=1}^NL(y_i,\; f_m(x_i)) \\ &\quad\;\;\;=\arg\min_{\Theta_m} \sum\limits_{i=1}^NL(y_i,\; f_{m-1}(x_i)+T(x_i;\Theta_m))\end{array}
Θ^m=argminΘmi=1∑NL(yi,fm(xi))=argminΘmi=1∑NL(yi,fm−1(xi)+T(xi;Θm))
针对不同问题的提升树学习算法,其主要区别在于使用的损失函数不同:
- 回归问题(平方误差损失函数)
- 分类问题(指数损失函数)
- 一般决策问题(一般损失函数)
2.3 分类问题的提升树算法
对于二分类问题,提升树算法只需将AdaBoost算法中的基本分类器限制
为二分类决策树
即可。下面直接贴出李航蓝皮书的Adaboost的算法描述,只需知道算法中基学习器
G
m
(
x
)
G_m(x)
Gm(x)限定为二分类决策树即为提升树算法:
关于AdaBoost算法的一些说明,将在集成学习的提升方法章节细讲。
这里提一点,为什么说分类问题的损失函数是指数损失函数
,首先看看什么是指数损失函数:
L ( y , f ( x ) ) = e x p [ − y f ( x ) ] L(y,f(x))=exp[-yf(x)] L(y,f(x))=exp[−yf(x)]
关于提升方法的分类问题的损失函数是指数函数的证明,在李航蓝皮书第2版p164页有详细证明
,后面在讲集成学习的提升方法时会细说,这里主要是针对与决策树相关集成方法的简单介绍,就先不说了。
2.4 回归问题的提升树算法
已知训练数据集
T
=
{
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
,
⋯
,
(
x
N
,
y
N
)
}
T=\left\{\left(x_{1}, y_{1}\right),\left(x_{2}, y_{2}\right), \cdots,\left(x_{N}, y_{N}\right)\right\}
T={(x1,y1),(x2,y2),⋯,(xN,yN)},其中
x
i
∈
X
⊆
R
n
,
y
i
∈
Y
=
{
−
1
,
+
1
}
x_{i} \in \mathcal{X} \subseteq \mathbf{R}^{n}, y_{i} \in \mathcal{Y} =\left\{-1,+1\right\}
xi∈X⊆Rn,yi∈Y={−1,+1}。回归问题的提升树可表示为:
T
(
x
;
Θ
)
=
∑
j
=
1
J
c
j
I
(
x
∈
R
j
)
T(x;\Theta)=\sum\limits_{j=1}^J c_jI(x\in R_j)
T(x;Θ)=j=1∑JcjI(x∈Rj)
其中
R
j
R_j
Rj表示将输入空间
X
\mathcal X
X划分为
J
J
J个互不相交的区域,并且在每个区域上确定的输出常量为
c
j
c_j
cj,参数
Θ
=
{
(
R
1
,
c
1
)
,
(
R
2
,
c
2
)
,
.
.
.
,
(
R
J
,
c
J
)
}
\Theta=\{(R_1,c_1),(R_2,c_2),...,(R_J,c_J)\}
Θ={(R1,c1),(R2,c2),...,(RJ,cJ)}表示树的区域划分和各区域上的常数。
J
J
J是回归树的复杂度即叶子结点个数
。
按照前向分步算法
,当给定当前模型
f
m
−
1
(
x
)
f_{m-1}(x)
fm−1(x)时,则第m步需求解:
Θ
^
m
=
arg
min
Θ
m
∑
i
=
1
N
L
(
y
i
,
f
m
(
x
i
)
)
=
arg
min
Θ
m
∑
i
=
1
N
L
(
y
i
,
f
m
−
1
(
x
i
)
+
T
(
x
i
;
Θ
m
)
)
\begin{array}{ll} &\hat{\Theta}_{m}=\arg\min_{\Theta_m} \sum\limits_{i=1}^NL(y_i,\; f_m(x_i)) \\ &\quad\;\;\;=\arg\min_{\Theta_m} \sum\limits_{i=1}^NL(y_i,\; f_{m-1}(x_i)+T(x_i;\Theta_m))\end{array}
Θ^m=argminΘmi=1∑NL(yi,fm(xi))=argminΘmi=1∑NL(yi,fm−1(xi)+T(xi;Θm))
而回归树采用“平方误差损失函数“
,则
L
(
y
,
f
(
x
)
)
=
(
y
−
f
(
x
)
)
2
L(y,f(x))=(y-f(x))^2
L(y,f(x))=(y−f(x))2,那么损失变为:
L
(
y
,
f
m
(
x
)
)
=
L
(
y
,
f
m
−
1
(
x
)
+
T
(
x
i
;
Θ
m
)
)
=
[
y
−
(
f
m
−
1
(
x
)
+
T
(
x
i
;
Θ
m
)
)
]
2
=
[
y
−
f
m
−
1
(
x
)
−
T
(
x
i
;
Θ
m
)
]
2
令
r
=
y
−
f
m
−
1
(
x
)
则
=
[
r
−
T
(
x
i
;
Θ
m
)
]
2
\begin{array}{ll} & L(y,f_m(x))=L(y,f_{m-1}(x)+T(x_i;\Theta_m)) \\ & \qquad\qquad\quad\; =[y-(f_{m-1}(x)+T(x_i;\Theta_m))]^2 \\ & \qquad\qquad\quad\;=[y-f_{m-1}(x)-T(x_i;\Theta_m)]^2\quad 令r=y-f_{m-1}(x)则 \\ & \qquad\qquad\quad\;=[r-T(x_i;\Theta_m)]^2\end{array}
L(y,fm(x))=L(y,fm−1(x)+T(xi;Θm))=[y−(fm−1(x)+T(xi;Θm))]2=[y−fm−1(x)−T(xi;Θm)]2令r=y−fm−1(x)则=[r−T(xi;Θm)]2
其中
r
=
y
−
f
m
−
1
(
x
)
r=y-f_{m-1}(x)
r=y−fm−1(x)是当前拟合数据的 残差(residual)
,所以 回归问题的提升树算法,就是在简单的拟合当前模型的残差!
算法描述如下:
3. 梯度提升树(gradient boosting)
GBDT针对一般损失函数提出,不再仅仅针对指数损失函数或者平方损失函数,GBDT算法更通用。
GBDT的基学习器限定为使用CART回归树
。
3.1 分类问题的梯度提升树算法
分类问题如果使用指数损失函数
,此时GBDT退化为AdaBoost算法;如果使用对数似然损失函数
,使用类别预测的概率值与真实概率值的差值来拟合损失,此时算法与GBDT回归算法过程基本相同,除了因损失函数不同而引起的负梯度(一阶泰勒展开)计算以及各叶子结点的最佳残差拟合值的计算不同
。
这部分可参考如下2篇博客:
深入理解GBDT二分类算法以及刘建平老师的梯度提升树(GBDT)原理小结。
3.2 回归问题的梯度提升树算法
对于回归问题,在前面一节中的普通提升树中,使用
r
=
y
−
f
m
−
1
(
x
)
r=y-f_{m-1}(x)
r=y−fm−1(x)来作为当前拟合数据的残差(residual)
,而本方法中,则使用 损失函数的负梯度(一阶泰勒展开)在当前模型的值,作为残差的近似值
,即:
r
=
−
[
∂
L
(
y
,
f
(
x
i
)
)
∂
f
(
x
i
)
]
f
(
x
)
=
f
m
−
1
(
x
)
r=-\big[\frac{\partial L(y,f(x_i))}{\partial f(x_i)}\big]_{f(x)=f_{m-1}(x)}
r=−[∂f(xi)∂L(y,f(xi))]f(x)=fm−1(x)
算法描述如下:
关于提升方法(Boosting,代表算法AdaBoost)
、提升树(BDT)
、梯度提升树(GDBT)
的一点总结:
- Adaboost(以分类问题为例)是去对训练样本reweight,使得后面的模型更关注那些被误分类的样本,每次选择在当前样本权重分布下分类误差率最小的 G m ( x ) G_m(x) Gm(x),得到 f m ( x ) = f m − 1 ( x ) + α G m ( x ) f_m(x)=f_{m-1}(x)+\alpha G_m(x) fm(x)=fm−1(x)+αGm(x)
- 而BDT(以回归问题为例)是每次与拟合当前模型与y值的
残差
,逐步缩小这个差距(例如“平方损失”
) - GDBT(通用)就是使用
损失函数的负梯度(一阶泰勒展开)在当前模型的值来近似表示残差
,然后在每一步中去拟合这个残差(更通用不仅针对分类的指数损失函数,回归的平方损失函数,还是一般的决策函数)
4. 代码示例
使用sklearn GBDT解决分类问题。数据集:白酒数据,共有13个特征,3个类别,在下面仅使用2个类别和2个特征作为示例。
先加载数据:
# Wine dataset and rank the 13 features by their respective importance measures
df_wine = pd.read_csv(data_dir+'wine.data',
header=None,
names=['Class label', 'Alcohol', 'Malic acid', 'Ash', 'Alcalinity of ash', 'Magnesium',
'Total phenols', 'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins', 'Color intensity',
'Hue', 'OD280/OD315 of diluted wines', 'Proline'])
print('Class labels', np.unique(df_wine['Class label'])) # 一共有3个类
df_wine = df_wine[df_wine['Class label']!=1] # 去掉一个类
y = df_wine['Class label'].values
X = df_wine[['Alcohol', 'OD280/OD315 of diluted wines']].values
输出:
Class labels [1 2 3]
选取2个特征,去除一个类别,划分数据集:
le = LabelEncoder()
y = le.fit_transform(y)
print('Class labels', np.unique(y))
print('numbers of features:', X.shape[1])
# 划分训练集测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1, stratify=y)
X_train.shape
输出:
Class labels [0 1]
numbers of features: 2
56
(95, 2)
先使用一颗决策树分类,作为GBDT的对比参照:
# 先使用决策树做分类,作为GBDT的对比参照
tree = DecisionTreeClassifier(criterion='entropy',
random_state=1,
max_depth=1)
tree = tree.fit(X_train, y_train)
y_train_pred = tree.predict(X_train)
y_test_pred = tree.predict(X_test)
tree_train = accuracy_score(y_train, y_train_pred)
tree_test = accuracy_score(y_test, y_test_pred)
print('Decision tree train/test accuracies %.3f/%.3f' % (tree_train, tree_test))
输出:
Decision tree train/test accuracies 0.916/0.875
再使用GBDT分类:
# 使用GBDT分类
gbm = GradientBoostingClassifier(n_estimators=500,
learning_rate=0.1,
random_state=1,
criterion='friedman_mse',
max_depth=1,
loss='deviance')
# 对于分类问题,损失函数默认是"deviance"对数似然损失函数,如果是"exponential"则表示指数损失函数
gbm = gbm.fit(X_train, y_train)
y_train_pred = gbm.predict(X_train)
y_test_pred = gbm.predict(X_test)
gbm_train = accuracy_score(y_train, y_train_pred)
gbm_test = accuracy_score(y_test, y_test_pred)
print('GBDT train/test accuracies %.3f/%.3f' % (gbm_train, gbm_test))
输出:
GBDT train/test accuracies 1.000/0.917
绘制决策边界,对比决策树和GBDT的分类效果:
# 绘制决策边界
def plot_decision_regions(X, y, classifier_list, classifier_names):
x_min = X[:, 0].min() - 1
x_max = X[:, 0].max() + 1
y_min = X[:, 1].min() - 1
y_max = X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
np.arange(y_min, y_max, 0.1))
f, axarr = plt.subplots(1, 2, sharex='col', sharey='row', figsize=(8, 3))
for idx, clf, tt in zip([0, 1],classifier_list,classifier_names):
clf.fit(X, y)
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
axarr[idx].contourf(xx, yy, Z, alpha=0.3)
axarr[idx].scatter(X[y==0, 0], X[y==0, 1], c='blue', marker='^')
axarr[idx].scatter(X[y==1, 0], X[y==1, 1], c='red', marker='o')
axarr[idx].set_title(tt)
axarr[0].set_ylabel('Alcohol', fontsize=12)
plt.text(10.2, -0.5, s='OD280/OD315 of diluted wines', ha='center', va='center', fontsize=12)
plt.show()
plot_decision_regions(X_train, y_train, [tree, gbm], ['Decision Tree', 'GBDT'])
输出:
5. 模型评价
优点:
- 能灵活处理各种类型的数据,包括连续值和离散值。
- 在相对少的调参时间情况下,预测的准确率也可以比较高。这个是相对SVM来说的。
- 用一些健壮的损失函数,对异常值的鲁棒性非常强。比如 Huber损失函数和Quantile损失函数。
缺点:
- 串行学习的特点,致使其难以并行训练。不过可以通过自采样的SGBT来达到部分并行
完整代码地址
完整代码请移步至: 我的github:https://github.com/qingyujean/Magic-NLPer,求赞求星求鼓励~~~
最后:如果本文中出现任何错误,请您一定要帮忙指正,感激~
参考
[1] 统计学习方法(第2版) 李航
[2] 梯度提升树(GBDT)原理小结 刘建平
[3] 机器学习(西瓜书) 周志华