在上一篇集成学习—Adaboost(论文研读)中已经将Adaboost的原始论文精读了一遍,这篇博客主要是对Adaboost算法(主要是二分类的Adaboost)进行更深入的理解和推导,以及尝试下关于它的基于Python的代码的应用。
模型回顾
Adaboost算法,全称为the adaptive boosting algorithm,它是boosting算法中的代表。对于boosting来说,由于它是一个串行生成的集成学习方法,主要有两方面需要考虑:一是在每一轮中如何改变训练数据的权值或者概率分布;二是如何将弱学习器组合成一个强学习器。Adaboost的做法是:提高那些被前一轮弱学习器错误学习的样本的权重,对于那些弱学习器学习的不够好的数据,下一轮的弱学习器会重点学习;弱学习器的组合方式,Adaboost采用的是加权多数表决的方法,即学习错误率小的弱分类器权重较大,在表决中起较大作用,相反错误率大的权重小,作用也相对较小。Adaboost的巧妙之处就在于它将这些想法自然且有效地实现在一种算法中。
在原始的论文中,Freund和Schapire将二分类
问题的Adaboost算法的伪代码描述为:
简单来说,算法的步骤如下:首先需要对数据样本的权重向量赋初值,一般是设为均匀分布,即 ,然后在 次迭代中计算分布 ,对第 个弱学习器赋予这个分布,得到弱学习器的输出假设 ,然后再计算这个假设的(相对于当前分布的)加权错误率 ,令 ,再对下一轮的新权重进行更新,更新规则为 ,这样经过 次迭代,得到 个弱学习器,最后将他们的结果进行加权,输出最后算法的最终假设 ,最后 的条件为 即所有弱假设的加权和若大于0.5则判定最后输出的标签为1,否则为0。
总的来看,这个算法的迭代过程主要更新的是数据分布的权重 以及每个弱分类器的权重 。具体来说,在弱学习器给出弱假设的结果后,若计算出来的错误率 较大,则算法第4步中计算出来的 就较大,同时它的精度 就小,因而权重更新中的系数 就趋于1(较大,因为弱学习器是PAC学习的,所以关于它的误差假设是 ,其中 ,因此这样定义的 ),导致下一步更新后的权重较大,从而上一步分类错误的那些数据将在下一个弱学习器中重点学习,由此一点点改善数据的分类错误率;此外,根据每次迭代得到的 值,得到每个弱分类器在最后假设中的权重,错误率较大的弱分类器它的 就较大,因此 较小,意味着它在最后的决策中占得比重较小,最后的结果由 个弱分类器加权投票得到,更多的是依靠分类正确率高的那些弱分类器的信息。
加性模型
Adaboost算法有多种推导方式,周志华老师的《机器学习》上说比较容易理解的是基于“加性模型”,即基学习器的线性组合来最小化指数损失函数的方法,这个方法是由Friedman等人在2000年正式发表,而且这个理解方法是现在大多数学习Adaboost算法接触到的,因此这里也对针对这篇文章进行了简单的理解。
在Additive logistic regression: a statistical view of boosting这篇文章中,作者主要是从统计学的角度出发来解释Adaboost算法,说明了Adaboost实际上是一个加性逻辑回归模型,它用牛顿方法来最优化一个特别的指数损失函数,文章最后还提出了提升决策树(boosting decision tree),但这里不做讨论。
Friedman总结的Adaboost算法如下(实际上与上图的是一样的,除了权重 的更新的系数表示上有些微差别,Freund的权重不需要做归一化处理,但是这里需要):
理解与推导过程:
Adaboost算法是模型为加性模型、损失函数为指数函数、学习算法为前向分步算法(forward stage-wise algorithm)时的二分类学习方法(摘自李航老师的《统计学习方法》)。由上图知道Adaboost最终的分类器为 个弱学习器 的加权投票 ,它的目标是最小化指数损失函数 。
因此,论文中首先给出了一个有用的引理:
引理1 在 处达到最小。因此, 证明:因为在 和 的联合分布上求期望,可以充分得到在 上的条件期望最小值,所以对 令 为0即可得到结论。
一般来说逻辑回归模型不像式(1)中有一个 的因子,但是通过上下同乘 可以得到通常意义下的逻辑回归模型 因此说这两个模型是等价的。这也是为什么作者提出来Adaboost实际上可以解释为加性的逻辑回归模型。然后再给出了解释这个算法过程的命题(命题的证明就是算法的主要推导过程)。
命题1 离散的Adaboost算法产生了自适应的牛顿迭代更新来最小化 ,这是对一个加性逻辑回归模型的分步贡献。
命题1的证明如下:
令
,假设我们已经有了一个当前的分类器
,想要得到一个改进的分类器
,也就是在当前分类器中继续添加一个弱学习器,希望新的弱学习器在上一步更新后的样本分布中能最小化损失函数
,也就是使前向分步算法(stage-wise)得到的
和
使
在训练集上的指数损失最小。现在的任务就是证明使得损失函数
达到最小的
和
,就是Adaboost算法所得到的
和
,证明可以分为两步:
首先,求 。对于固定的 和 ,我们在 处将 泰勒展开到二阶: 关于 逐点最小化,得到最优的弱学习器为: 这里的 ,因此这里得到了下一步最佳的弱分类器 。
其次,求 。给定 ,我们就可以直接最小化 来求得系数 ,因为 部分与 和 都无关,因此最小化时可以不用考虑,相当于常数,因此: 利用引理1的方法可以得到 令 等于0,即可得 其中 表示错误率,这也是算法图中(b)的那一步的更新规则,因此就得到了最后输出结果 的更新规则:
最后,对于权重的更新,由 的定义就可以得到更新的规则: 再加上归一化即可。由于 ,因此上面的更新规则等价于: 与给出的Adaboost算法图中的一样。
至此,证明了二分类的Adaboost的过程。
Adaboost算法优缺点:
- 运行很快;
- 十分灵活,它提供的是一个框架,它的弱学习器可以搭配各种学习算法,例如决策树、SVM等;
- 多用途,可以用来做二分类、多分类、回归等,也适用于文本数据、连续数据、离散数据等;
- 不需要知道弱学习器的任何先验知识;
- 易用,需要调节的参数很少,只有一个迭代次数;
- 有效性,训练的错误率随着迭代次数的增加呈指数级下降,因此很快就能达到很好的效果;
- 大部分时候能抵抗过拟合。
但是主要的缺点也很明显:
- Adaboost的性能取决于数据和使用的弱学习器,如果弱学习器太强可能会导致算法过拟合,太弱可能会欠拟合;
- 对异常样本敏感,异常样本在迭代中可能会获得较高的权重,弱学习器在这些样本的关注较多,影响最终的预测准确性。
Python与应用
-
搭配Adaboost的基学习器一般都选择决策树,此时这样的算法Adaboost-Decision tree,总结起来就是:Adaboost + 根据权重sampling + 剪枝的decision tree(具体可以参考机器学习技法视频课)。使用Adaboost的方法计算每次的数据权重,然后根据这个权重采样出部分的样本给决策树进行学习,这个决策树一般会限制它的高度,让每个弱学习器不要学得太好,最后综合投票这些弱学习器的结果。
-
Python:sklearn.ensemble模块中包含了AdaBoost, 包括用于分类的AdaBoostClassifier以及用于回归的AdaBoostRegressor。由于这里使用的数据集是连续回归问题,因此这里只讨论AdaBoostRegressor,它的算法主要是用Drucker, Harris. “Improving Regressors Using Boosting Techniques.” Fourteenth International Conference on Machine Learning 1997. 的这篇论文中的AdaBoost.R2算法,基本上和Freund提出的AdaBoost.R类似,不过弱学习器默认选择的是CART决策树,使用MSE作为划分准则,具体可以参考之前的博客描述的CART决策树的内容。
-
数据来源及说明:天池新人赛工业蒸汽量预测,经脱敏后的锅炉传感器采集的数据(采集频率是分钟级别),根据锅炉的工况,预测产生的蒸汽量。数据分成训练数据(train.txt)和测试数据(test.txt),其中字段”V0”-“V37”,这38个字段是作为特征变量,”target”作为目标变量。选手利用训练数据训练出模型,预测测试数据的目标变量,排名结果依据预测结果的MSE(mean square error)。
AdaBoostRegressor某些参数说明,具体可参考文档说明:
- base_estimator:基学习器,可选(默认为回归决策树,max_depth=3,分类的话默认为决策树桩)
- n_estimators:终止boosting的最大估计数,也就是基学习器的个数。如果完全匹配,则提前停止学习。可选(默认为50)
- learning_rate:学习率,学习率缩小了每个回归器的贡献,在学习率和学习器的个数之间有一个平衡, 可选(默认为1.)
- loss:在每次boosting迭代后更新权重时使用的损失函数。可选线性、平方、指数损失函数,即{‘linear’, ‘square’, ‘exponential’},可选(默认为线性损失)
- X:训练集样本,数组类型的稀疏矩阵,shape = [n_samples, n_features]
- y:目标变量(实数),数组类型,shape = [n_samples]
- sample_weight:初始的样本权重,数组类型,shape = [n_samples],可选,默认为
代码:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split #划分数据集
from sklearn.metrics import mean_squared_error #MSE,与下面的定义的是一样的
#训练集和测试集导入
training_data = pd.read_csv('zhengqi_train.txt', sep='\t')
test_data = pd.read_csv('zhengqi_test.txt', sep='\t')
training_x = training_data.iloc[:,0:38] #用索引取前38列得到训练数据
training_target = training_data['target'] #最后一列为label
#选出30%为验证集(validation data set)
x_train, x_val, y_train, y_val = train_test_split(training_x, training_target, test_size=0.3, random_state=0)
#mean square error
def MSE(predict_x, y):
m = len(predict_x)
return np.sum((predict_x-y)**2)/(m)
'''============================ Adaboost =============================='''
from sklearn.ensemble import AdaBoostRegressor
#随着参数变化的验证集MSE
def n_estimators(n):
rg = AdaBoostRegressor(n_estimators = n, loss = 'square')
rg.fit(x_train, y_train)
return MSE(rg.predict(x_val), y_val)
#画图选择参数
a = np.linspace(10, 310, num=31)
b = np.zeros((31,))
for i in range(31):
b[i,] = n_estimators(int(a[i,]))
plt.scatter(a, b)
plt.plot(a, b)
plt.scatter(a[np.argmin(b),], min(b), c='r') #最优点标红
#用较优的参数来训练模型
best_n = int(a[np.argmin(b),]) #返回最优的参数值
rg = AdaBoostRegressor(n_estimators=best_n, loss='square')
rg.fit(x_train, y_train)
MSE(rg.predict(x_val), y_val) #验证集MSE为0.15772145020073947
'''保存测试集的预测结果'''
predict_x = pd.DataFrame(rg.predict(test_data))
predict_x.to_csv('/Users/Desktop/Adaboost_results1.txt', header=0, index=0) #不保留列名和索引
根据验证集的MSE变化,选择最优的参数为80
AdaBoost分类
稍微尝试了下AdaBoostClassifier,这里给出了一个比较直观的例子,由于可视化的原因,这里的数据源采用的是吴恩达|机器学习作业7.0.k-means聚类的数据源,但是因为k-means是无监督学习方法,Adaboost是监督学习方法,这个数据是无标签的数据,因此这里先用k-means聚类的结果当做是这个数据源的标签,然后再改变Adaboost中的弱学习器的个数来观察它的效果。具体代码如下:
import numpy as np
import matplotlib.pyplot as plt
import scipy.io as scio
from sklearn.ensemble import AdaBoostClassifier
from sklearn.cluster import MiniBatchKMeans #Mini Batch K-Means聚类
data = scio.loadmat('C:/Users/dell/Desktop/ex7data2.mat')
x = data['X']
'''可视化数据集'''
plt.scatter(x[:,0], x[:,1], marker='o', c='w', edgecolors='k')
'''聚类得到数据标签'''
K = 3 #设置聚类数量
kmeans = MiniBatchKMeans(K)
kmeans.fit(x)
#画出标签之后的数据
def plot_idx(x, idx):
color = ['r', 'g', 'b'] #颜色序列
for i in range(3):
plt.scatter(x[idx==i][:,0], x[idx==i][:,1], marker='o', c='w', edgecolors=color[i-1])
plot_idx(x, kmeans.predict(x))
#得到数据标签
y = kmeans.predict(x)
'''Adaboost分类'''
def plot_process(n):
clf = AdaBoostClassifier(n_estimators=n)
clf.fit(x, y)
plot_idx(x, clf.predict(x))
plot_process(4) #改变这里的弱学习器个数,观察分类效果随学习器个数的变化
首先数据集是这样的:
然后用k-means聚类得到数据标签:
最后改变Adaboost中弱学习器的个数,从1到4个,观察算法的学习效果:
很明显当只有一个弱学习器的时候显然效果不是很好,两个的时候显著提升,从弱学习器个数增加到3个开始,这个算法基本上已经把分类错误的样本全都纠正过来了,但由于这个数据集十分简单,所以学习器的个数不需要很多,在面对复杂的数据集的时候Adaboost表现还是值得期待的。
参考资料:
《机器学习》周志华
《统计学习方法》李航
Friedman J, Hastie T, Tibshirani R. Additive logistic regression: a statistical view of boosting (With discussion and a rejoinder by the authors)[J]. Annals of Statistics, 2000, 28(2):337-374.
Drucker, Harris. “Improving Regressors Using Boosting Techniques.” Fourteenth International Conference on Machine Learning 1997.
机器学习技法视频课