利用AdaBoost元算法提高分类性能
运行环境:Anaconda——Jupyter Notebook
Python版本为:3.6.6
数据集:horse
提取码:63w9
元算法是对其他算法进行组合的一种方式。
1.基于数据集多重抽样的分类器
我们可以将不同的分类器组合起来,而这种组合结果则被称为集成方法(ensemble method)或者元算法(meta-algorithm)。使用集成方法时会有多种形式:可以是不同算法的集成,也可以是同一算法在不同设置下的集成,还可以是数据集不同部分分配给不同分类器之后的集成。
AdaBoost
优点:泛化错误率低,易编码,可以应用在大部分分类器上,无参数调整。
缺点:对离群点敏感。
适用数据类型:数值型和标称型数据。
1.1 bagging:基于数据随机重抽样的分类器构建方法
自举汇聚法(bootstrap aggregating),也称为bagging方法,是在从原始数据集选择S次后得到S个新数据集的一种技术。新数据集和原数据集的大小相等。每个数据集都是通过在原始数据集中随机选择一个样本来进行替换而得到的。这里的替换就意味着可以多次地选择同一样本。这一性质就允许新数据集中可以有重复的值,而原始数据集的某些值在新集合中则不再出现。
在S个数据集建好之后,将某个学习算法分别作用于每个数据集就得到了S个分类器。当我们要对新数据进行分类时,就可以应用这S个分类器进行分类。与此同时,选择分类器投票结果中最多的类别作为最后的分类结果。
1.2 boosting
boosting是一种与bagging很类似的技术。不论是在boosting还是bagging当中,所使用的多个分类器的类型都是一致的。但是在前者当中,不同的分类器是通过串行训练而获得的,每个新分类器都根据已训练出的分类器的性能来进行训练。boosting是通过集中关注被已有分类器错分的那些数据来获得新的分类器。
由于boosting分类的结果是基于所有分类器的加权求和结果的,因此boosting与bagging不太一样。bagging中的分类器权重是相等的,而boosting中的分类器权重并不相等,每个权重代表的是其对应分类器在上一轮迭代中的成功度。
AdaBoost的一般流程
(1) 收集数据:可以使用任意方法。
(2) 准备数据:依赖于所使用的弱分类器类型,本章使用的是单层决策树,这种分类器可以处理任何数据类型。当然也可以使用任意分类器作为弱分类器,第2章到第6章中的任一分类器都可以充当弱分类器。作为弱分类器,简单分类器的效果更好。
(3) 分析数据:可以使用任意方法。
(4) 训练算法:AdaBoost的大部分时间都用在训练上,分类器将多次在同一数据集上训练弱分类器。
(5) 测试算法:计算分类的错误率。
(6) 使用算法:同SVM一样,AdaBoost预测两个类别中的一个。如果想把它应用到多个类别的场合,那么就要像多类SVM中的做法一样对AdaBoost进行修改。
2.训练算法:基于错误提升分类器的性能
AdaBoost是adaptive boosting(自适应boosting)的缩写,其运行过程如下:训练数据中的每个样本,并赋予其一个权重,这些权重构成了向量D。一开始,这些权重都初始化成相等值。首先在训练数据上训练出一个弱分类器并计算该分类器的错误率,然后在同一数据集上再次训练弱分类器。在分类器的第二次训练当中,将会重新调整每个样本的权重,其中第一次分对的样本的权重将会降低,而第一次分错的样本的权重将会提高。为了从所有弱分类器中得到最终的分类结
果,AdaBoost为每个分类器都分配了一个权重值alpha,这些alpha值是基于每个弱分类器的错误率进行计算的。其中,错误率ε的定义为:
而alpha的计算公式如下:
AdaBoost算法的流程如图所示:
AdaBoost算法的示意图。左边是数据集,其中直方图的不同宽度表示每个样例上的不同权重。在经过一个分类器之后,加权的预测结果会通过三角形中的alpha值进行加权。每个三角形中输出的加权结果在圆形中求和,从而得到最终的输出结果。
计算出alpha值之后,可以对权重向量D进行更新,以使得那些正确分类的样本的权重降低而错分样本的权重升高。D的计算方法如下。如果某个样本被正确分类,那么该样本的权重更改为:
而如果某个样本被错分,那么该样本的权重更改为:
在计算出D之后,AdaBoost又开始进入下一轮迭代。AdaBoost算法会不断地重复训练和调整权重的过程,直到训练错误率为0或者弱分类器的数目达到用户的指定值为止。
3.基于单层决策树构建弱分类器
单层决策树(decision stump,也称决策树桩)是一种简单的决策树,而它仅基于单个特征来做决策。由于这棵树只有一次分裂过程,因此它实际上就是一个树桩。
# 构建简单数据集
import numpy as np
def loadSimpleData():
dataMat = np.mat([[1., 2.1],[2., 1.1],[1.3, 1.],[1., 1.],[2., 1.]])
classLabels = [1.0,1.0,-1.0,-1.0,1.0]
return dataMat,classLabels
dataMat,classLabels = loadSimpleData()
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
# 改为plt.scatter(dataSet[:,0].tolist(),dataSet[:,1].tolist())或plt.scatter(array(dataSet[:,0]),array(dataSet[:,1].tolist())),
# 否则会出现ValueError: Masked arrays must be 1-D
# ax.scatter(dataMat[:,0].tolist(),dataMat[:,1].tolist())
ax.scatter([1,1.3],[1,1],marker='s')
ax.scatter([1,2,2],[2.1,1.1,1])
plt.show()
程序的伪代码:
将最小错误率minError设为+∞
对数据集中的每一个特征(第一层循环):
对每个步长(第二层循环):
对每个不等号(第三层循环):
建立一棵单层决策树并利用加权数据集对它进行测试
如果错误率低于minError,则将当前单层决策树设为最佳单层决策树
返回最佳单层决策树
# 程序清单 单层决策树生成函数
"""用于测试是否有某个值小于或者大于我们正在测试的阈值"""
def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):
retArray = np.ones((np.shape(dataMatrix)[0],1))
if threshIneq == 'lt':
retArray[dataMatrix[:,dimen] <= threshVal] = -1.0
else:
retArray[dataMatrix[:,dimen] > threshVal] = -1.0
return retArray
"""它会在一个加权数据集中循环,并找到具有最低错误率的单层决策树。"""
def buildStump(dataArr,classLabels,D):
dataMat = np.mat(dataArr)
classLabels = np.mat(classLabels).T
m,n = np.shape(dataMat)
numSteps = 10
bestStump = {}
bestClasEst = np.mat(np.zeros((m,1)))
minError = np.inf
# 对数据集中的每一个特征
for i in range(n):
rangeMin = dataMat[:,i].min()
rangeMax = dataMat[:,i].max()
stepSize = (rangeMax-rangeMin)/numSteps
# 对每一个步长,可在范围外
for j in range(-1,int(numSteps)+1):
# 对每个不等号
for inequal in ['lt','gt']:
threshVal = (rangeMin+float(j)*stepSize)
predictedVals = stumpClassify(dataMat,i,threshVal,inequal)
errArr = np.ones((m,1))
errArr[predictedVals==classLabels] = 0
# 计算加权错误率
weightedError = D.T*errArr
# print('split:dim %d,thresh % .2f,thresh inequal: %s,the weighted Error is %.3f' %(i,threshVal,inequal,weightedError))
# print(predictedVals,errArr)
if weightedError < minError:
minError = weightedError
bestClasEst = predictedVals.copy()
bestStump['dim'] = i
bestStump['thresh'] = threshVal
bestStump['ineq'] = inequal
return bestStump,minError,bestClasEst
D = np.mat(np.ones((5,1))/5)
buildStump(dataMat,classLabels,D)
({'dim': 0, 'thresh': 1.3, 'ineq': 'lt'}, matrix([[0.2]]), array([[-1.],
[ 1.],
[-1.],
[-1.],
[ 1.]]))
4.完整AdaBoost 算法的实现
程序清单 基于单层决策树的AdaBoost训练过程
整个实现的伪代码如下:
对每次迭代:
利用buildStump()函数找到最佳的单层决策树
将最佳单层决策树加入到单层决策树数组
计算alpha
计算新的权重向量D
更新累计类别估计值
如果错误率等于0.0,则退出循环
def adaBoostTrainDS(dataArr,classLabels,numIt=40):
# weakClassArr列表存储输出的单层决策树的数组
weakClassArr = []
m,n = np.shape(dataArr)
# D包含了每个数据点的权重,所有元素之和为1
D = np.mat(np.ones((m,1))/m)
# 记录数据点的类别估计累计值
aggClassEst = np.mat(np.zeros((m,1)))
for i in range(numIt):
# 返回利用D得到的具有最小错误率的单层决策树,同时返回最小错误率及估计的类别向量
bestStump,error,classEst = buildStump(dataArr,classLabels,D)
# print('D:',D.T)
# 该值会告诉分类器本次单层决策树输出结果的权重,max(error,1e-16)用于确保在没有错误时不会发生除0溢出
alpha = float(0.5* np.log((1-error)/max(error,1e-16)))
bestStump['alpha'] = alpha
weakClassArr.append(bestStump)
# print('classEst:',classEst.T)
# 为下一次迭代计算D
expon = np.multiply(-1*alpha*np.mat(classLabels).T,np.mat(classEst))
D = np.multiply(D,np.exp(expon))
D = D/D.sum()
# 错误率累加计算
aggClassEst += alpha*classEst
# print('aggClassEst:',aggClassEst.T)
# 为了得到二值分类结果还需要调用sign()函数
aggErrors = np.multiply(np.sign(aggClassEst)!=np.mat(classLabels).T,np.mat(np.ones((m,1))))
errorRate = aggErrors.sum()/m
print('total errorRate:',errorRate)
if errorRate==0.0:
break
return weakClassArr
classifierArray = adaBoostTrainDS(dataMat,classLabels,9)
total errorRate: 0.2
total errorRate: 0.2
total errorRate: 0.0
classifierArray
[{'dim': 0, 'thresh': 1.3, 'ineq': 'lt', 'alpha': 0.6931471805599453},
{'dim': 1, 'thresh': 1.0, 'ineq': 'lt', 'alpha': 0.9729550745276565},
{'dim': 0, 'thresh': 0.9, 'ineq': 'lt', 'alpha': 0.8958797346140273}]
5.测试算法:基于AdaBoost 的分类
将弱分类器的训练过程从程序中抽出来,然后应用到某个具体的实例上去。每个弱分类器的结果以其对应的alpha值作为权重。所有这些弱分类器的结果加权求和就得到了最后的结果。
# 该函数的输入是由一个或者多个待分类样例datToClass以及多个弱分类器组成的数组classifierArr
def adaClassify(datToClass,classifierArr):
dataMat = np.mat(datToClass)
m = np.shape(dataMat)[0]
# 记录数据点的类别估计累计值
aggClassEst = np.zeros((m,1))
# 遍历classifierArr中的所有弱分类器
for i in range(len(classifierArr)):
# 基于stumpClassify()对每个分类器得到一个类别的估计值
classEst = stumpClassify(dataMat,classifierArr[i]['dim'],classifierArr[i]['thresh'],classifierArr[i]['ineq'])
aggClassEst += classifierArr[i]['alpha']*classEst
# print(aggClassEst)
return np.sign(aggClassEst)
adaClassify([0,0],classifierArray)
array([[-1.]])
adaClassify([[5,5],[0,0]],classifierArray)
array([[ 1.],
[-1.]])
6.示例:在一个难数据集上应用AdaBoost
在一个难数据集上的AdaBoost应用
(1) 收集数据:提供的文本文件。
(2) 准备数据:确保类别标签是+1和-1而非1和0。
(3) 分析数据:手工检查数据。
(4) 训练算法:在数据上,利用adaBoostTrainDS()函数训练出一系列的分类器。
(5) 测试算法:我们拥有两个数据集。在不采用随机抽样的方法下,我们就会对AdaBoost和Logistic回归的结果进行完全对等的比较。
(6) 使用算法:观察该例子上的错误率。不过,也可以构建一个Web网站,让驯马师输入马的症状然后预测马是否会死去。
# 自适应数据加载函数
def loadDataSet(fileName):
numFeat = len(open(fileName).readline().split('\t'))
dataMat = []
labelMat = []
fr = open(fileName)
for line in fr.readlines():
lineArr = []
curLine = line.strip().split('\t')
for i in range(numFeat-1):
lineArr.append(float(curLine[i]))
dataMat.append(lineArr)
labelMat.append(float(curLine[-1]))
return dataMat,labelMat
dataArr,labelArr = loadDataSet('dataSet/horseColicTraining2.txt')
classifierArray = adaBoostTrainDS(np.array(dataArr),labelArr,10)
total errorRate: 0.2842809364548495
total errorRate: 0.2842809364548495
total errorRate: 0.24749163879598662
total errorRate: 0.24749163879598662
total errorRate: 0.25418060200668896
total errorRate: 0.2408026755852843
total errorRate: 0.2408026755852843
total errorRate: 0.22073578595317725
total errorRate: 0.24749163879598662
total errorRate: 0.23076923076923078
testArr,testLabelArr = loadDataSet('dataSet/horseColicTest2.txt')
prediction10 = adaClassify(testArr,classifierArray)
error = np.mat(np.ones((67,1)))
error[prediction10!=np.mat(testLabelArr).T].sum()/67
0.23880597014925373
不同弱分类器数目情况下的AdaBoost测试和分类错误率。该数据集是个难数据集。通常情况下,AdaBoost会达到一个稳定的测试错误率,而并不会随分类器数目的增多而提高.
测试错误率在达到了一个最小值之后又开始上升了。这类现象称之为过拟合(overfitting,也称过学习)
非均衡分类问题
(1)错误率指的是在所有测试样例中错分的样例比例。实际上,这样的度量错误掩盖了样例如何被分错的事实。在机器学习中,有一个普遍适用的称为混淆矩阵(confusion matrix)的工具,它可以帮助人们更好地了解分类中的错误。
在这个二类问题中,如果将一个正例判为正例,那么就可以认为产生了一个真正例(True Positive,TP,也称真阳);如果对一个反例正确地判为反例,则认为
产生了一个真反例(True Negative,TN,也称真阴)。相应地,另外两种情况则分别称为伪反例(False Negative,FN,也称假阴)和伪正例(False
Positive,FP,也称假阳)。
第一个指标是正确率(Precision),它等于TP/(TP+FP),给出的是预测为正例的样本中的真正正例的比例。第二个指标是召回率(Recall),它等于
TP/(TP+FN),给出的是预测为正例的真实正例占所有真实正例的比例。在召回率很大的分类器中,真正判错的正例的数目并不多。
(2)另一个用于度量分类中的非均衡性的工具是ROC曲线(ROC curve),ROC代表接收者操作特征(receiver operating characteristic)。
ROC曲线给出的是当阈值变化时假阳率和真阳率的变化情况。左下角的点所对应的是将所有样例判为反例的情况,而右上角的点对应的则是将所有样例判为正例的情况。虚线给出的是随机猜测的结果曲线。
在理想的情况下,最佳的分类器应该尽可能地处于左上角,这就意味着分类器在假阳率很低的同时获得了很高的真阳率。
对不同的ROC曲线进行比较的一个指标是曲线下的面积(Area Unser the Curve,AUC)。AUC给出的是分类器的平均性能值,当然它并不能完全代替对整条曲线的观察。一个完美分类器的AUC为1.0,而随机猜测的AUC则为0.5。
为了画出ROC曲线,分类器必须提供每个样例被判为阳性或者阴性的可信程度值。
然后,将其移到排名次低的样例中去,如果该样例属于正例,那么对真阳率进行修改;如果该样例属于反例,那么对假阴率进行修改。