前言
Boosting方法:使用同一组数据集进行反复学习,得到一系列简单模型,然后组合这些模型构成一个预测性能十分强大的机器学习模型。显然。Boosting思想目的:通过不断减少偏差的形式
而前一篇的Bagging方法:通过Bootstrap 的方式对全样本数据集进行抽样得到抽样子集,对不同的子集使用同一种基本模型进行拟合,然后投票得出最终的预测。Bagging主要通过降低方差的方式减少预测误差
两类常用的Boosting方式:Adaptive Boosting 和 Gradient Boosting 以及它们的变体Xgboost、LightGBM以及Catboost。
Boosting方法思路
Boosting方法由重复学习达到最终要求。常用老话“三个臭皮匠顶个诸葛亮”来通俗表达。
历史:Valiant和 Kearns提出了"强可学习"和"弱可学习"的概念。
从概率近似正确的 PAC学习 框架下:
- 弱学习:识别错误率小于1/2(即准确率仅比随机猜测略高的学习算法)
- 强学习:识别准确率很高并能在多项式时间内完成的学习算法
PAC学习有待了解
PAC学习框架下,强学习和弱学习是等价的,即强可学习的充分必要条件是若可学习的。
可以冲弱学习算法出发,反复学习,得到一系列若分类器(又称基本分类器),然后通过一定形式去组合这些弱分类器,构成一个强分类器。
许多Boosting方法通过改变训练数据集的概率分布(训练数据不同样本的权值),针对不同概率分布的数据调用弱分类算法学习一系列弱分类器。
那么,就面临两个问题:
- 每一轮学习如何改变数据的概率分布?
- 如何将各个弱分类器组合起来?
以上两个问题,使用不同的Boosting算法会有不同的解决方法。
Adaboost算法
Adaboost算法:(参考李航老师的《统计学习方法》)
假设给定一个二分类的训练数据集:
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
x_{i} \in \mathcal{X} \subseteq \mathbf{R}^{n}
xi∈X⊆Rn,类别
y
i
∈
Y
=
{
−
1
,
+
1
}
y_{i} \in \mathcal{Y}=\{-1,+1\}
yi∈Y={−1,+1},
X
\mathcal{X}
X是特征空间,
Y
\mathcal{Y}
Y是类别集合,输出最终分类器
G
(
x
)
G(x)
G(x)。Adaboost算法如下:
(1) 初始化训练数据的分布:
D
1
=
(
w
11
,
⋯
,
w
1
i
,
⋯
,
w
1
N
)
,
w
1
i
=
1
N
,
i
=
1
,
2
,
⋯
,
N
D_{1}=\left(w_{11}, \cdots, w_{1 i}, \cdots, w_{1 N}\right), \quad w_{1 i}=\frac{1}{N}, \quad i=1,2, \cdots, N
D1=(w11,⋯,w1i,⋯,w1N),w1i=N1,i=1,2,⋯,N
(2) 对于m=1,2,…,M次训练
- 使用具有权值分布 D m D_m Dm的训练数据集进行学习,得到基本分类器: G m ( x ) : X → { − 1 , + 1 } G_{m}(x): \mathcal{X} \rightarrow\{-1,+1\} Gm(x):X→{−1,+1}
- 计算 G m ( x ) G_m(x) Gm(x)在训练集上的分类误差率 e m = ∑ i = 1 N P ( G m ( x i ) ≠ y i ) = ∑ i = 1 N w m i I ( G m ( x i ) ≠ y i ) e_{m}=\sum_{i=1}^{N} P\left(G_{m}\left(x_{i}\right) \neq y_{i}\right)=\sum_{i=1}^{N} w_{m i} I\left(G_{m}\left(x_{i}\right) \neq y_{i}\right) em=∑i=1NP(Gm(xi)=yi)=∑i=1NwmiI(Gm(xi)=yi)
其中 I ( . ) I(.) I(.)是指示函数:括号内成立,函数 I I I值为1;括号内不成立则函数值为0。
e m e_{m} em的最后一个等式表示:被 G m ( x ) G_{m}(x) Gm(x)误分类的权值之后
- 计算 G m ( x ) G_m(x) Gm(x)的系数 α m = 1 2 log 1 − e m e m \alpha_{m}=\frac{1}{2} \log \frac{1-e_{m}}{e_{m}} αm=21logem1−em,这里的log是自然对数ln
- 更新训练数据集的权重分布
D m + 1 = ( w m + 1 , 1 , ⋯ , w m + 1 , i , ⋯ , w m + 1 , N ) w m + 1 , i = w m i Z m exp ( − α m y i G m ( x i ) ) , i = 1 , 2 , ⋯ , N \begin{array}{c} D_{m+1}=\left(w_{m+1,1}, \cdots, w_{m+1, i}, \cdots, w_{m+1, N}\right) \\ w_{m+1, i}=\frac{w_{m i}}{Z_{m}} \exp \left(-\alpha_{m} y_{i} G_{m}\left(x_{i}\right)\right), \quad i=1,2, \cdots, N \end{array} Dm+1=(wm+1,1,⋯,wm+1,i,⋯,wm+1,N)wm+1,i=Zmwmiexp(−αmyiGm(xi)),i=1,2,⋯,N
这里的 Z m Z_m Zm是规范化因子,使得 D m + 1 D_{m+1} Dm+1称为概率分布, Z m = ∑ i = 1 N w m i exp ( − α m y i G m ( x i ) ) Z_{m}=\sum_{i=1}^{N} w_{m i} \exp \left(-\alpha_{m} y_{i} G_{m}\left(x_{i}\right)\right) Zm=∑i=1Nwmiexp(−αmyiGm(xi))
(3) 构建基本分类器的线性组合 f ( x ) = ∑ m = 1 M α m G m ( x ) f(x)=\sum_{m=1}^{M} \alpha_{m} G_{m}(x) f(x)=∑m=1MαmGm(x),得到最终的分类器
求和符号 ∑ \sum ∑后面的那部分乘积表示:基分类器 G m ( x ) G_{m}(x) Gm(x)的重要性
G ( x ) = sign ( f ( x ) ) = sign ( ∑ m = 1 M α m G m ( x ) ) \begin{aligned} G(x) &=\operatorname{sign}(f(x)) \\ &=\operatorname{sign}\left(\sum_{m=1}^{M} \alpha_{m} G_{m}(x)\right) \end{aligned} G(x)=sign(f(x))=sign(m=1∑MαmGm(x))
下面对Adaboost算法做如下说明:
对于步骤(1),假设训练数据的权值分布是均匀分布,是为了使得第一次没有先验信息的条件下每个样本在基本分类器的学习中作用一样。
对于步骤(2),每一次迭代产生的基本分类器
G
m
(
x
)
G_m(x)
Gm(x)在加权训练数据集上的分类错误率
e
m
=
∑
i
=
1
N
P
(
G
m
(
x
i
)
≠
y
i
)
=
∑
G
m
(
x
i
)
≠
y
i
w
m
i
\begin{aligned}e_{m} &=\sum_{i=1}^{N} P\left(G_{m}\left(x_{i}\right) \neq y_{i}\right) =\sum_{G_{m}\left(x_{i}\right) \neq y_{i}} w_{m i}\end{aligned}
em=i=1∑NP(Gm(xi)=yi)=Gm(xi)=yi∑wmi,其中
∑
m
=
1
M
w
m
i
=
1
\sum_{m=1}^{M}w_{m i}=1
∑m=1Mwmi=1 。
G
m
(
x
)
代
表
数
据
集
上
分
类
错
误
的
样
本
权
重
和
,
这
点
直
接
说
明
了
权
重
分
布
G_m(x)代表数据集上分类错误的样本权重和,这点直接说明了权重分布
Gm(x)代表数据集上分类错误的样本权重和,这点直接说明了权重分布D_m
与
与
与G_m(x)
的
分
类
错
误
率
的分类错误率
的分类错误率e_m
有
直
接
关
系
。
同
时
,
在
步
骤
(
2
)
中
,
计
算
基
本
分
类
器
有直接关系。同时,在步骤(2)中,计算基本分类器
有直接关系。同时,在步骤(2)中,计算基本分类器G_m(x)$的系数
α
m
\alpha_m
αm,
α
m
=
1
2
log
1
−
e
m
e
m
\alpha_{m}=\frac{1}{2} \log \frac{1-e_{m}}{e_{m}}
αm=21logem1−em,它表示了
G
m
(
x
)
G_m(x)
Gm(x)在最终分类器的重要性程度,
α
m
\alpha_m
αm的取值由基本分类器
G
m
(
x
)
G_m(x)
Gm(x)的分类错误率有直接关系,当
e
m
⩽
1
2
e_{m} \leqslant \frac{1}{2}
em⩽21时,
α
m
⩾
0
\alpha_{m} \geqslant 0
αm⩾0,并且
α
m
\alpha_m
αm随着
e
m
e_m
em的减少而增大,因此分类错误率越小的基本分类器在最终分类器的作用越大!
最重要的,对于步骤(2)中的样本权重的更新:
w
m
+
1
,
i
=
{
w
m
i
Z
m
e
−
α
m
,
G
m
(
x
i
)
=
y
i
w
m
i
Z
m
e
α
m
,
G
m
(
x
i
)
≠
y
i
w_{m+1, i}=\left\{\begin{array}{ll} \frac{w_{m i}}{Z_{m}} \mathrm{e}^{-\alpha_{m}}, & G_{m}\left(x_{i}\right)=y_{i} \\ \frac{w_{m i}}{Z_{m}} \mathrm{e}^{\alpha_{m}}, & G_{m}\left(x_{i}\right) \neq y_{i} \end{array}\right.
wm+1,i={Zmwmie−αm,Zmwmieαm,Gm(xi)=yiGm(xi)=yi
因此,从上式可以看到:被基本分类器
G
m
(
x
)
G_m(x)
Gm(x)错误分类的样本的权重扩大,被正确分类的样本权重减少,二者相比相差
e
2
α
m
=
1
−
e
m
e
m
\mathrm{e}^{2 \alpha_{m}}=\frac{1-e_{m}}{e_{m}}
e2αm=em1−em倍。
对于步骤(3),线性组合 f ( x ) f(x) f(x)实现了将M个基本分类器的加权表决,系数 α m \alpha_m αm标志了基本分类器 G m ( x ) G_m(x) Gm(x)的重要性,值得注意的是:所有的 α m \alpha_m αm之和不为1。 f ( x ) f(x) f(x)的符号决定了样本x属于哪一类。
李航《统计学习方法》上的例子
简单的数据来手动计算Adaboost算法的过程:(例子来源:http://www.csie.edu.tw)
怎么取分类误差率 e m e_m em是 怎么取阈值v ,使得分类误差率最低?
答:遍历每个结点,并计算以当前节点做分割点是不是可以达到误分率最小,直到所有节点都算完,找到误分率最小的那个结点做分割点的阈值。
为什么第三次分类的误分类点个数就是0个,第三次分类的时候序号为1、2、3、10的点不是分错了吗?
答:注意:这时的分类器是线性加权的分类器 G ( x ) = sign ( f ( x ) ) = sign ( ∑ m = 1 M α m G m ( x ) ) \begin{aligned} G(x) &=\operatorname{sign}(f(x)) =\operatorname{sign}\left(\sum_{m=1}^{M} \alpha_{m} G_{m}(x)\right) \end{aligned} G(x)=sign(f(x))=sign(m=1∑MαmGm(x)) ,不是只看第三次训练出来的基本分类器。
下面这张图更形象化的说明最终分类器是什么样的:
(图片来源从下面那个参考链接)
以上的计算过程有代码形式,可参考
代码实现
使用sklearn对Adaboost算法,UCI的机器学习库里的开源数据集:葡萄酒数据集,该数据集可以在 ( https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data )上获得。该数据集包含了178个样本和13个特征,从不同的角度对不同的化学特性进行描述,我们的任务是根据这些数据预测红酒属于哪一个类别。(案例来源《python机器学习(第二版》)
# 引入数据科学相关工具包:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use("ggplot")
%matplotlib inline
import seaborn as sns
# 加载训练数据:
wine = pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data",header=None)
# 这时的数据集没有列标签,手动加上
wine.columns = ['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(wine["Class label"]))
wine
列标签解释:
- Class label:分类标签
- Alcohol:酒精
- Malic acid:苹果酸
- Ash:灰
- Alcalinity of ash:灰的碱度
- Magnesium:镁
- Total phenols:总酚
- Flavanoids:黄酮类化合物
- Nonflavanoid phenols:非黄烷类酚类
- Proanthocyanins:原花青素
- Color intensity:色彩强度
- Hue:色调
- OD280/OD315 of diluted wines:稀释酒OD280 OD350
- Proline:脯氨酸
# 数据预处理
# 仅仅考虑2,3类葡萄酒,去除1类,列标签也只使用了一部分
wine = wine[wine['Class label'] != 1]
y = wine['Class label'].values
X = wine[['Alcohol','OD280/OD315 of diluted wines']].values
# 将分类标签变成二进制编码:
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
y = le.fit_transform(y)
# 按8:2分割训练集和测试集
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2,random_state=1,stratify=y) # stratify参数代表了按照y的类别等比例抽样
# 使用单一决策树建模
from sklearn.tree import DecisionTreeClassifier
tree = DecisionTreeClassifier(criterion='entropy',random_state=1,max_depth=1) # criterion参数设置分类结点的标准:gini/entropy
from sklearn.metrics import accuracy_score
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
# 使用sklearn实现Adaboost(基分类器为决策树)
'''
AdaBoostClassifier相关参数:
base_estimator:基本分类器,默认为DecisionTreeClassifier(max_depth=1):1层高的深度
n_estimators:终止迭代的次数
learning_rate:学习率
algorithm:训练的相关算法,{'SAMME','SAMME.R'},默认='SAMME.R'
random_state:随机种子
'''
from sklearn.ensemble import AdaBoostClassifier
ada = AdaBoostClassifier(base_estimator=tree,n_estimators=500,learning_rate=0.1,random_state=1)
ada = ada.fit(X_train,y_train)
y_train_pred = ada.predict(X_train)
y_test_pred = ada.predict(X_test)
ada_train = accuracy_score(y_train,y_train_pred)
ada_test = accuracy_score(y_test,y_test_pred)
print('Adaboost train/test accuracies %.3f/%.3f' % (ada_train,ada_test))
# Adaboost train/test accuracies 1.000/0.917
对比两颗不同的树,发现在训练集合测试集上,都是AdaBoost表现的要好。
下面从图像上来看
# 画出单层决策树与Adaboost的决策边界:
x_min = X_train[:, 0].min() - 1
x_max = X_train[:, 0].max() + 1
y_min = X_train[:, 1].min() - 1
y_max = X_train[:, 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(nrows=1, ncols=2,sharex='col',sharey='row',figsize=(12, 6))
for idx, clf, tt in zip([0, 1],[tree, ada],['Decision tree', 'Adaboost']):
clf.fit(X_train, y_train)
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_train[y_train==0, 0],X_train[y_train==0, 1],c='blue', marker='^')
axarr[idx].scatter(X_train[y_train==1, 0],X_train[y_train==1, 1],c='red', marker='o')
axarr[idx].set_title(tt)
axarr[0].set_ylabel('Alcohol', fontsize=12)
plt.tight_layout()
plt.text(0, -0.2,s='OD280/OD315 of diluted wines',ha='center',va='center',fontsize=12,transform=axarr[1].transAxes)
plt.show()
学费了学费了,还可以这样画图
Adaboost模型的决策边界比单层决策树的决策边界要复杂的多。即Adaboost试图用增加模型复杂度而降低偏差的方式去减少总误差,但是过程中引入了方差,可能出现过拟合。
与单个分类器相比,Adaboost等Boosting模型增加了计算的复杂度,在实践中需要仔细思考是否愿意为预测性能的相对改善而增加计算成本,而且Boosting方式无法做到现在流行的并行计算的方式进行训练,因为每一步迭代都要基于上一步的基本分类器。