上一篇,Bagging,点我
下一篇,Stacking,点我
上文中我们讲述了并行的Bagging算法,以及其著名的变形随机森林算法。今天我们对集成学习三大方法的第二种算法进行学习。Boosting:提升算法。主要的算法有以下三类
AdaBoost
从 前面的Bagging算法可知,Bagging算法采用的是多个基学习器并行学习,彼此之间互不关联,等权重投票。是一种并行的学习方式。而Boosting是一种串行的学习方式,就像我们电路中的串联方式,根据串行的特性,就决定了后续的弱学习器是基于前面学习器的输出进行学习训练。通过一次一次的迭代性质学习最终得到最终的结果。
滚球兽→亚古兽→暴龙兽→机械暴龙兽→战斗暴龙兽(是不是已经忘了!!!)
上面这幅图生动的展现了boosting的内涵,和上述的数码宝贝的进化一般,后一个状态在前一个状态的基础上进行。
Adaboost算法流程:
1:为样本中的每个实例设置一个等权重
1
m
\dfrac{1}{m}
m1
2:首先训练第一个弱学习器,根据其输出计算其错误率
ϵ
t
\epsilon_t
ϵt
3:动态计算当前弱学习器的权重
α
t
=
1
2
l
n
(
1
−
ϵ
t
ϵ
t
)
α_t = \dfrac{1}{2}ln(\dfrac{1-\epsilon_t}{\epsilon_t})
αt=21ln(ϵt1−ϵt)
4:对输出的结果进行奖惩,让预测对的变瘦(权重降低),预测错的变胖(权重增大)
D
t
+
1
=
D
t
(
x
)
Z
t
{
e
x
p
−
α
t
预测对了
e
x
p
α
t
预测错了
D_{t+1}=\dfrac{D_t(x)}{Z_t}\left\{ \begin{aligned} exp^{-α_t} & &\text{预测对了} \\ exp^{α_t} & & \text{预测错了} \end{aligned} \right.
Dt+1=ZtDt(x){exp−αtexpαt预测对了预测错了
其中
D
t
(
x
)
D_t(x)
Dt(x)为上一个样本实例权重,
Z
t
Z_t
Zt为缩放因子维持权重在0-1之间,考虑e指数图像,不难发现就是对实例进行奖惩,对的权重变小,错的变大。为了让后续的学习器更加的去关注上次预测错误的实例。
5:根据设定的学习器个数不断的迭代更新
6:加权输出
H
(
x
)
=
s
i
g
n
(
∑
t
=
1
T
α
t
h
t
(
x
)
)
H(x)=sign(\sum_{t=1}^Tα_th_t(x))
H(x)=sign(∑t=1Tαtht(x)),
h
t
(
x
)
h_t(x)
ht(x)为每层的弱学习器的输出
对照下图可以理解一下
实例端口调用
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split,GridSearchCV
from sklearn.metrics import accuracy_score,mean_absolute_error,mean_squared_error
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import SVC
import numpy as np
import matplotlib.pyplot as plt
from pylab import mpl
mpl.rcParams['font.sans-serif']=["Simhei"]
dataset = load_iris()
features,target = dataset.data,dataset.target
x_train,x_test,y_train,y_test = train_test_split(features,target,test_size=0.7,random_state=100)
# 这里学习率指的是模型收缩步长,相当于控制单个学习器的权重,外加了一个权重系数,增强泛化,防止过拟合的
params = {"n_estimators":[50,100,150,200],"learning_rate":[0.01,0.1,0.2,0.5,1.0]}
estimator = AdaBoostClassifier(base_estimator=DecisionTreeClassifier(max_depth=6),algorithm="SAMME.R",random_state=200)
# estimator = AdaBoostClassifier(base_estimator=SVC(kernel='rbf'),algorithm='SAMME',random_state=200)
estimator = GridSearchCV(estimator,param_grid=params,n_jobs=4,cv=10)
estimator.fit(x_train,y_train)
# 模型预测
y_pred = estimator.predict(x_test)
score = estimator.score(x_test,y_test)
# 最优模型与参数
estimator.best_estimator_
estimator.best_params_
GBDT
GB指代的事梯度提升法(Gradient Boosting),DT指代的决策树。合起来就是基于决策树为弱学习器模型的梯度提升法。以下简称GB
GB相较于Adaboost来说,最大的区别就在于关注点不同,Adaboost关注于针对每层弱学习器输出后的对错进行奖惩,进而赋予每层弱学习器以不同的权重。GB则关注于每层之间的残差,让新的学习器针对前面学习器输出的残差进行拟合。换句话说就是第一个学习器输出后,第二个学习器针对于基本事实与第一层预测的差值,第三个学习器关注于第二个学习器的差值与第二个学习器的基本事实的差值,依次类推。
from sklearn.ensemble import GradientBoostingClassifier
# 支持早停,迭代一定次数验证误差不再改善就会自动停止,不必运行完所有迭代warm_start=True
# 支持样本采样subsample = 0.8,采样一方面提速,另一方面相当于偏差换取更低地方差。
model = GradientBoostingClassifier(max_depth=4,n_estimators=50,random_state=300,awrm_start=True,subsample=0.8)
model.fit(x_train,y_train)
# staged_predict(),每迭代一次就会以迭代器形式返回预测结果,用以计算误差
errors = [mean_absolute_error(y_test,y_pred) for y_pred in model.staged_predict(x_test)]
XgBoost
xgboost总的来说是一种不断添加树的算法,如何添加树,依据什么添加树,依据什么分裂树成为了算法的核心。以官网给出的例子为例:通过树学习器利用集成学习判定是否喜欢电脑学习?
一棵树的情况下依据年龄进行判断,会有一个输出,输出越大代表有更高几率喜爱电脑学习。单一一棵树总会受到很多的限制,预测精度不是很好,因此我们采用集成学习方式使用多颗树来进行集成学习。
如上图所示,两棵树中我们利用不同的特征作为根,得到了不一样的输出。最终我们将输出进行累加得到最终的输出结果。那么我们如何以什么依据添加树呢?针对于监督学习我们就是通过设定其目标函数来进行不断的优化最终来选择合适的树。
算法表示:
y
^
=
∑
i
=
1
K
f
t
(
x
i
)
,
f
k
∈
F
\hat{y}=\sum_{i=1}^Kf_t(x_i),fk∈F
y^=∑i=1Kft(xi),fk∈F,F是所有可能的CART树。
目标函数:
∑
i
=
1
n
l
(
y
i
,
y
i
^
)
=
∑
i
=
1
n
(
y
i
−
y
i
^
)
2
\sum_{i=1}^nl(y_i,\hat{y_i})=\sum_{i=1}^n(y_i-\hat{y_i})^2
∑i=1nl(yi,yi^)=∑i=1n(yi−yi^)2
下面我们就朝着优化目标函数迈进
y
^
0
=
0
\hat{y}^{0}=0
y^0=0
y
^
1
=
y
^
0
+
f
t
(
x
1
)
\hat{y}^1=\hat{y}^0+f_t(x_1)
y^1=y^0+ft(x1)
.
.
.
...
...
y
^
n
=
y
^
n
−
1
+
f
t
(
x
n
)
\hat{y}^{n}=\hat{y}^{n-1}+f_t(x_{n})
y^n=y^n−1+ft(xn)
也就是说第n层学习器的预测等于前n-1层学习器输出加上第n层学习器的输出
q(x)输出叶子结点的索引号,wq(x)表示每个叶子的输出预测结果。因此f(x)为每层学习器的额输出结果
我们不希望一个树模型十分的复杂,因此我们使用叶子结点数和正则化来定义模型的复杂程度
Ω
(
f
t
)
=
r
T
+
1
2
λ
∑
j
=
1
T
w
j
2
Ω(f_t)=rT+\dfrac{1}{2}λ\sum_{j=1}^{T}w_j^2
Ω(ft)=rT+21λ∑j=1Twj2
r,λ权重系数,T叶子节点数,Wj输出预测结果。前一项是对叶子节点数的惩罚,后一项带有权重系数的L2正则化奖惩
再加上此约束,目标函数转变为:
o
b
j
(
x
)
=
∑
i
=
1
n
l
(
y
i
,
y
i
^
)
+
Ω
(
f
t
)
obj(x)=\sum_{i=1}^nl(y_i,\hat{y_i})+Ω(f_t)
obj(x)=∑i=1nl(yi,yi^)+Ω(ft)
=
∑
i
=
1
n
l
(
y
i
,
y
i
^
n
−
1
+
f
t
(
x
n
)
)
+
Ω
(
f
t
)
=\sum_{i=1}^nl(y_i,\hat{y_i}^{n-1}+f_t(x_n))+Ω(f_t)
=∑i=1nl(yi,yi^n−1+ft(xn))+Ω(ft)
=
∑
i
=
1
n
(
y
i
−
y
i
^
)
2
+
Ω
(
f
t
)
=\sum_{i=1}^n(y_i-\hat{y_i})^2+Ω(f_t)
=∑i=1n(yi−yi^)2+Ω(ft)
=
∑
i
=
1
n
[
y
i
−
(
y
i
^
n
−
1
+
f
t
(
x
n
)
]
2
+
r
T
+
1
2
λ
∑
j
=
1
T
w
j
2
=\sum_{i=1}^n[y_i-(\hat{y_i}^{n-1}+f_t(x_n)]^2+rT+\dfrac{1}{2}λ\sum_{j=1}^{T}w_j^2
=∑i=1n[yi−(yi^n−1+ft(xn)]2+rT+21λ∑j=1Twj2
=
∑
i
=
1
n
[
(
y
i
−
y
i
^
n
−
1
)
2
+
2
(
y
^
n
−
1
−
y
i
)
f
t
(
x
n
)
+
f
t
(
x
n
)
2
]
+
r
T
+
1
2
λ
∑
j
=
1
T
w
j
2
=\sum_{i=1}^n[(y_i-\hat{y_i}^{n-1})^2+2(\hat{y}^{n-1}-y_i)f_t(x_n)+f_t(x_n)^2]+rT+\dfrac{1}{2}λ\sum_{j=1}^{T}w_j^2
=∑i=1n[(yi−yi^n−1)2+2(y^n−1−yi)ft(xn)+ft(xn)2]+rT+21λ∑j=1Twj2
又因为
(
y
i
−
y
i
^
n
−
1
)
2
(y_i-\hat{y_i}^{n-1})^2
(yi−yi^n−1)2是目标值与前一个学习器预测值的差为一个常数,所以可以化简为:
=
∑
i
=
1
n
[
2
(
y
^
n
−
1
−
y
i
)
f
t
(
x
n
)
+
f
t
(
x
n
)
2
]
+
r
T
+
1
2
λ
∑
j
=
1
T
w
j
2
+
c
o
n
s
t
a
n
t
=\sum_{i=1}^n[2(\hat{y}^{n-1}-y_i)f_t(x_n)+f_t(x_n)^2]+rT+\dfrac{1}{2}λ\sum_{j=1}^{T}w_j^2+constant
=∑i=1n[2(y^n−1−yi)ft(xn)+ft(xn)2]+rT+21λ∑j=1Twj2+constant
上式
2
(
y
^
n
−
1
−
y
i
)
f
t
(
x
n
)
2(\hat{y}^{n-1}-y_i)f_t(x_n)
2(y^n−1−yi)ft(xn),可知,xgboost关注的仍是学习器之间的残差,这点与GB保持一致
通过拆分集成算法函数,我们可以将目标函数写成红色的形式,自此我们所有重心都转移为寻找一个
f
t
(
x
i
)
f_t(x_i)
ft(xi)来使得目标函数尽可能地小。
引入泰勒展开式:
f
(
x
+
△
x
)
≈
f
(
x
)
+
x
+
△
x
−
x
1
!
f
′
(
x
)
+
(
x
+
△
x
−
x
)
2
2
!
f
′
′
(
x
)
+
.
.
.
+
(
x
+
△
x
−
x
)
n
n
!
f
n
(
x
)
f(x+△x)≈f(x)+\dfrac{x+△x-x}{1!}f^{'}(x)+\dfrac{(x+△x-x)^2}{2!}f^{''}(x)+...+\dfrac{(x+△x-x)^n}{n!}f^{n}(x)
f(x+△x)≈f(x)+1!x+△x−xf′(x)+2!(x+△x−x)2f′′(x)+...+n!(x+△x−x)nfn(x)
这里我们另
f
t
(
x
)
=
△
x
f_t(x)=△x
ft(x)=△x,所以(下式方便起见不再对常量考虑)
其中n为样本实例数,也就是说现在是按照每个一样本进行累加求和的,但是Ω按照叶子数遍历,我们需要先将其转化到
一种遍历方式方便后续计算。
其含义为原本我们按照上述的小男孩、小女孩、妈妈、爷爷、奶奶进行遍历转变为按照叶子1、叶子2、叶子3进行遍历
G,H分别为目标函数的一阶导数与二阶导数
上式对
w
j
w_j
wj求导可得:
我们目标就转换为如何构造树(调整G,H),是的obj最小
下面我们就来介绍如何构造树,如何确定划分依据
如上图所示,根据不同输入特征我们将会有多种的分割方式。这样也就会形成多种决策树。构造不同烦人树,最终将得
到不同的G,H。因此最愚蠢的方法便是遍历特征,分别形成树,计算G,H。再考虑最为合适的G,H.这里我们通过信息增益
比Gain指数来判定决策树的好坏。越小代表形成的树越好。
Gain = 左子树+右子树-父节点
分割扩展
上述我们说到,最繁琐的办法就是遍历特征,逐一生成树,再一一计算Gain。这也计算量过于繁琐。而xgboost采用的是二阶梯度分桶法。
1:原始的遍历方法
2:近似算法(分桶算法)
而xgboost采用得到基于H的二阶梯度分桶法,就是近似算法的衍生,分别计算每个特征的
H
i
H_i
Hi,两个切分点之间要满足一定的数值才会进行切分,这样大大降低了切分点数。
至于为何采取二阶导作为划分依据可以通过前面目标函数添加
h
i
2
g
i
2
\dfrac{h_i^2}{g_i}^2
gihi22可以推倒出目标函数最终可以转化了由H主导的函数(这里不再证明,了解即可)
API调用实例:首先pip/conda install xgboost
(分类)
import xgboost as xgb
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from xgboost import plot_importance,plot_tree,to_graphviz
from sklearn.datasets import load_iris,load_boston
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from pylab import mpl
mpl.rcParams["font.sans-serif"]=["SimHei"]
%matplotlib inline
# 获取数据
dataset = load_iris()
features,target = dataset.data,dataset.target
# 设置参数
params = {
# 通用参数
"booster":"gbtree",
"verbosity":0,
"nthread":4,
"num_feature":4,
# 任务参数
"objective":"multi:softmax",
"num_class":3,# 类别数
"base_score":0.5,
"seed":123,
# Boosting 优化参数
"eta":0.2,# 防止过拟合,收缩步长,降低特征权重
"gamma":0.1, # 进一步划分叶子结点时所需要的的损失衰减
"max_depth":6, # 树的最大层数,越大越容易过拟合
"min_child_weight":3, # 叶子结点权重,低于设定值就不在往下划分
"subsample":0.8, # 样本采样,防止过拟合
"sampling_method":"uniform", # 样本采样方法
"colsample_betree":0.7, # 对树的特征进行采样,共有对树级别采样,对层级别采样,对节点级别采样三种
"lambda":2 # 对L2正则化的权重
}
# 数据集划分
x_train,x_test,y_train,y_test = train_test_split(features,target,test_size=0.8,random_state=100)
# 数据格式转换,转换成DMatrix,计算效率高
dtrain = xgb.DMatrix(x_train,y_train,feature_names=dataset.feature_names)
dtest = xgb.DMatrix(x_test,feature_names=dataset.feature_names)
# 训练模型
# num_boost_round每个类别得到迭代次数,num_boost_round*num_class = 学习器数
model = xgb.train(params,dtrain,num_boost_round=10)
y_pred = model.predict(dtest)
score = accuracy_score(y_test,y_pred)
# 第二种方法,采用xgboost封装的sklearn接口
estimator = xgb.XGBClassifier(n_estimator=100,max_depth=6,learning_rate=0.2,verbosity=0,objective="multi:softmax",booster="gbtree",\
n_jobs=4,min_child_weight=3,subsample=0.8,colsample_betree=0.7,random_state=200,feature_names = dataset.feature_names
)
# 训练模型
estimator.fit(x_train,y_train)
# 绘制特征重要性
ax = plot_importance(model,importance_type='weight')
# 绘制集成学习器中的单个树结构
xgb.to_graphviz(model,num_trees=4)
(回归)
x_train,x_test,y_train,y_test = train_test_split(data,dataset.target,test_size=0.7,random_state=100)
# 设置参数
# 参数设置
params = {
# 通用参数
"booster":"gbtree",
"verbosity":0,
"nthread":4,
"num_feature":13,
# 任务参数
"objective":"reg:squarederror",# 使用回归任务损失函数,这里采用的平方损失
# "num_class":3,# 类别数
"base_score":0.5,
"seed":123,
# Boosting 优化参数
"eta":0.2,# 防止过拟合,收缩步长,降低特征权重
"gamma":0.1, # 进一步划分叶子结点时所需要的的损失衰减
"max_depth":6, # 树的最大层数,越大越容易过拟合
"min_child_weight":3, # 叶子结点权重,低于设定值就不在往下划分
"subsample":0.8, # 样本采样,防止过拟合
"sampling_method":"uniform", # 样本采样方法
"colsample_betree":0.7, # 对树的特征进行采样,共有对树级别采样,对层级别采样,对节点级别采样三种
"lambda":2 # 对L2正则化的权重
}
# xgb原生接口,数据格式转换
dtrain = xgb.DMatrix(x_train,y_train,feature_names=dataset.feature_names)
dtest = xgb.DMatrix(x_test,feature_names=dataset.feature_names)
# # 设置、训练模型
model = xgb.train(params,dtrain,num_boost_round=100)
# 预测
y_pred = model.predict(dtest)
# 使用提供的sklearn接口
#estimator = xgb.XGBRegressor(n_estimator=100,max_depth=6,learning_rate=0.2,verbosity=0,objective="reg:squarederror", early_stopping_rounds=10,booster="gbtree",\
#n_jobs=4,min_child_weight=3,subsample=0.8,colsample_betree=0.7,random_state=200,feature_names = dataset.feature_names
)
#estimator.fit(x_train,y_train)
#y_pred = estimator.predict(x_test)
# 可视化拟合程度
plt.figure(figsize=(20,8),dpi=100)
plt.plot([i for i in range(355)],y_pred,color="black",label="预测值")
plt.plot([i for i in range(355)],y_test,color="red",label="真实值")
plt.legend(loc="upper right",fontsize=15)
# y_pred.shape
# 会址特征重要性
plot_importance(estimator)
Boost小结
1:boost采用串行学习方式
2:所有学习器采取的是加权投票
3:Boost一般关注于前者学习器的输出或者输出残差
4:Boost用以解决欠拟合,使用方差换区低的偏差。提升预测精度
本文主要参考Xgboost官方文档:官方文档
上一篇,Bagging,点我
下一篇,Stacking,点我