34丨AdaBoost(上):如何使用AdaBoost提升分类器性能?
今天我们学习 AdaBoost 算法。在数据挖掘中,分类算法可以说是核心算法,其中 AdaBoost 算法与随机森林算法一样都属于分类算法中的集成算法。集成的含义就是集思广益,博取众长,当我们做决定的时候,我们先听取多个专家的意见,再做决定。集成算法通常有两种方式,分别是投票选举(bagging)和再学习(boosting)。投票选举的场景类似把专家召集到一个会议桌前,当做一个决定的时候,让 K 个专家(K 个模型)分别进行分类,然后选择出现次数最多的那个类作为最终的分类结果。再学习相当于把 K 个专家(K 个分类器)进行加权融合,形成一个新的超级专家(强分类器),让这个超级专家做判断。所以你能看出来,投票选举和再学习还是有区别的。Boosting 的含义是提升,它的作用是每一次训练的时候都对上一次的训练进行改进提升,在训练的过程中这 K 个“专家”之间是有依赖性的,当引入第 K 个“专家”(第 K 个分类器)的时候,实际上是对前 K-1 个专家的优化。而 bagging 在做投票选举的时候可以并行计算,也就是 K 个“专家”在做判断的时候是相互独立的,不存在依赖性。
AdaBoost 的工作原理
了解了集成算法的两种模式之后,我们来看下今天要讲的 AdaBoost 算法。AdaBoost 的英文全称是 Adaptive Boosting,中文含义是自适应提升算法。它由 Freund 等人于 1995 年提出,是对 Boosting 算法的一种实现。什么是 Boosting 算法呢?Boosting 算法是集成算法中的一种,同时也是一类算法的总称。这类算法通过训练多个弱分类器,将它们组合成一个强分类器,也就是我们俗话说的“三个臭皮匠,顶个诸葛亮”。为什么要这么做呢?因为臭皮匠好训练,诸葛亮却不好求。因此要打造一个诸葛亮,最好的方式就是训练多个臭皮匠,然后让这些臭皮匠组合起来,这样往往可以得到很好的效果。这就是 Boosting 算法的原理。
假设弱分类器为 Gi(x),它在强分类器中的权重 αi,那么就可以得出强分类器 f(x):
有了这个公式,为了求解强分类器,你会关注两个问题:如何得到弱分类器,也就是在每次迭代训练的过程中,如何得到最优弱分类器?每个弱分类器在强分类器中的权重是如何计算的?我们先来看下第二个问题。实际上在一个由 K 个弱分类器中组成的强分类器中,如果弱分类器的分类效果好,那么权重应该比较大,如果弱分类器的分类效果一般,权重应该降低。所以我们需要基于这个弱分类器对样本的分类错误率来决定它的权重,用公式表示就是:
其中 ei 代表第 i 个分类器的分类错误率。然后我们再来看下第一个问题,如何在每次训练迭代的过程中选择最优的弱分类器?实际上,AdaBoost 算法是通过改变样本的数据分布来实现的。AdaBoost 会判断每次训练的样本是否正确分类,对于正确分类的样本,降低它的权重,对于被错误分类的样本,增加它的权重。再基于上一次得到的分类准确率,来确定这次训练样本中每个样本的权重。然后将修改过权重的新数据集传递给下一层的分类器进行训练。这样做的好处就是,通过每一轮训练样本的动态权重,可以让训练的焦点集中到难分类的样本上,最终得到的弱分类器的组合更容易得到更高的分类准确率。我们可以用 Dk+1 代表第 k+1 轮训练中,样本的权重集合,其中 Wk+1,1 代表第 k+1 轮中第一个样本的权重,以此类推 Wk+1,N 代表第 k+1 轮中第 N 个样本的权重,因此用公式表示为:
第 k+1 轮中的样本权重,是根据该样本在第 k 轮的权重以及第 k 个分类器的准确率而定,具体的公式为:
AdaBoost 算法示例
现在我希望通过 AdaBoost 构建一个强分类器。该怎么做呢?按照上面的 AdaBoost 工作原理,我们来模拟一下。首先在第一轮训练中,我们得到 10 个样本的权重为 1/10,即初始的 10 个样本权重一致,D1=(0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1)。假设我有 3 个基础分类器:
我们可以知道分类器 f1 的错误率为 0.3,也就是 x 取值 6、7、8 时分类错误;分类器 f2 的错误率为 0.4,即 x 取值 0、1、2、9 时分类错误;分类器 f3 的错误率为 0.3,即 x 取值为 3、4、5 时分类错误。这 3 个分类器中,f1、f3 分类器的错误率最低,因此我们选择 f1 或 f3 作为最优分类器,假设我们选 f1 分类器作为最优分类器,即第一轮训练得到:
根据分类器权重公式得到:
然后我们对下一轮的样本更新求权重值,代入 Wk+1,i 和 Dk+1 的公式,可以得到新的权重矩阵:D2=(0.0715, 0.0715, 0.0715, 0.0715, 0.0715, 0.0715, 0.1666, 0.1666, 0.1666, 0.0715)。在第二轮训练中,我们继续统计三个分类器的准确率,可以得到分类器 f1 的错误率为 0.16663,也就是 x 取值为 6、7、8 时分类错误。分类器 f2 的错误率为 0.07154,即 x 取值为 0、1、2、9 时分类错误。分类器 f3 的错误率为 0.0715*3,即 x 取值 3、4、5 时分类错误。在这 3 个分类器中,f3 分类器的错误率最低,因此我们选择 f3 作为第二轮训练的最优分类器,即:
同样,我们对下一轮的样本更新求权重值,代入 Wk+1,i 和 Dk+1 的公式,可以得到 D3=(0.0455,0.0455,0.0455,0.1667, 0.1667,0.01667,0.1060, 0.1060, 0.1060, 0.0455)。在第三轮训练中,我们继续统计三个分类器的准确率,可以得到分类器 f1 的错误率为 0.10603,也就是 x 取值 6、7、8 时分类错误。分类器 f2 的错误率为 0.04554,即 x 取值为 0、1、2、9 时分类错误。分类器 f3 的错误率为 0.1667*3,即 x 取值 3、4、5 时分类错误。在这 3 个分类器中,f2 分类器的错误率最低,因此我们选择 f2 作为第三轮训练的最优分类器,即:
假设我们只进行 3 轮的训练,选择 3 个弱分类器,组合成一个强分类器,那么最终的强分类器 G(x) = 0.4236G1(x) + 0.6496G2(x)+0.7514G3(x)。
实际上 AdaBoost 算法是一个框架,你可以指定任意的分类器,通常我们可以采用 CART 分类器作为弱分类器。通过上面这个示例的运算,你体会一下 AdaBoost 的计算流程即可。
总结今天我给你讲了 AdaBoost 算法的原理,你可以把它理解为一种集成算法,通过训练不同的弱分类器,将这些弱分类器集成起来形成一个强分类器。在每一轮的训练中都会加入一个新的弱分类器,直到达到足够低的错误率或者达到指定的最大迭代次数为止。实际上每一次迭代都会引入一个新的弱分类器(这个分类器是每一次迭代中计算出来的,是新的分类器,不是事先准备好的)。在弱分类器的集合中,你不必担心弱分类器太弱了。实际上它只需要比随机猜测的效果略好一些即可。如果随机猜测的准确率是 50% 的话,那么每个弱分类器的准确率只要大于 50% 就可用。AdaBoost 的强大在于迭代训练的机制,这样通过 K 个“臭皮匠”的组合也可以得到一个“诸葛亮”(强分类器)。当然在每一轮的训练中,我们都需要从众多“臭皮匠”中选择一个拔尖的,也就是这一轮训练评比中的最优“臭皮匠”,对应的就是错误率最低的分类器。当然每一轮的样本的权重都会发生变化,这样做的目的是为了让之前错误分类的样本得到更多概率的重复训练机会。同样的原理在我们的学习生活中也经常出现,比如善于利用错题本来提升学习效率和学习成绩。
35丨AdaBoost(下):如何使用AdaBoost对房价进行预测?
这次我们的主要目标是使用 AdaBoost 预测房价,这是一个回归问题。除了对项目进行编码实战外,我希望你能掌握:AdaBoost 工具的使用,包括使用 AdaBoost 进行分类,以及回归分析。使用其他的回归工具,比如决策树回归,对比 AdaBoost 回归和决策树回归的结果。
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import AdaBoostRegressor
我们需要使用 AdaBoostClassifier(base_estimator=None, n_estimators=50, learning_rate=1.0, algorithm=’SAMME.R’, random_state=None) 这个函数,其中有几个比较主要的参数,我分别来讲解下:base_estimator:代表的是弱分类器。在 AdaBoost 的分类器和回归器中都有这个参数,在 AdaBoost 中默认使用的是决策树,一般我们不需要修改这个参数,当然你也可以指定具体的分类器。n_estimators:算法的最大迭代次数,也是分类器的个数,每一次迭代都会引入一个新的弱分类器来增加原有的分类器的组合能力。默认是 50。learning_rate:代表学习率,取值在 0-1 之间,默认是 1.0。如果学习率较小,就需要比较多的迭代次数才能收敛,也就是说学习率和迭代次数是有相关性的。当你调整 learning_rate 的时候,往往也需要调整 n_estimators 这个参数。algorithm:代表我们要采用哪种 boosting 算法,一共有两种选择:SAMME 和 SAMME.R。默认是 SAMME.R。这两者之间的区别在于对弱分类权重的计算方式不同。random_state:代表随机数种子的设置,默认是 None。随机种子是用来控制随机模式的,当随机种子取了一个值,也就确定了一种随机规则,其他人取这个值可以得到同样的结果。如果不设置随机种子,每次得到的随机数也就不同。
那么如何创建 AdaBoost 回归呢?
我们可以使用 AdaBoostRegressor(base_estimator=None, n_estimators=50, learning_rate=1.0, loss=‘linear’, random_state=None) 这个函数。你能看出来回归和分类的参数基本是一致的,不同点在于回归算法里没有 algorithm 这个参数,但多了一个 loss 参数。loss 代表损失函数的设置,一共有 3 种选择,分别为 linear、square 和 exponential,它们的含义分别是线性、平方和指数。默认是线性。一般采用线性就可以得到不错的效果。创建好 AdaBoost 分类器或回归器之后,我们就可以输入训练集对它进行训练。我们使用 fit 函数,传入训练集中的样本特征值 train_X 和结果 train_y,模型会自动拟合。使用 predict 函数进行预测,传入测试集中的样本特征值 test_X,然后就可以得到预测结果。
如何用 AdaBoost 对房价进行预测
了解了 AdaBoost 工具包之后,我们看下 sklearn 中自带的波士顿房价数据集。
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from sklearn.datasets import load_boston
from sklearn.ensemble import AdaBoostRegressor
# 加载数据
data=load_boston()
# 分割数据
train_x, test_x, train_y, test_y = train_test_split(data.data, data.target, test_size=0.25, random_state=33)
# 使用AdaBoost回归模型
regressor=AdaBoostRegressor()
regressor.fit(train_x,train_y)
pred_y = regressor.predict(test_x)
mse = mean_squared_error(test_y, pred_y)
print("房价预测结果 ", pred_y)
print("均方误差 = ",round(mse,2))
这个数据集是比较规范的,我们并不需要在数据清洗,数据规范化上花太多精力,代码编写起来比较简单。同样,我们可以使用不同的回归分析模型分析这个数据集,比如使用决策树回归和 KNN 回归。编写代码如下:
# 使用决策树回归模型
dec_regressor=DecisionTreeRegressor()
dec_regressor.fit(train_x,train_y)
pred_y = dec_regressor.predict(test_x)
mse = mean_squared_error(test_y, pred_y)
print("决策树均方误差 = ",round(mse,2))
# 使用KNN回归模型
knn_regressor=KNeighborsRegressor()
knn_regressor.fit(train_x,train_y)
pred_y = knn_regressor.predict(test_x)
mse = mean_squared_error(test_y, pred_y)
print("KNN均方误差 = ",round(mse,2))
AdaBoost 与决策树模型的比较
在 sklearn 中 AdaBoost 默认采用的是决策树模型,我们可以随机生成一些数据,然后对比下 AdaBoost 中的弱分类器(也就是决策树弱分类器)、决策树分类器和 AdaBoost 模型在分类准确率上的表现。如果想要随机生成数据,我们可以使用 sklearn 中的 make_hastie_10_2 函数生成二分类数据。假设我们生成 12000 个数据,取前 2000 个作为测试集,其余作为训练集。有了数据和训练模型后,我们就可以编写代码。我设置了 AdaBoost 的迭代次数为 200,代表 AdaBoost 由 200 个弱分类器组成。针对训练集,我们用三种模型分别进行训练,然后用测试集进行预测,并将三个分类器的错误率进行可视化对比,可以看到这三者之间的区别:
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.metrics import zero_one_loss
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import AdaBoostClassifier
# 设置AdaBoost迭代次数
n_estimators=200
# 使用
X,y=datasets.make_hastie_10_2(n_samples=12000,random_state=1)
# 从12000个数据中取前2000行作为测试集,其余作为训练集
train_x, train_y = X[2000:],y[2000:]
test_x, test_y = X[:2000],y[:2000]
# 弱分类器
dt_stump = DecisionTreeClassifier(max_depth=1,min_samples_leaf=1)
dt_stump.fit(train_x, train_y)
dt_stump_err = 1.0-dt_stump.score(test_x, test_y)
# 决策树分类器
dt = DecisionTreeClassifier()
dt.fit(train_x, train_y)
dt_err = 1.0-dt.score(test_x, test_y)
# AdaBoost分类器
ada = AdaBoostClassifier(base_estimator=dt_stump,n_estimators=n_estimators)
ada.fit(train_x, train_y)
# 三个分类器的错误率可视化
fig = plt.figure()
# 设置plt正确显示中文
plt.rcParams['font.sans-serif'] = ['SimHei']
ax = fig.add_subplot(111)
ax.plot([1,n_estimators],[dt_stump_err]*2, 'k-', label=u'决策树弱分类器 错误率')
ax.plot([1,n_estimators],[dt_err]*2,'k--', label=u'决策树模型 错误率')
ada_err = np.zeros((n_estimators,))
# 遍历每次迭代的结果 i为迭代次数, pred_y为预测结果
for i,pred_y in enumerate(ada.staged_predict(test_x)):
# 统计错误率
ada_err[i]=zero_one_loss(pred_y, test_y)
# 绘制每次迭代的AdaBoost错误率
ax.plot(np.arange(n_estimators)+1, ada_err, label='AdaBoost Test 错误率', color='orange')
ax.set_xlabel('迭代次数')
ax.set_ylabel('错误率')
leg=ax.legend(loc='upper right',fancybox=True)
plt.show()
从图中你能看出来,弱分类器的错误率最高,只比随机分类结果略好,准确率稍微大于 50%。决策树模型的错误率明显要低很多。而 AdaBoost 模型在迭代次数超过 25 次之后,错误率有了明显下降,经过 125 次迭代之后错误率的变化形势趋于平缓。因此我们能看出,虽然单独的一个决策树弱分类器效果不好,但是多个决策树弱分类器组合起来形成的 AdaBoost 分类器,分类效果要好于决策树模型。
总结
今天我带你用 AdaBoost 回归分析对波士顿房价进行了预测。因为这是个回归分析的问题,我们直接使用 sklearn 中的 AdaBoostRegressor 即可。如果是分类,我们使用 AdaBoostClassifier。另外我们将 AdaBoost 分类器、弱分类器和决策树分类器做了对比,可以看出经过多个弱分类器组合形成的 AdaBoost 强分类器,准确率要明显高于决策树算法。所以 AdaBoost 的优势在于框架本身,它通过一种迭代机制让原本性能不强的分类器组合起来,形成一个强分类器。其实在现实工作中,我们也能找到类似的案例。IBM 服务器追求的是单个服务器性能的强大,比如打造超级服务器。而 Google 在创建集群的时候,利用了很多 PC 级的服务器,将它们组成集群,整体性能远比一个超级服务器的性能强大。再比如我们讲的“三个臭皮匠,顶个诸葛亮”,也就是 AdaBoost 的价值所在。