当作重要决定的时候我们往往参考的不只是一个专家的意见。机器学习问题也是这样,这就是元算法背后的思路。元算法是对其他算法的一种组合方式。我们集中关注于一个称作adaboost的最流行算法的元算法。该方法是机器学习工具箱中最强有力的工具之一。
我们先讨论不同分类器的集成方法,然后主要关注boosting方法机器代表分类器adaboost。在接下来我们会建立一个单层决策树分类器。adaboost将会运行在上述的单层决策树上。
非均衡分类问题:我们试图对样例比例数据不均衡的数据进行分类测试时就会遇到这个问题。稍后会介绍到利用修改后的指标来评价分类器的性能。
1.基于数据集多重抽样的分类器
接下来,先看一种基于同一种分类器对多个不同实例的两种计算方法。在这些方法中,数据集也会在不断地变化,而后应用于不同的实例分类器上。最后我们会讨论如何利用机器学习问题的通用框架来应用adaboost算法。
1.1bagging:基于数据随机重抽样的分类器构建方法
自举汇聚法(bootstrap aggregating),也成bagging方法,是在原始数据集选择S次后得到S个新数据集的一种技术。新数据集和原始数据集的大小相等。每个数据集都是通过在原始数据集中随机选择一个样本来进行替换而得到。这里的替换就意味着可以多次选择同一样本。这一性质就允许新数据集中可以有重复的值,而原始数据集中的某些值在新集合中则不再出现。
在S个数据集建好后,将某个学习算法分别作用与每个数据集就得到了S个分类器。当我们要对新的数据进行分类时,可以使用这S个分类器来分类,最后选择分类器投票结果最多的选项。
1.2 boosting
boosting分类的结果是基于所有的分类结果的加权求和结果,bagging中的分类器权重是相等的,而boosting中的分类器权重并不相等,每个权重代表的是对应分类器在上一轮迭代中的成功度。
boosting方法有多个版本,本章只关注其中最流行的adaboost。
2.训练算法:基于错误提升分类器性能
能否用弱分类器和多个实例来构建一个强分类器?‘弱’意味着分类器的性能比随机猜测要好,但是不会好太多。就是说在二分类的情况下,弱分类的错误率会高于50%,而强分类器的错误率会下降很多。adaboost算法即脱胎于上述理论问题。
adaboost即adaptive boosting(自适应boosting)的缩写。运行过程如下:训练数据中的每个样本,并赋予其一个权重,这些权重构成了向量D。一开始,这些权重都初始化为相等值。首先在训练数据上训练一个弱分类器并计算该分类器的错误率,然后在同一数据集上再次训练弱分类器。在分类器的第二次训练当中,将会调整整个样本的权重,其中第一次分对的样本的权重会降低,而第一次分错的样本的权重会提高。为了从所有的弱分类器中得到最终的分类结果,adaboost为每个分类器都分配了一个权重值alpha,这些alpha值是基于每个弱分类器的错误率进行计算的。错误率定义:
alpha计算公式:
adaboost算法的流程如下所示:
计算出alpha值之后,可以对权重向量D进行更新。使那些正确分类的样本的权重降低而错分样本的权重升高。D的计算方法:
计算完D之后,adaboost又开始进入下一轮的迭代。adaboost算法会不断重复训练和调整权重的过程,直到训练错误率为0或则弱分类器的数目达到用户的指定值为止。
接下来我们将建立完整的adaboost算法。
3. 基于单层决策树构建弱分类器
单层决策树(决策树桩):是一种简单决策树。前面我们介绍了决策树的简单原理,我们现在要用它来构建一个单层决策树。仅基于一个特征来做决策,由于这棵树只有一个分裂过程,因此它实际上就是一个树桩。
在构造adaboost的代码时,我们先通过一个简单的数据集来确保在算法上实现一切就绪。
def LoadSimpData():
datMat = matrix([[1., 2.1]
[2., 1.1]
[1.3, 1.]
[1., 1.]
[2., 1.]])
classLabels = [1.0, 1.0, -1.0, -1.0, 1.0]
加入上述数据集:
想要试着从某个坐标轴上选择一个值来分类所有的点不是不可能的,这就是单层决策树难以处理的一个著名问题,但是通过多棵决策树我们就可以构建出一个能够对数据集完全分类正确的分类器。
第一个函数将用于测试是否有某个值小于或大于我们正在测试的阈值。第二个函数要复杂一些,它会在一个加权数据集中循环,并找到具有最低错误率的单层决策树。
伪代码如下:
单层决策树生成函数:
def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):#just classify the data
'''
通过阈值比较分类,在阈值一遍的分类为1,另一边分类为-1
通过数组过滤来实现
'''
retArray = ones((shape(dataMatrix)[0],1))
if threshIneq == 'lt':
# print(dataMatrix[:,dimen] <= threshVal)
# print('############')
retArray[dataMatrix[:,dimen] <= threshVal] = -1.0
else:
retArray[dataMatrix[:,dimen] > threshVal] = -1.0
return retArray
def buildStump(dataArr,classLabels,D):
# 权重向量D
dataMatrix = mat(dataArr); labelMat = mat(classLabels).T
m,n = shape(dataMatrix)
# beststump空字典,用于存储给定权重向量D时所得到的最佳单层决策树
numSteps = 10.0; bestStump = {}; bestClasEst = mat(zeros((m,1)))
minError = inf # 无穷大
# 在数据集的所有特征上遍历
for i in range(n):
# 通过计算最小值和最大值来了解应该取多大的步长
rangeMin = dataMatrix[:,i].min(); rangeMax = dataMatrix[:,i].max();
stepSize = (rangeMax-rangeMin)/numSteps
for j in range(-1,int(numSteps)+1):#loop over all range in current dimension
for inequal in ['lt', 'gt']: #go over less than and greater than
threshVal = (rangeMin + float(j) * stepSize)
predictedVals = stumpClassify(dataMatrix,i,threshVal,inequal) # call stump classify with i, j, lessThan
# print('**********')
# print(predictedVals)
# print('***********')
errArr = mat(ones((m,1)))
errArr[predictedVals == labelMat] = 0
# adaboost 与分类器交互的地方,基于D来评价分类器
weightedError = D.T*errArr #calc total error multiplied by D
print ("split: dim %d, thresh %.2f, thresh ineqal: %s, the weighted error is %.3f" % (i, threshVal, inequal, weightedError))
if weightedError < minError:
minError = weightedError
bestClasEst = predictedVals.copy()
bestStump['dim'] = i
bestStump['thresh'] = threshVal
bestStump['ineq'] = inequal
return bestStump,minError,bestClasEst
程序输出所有的值:
上述的单层决策树的生成函数就是决策树的一个简化版本。他就是弱分类器,即弱分类器算法。做好了过度adaboost算法的准备。接下来我们就会使用多个弱分类器来构建adaboost代码。
4. 完整的adaboost算法实现

def adaBoostTrainDS(dataArr,classLabels,numIt=40):
weakClassArr = []
m = shape(dataArr)[0]
D = mat(ones((m,1))/m) #init D to all equal
aggClassEst = mat(zeros((m,1))) # 记录每个数据点的类别估计累计值
for i in range(numIt):
# 建立单层决策树
bestStump,error,classEst = buildStump(dataArr,classLabels,D)
print ("D:",D.T)
alpha = float(0.5*log((1.0-error)/max(error,1e-16)))#calc alpha, throw in max(error,eps) to account for error=0
# alpha值加入到bestStump字典中,字典再添加到列表中
bestStump['alpha'] = alpha
weakClassArr.append(bestStump) #store Stump Params in Array
print ("classEst: ",classEst.T)
expon = multiply(-1*alpha*mat(classLabels).T,classEst) # exponent for D calc, getting messy
D = multiply(D,exp(expon)) # Calc New D for next iteration
D = D/D.sum()
#calc training error of all classifiers, if this is 0 quit for loop early (use break)
aggClassEst += alpha*classEst
print ("aggClassEst: ",aggClassEst.T)
aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T,ones((m,1)))
errorRate = aggErrors.sum()/m
print ("total error: ",errorRate)
if errorRate == 0.0: break
return weakClassArr,aggClassEst
假定迭代次数是9次,如果算法在第三次迭代之后为0 ,那么就会退出迭代过程。向量D包含了每个数据点的权重,一开始的所有元素都会被初始化为1/m。同时程序还会建立另外一个列向量aggClassEst,记录每个数据点的类别估计累计值。
程序的核心在于for循环,该循环运行numIt次或者知道训练错误率为0为止。循环的第一件事就是建立一个单侧决策树。该函数的输入为权重向量D,返回的是利用D得到的最小错误率的单层决策树,同时返回的还有最小的错误率以及估计的类别向量。
接下来,计算alpha值。该值会告诉分类器本次单侧决策树输出结果的权重。
运行结果:
数据标签为[[1.0, 1.0, -1.0, -1.0, 1.0]],在第一轮迭代中D中所有的值都相等。于是只有第一个数据点被错分了。D向量给第一个数据点0.5的权重、第二次的分类中,第一个数据点被分类正确了,但是最后一个数据点确实错误的,D中的最后一个向量变成0.5.其他的值变得非常小,第三次后所有值都和真是标签相同了,错误率为0,程序就此退出。
5. 测试算法:基于adaboost的分类
一旦拥有多个弱分类器和对应的alpha值,进行测试就变得容易了。在下面程序中,我们实际已经写完了大部分代码。现在需要做的就是讲弱分类器的训练过程从程序中抽出来,然后应用到某个具体的例子中去。每个弱分类器的结果以其对应的alpha值作为权重。这些弱分类器的结果加权求和就得到了最后的结果。
adaboost分类函数:
def adaClassify(datToClass,classifierArr):
'''
输入:一个或多个待分类样例datToClass
多个弱分类器组成的数组classifierArr
'''
dataMatrix = mat(datToClass)# 转换为numpy矩阵,在得到样例个数
m = shape(dataMatrix)[0]
aggClassEst = mat(zeros((m,1)))
# 遍历所有的弱分类器
for i in range(len(classifierArr)+1):
# 对每个分类器得到一个类别的估计值
classEst = stumpClassify(dataMatrix,classifierArr[0][i]['dim'],
classifierArr[0][i]['thresh'],
classifierArr[0][i]['ineq'])#call stump classify
aggClassEst += classifierArr[0][i]['alpha']*classEst
print (aggClassEst)
return sign(aggClassEst)
运行效果:
可以看到随着迭代的进行,数据点[0,0],[5,5]的分类结果越来越强。在后面,我们将会使用该分类器应用到一个规模更大,难度更大的真实数据集中去。