在做一些决定的时候,我们往往需要吸取多个专家的意见,这就是元算法背后的思路,这里的专家就相当于前几个博文的分类器一样,结合多个分类器的结果得出的结果往往比单一一个分类器的结果要精准一些。
adaboost是boosting方法多个版本中最流行的一个版本,它是通过构建多个弱分类器,通过各个分类器的结果加权之后得到分类结果的。这里构建多个分类器的过程也是有讲究的,通过关注之前构建的分类器错分的那些数据而获得新的分类器。这样的多个分类器在训练时很容易得到收敛。
本文主要介绍了通过单层决策树构建弱分类器,同理,也可以用其他的分类算法构建弱分类器。
adaboost全称是adaptive boosting(自适应boosting),为什么是自适应呢,首先,先介绍一下它的原理:首先,对训练数据中每一个样本附上一个权重,这些权重构成向量D,一开始给这些权重初始化为相同的值。第一次训练时,权重相同,和原先的训练方法一样,训练结束后,根据训练的错误率,重新分配权重,第一次分对的样本的权重会降低,分错的样本权重会增大,这样再对第二个分类器进行训练,每一个分类器都对应一个alpha权重值,这里的alpha是对于分类器而言,前面的D是对于样本而言。最后训练出一系列的弱分类器,对每一个分类器的结果乘以权重值alpha再求和,就是最终的分类结果。自适应就体现在这里,通过对D的一次次的优化,最后的结果往往可以快速收敛。
这里错误率的定义如下:
alpha定义如下:
权重D的更新函数如下:
这里分为两种情况
1.该样本被正确分类:
2.该样本没有被正确分类:
这里的i代表的是第i个样本,t代表的是第t次训练。
下面介绍基于单层决策树构建弱分类器的代码实现:
首先加载训练数据
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]
return datMat,classLabels
接下来构建最优的单层决策树,也就是错误率最低的单层决策树,通过遍历每一维特征的所有可能值计算错误率得到:
def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):#分类函数
retArray=ones((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):#建立最优的单层决策树
dataMatrix=mat(dataArr)
labelMat=mat(classLabels).T
m,n=shape(dataMatrix)
numSteps=10.0
bestStump={}
bestClasEst=mat(zeros((m,1)))
minError=inf
for i in range(n):#遍历每列
rangeMin=dataArr[:,i].min()
rangeMax=dataArr[:,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(dataMatrix,i,threshVal,inequal)
errArr=mat(ones((m,1)))
errArr[predictedVals==labelMat]=0
weightedError=D.T*errArr
#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,我们需要根据决策结果构建一系列的弱分类器,函数如下:
def adaBoostTrainDS(dataArr,classLabels,numIt=40):
weakClassArr = []
m = shape(dataArr)[0]
D = mat(ones((m,1))/m) #初始化矩阵D,为1/m
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)))#计算alpha,max项是为了防止error为0
bestStump['alpha'] = alpha
weakClassArr.append(bestStump) #将最佳单层决策树放入到决策树数组
print "classEst: ",classEst.T
expon = multiply(-1*alpha*mat(classLabels).T,classEst) #对每一个样本计算
D = multiply(D,exp(expon))
D = D/D.sum()#计算D
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
这样,就构建好了多个弱分类器。
分类函数如下:
def adaClassify(datToClass,classifierArr):#adaboost分类函数
dataMatrix = mat(datToClass)
m = shape(dataMatrix)[0]
aggClassEst = mat(zeros((m,1)))
for i in range(len(classifierArr)):#通过单个分类器的结果加权求和得到最终结果
classEst = stumpClassify(dataMatrix,classifierArr[i]['dim'],classifierArr[i]['thresh'],classifierArr[i]['ineq'])
aggClassEst += classifierArr[i]['alpha']*classEst
print aggClassEst
return sign(aggClassEst)
下面,我随机生成了500组数据对分类进行测试,并通过pylab库对结果进行可视化
def test():#测试函数,随机生成100组测试样本,进行分类测试
datMat,classLabels=loadSimpData()
xMax=datMat[:,0].max()+1
xMin=datMat[:,0].min()-1
yMax=datMat[:,1].max()+1
yMin=datMat[:,1].min()-1
pl.xlim((xMin,xMax))
pl.ylim((yMin,yMax))
weakClassArr,aggClassEst=adaBoostTrainDS(datMat,classLabels,9)
m=shape(datMat)[0]
for i in range(m):
if classLabels[i]==1.0:
pl.plot(array(datMat[i])[0][0],array(datMat[i])[0][1],'ro')
else:
pl.plot(array(datMat[i])[0][0],array(datMat[i])[0][1],'r*')
testdata=[]
#testdata.append([5,5])
for i in range(100):
testdata.append(list(2*random.random(2)))
classResult=adaClassify(testdata,weakClassArr)
m=shape(testdata)[0]
for i in range(m):
if classResult[i]==1.0:
pl.plot(testdata[i][0],testdata[i][1],'bo')
else:
pl.plot(testdata[i][0],testdata[i][1],'b*')
print classResult
pl.show()
红色是训练数据,只有5个点,可以通过测试结果大致看出分类的间隔。
下面给出一个根据adaboost元算法的一个实际应用的例子,通过在给出的马疝病数据集,训练分类器,最后来预测患有马疝病的马是否能存活。这个例子在书中的logistic回归中进行预测过,那里的平均错误率为35%,那么我们看看这里用adaboost元算法的错误率是多少。
首先给出将txt文本转换为训练矩阵的函数:
def loadDataSet(fileName): #将txt文本转换为训练矩阵
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
训练函数如下:
def testHorse():
dataMat,labelMat=loadDataSet('horseColicTraining2.txt')
weakClassArr,aggClassEst,listErrRate=adaBoostTrainDS(dataMat,labelMat,100)
n=len(listErrRate)
x=range(n)
pl.plot(x,listErrRate)
pl.show()
print listErrRate
上图反映了采用不同弱分类器数目的训练错误率,可见,弱分类器越多,训练错误率总体是减少的,下面我们看看不同的弱分类器,测试错误率怎么样。
def testHorse2():
dataMat,labelMat=loadDataSet('horseColicTraining2.txt')
count=[1,10,50,100,300,500,700]
testArr,testLabel=loadDataSet('horseColicTest2.txt')
errRate=[]
for i in count:
weakClassArr,aggClassEst,listErrRate=adaBoostTrainDS(dataMat,labelMat,i)
predict=adaClassify(testArr,weakClassArr)
errArr=mat(ones((67,1)))
errnum=errArr[predict!=mat(testLabel).T].sum()
errRate.append(errnum/67.0)
pl.plot(count,errRate)
pl.show()
print errRate
如图所示,显示了不同的弱分类器个数对应的测试识别率,结合之前的训练识别率,可以看出来,并不是弱分类器个数越多,识别效果越好,而是在弱分类器个数为50个左右有一个极小值。之后的错误率上升也就是过拟合的情况了。所以在实际应用中,应根据不同的情况选择不同的弱分类器个数。不难发现,adaboost算法的测试错误率可以达到0.21左右,对于采用logistic回归对本例的预测而言,识别效果确实好上很多。