这篇博客主要介绍提升方法中的Adaboost算法。
在现实中,得到弱分类器要比得到强分类器更加的容易,因此,我们往往能够得到一些弱分类器。如果我们已经得到了一些弱分类器,我们能否将其提升为强分类器呢?其实是可以的,实际上我们只需要对每个弱分类器进行一定的组合,使每个弱分类器对最后的分类结果的决定都有一定的“话语权”,然而每个弱分类器的话语的分量又有所不同,这样由多个弱分类器进行组合,我们就得到了一个强分类器。这便是Adaboost算法的基本原理,一句话概括就是,“三个臭皮匠顶个诸葛亮”。
那么接下来主要问题就是如何构造这么一系列的弱分类器,从而使其组合成为强分类器。其实大部分提升方法都是通过不断的改变训练样本点的权值,或者说是改版训练数据的分布,针对不同的分布训练得到不同的弱分类器。这样我们便得到了一系列的弱分类器,接下来就面临两个问题:第一个问题就是每一轮构造弱分类器时如何改变训练样本的权重,或者说如何改变训练样本的概率分布;第二个问题是当我们有了一系列弱分类器,如何将其组合为强分类器。
至于上述第一个问题,Adaboost往往是通过提升上一轮弱分类器分类错误样本点的权重,降低分类正确样本点的权重,这样就能够使得在构造下一个弱分类器时能够更加重视那些在上一轮中被分类错误的样本点;而对于第二个问题,Adaboost通过加权多数表决法来将弱分类器进行组合,具体就是加大分类误差率较小的弱分类器的权重,降低分类误差率较大的弱分类器的权重,这样每个弱分类器都有自己各自的权重,只需要对每个弱分类器的预测结果进行加权求和即可。
如果上述文字描述还是没有理解,那么我们通过下图来进行说明:
图a:所有样本点都有相同的权重,此时我们构建了第一个弱分类器week classifier 1,然而通过观察我们发现,week classifier 1将部分样本点类别归类错误了
图b:将弱分类器week classifier 1分类错误的样本点权重加大,可以从图中看出分类器上方的两个蓝点和下方的一个红点样本权重均被增大了
图c:在图b更改误分类点权重的基础上,我们继续构造弱分类器week classifier 2,这时,弱分类器将更加重视权重大的样本点,此时week classifier 1分类错误的样本点在week classifier 2下均分类正确了,但此时我们发现又出现了两个新的误分类点,就是week classifier 2左侧的两个蓝色样本点
之后我们再次调整误分类点权重重复上述步骤便可构造出一系列弱分类器,然而每个弱分类器也有权重,这是根据它们对于训练样本的误分类率来决定的,误差率越小的弱分类器其权重就越高,否则就越低,最后当新出现一个样本点时,我们就可以通过加权投票来决定其类别。
那么接下来就来给出Adaboost的数学推导:
假设存在训练集
第一步(初始化样本权重):我们需要对所有样本点的权重进行初始化,在构造第一个弱分类器时,我们认为每个样本的初始权重是一样的,因此我们设初始权重为,其中
表示用于构造第
个弱分类器的第
个样本的权重。
第二步(递归构造弱分类器):假设我们需要构造个弱分类器,我们用
来作为标号
- 2.1:使用具有权值分布的训练数据
来进行训练,得到弱分类器
- 2.2:计算弱分类器
的训练误差率:
- 2.3:计算弱分类器
的权重系数(log为自然对数):
- 2.4:更新样本的权值分布:
,其中
,其中
是规范化因子,它使得样本所有权重之和为1,即使得
为一个概率分布,
- 再次执行2.1~2.4直至构造完
个弱分类器
第三步(弱分类器的线性组合形成最终分类器):,我们构造得到的最终分类器为
我们来分析第二步中的2.3,从2.3中我们可以看出,对于训练误差较大的弱分类器,其权重也比较低如下表所示:
0.3 | 0.4 | 0.6 | |
0.4236 | 0.2027 | -0.2027 |
因此最终分类误差率较低的弱分类器的“话语权”较重,但我们也可以发现,有些弱分类器权重为负值,这是因为其预测错误的样本点超过了百分之五十,那么在这种情况下我们反着预测就可以了,因此当这种弱分类器预测某个样本为+1类时,我们就认为这个样本为-1类,如果预测这个样本为-1类,我们就认为这个样本为+1类。这里我们结合第三步来看,是根据符号来确定类别的,也就是说符号为正,我们认为最终预测为+1类,否则预测为-1类。因此,如果某个弱分类器
误差率大于0.5,那么我们就认为,当这个弱分类器预测为+1类时,这个样本更有可能为-1类,因此其对于
这个值应当有负贡献度,即使得这个值减小,使其向负值方向靠近,因此可以看出
来表示弱分类器权重是合理的。
我们再来分析第二步中的2.4,从2.4我们可以看出如果一个样本被分类错误了则,否则
,因此当一个弱分类器(
时)对某个样本预测错误了,则我们可以很容易的通过这个式子看出,此时
,因此这个样本的权重会增大,否则相反地会减小。这样我们就使得在构造下一个弱分类器时更加注重上一个弱分类器分类错误的样本点。
最后我们来分析一下第三步,说明为什么用符号来决定最后的类别。我们可以这样想,如果符号为正,说明大部分权重较重(或者说是可信度较高)的弱分类器都将此样本点分类为了+1类,因此我们最终将样本点归为+1类;同理,如果符号为负,则说明大部分权重较重的弱分类器都将此样本分为了-1类,因此我们最终将样本归为-1类。
最后在附上推导Adaboost的过程,来说明Adaboost这样做为什么是有道理的,如下图所示:
以上便是集成学习中的Adaboost算法,下面附上运用Python简单编写的一个Adaboost程序:
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression, Perceptron
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import numpy as np
from numpy import random as rd
np.set_printoptions(linewidth=1000)
class boost(object):
def __init__(self, n, x, y):
"""
n:表示基分类器的个数
"""
self.n = n
self.x_train, self.x_test, self.y_train, self.y_test = train_test_split(x, y, random_state=2, test_size=0.3)
self.base_classifier = []
for i in range(self.n):
if i % 2 == 0:
self.base_classifier.append(LogisticRegression())
elif i % 3 == 0:
self.base_classifier.append(DecisionTreeClassifier(max_depth=2))
else:
self.base_classifier.append(Perceptron())
self.sample_weight = np.array([1 / self.x_train.shape[0]] * self.x_train.shape[0])
self.classifier_weight = []
self.e_m_lst = []
def train(self):
for clsf in self.base_classifier:
clsf.fit(self.x_train, self.y_train, sample_weight=self.sample_weight)
y_train_predict = clsf.predict(self.x_train)
wrong_classification_index = (y_train_predict - self.y_train).astype(np.bool)
e_m = np.sum(self.sample_weight[wrong_classification_index])
self.e_m_lst.append(e_m)
alpha_m = 0.5 * np.log((1 - e_m) / e_m)
self.classifier_weight.append(alpha_m)
Z_m = np.sum(self.sample_weight * np.exp(-alpha_m * self.y_train * y_train_predict))
self.sample_weight = self.sample_weight * np.exp(-alpha_m * self.y_train * y_train_predict)
print("基分类器权重:\n", self.classifier_weight)
print("各个基分类器训练集的误差率:\n", self.e_m_lst)
def test(self):
predict_result = np.zeros((1, self.x_test.shape[0])).ravel()
for clsf, alpha in zip(self.base_classifier, self.classifier_weight):
predict_result += alpha * clsf.predict(self.x_test)
clsf_test_predict = clsf.predict(self.x_test)
y_test_predict = [1 if i > 0 else -1 for i in predict_result]
print("adaboost测试集上的准确率:%.2f%s" % (accuracy_score(y_true=self.y_test, y_pred=y_test_predict) * 100, "%"))
print("测试集真实标记:\n", self.y_test)
print("测试集预测标记:\n", np.array(y_test_predict))
def main():
x = load_iris()["data"][50:, :]
y = np.array([1 if i == 1 else -1 for i in load_iris()["target"][50:]])
adb = boost(10, x, y)
adb.train()
adb.test()
if __name__ == "__main__":
main()