1.Boosting
Boosting 是个非常强大的学习方法, 它也是一个监督的分类学习方法。它组合许多“弱”分类器来产生一个强大的分类器组。一个弱分类器的性能只是比随机选择好一点,因此它可以被设计的非常简单并且不会有太大的计算花费。将很多弱分类器结合起来组成一个集成的类似于SVM或者神经网络的强分类器。
2. 基本思路
Boosting的提出与发展离不开Valiant和 Kearns的努力,历史上正是Valiant和 Kearns提出了"强可学习"和"弱可学习"的概念。那什么是"强可学习"和"弱可学习"呢?在概率近似正确PAC学习的框架下:
- 弱学习:识别错误率小于1/2(即准确率仅比随机猜测略高的学习算法)
- 强学习:识别准确率很高并能在多项式时间内完成的学习算法
非常有趣的是,在PAC 学习的框架下,强可学习和弱可学习是等价的,也就是说一个概念是强可学习的充分必要条件是这个概念是弱可学习的。这样一来,问题便是:在学习中,如果已经发现了弱可学习算法,能否将他提升至强可学习算法。因为,弱可学习算法比强可学习算法容易得多。提升方法就是从弱学习算法出发,反复学习,得到一系列弱分类器(又称为基本分类器),然后通过一定的形式去组合这些弱分类器构成一个强分类器。大多数的Boosting方法都是通过改变训练数据集的概率分布(训练数据不同样本的权值),针对不同概率分布的数据调用弱分类算法学习一系列的弱分类器。
Boosting的基本流程如下
- 步骤1:所有分布下的基础学习器对于每个观测值都应该有相同的权重
- 步骤2:如果第一个基础的学习算法预测错误,则该点在下一次的基础学习算法中有更高的权重
- 步骤3:迭代第2步,直到到达预定的学习器数量或预定的预测精度。
最后,将输出的多个弱学习器组合成一个强的学习器,提高模型的整体预测精度。Boosting总是更加关注被错误分类的弱规则。
Boosting算法的底层可以是任何算法,关于boosting算法,我们需要知道其中最有名的3个算法:
- AdaBoost(Adaptive Boosting)
- GBM(Gradient Boosting Machine)
- XGBoost
PAC学习
理论
机器学习有两个元素:模型与数据。其中模型又包含两部分:优化算法与假设空间。所谓机器学习就是用优化算法从假设空间中选择一个假设,使此假设能符合给定的数据描述。因此优化算法通俗的讲就是假设选择算法。
而PAC学习理论不关心假设选择算法,他关心的是能否从假设空间 H \mathcal{H} H中学习一个好的假设 h h h。看到能否二字了没?此理论不关心怎样在假设空间中寻找好的假设,只关心能不能找得到。现在我们在来看一下什么叫“好假设”?只要满足两个条件(PAC辨识条件)即可:
-
近似正确:泛化误差 E ( h ) E(h) E(h)足够小
E ( h ) E(h) E(h)越小越好,最好泛化误差能能于0,但一般是不可能的。那我们就把 E ( h ) E(h) E(h)限定在一个很小的数 ϵ \epsilon ϵ之内,即只要假设 h h h满足 E ( h ) ≤ ϵ E(h)\le\epsilon E(h)≤ϵ,我们就认为 h h h是正确的。
-
可能正确
不指望选择的假设 h h h百分之百是近似正确的(按上段所述,即$E(h) \le \epsilon ) , 只 要 ∗ ∗ 很 可 能 是 近 似 正 确 ∗ ∗ 的 就 可 以 , 即 我 们 给 定 一 个 值 ),只要**很可能是近似正确**的就可以,即我们给定一个值 ),只要∗∗很可能是近似正确∗∗的就可以,即我们给定一个值\delta , 假 设 ,假设 ,假设h 满 足 满足 满足P(h近似正确)\ge1-\delta$。
综上两点,就得到了PAC(可能近似正确,probably approximate correct)可学习的定义。简单的讲就是模型在短时间内利用少量的(多项式级别)样本能够找到一个假设 h ′ h' h′,使其满足 P ( E ( h ) ≤ ϵ ) ≥ 1 − δ 0 < ϵ , δ < 1 \displaystyle P(E(h)\le\epsilon)\ge1-\delta\qquad0\lt\epsilon,\delta\lt1 P(E(h)≤ϵ)≥1−δ0<ϵ,δ<1
对于Boosting方法来说,有两个问题需要给出答案:
- 在每一轮如何改变训练数据的权值或概率分布?
- 如何将弱分类器组合成一个强分类器?
关于这两个问题,不同的Boosting算法会有不同的答案,我们接下来介绍一种最经典的Boosting算法----Adaboost,我们需要理解Adaboost是怎么处理这两个问题以及为什么这么处理的。
3. AdaBoost
3.1 思想
AdaBoost针对第一个问题的做法是提高那些被前一轮弱分类器错误分类样本的权值,并降低那些被正确分类的样本的权值。经过一轮的权值加大后,后一轮的弱分类器就会更关注那些没有被正确分类的样本。持续下去,分类问题便会被一系列弱分类器“分而治之”。而对于第二个问题,即弱分类器的组合,AdaBoost采取加权多数表决法,具体的方法就是加大误差率小的弱分类器的权值,使其在表决中起更大的作用,另一方面,减小分类误差率大的弱分类器的权值,使其在表决中起较小的作用。
下面这张图对Ada-boost做了恰当的解释:
- Box 1: 你可以看到我们假设所有的数据点有相同的权重(正号、负号的大小都一样),并用一个决策树桩D1将它们分为两部分。我们可以看到这个决策树桩将其中的三个正号标记的数据点分类错误,因此我们将这三个点赋予更大的权重交由下一个预测树桩进行分类。
- Box 2: 在这里你可以看到三个未被正确分类的(+)号的点的权重变大。在这种情况下,第二个决策树桩D2试图将这三个错误的点准确的分类,但是这又引起新的分类错误,将三个(-)号标记的点识别错误,因此在下一次分类中,这三个(-)号标记的点被赋予更大的权重。
- Box 3: 在这里三个被错误分类的(-)号标记的点被赋予更大的权重,利用决策树桩D3进行新的分类,这时候又产生了新的分类错误,图中用小圆圈圈起来的一个负号点和两个正号点
- Box 4: 在这里,我们将D1、D2和D3三个决策器组合起来形成一个复杂的规则,你可以看到这个组合后的决策器比它们任何一个弱分类器表现的都足够好。
3.2 算法流程:
假设给定一个二分类的训练数据集: 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是特征空间,$ \mathcal{Y} 是 类 别 集 合 , 输 出 最 终 分 类 器 是类别集合,输出最终分类器 是类别集合,输出最终分类器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)
- 计算 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) \\ \large 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),得到最终的分类器
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
代表了在
G
m
(
x
)
G_m(x)
Gm(x)中分类错误的样本权重和,这点直接说明了权重分布
D
m
D_m
Dm与
G
m
(
x
)
G_m(x)
Gm(x)的分类错误率
e
m
e_m
em有直接关系。同时,在步骤(2)中,计算基本分类器
G
m
(
x
)
G_m(x)
Gm(x)的系数
α
m
\alpha_m
αm,
α
m
=
1
2
log
1
−
e
m
e
m
\large \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
\large 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
\large \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属于哪一类。
3.3 代码实战
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use("ggplot")
import seaborn as sns
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
#pandas设置最大显示行和列
pd.set_option('display.max_columns',50)
pd.set_option('display.max_rows',300)
#调整显示宽度,以便整行显示
pd.set_option('display.width',1000)
if __name__ == '__main__':
# 加载训练数据:
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"]))
# 查看前五行数据
print(wine.head())
输出结果:Class labels [1 2 3]
下面对数据做简单解读:
- 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 #选取两列数据作为X
# 将分类标签变成二进制编码
le = LabelEncoder()
y = le.fit_transform(y)
# 按8:2分割训练集和测试集
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)
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)
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的测试性能也略有提高。然而,为什么模型在训练集和测试集的性能相差这么大呢?我们使用图像来简单说明下这个道理!
# 画出单层决策树与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))
# nrows,ncols:
for idx, clf, tt in zip([0, 1],[tree, ada],['Decision tree', 'Adaboost']):
# zip :将对象中对应的元素打包成一个个元组
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()
matplotlib.pyplot.``subplots
(nrows=1, ncols=1, ***, sharex=False, sharey=False, squeeze=True, subplot_kw=None, gridspec_kw=None, **fig_kw)[source]
- nrows, ncolsint, default: 1
子图网格的行数/列数。
- sharex, sharey:bool or {‘none’, ‘all’, ‘row’, ‘col’}, default: False
控制x(sharex)或y(sharey)轴之间的属性共享:
True or ‘all’: x或y轴将在所有子图中共享
False or ‘none’:每个子图的x轴或y轴将是独立的。
‘row’: 每个子图行将共享一个x轴或y轴。
‘col’: 每个子图行将共享一个x轴或y轴。
当子图沿一列具有共享的x轴时,将仅创建底部子图的x刻度标签。类似地,当子图沿行具有共享的y轴时,仅创建第一列子图的y刻度标签。若要稍后打开其他子图的刻度标签,请使用
tick_params
。当子图的共享轴包含单位时,调用
set_units
将使用新单位更新每个轴。
- squeeze:bool, default: True
默认为 True,是设置返回的子图对象的数组格式。
当为 False 时,不论返回的子图是只有一个还是只有一行,都会用二维数组格式返回他的对象。
当为 True 时,如果设置的子图是(nrows=ncols=1),即子图只有一个,则返回的子图对象是一个标量的形式,如果子图有(N×1)或者(1×N)个,则返回的子图对象是一个一维数组的格式,如果是(N×M)则是返回二位格式。
- subplot_kw:dict, optional
字典格式,传递给 add_subplot() ,用于创建子图。
- gridspec_kw:dict, optional
字典格式,传递给 GridSpec 的构造函数,用于创建子图所摆放的网格。
- **fig_kw
所有其他关键字参数都传递给 figure()调用。
如,设置 figsize=(21, 12) ,则设置了图像大小。
参考文献:
https://www.biaodianfu.com/boosting.html
https://blog.csdn.net/wangjianguobj/article/details/57413819