AdaBoost算法源码分析
基本的理論知識可以參考李航的統計學習和西瓜書,在這里簡單介紹:
- bagging:基于數據隨機抽樣的分類器,更先進的bagging方法有隨機森林等。
boosting:是一種與bagging類似的技術,但boosting是不同的分類器通過串行訓練獲得的,每個新分類器都是根據已訓練出的分類器的性能進行訓練。boosting是通過集中關注被已有分類器錯分的那些數據來獲得新的分類器。
AdaBoost算法的流程如圖:
其運行過程下:訓練數據中的每個樣本,并賦予其一個權重,這些權重構成了向量乃。一開始,這些權重都初始化成相等值。首先在訓練數據上訓練出一個弱分類器并計算該分類器的錯誤率,然后在同一數據集上再次訓練弱分類器。在分類器的第二次訓練當中,將會重新調整每個樣本的權重,其中第一次分對的樣本的權重將會降低,而第一次分錯的樣本的權重將會提高。為了從所有弱分類器中得到最終的分類結果,AdaBoost為每個分類器都分配了一個權重值alpha,這些alpha值是基于每個弱分類器的錯誤率進行計算的。
這里只是簡單說了下,下面是adaboost的代碼實現:
1. 基于單層決策樹的AdaBoost算法
# -*- coding: utf-8 -*- from numpy import *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): # dimen:那一列的特征,threshVal:閾值retArray = ones((shape(dataMatrix)[0],1))if threshIneq == 'lt': retArray[dataMatrix[:,dimen] <= threshVal] = -1.0 # <=閾值的相應的下標的值會被賦予-1else:retArray[dataMatrix[:,dimen] > threshVal] = -1.0 # >閾值的相應的下標的值會被賦予-1return retArray # 返回的是該特征列的分類的列向量# 遍歷所有的可能輸入值,并找到數據集上最佳的單層決策樹 def buildStump(dataArr,classLabels,D): # D是權重向量,在更新過程中保持不變dataMatrix = mat(dataArr); labelMat = mat(classLabels).T # 確保轉化為矩陣格式m,n = shape(dataMatrix) # m個樣本,n個特征# bestStump字典用于存儲給定權重向量D時所得到的最佳單層決策樹,numSteps用于在特征的所有可能值上進行遍歷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): # 遍歷[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]for inequal in ['lt', 'gt']: # 在大于和小于之間切換threshVal = (rangeMin + float(j) * stepSize) # 閾值大小predictedVals = stumpClassify(dataMatrix,i,threshVal,inequal) # 調用stumpClassify來分類errArr = mat(ones((m,1)))errArr[predictedVals == labelMat] = 0 # 正確分類的被賦予0weightedError = 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 = weightedErrorbestClasEst = predictedVals.copy() # numpy的copy()是深拷貝bestStump['dim'] = ibestStump['thresh'] = threshValbestStump['ineq'] = inequal #print 'bestStump:',bestStumpprint 'finish'# 返回的:對應最小分類誤差的{特征,閾值,和正負切換},既是單層決策樹,最小誤差率,該特征列的估計分類的列向量return bestStump,minError,bestClasEst # 基于單層決策樹的AdaBoost訓練過程 def adaBoostTrainDS(dataArr,classLabels,numIt=40): # 輸入參數:數據集,類別,迭代次數(唯一需要用戶指定的)weakClassArr = [] m = shape(dataArr)[0] # m代表多少個樣本數D = mat(ones((m,1))/m) # 初始化所有樣本的權重D為1/m,為了滿足D是一個概率分布向量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))) # 每個分類器對應的更新權重值,max(error,1e-16)確保沒有錯誤時不會發生下溢bestStump['alpha'] = alpha weakClassArr.append(bestStump) # 存儲新樹的參數print "classEst: ",classEst.T # 輸出最小誤差分類的相應特征的列向量# 對應的樣本權重更新 expon = multiply(-1*alpha*mat(classLabels).T,classEst) # classEst:分類器的分類結果D = multiply(D,exp(expon)) D = D/D.sum() # D.sum():規范化因子#計算所有分類器的訓練誤差,如果為0,則退出循環aggClassEst += alpha*classEst # 每個數據點的類別估計累計值print "aggClassEst: ",aggClassEst.TaggErrors = multiply(sign(aggClassEst) != mat(classLabels).T,ones((m,1))) # errorRate = aggErrors.sum()/mprint "total error: ",errorRate,'\n'if errorRate == 0.0: breakreturn weakClassArr,aggClassEst# 主函數 datMat,classLabels=loadSimpData() #D=mat(ones((5,1))/5) #print buildStump(datMat,classLabels,D) classifierArray=adaBoostTrainDS(datMat,classLabels,9)運行結果:
split: dim 0, thresh 0.90, thresh ineqal: lt, the weighted error is 0.400 split: dim 0, thresh 0.90, thresh ineqal: gt, the weighted error is 0.600 split: dim 0, thresh 1.00, thresh ineqal: lt, the weighted error is 0.400 . . . split: dim 1, thresh 2.10, thresh ineqal: lt, the weighted error is 0.600 split: dim 1, thresh 2.10, thresh ineqal: gt, the weighted error is 0.400 finish D: [[ 0.2 0.2 0.2 0.2 0.2]] classEst: [[-1. 1. -1. -1. 1.]] aggClassEst: [[-0.69314718 0.69314718 -0.69314718 -0.69314718 0.69314718]] total error: 0.2 . . . split: dim 1, thresh 2.10, thresh ineqal: gt, the weighted error is 0.143 finish D: [[ 0.28571429 0.07142857 0.07142857 0.07142857 0.5 ]] classEst: [[ 1. 1. 1. 1. 1.]] aggClassEst: [[ 1.17568763 2.56198199 -0.77022252 -0.77022252 0.61607184]] total error: 0.0接下來我們觀察一下中間的運行結果。數據的類別標簽為[1.0,1.0,?1.0,?1.0,1.0]。在第一輪迭代中,D中的所有值都相等。于是,只有第一個數據點被錯分了。因此在第二輪迭代中,D向量給第一個數據點0.5的權重。這就可以通過變量aggClassEst的符號來了解總的類別。第二次迭代之后,我們就會發現第一個數據點已經正確分類了,但此時最后一個數據點卻是錯分了。D向量中的最后一個元素變成0.5,而D向量中的其他值都變得非常小。最后,第三次迭代之后aggClassEst所有值的符號和真實類別標簽都完全吻合,那么訓練錯誤率為0,程序就此退出.
numpy的一些使用:
代碼中:retArray[dataMatrix[:,dimen] <= threshVal] = -1.0
例子:
>>> from numpy import * >>> datMat = matrix([[ 1. , 2.1], ... [ 2. , 1.1], ... [ 1.3, 1. ], ... [ 1. , 1. ], ... [ 2. , 1. ]]) >>> datMat matrix([[ 1. , 2.1],[ 2. , 1.1],[ 1.3, 1. ],[ 1. , 1. ],[ 2. , 1. ]]) >>> datMat[:,1]<=2 matrix([[False],[ True],[ True],[ True],[ True]], dtype=bool) >>> retArray =ones((shape(datMat)[0],1)) >>> retArray array([[ 1.],[ 1.],[ 1.],[ 1.],[ 1.]]) >>> retArray[datMat[:,1]<=2]=5 >>> retArray array([[ 1.],[ 5.],[ 5.],[ 5.],[ 5.]]) >>>代碼:
aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T,ones((m,1)))例子:
In [8]: a=mat([1,2,1])In [10]: a.T Out[10]: matrix([[1],[2],[1]])In [11]: sign(a.T) Out[11]: matrix([[1],[1],[1]])In [13]: b=mat([1,-1,-1]).TIn [14]: b Out[14]: matrix([[ 1],[-1],[-1]])In [15]: sign(a.T)!=b Out[15]: matrix([[False],[ True],[ True]], dtype=bool)In [16]: c=sign(a.T)!=bIn [17]: c Out[17]: matrix([[False],[ True],[ True]], dtype=bool)In [18]: multiply(c,ones((3,1))) Out[18]: matrix([[ 0.],[ 1.],[ 1.]])In [19]:其中更新值的幾個代碼是:
(1)
對應的是:αm=12log1?emem
其中:em=p(Gm(xi)≠yi)=∑Ni=1wmiI(Gm(xi)≠yi)
此更新的是分類器的權值
(2)
expon = multiply(-1*alpha*mat(classLabels).T,classEst) D = multiply(D,exp(expon)) D = D/D.sum()此處是更新訓練集的權值分布:
Dm+1=(wm+1,1,...wm+1,i,...wm+1,N)
wm+1,i=wmiZmexp(?αmyiGm(xi))
其中Zm是規范化因子
Zm=∑Ni=1wmiexp(?αmyiGm(xi))
2. AdaBoost的應用
測試基于AdaBoost的應用:
# 基于adaboost的分類 def adaClassify(datToClass,classifierArr): # 輸入參數:待分類樣例,多個弱分類器組成的數組dataMatrix = mat(datToClass) # 把樣例轉換成numpy矩陣,m = shape(dataMatrix)[0]aggClassEst = mat(zeros((m,1)))print 'classifierArr:', '\n',classifierArr# 遍歷所有的弱分類器,然后進行加權和for i in range(len(classifierArr)): classEst = stumpClassify(dataMatrix,classifierArr[i]['dim'],\classifierArr[i]['thresh'],\classifierArr[i]['ineq'])print 'classEst',classEstaggClassEst += classifierArr[i]['alpha']*classEst # 加權和print 'aggClassEst:',aggClassEstreturn sign(aggClassEst)# 主函數 datMat,classLabels=loadSimpData() #D=mat(ones((5,1))/5) #print buildStump(datMat,classLabels,D) classifierArray,aggClassEst=adaBoostTrainDS(datMat,classLabels,30) # 此處和機器學習實戰上的不同 classify=adaClassify([0,0],classifierArray) print 'adaClassify:',classify只需要在最上面的代碼中加入adaClassify函數,并且把主函數改成上面這種形式就可以運行了,注意此處在機器學習實戰中原書中有錯誤:
classifierArray=adaBoostTrainDS(datMat,classLabels,30)由于adaBoostTrainDS返回的是兩個數組,但這里僅有一個對象接受返回變量,所以在調用classifierArray時出現報錯:
...File "C:/Users/LiLong/Desktop/adaboost_learning/adaboost.py", line 85, in adaClassifyclassEst = stumpClassify(dataMatrix,classifierArr[i]['dim'],\TypeError: list indices must be integers, not str改成我代碼里的形式就可以了。
正常的運行結果:
... split: dim 1, thresh 2.10, thresh ineqal: lt, the weighted error is 0.857 split: dim 1, thresh 2.10, thresh ineqal: gt, the weighted error is 0.143 finish D: [[ 0.28571429 0.07142857 0.07142857 0.07142857 0.5 ]] classEst: [[ 1. 1. 1. 1. 1.]] aggClassEst: [[ 1.17568763 2.56198199 -0.77022252 -0.77022252 0.61607184]] total error: 0.0 classifierArr: [{'dim': 0, 'ineq': 'lt', 'thresh': 1.3, 'alpha': 0.6931471805599453}, {'dim': 1, 'ineq': 'lt', 'thresh': 1.0, 'alpha': 0.9729550745276565}, {'dim': 0, 'ineq': 'lt', 'thresh': 0.90000000000000002, 'alpha': 0.8958797346140273}] classEst [[-1.]] aggClassEst: [[-0.69314718]] classEst [[-1.]] aggClassEst: [[-1.66610226]] classEst [[-1.]] aggClassEst: [[-2.56198199]] adaClassify: [[-1.]]可以發現,隨著迭代的進行,數據點[0,0]的分類結果越來越強。
3. 在難數據上應用adaboost算法
# 自適應數據加載函數 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加載函數改為上面的形式,并且把主函數改為下面的即可:
# 難數據上應用adaboost算法 datArr,labelArr=loadDataSet('horseColicTraining2.txt') classifierArr,aggClassEst=adaBoostTrainDS(datArr,labelArr,10) testArr,testlabelArr=loadDataSet('horseColicTest2.txt') prediction10=adaClassify(testArr,classifierArr) errArr=mat(ones((67,1))) numerr=errArr[prediction10!=mat(testlabelArr).T].sum() print 'numerr:',numerr print 'error rate:',float(numerr)/67運行結果:
... classEst [[-1.][ 1.][-1.]..., [-1.][-1.][ 1.]] aggClassEst: [[ 0.95108899][ 1.20719077][ 0.18915694]..., [ 0.80958618][ 0.54030781][ 0.5273375 ]] numerr: 16.0 error rate: 0.238805970149本代碼將在馬疝病數據集上應用adaboost分類器。
將弱分類器的數目設定為1到10000之間的幾個不同數字,并運行上述過程。這時,得到的結果就會如表所示:
測試錯誤率一欄,就會發現測試錯誤率在達到了一個最小值之后又開始上升了。這類現象稱之為過擬合。有文獻聲稱,對于表現好的數據集,adaboost的測試錯誤率就會達到一個穩定值,并不會隨著分類器的增多而上升。
4. 分類性能度量:ROC曲線
在上述1的代碼基礎之上加入下面的plotROC()函數,并把主函數改為如下形式:
# ROC曲線的繪制和AUC計算函數 def plotROC(predStrengths, classLabels): # predStrengths代表分類器的預測強度,classLabels使用過的類別標簽import matplotlib.pyplot as plt # 導入畫圖函數cur = (1.0,1.0) # 浮點數二元組,保留繪制光標的位置ySum = 0.0 # ysum用于計算AUC的值numPosClas = sum(array(classLabels)==1.0) # 數組過濾的方式計算正例的數目yStep = 1/float(numPosClas); xStep = 1/float(len(classLabels)-numPosClas)sortedIndicies = predStrengths.argsort() # 元素升序排列,得到對應的元素的索引值排序fig = plt.figure() # 繪圖fig.clf() # clear:清除原有變量 clc:清楚命令窗口的內容ax = plt.subplot(111)# 循環遍歷所有值,索引值是按照元素值從小到大排列的for index in sortedIndicies.tolist()[0]:if classLabels[index] == 1.0:delX = 0; delY = yStep;else:delX = xStep; delY = 0;ySum += cur[1]#draw line from cur to (cur[0]-delX,cur[1]-delY)ax.plot([cur[0],cur[0]-delX],[cur[1],cur[1]-delY], c='b')cur = (cur[0]-delX,cur[1]-delY)ax.plot([0,1],[0,1],'b--')plt.xlabel('False positive rate'); plt.ylabel('True positive rate')plt.title('ROC curve for AdaBoost horse colic detection system')ax.axis([0,1,0,1])plt.show()print "the Area Under the Curve is: ",ySum*xStep# 主函數 datArr,labelArr=loadDataSet('horseColicTraining2.txt') classifierArr,aggClassEst=adaBoostTrainDS(datArr,labelArr,10) plotROC(aggClassEst.T, labelArr) # 傳入的參數:aggClassEst代表adaboost的集成累加 labelArr:真實類別值argsort()函數的使用
運行結果:
總結
以上是生活随笔為你收集整理的AdaBoost算法源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 八一中学国防班直升军校吗
- 下一篇: 玩具手枪哪里最容易坏