AdaBoost分别应用于分类和回归及其python实现
前言:近期在做比赛的时候建模阶段普遍都是使用集成模型效果更好,如xgboost、lgb、catboost等,但是对其中原理并不了解,所以准备从adaboost开始慢慢学习这一系列的集成模型…
1.AdaBoost分类
集成模型主要有bagging和boosting两种,这里都是boosting类的。
1.1Boosting基本思路
- 先用每个样本权重相等的训练集训练一个初始的基学习器。
- 根据上轮得到的学习器对训练集的预测表现情况调整训练集中的样本权重,然后据此训练一个新的基学习器。
- 重复上面的步骤2得到M个基学习器,最终的集成结果就是这M个基学习器的组合。
上面的步骤很明显是一个串行的过程,并且所使用到的基学习器都是比较简单的。
1.2AdaBoost分类的基本思路
- 提高上一轮被错误分类的样本的权值,降低被正确正确分类的样本权值。
- 在最后集成结果线性加权求和过程中,误差率小的基学习器拥有更大的权值,误差率大的基学习器拥有较小的权值。
其中第二步集成的思想也经常用于模型融合。
1.3AdaBoost的算法步骤
首先假设样本数量为N,基学习器的数目为M。
- 初始化样本权重: D 1 = ( w 11 , w 12 , . . . , w 1 N ) , w 1 i = 1 N , i = 1 , 2 , . . . , N D_1=(w_{11},w_{12},...,w_{1N}), w_{1i}=\frac{1}{N},i=1,2,...,N D1=(w11,w12,...,w1N),w1i=N1,i=1,2,...,N;
- 对
m
=
1
,
2
,
.
.
.
,
M
m=1,2,...,M
m=1,2,...,M重复以下操作得到M个基学习器:
(1)按照样本权重分布 D m D_m Dm训练数据得到第 m m m个基学习器 G m ( x ) G_m(x) Gm(x);
(2)计算 G m ( x ) G_m(x) Gm(x)在加权训练数据集上的分类误差率: e m = ∑ i = 1 N D ( G m ( x i ) ≠ y i ) = ∑ i = 1 N w m , i I ( G m ( x i ) ≠ y i ) e_m=\sum_{i=1}^ND(G_m(x_i)\neq y_i)=\sum_{i=1}^Nw_{m,i}I(G_m(x_i)\neq y_i) em=∑i=1ND(Gm(xi)=yi)=∑i=1Nwm,iI(Gm(xi)=yi)
(3)计算 G m ( x ) G_m(x) Gm(x)的系数(即最终集成的基学习器的权重): α m = 1 2 l o g ( 1 − e m e m ) \alpha_m=\frac{1}{2}log(\frac{1-e_m}{e_m}) αm=21log(em1−em),可以看到误差率 e m e_m em越大,基学习器权重 α m \alpha_m αm越小。
(4)更新样本权重: D m + 1 = ( w m , 1 , w m , 2 , . . . , w m , N ) D_{m+1}=(w_{m,1},w_{m,2},...,w_{m,N}) Dm+1=(wm,1,wm,2,...,wm,N), w m + 1 , i = w m , i Z m e x p ( − α m y i G m ( x i ) ) , i = 1 , 2 , . . . , N w_{m+1,i}=\frac{w_{m,i}}{Z_m}exp(-\alpha_my_iG_m(x_i)),i=1,2,...,N wm+1,i=Zmwm,iexp(−αmyiGm(xi)),i=1,2,...,N。其中 Z m Z_m Zm是规范化因子,目的就是为了使 D m + 1 D_{m+1} Dm+1得所有元素之和为1, Z m = ∑ i = 1 N w m , i e x p ( − α m y i G m ( x i ) ) Z_m=\sum_{i=1}^{N}w_{m,i}exp(-\alpha_my_iG_m(x_i)) Zm=∑i=1Nwm,iexp(−αmyiGm(xi))。可以看到当分类错误时, y i G m ( x i ) = − 1 y_iG_m(x_i)=-1 yiGm(xi)=−1,对应的权值增加。 - 构建最终的分类器线性组合: f ( x ) = ∑ m = 1 M α m G m ( x ) f(x)=\sum_{m=1}^M\alpha_mG_m(x) f(x)=∑m=1MαmGm(x),分类器 G ( x ) = s i g n ( f ( x ) ) G(x)=sign(f(x)) G(x)=sign(f(x))。
1.4AdaBoost算法的解释
在上面算法步骤中可以看到基学习器权重 α m \alpha_m αm得更新公式和样本权重 w m w_m wm的更新公式是符合基本思路的,但是对于为什么是要这么更新?我们可以从另一个角度出发,把AdaBoost看作模型是加法模型、损失函数是指数损失函数、学习算法为前向分步算法的二分类学习方法。
(1)首先看一下前向分步算法的基本思想(这里为了一般化,用其他符号进行表示):
对于我们的加法模型:
f
(
x
)
=
∑
m
=
1
M
β
m
b
(
x
;
γ
m
)
f(x)=\sum_{m=1}^M\beta_mb(x;\gamma_m)
f(x)=∑m=1Mβmb(x;γm), 其中
b
(
x
;
γ
m
)
b(x;\gamma_m)
b(x;γm)是基函数,
γ
m
\gamma_m
γm是基函数的参数,
β
m
\beta_m
βm是基函数的系数;在给定损失函数
L
(
y
,
f
(
x
)
)
L(y,f(x))
L(y,f(x))的条件下,学习加法模型的过程就是最小化损失函数:
m
i
n
∑
i
=
1
N
L
(
y
i
,
∑
m
=
1
M
β
m
b
(
x
;
γ
m
)
)
min\sum_{i=1}^NL(y_i,\sum_{m=1}^M\beta_mb(x;\gamma_m))
min∑i=1NL(yi,∑m=1Mβmb(x;γm)),而这个最优化的过程通常比较复杂,前向分布算法解决这个优化问题的思想就是每次只学习一个基函数,然后再用学习得到的这个基函数更新我们的加法模型。
(2)算法步骤:
输入训练集
T
=
{
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
,
.
.
.
,
(
x
N
,
y
N
)
}
T=\{(x_1,y_1), (x_2,y_2),...,(x_N,y_N)\}
T={(x1,y1),(x2,y2),...,(xN,yN)};损失函数
L
(
y
,
f
(
x
)
)
L(y,f(x))
L(y,f(x));基函数集
{
b
(
x
;
γ
)
}
\{b(x;\gamma)\}
{b(x;γ)}
- 初始化 f 0 ( x ) = 0 f_0(x)=0 f0(x)=0
- 对
m
=
1
,
2
,
.
.
.
,
M
m=1,2,...,M
m=1,2,...,M
(1)极小化损失函数: ( β m , γ m ) = a r g m i n β , γ ∑ i = 1 N L ( y i , f m − 1 ( x i ) + β b ( x ; γ ) ) (\beta_m,\gamma_m)=argmin_{\beta,\gamma}\sum_{i=1}^{N}L(y_i,f_{m-1}(x_i)+\beta b(x;\gamma)) (βm,γm)=argminβ,γ∑i=1NL(yi,fm−1(xi)+βb(x;γ)),得到第m个基函数参数
(2)更新加法模型: f m ( x ) = f m − 1 ( x ) + β m b ( x ; γ m ) f_m(x)=f_{m-1}(x)+\beta_mb(x;\gamma_m) fm(x)=fm−1(x)+βmb(x;γm) - 得到加法模型: f ( x ) = f M ( x ) = ∑ m = 1 M β m b ( x ; γ m ) f(x)=f_M(x)=\sum_{m=1}^M\beta_mb(x;\gamma_m) f(x)=fM(x)=∑m=1Mβmb(x;γm)
我们在1.3中得到的加法分类器也是这样的一个加法模型,所以也可以使用前向分步算法来进行求解。
(3)具体推导:
首先基于前向分步算法的思想和AdaBoost得到的加法模型,AdaBoost可以看作前向分步算法的一个特例,其基函数为基本分类器
G
(
x
)
G(x)
G(x),损失函数为指数损失函数
L
(
y
,
f
(
x
)
)
=
e
x
p
(
−
y
f
(
x
)
)
L(y,f(x))=exp(-yf(x))
L(y,f(x))=exp(−yf(x))。结合上面的算法步骤,假设前m-1轮已经得到
f
m
−
1
(
x
)
f_{m-1}(x)
fm−1(x),我们的第m轮的优化目标如下:
a
r
g
m
i
n
α
m
,
G
m
∑
i
=
1
N
L
(
y
i
,
f
m
−
1
(
x
i
)
+
α
m
G
m
(
x
i
)
)
⇒
a
r
g
m
i
n
α
m
,
G
m
∑
i
=
1
N
e
x
p
[
−
y
i
(
f
m
−
1
(
x
i
)
+
α
m
G
m
(
x
i
)
)
]
⇒
a
r
g
m
i
n
α
m
,
G
m
∑
i
=
1
N
e
x
p
[
−
y
i
f
m
−
1
(
x
i
)
]
e
x
p
[
−
y
i
α
m
G
m
(
x
i
)
]
argmin_{\alpha_m,G_m}\sum_{i=1}^{N}L(y_i,f_{m-1}(x_i)+\alpha_m G_m(x_i))\\\Rarr argmin_{\alpha_m,G_m}\sum_{i=1}^{N}exp[-y_i(f_{m-1}(x_i)+\alpha_m G_m(x_i))]\\\Rarr argmin_{\alpha_m,G_m}\sum_{i=1}^{N}exp[-y_if_{m-1}(x_i)]exp[-y_i\alpha_m G_m(x_i)]
argminαm,Gmi=1∑NL(yi,fm−1(xi)+αmGm(xi))⇒argminαm,Gmi=1∑Nexp[−yi(fm−1(xi)+αmGm(xi))]⇒argminαm,Gmi=1∑Nexp[−yifm−1(xi)]exp[−yiαmGm(xi)]
因为
y
i
y_i
yi和
f
m
−
1
(
x
)
f_{m-1}(x)
fm−1(x)已经得到,所以可以令
w
m
,
i
=
e
x
p
[
−
y
i
f
m
−
1
(
x
i
)
]
w_{m,i}=exp[-y_if_{m-1}(x_i)]
wm,i=exp[−yifm−1(xi)],然后上面的优化目标变成如下形式:
a
r
g
m
i
n
α
m
,
G
m
∑
i
=
1
N
w
m
,
i
e
x
p
[
−
y
i
α
m
G
m
(
x
i
)
]
⇒
a
r
g
m
i
n
α
m
,
G
m
∑
y
i
=
G
m
(
x
i
)
w
m
,
i
e
−
α
m
+
∑
y
i
≠
G
m
(
x
i
)
w
m
,
i
e
α
m
⇒
a
r
g
m
i
n
α
m
,
G
m
∑
y
i
=
G
m
(
x
i
)
w
m
,
i
e
−
α
m
+
∑
y
i
≠
G
m
(
x
i
)
w
m
,
i
e
−
α
m
−
∑
y
i
≠
G
m
(
x
i
)
w
m
,
i
e
−
α
m
+
∑
y
i
≠
G
m
(
x
i
)
w
m
,
i
e
α
m
⇒
a
r
g
m
i
n
α
m
,
G
m
(
e
α
m
−
a
−
α
m
)
∑
i
=
1
N
w
m
,
i
I
(
y
i
≠
G
m
(
x
i
)
)
+
e
−
α
m
∑
i
=
1
N
w
m
,
i
argmin_{\alpha_m,G_m}\sum_{i=1}^{N}w_{m,i}exp[-y_i\alpha_m G_m(x_i)]\\\Rarr argmin_{\alpha_m,G_m}\sum_{y_i=G_m(x_i)}w_{m,i}e^{-\alpha_m}+\sum_{y_i\neq G_m(x_i)}w_{m,i}e^{\alpha_m}\\\Rarr argmin_{\alpha_m,G_m}\sum_{y_i=G_m(x_i)}w_{m,i}e^{-\alpha_m}+\sum_{y_i\neq G_m(x_i)}w_{m,i}e^{-\alpha_m}-\sum_{y_i\neq G_m(x_i)}w_{m,i}e^{-\alpha_m}+\sum_{y_i\neq G_m(x_i)}w_{m,i}e^{\alpha_m} \\\Rarr argmin_{\alpha_m,G_m}(e^{\alpha_m}-a^{-\alpha_m})\sum_{i=1}^Nw_{m,i}I(y_i\neq G_m(x_i))+e^{-\alpha_m}\sum_{i=1}^Nw_{m,i}
argminαm,Gmi=1∑Nwm,iexp[−yiαmGm(xi)]⇒argminαm,Gmyi=Gm(xi)∑wm,ie−αm+yi=Gm(xi)∑wm,ieαm⇒argminαm,Gmyi=Gm(xi)∑wm,ie−αm+yi=Gm(xi)∑wm,ie−αm−yi=Gm(xi)∑wm,ie−αm+yi=Gm(xi)∑wm,ieαm⇒argminαm,Gm(eαm−a−αm)i=1∑Nwm,iI(yi=Gm(xi))+e−αmi=1∑Nwm,i
然后上式关于
α
m
\alpha_m
αm求导,并令其为0:
(
e
α
m
+
a
−
α
m
)
∑
i
=
1
N
w
m
,
i
I
(
y
i
≠
G
m
(
x
i
)
)
−
e
−
α
m
∑
i
=
1
N
w
m
,
i
=
0
⇒
∑
i
=
1
N
w
m
,
i
I
(
y
i
≠
G
m
(
x
i
)
)
∑
i
=
1
N
w
m
,
i
=
e
−
α
m
(
e
α
m
+
a
−
α
m
)
(e^{\alpha_m}+a^{-\alpha_m})\sum_{i=1}^Nw_{m,i}I(y_i\neq G_m(x_i))-e^{-\alpha_m}\sum_{i=1}^Nw_{m,i}=0\\\Rarr \frac{\sum_{i=1}^Nw_{m,i}I(y_i\neq G_m(x_i))}{\sum_{i=1}^Nw_{m,i}}=\frac{e^{-\alpha_m}}{(e^{\alpha_m}+a^{-\alpha_m})}
(eαm+a−αm)i=1∑Nwm,iI(yi=Gm(xi))−e−αmi=1∑Nwm,i=0⇒∑i=1Nwm,i∑i=1Nwm,iI(yi=Gm(xi))=(eαm+a−αm)e−αm
可以看到上式左边部分就是我们的当前第m轮模型的误差率,所以令
e
m
=
∑
i
=
1
N
w
m
,
i
I
(
y
i
≠
G
m
(
x
i
)
)
∑
i
=
1
N
w
m
,
i
e_m=\frac{\sum_{i=1}^Nw_{m,i}I(y_i\neq G_m(x_i))}{\sum_{i=1}^Nw_{m,i}}
em=∑i=1Nwm,i∑i=1Nwm,iI(yi=Gm(xi)),这里的e时误差的意思,和上式右半部分的e的不同,然后将其带入上式可得到:
α
m
=
1
2
l
o
g
1
−
e
m
e
m
\alpha_m=\frac{1}{2}log\frac{1-e_m}{e_m}
αm=21logem1−em
然后结合
w
m
,
i
=
e
x
p
[
−
y
i
f
m
−
1
(
x
i
)
]
w_{m,i}=exp[-y_if_{m-1}(x_i)]
wm,i=exp[−yifm−1(xi)]和
f
m
(
x
)
=
f
m
−
1
(
x
)
+
α
m
G
m
(
x
)
f_m(x)=f_{m-1}(x)+\alpha_mG_m(x)
fm(x)=fm−1(x)+αmGm(x)得出权值更新的公式:
f
m
(
x
i
)
=
f
m
−
1
(
x
i
)
+
α
m
G
m
(
x
i
)
⇒
−
y
i
f
m
(
x
i
)
=
−
y
i
f
m
−
1
(
x
i
)
−
y
i
α
m
G
m
(
x
i
)
⇒
e
−
y
i
f
m
(
x
i
)
=
e
−
y
i
f
m
−
1
(
x
i
)
e
−
y
i
α
m
G
m
(
x
i
)
⇒
w
m
+
1
,
i
=
w
m
,
i
e
−
y
i
α
m
G
m
(
x
i
)
f_m(x_i)=f_{m-1}(x_i)+\alpha_mG_m(x_i)\\\Rarr -y_if_m(x_i)=-y_if_{m-1}(x_i)-y_i\alpha_mG_m(x_i)\\\Rarr e^{-y_if_m(x_i)}=e^{-y_if_{m-1}(x_i)}e^{-y_i\alpha_mG_m(x_i)}\\\Rarr w_{m+1,i}=w_{m,i}e^{-y_i\alpha_mG_m(x_i)}
fm(xi)=fm−1(xi)+αmGm(xi)⇒−yifm(xi)=−yifm−1(xi)−yiαmGm(xi)⇒e−yifm(xi)=e−yifm−1(xi)e−yiαmGm(xi)⇒wm+1,i=wm,ie−yiαmGm(xi)
然后对权值继续规范化,使其之和为1,然后对应的误差
e
m
e_m
em中的分母也就为1。
1.5python实现
这里实现的一个简易版的,基学习器使用一个根节点的决策树,数据集采用西瓜书对应章节的数据进行简单测试。
import numpy as np
import pandas as pd
#构建单层决策树为基分类器的提升模型
def load_data():
df = pd.DataFrame()
df['密度'] = [0.697, 0.774, 0.634, 0.608, 0.556, 0.403, 0.481, 0.437, 0.666, 0.243, 0.245, 0.343, 0.639, 0.657,
0.360, 0.593, 0.719]
df['含糖量'] = [0.460, 0.376, 0.264, 0.318, 0.215, 0.237, 0.149, 0.211, 0.091, 0.267, 0.057, 0.099, 0.161, 0.198,
0.370, 0.042, 0.103]
df["好瓜"] = ["是", "是", "是", "是", "是", "是", "是", "是", "否", "否", "否", "否", "否", "否", "否", "否", "否"]
df.to_csv('data/watermelon45.csv', index=0)
#计算更新样本的权重
def cal_sample_weight(weight_list:[],y_true:[],y_pred:[],am):
weight_list = np.array(weight_list)*np.exp(-am*(y_true*y_pred))
Zm = np.sum(weight_list)
return list(weight_list/Zm)
#计算第m轮模型的权重
def cal_tree_weight(em):
return np.log((1 - em) / em) / 2
def cal_em(feat_value_list:[], threshold, y_true:[], weight_list:[], flag='l'):
:param feat_value_list: 划分特征的所有取值
:param threshold: 阈值
:param y_true: 样本的真实标签列表
:param weight_list: 样本当前的权重
:return: 误差率
y_pred = np.ones(len(y_true))
if flag == 'l':
y_pred[np.array(feat_value_list)>threshold] = -1 #大于阈值的预测为-1,小于阈值的预测为1
elif flag == 'r':
y_pred[np.array(feat_value_list) < threshold] = -1 #大于阈值的预测为1,小于阈值的预测为-1
em = np.sum(np.array(weight_list)[y_pred != y_true])
return em,y_pred
#构建单层决策树
def build_stump(data:pd.DataFrame, weight_list:[], label:[]):
:param data: 训练数据
:param weight_list: 训练样本的权重
:param label: 数据的真实标签
:return:
min_em = np.inf #找到使得当前权重下误差率最小的分类器
tree = {} #返回的单层决策树
best_feat = '' #最优划分特征
best_threshold = -1 #最优划分特征下产生的最优化阈值
ret_y_pred = [] #最优划分下决策树的预测结果
best_flag = ''
for feat in data.columns.values:
feat_value_list = list(data[feat]) #该属性下的取值
sorted_feat_value_list = sorted(list(set(feat_value_list)))
threshold_list = [(sorted_feat_value_list[i]+sorted_feat_value_list[i+1])/2 for i in np.arange(len(sorted_feat_value_list)-1)]
for threshold in threshold_list:
for flag in ['l','r']:
cur_em, y_pred = cal_em(feat_value_list, threshold, label, weight_list, flag) #计算误差率
if cur_em < min_em:
min_em = cur_em
best_feat = feat
best_threshold = threshold
ret_y_pred = y_pred
best_flag = flag
return best_feat, best_threshold, min_em, ret_y_pred, best_flag
#构建提升树分类模型
def build_adaboost_classifier(data:pd.DataFrame, n=5):
:param data: 训练数据集
:param n: 指定基分类器的数量
:return: 返回集成分类器
train_features = np.array(data.columns)[:-1]
label_feature = np.array(data.columns)[-1]
train_data = data[train_features]
label = data[label_feature]
weight_list = np.ones(len(label))/len(label) #初始化权重
adaboost_clf = []
for _ in np.arange(n):
best_feat, best_threshold, min_em, y_pred,best_flag = build_stump(train_data, weight_list, label)
am = cal_tree_weight(min_em)
weight_list = cal_sample_weight(weight_list,label,y_pred,am)
adaboost_clf.append((am,best_feat,best_threshold,best_flag))
print(weight_list)
print('误差率:.{} 阈值:.{} flag:.{}'.format(min_em, best_threshold, best_flag))
return adaboost_clf
#分类器预测
def predict(data:pd.DataFrame, clf_list:[]):
y_pred = np.zeros(len(data))
for (am,best_feat,best_threshold,best_flag) in clf_list:
y_pred_tmp = np.ones(len(data))
if best_flag == 'l':
y_pred_tmp[data[best_feat]>best_threshold] = -1
elif best_flag == 'r':
y_pred_tmp[data[best_feat] < best_threshold] = -1
y_pred += am*y_pred_tmp
y_pred[y_pred>0] = 1
y_pred[y_pred<=0] = -1
return y_pred
if __name__ == '__main__':
watermelon = pd.read_csv('data/watermelon45.csv')
watermelon['好瓜'] = watermelon['好瓜'].map({'是':1,'否':-1})
print(watermelon)
# feat_value_list = [0,1,2,3,4,5,6,7,8,9]
# df = pd.DataFrame(columns=['feat1','feat2'])
# y_true = [1, 1, 1, -1, -1, -1, 1, 1, 1, -1]
# df['feat1'] = feat_value_list
# df['feat2'] = feat_value_list
# df['label'] = y_true
# weight_list = [0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]
# print(build_stump(df,weight_list,y_true))
clf_list = build_adaboost_classifier(watermelon, n=11)
y_pred = predict(watermelon, clf_list)
print(list(watermelon['好瓜']))
print(y_pred)
print(np.sum(np.ones(len(y_pred))[y_pred!=list(watermelon['好瓜'])]))
2.AdaBoost回归
2.1基本思想
AdaBoost用于回归问题时也是采用前向分步算法的思想,基函数使用回归树
T
(
x
)
T(x)
T(x)(回归树的原理可以参考机器学习决策树DecisionTree以及python代码实现),损失函数使用平方误差损失
L
(
y
,
f
(
x
)
)
=
(
y
−
f
(
x
)
)
2
L(y,f(x))=(y-f(x))^2
L(y,f(x))=(y−f(x))2,根据前向分步算法的思想,我们当前m轮的加法模型就是
f
m
(
x
)
=
f
m
−
1
(
x
)
+
T
m
(
x
)
f_{m}(x)=f_{m-1}(x)+T_m(x)
fm(x)=fm−1(x)+Tm(x),假设我们已经经过m-1轮得到了
f
m
−
1
(
x
)
f_{m-1}(x)
fm−1(x),那么我们第m轮的优化目标就变成如下形式:
a
r
g
m
i
n
T
m
∑
i
=
1
N
L
(
y
i
,
f
m
−
1
(
x
i
)
+
T
m
(
x
i
)
)
2
⇒
a
r
g
m
i
n
T
m
∑
i
=
1
N
(
y
i
−
f
m
−
1
(
x
i
)
−
T
m
(
x
i
)
)
2
⇒
a
r
g
m
i
n
T
m
∑
i
=
1
N
(
r
m
,
i
−
T
m
(
x
i
)
)
2
argmin_{T_m}\sum_{i=1}^{N}L(y_i,f_{m-1}(x_i)+T_m(x_i))^2\\\Rarr argmin_{T_m}\sum_{i=1}^{N}(y_i-f_{m-1}(x_i)-T_m(x_i))^2\\\Rarr argmin_{T_m}\sum_{i=1}^{N}(r_{m,i}-T_m(x_i))^2
argminTmi=1∑NL(yi,fm−1(xi)+Tm(xi))2⇒argminTmi=1∑N(yi−fm−1(xi)−Tm(xi))2⇒argminTmi=1∑N(rm,i−Tm(xi))2
其中,
r
m
,
i
=
y
i
−
f
m
−
1
(
x
i
)
r_{m,i}=y_i-f_{m-1}(x_i)
rm,i=yi−fm−1(xi)表示当前模型的残差(即当前模型预测值和真实值的差),所以第m轮的优化目标就变成去拟合当前模型
f
m
−
1
(
x
)
f_{m-1}(x)
fm−1(x)的残差,所以我们只需要每一轮使用一个回归树去拟合残差即可,最后将模型线性相加即可。
2.2算法的具体步骤:
输入:训练数据集
T
=
{
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
,
.
.
.
,
(
x
N
,
y
N
)
}
T=\{(x_1,y_1),(x_2,y_2),...,(x_N,y_N)\}
T={(x1,y1),(x2,y2),...,(xN,yN)},
x
∈
R
n
,
y
∈
R
x\in R^n, y\in R
x∈Rn,y∈R
输出:提升树
- 初始化 f 0 ( x ) = 0 f_0(x)=0 f0(x)=0
- 对m=1,2,…,M。
(1)计算残差: r m , i = y i − f m − 1 ( x i ) , i = 1 , 2 , . . . , N r_{m,i}=y_i-f_{m-1}(x_i), i=1,2,...,N rm,i=yi−fm−1(xi),i=1,2,...,N
(2)通过拟合 r m , i r_{m,i} rm,i构建一个回归树 T m ( x ) T_m(x) Tm(x)
(3)更新: f m ( x ) = f m − 1 ( x ) + T m ( x ) f_m(x)=f_{m-1}(x)+T_{m}(x) fm(x)=fm−1(x)+Tm(x) - 得到回归提升树: f M ( x ) = ∑ m = 1 M T m ( x ) f_M(x)=\sum_{m=1}^MT_m(x) fM(x)=∑m=1MTm(x)
2.3python实现
这里我们调用的前面决策树中实现的回归树,来构建一个简易版的提升树。
import numpy as np
import pandas as pd
from DecisionTree import decisiontreeRegressor
'''构建回归树为基模型的提升树回归模型'''
def build_adaboost_regressor(data:pd.DataFrame, n=5):
'''
:param data: 训练集
:param n: 基分类器的数量,基模型默认使用max_depth=1的回归树
:return:
'''
adaboost_regressor = [] #集成模型
train_data = data[np.array(data.columns)[:-1]]
y_true = data[np.array(data.columns)[-1]]
base_model = decisiontreeRegressor.build_regressionTree(train_data, y_true, cur_depth=1,max_depth=1)
y_pred = decisiontreeRegressor.predict(base_model, train_data) #当前加法模型的预测结果
adaboost_regressor.append(base_model)
for _ in np.arange(n-1):
r = y_true - y_pred #当前模型和真实值的残差
# print('mse:.{}'.format(np.sum(r**2)))
base_model = decisiontreeRegressor.build_regressionTree(train_data, r, cur_depth=1, max_depth=1)
r_pred = decisiontreeRegressor.predict(base_model, train_data)
y_pred = y_pred + r_pred # 模型相加的预测结果
adaboost_regressor.append(base_model)
return adaboost_regressor
def predict(data:pd.DataFrame, model:[]):
y_pred = np.zeros(len(data))
for base_model in model:
y_pred += decisiontreeRegressor.predict(base_model, data)
return y_pred
if __name__ == '__main__':
df = pd.DataFrame(columns=['x1'])
df['x1'] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
y_true = [5.56, 5.70, 5.91, 6.40, 6.80, 7.05, 8.90, 8.70, 9.00, 9.05]
df['y'] = y_true
tree_list = build_adaboost_regressor(df, 6)
for tree in tree_list:
print(tree)
y_pred = predict(df.loc[:,['x1']],tree_list)
# print(np.sum((y_pred-y_true)**2)/len(y_pred))