---------------------------------------------------------------------------------------
本系列文章為《機器學習實戰》學習筆記,內容整理自書本,網絡以及自己的理解,如有錯誤歡迎指正。
源碼在Python3.5上測試均通過,代碼及數據 --> https://github.com/Wellat/MLaction
---------------------------------------------------------------------------------------
1、基於數據集多重抽樣的分類器
1.1 bagging
自舉匯聚法(bootstrap aggregating),也稱為bagging方法,是在從原始數據集選擇S次后得到S個新數據集的一種技術。新數據集和原數據集的大小相等,每個數據集都是在原始數據集中有放回隨機選擇樣本得到,這意味着新數據集中可以有重復的樣本,也可能沒有包括原數據集的所有樣本。
在S個數據集建好之后,將某個學習算法分別作用於每個數據集就得到了S個分類器。當我們要對新數據進行分類時,就可以應用這S個分類器進行分類。與此同時,選擇分類器投票結果中最多的類別作為最后的分類結果。
1.2 boosting
boosting和bagging很類似,他們使用相同類型的分類器,但是在boosting中,不同的分類器是通過串行訓練而獲得的。Boosting集中關注被已有分類器錯分的那些數據來獲得新的分類器。
由於boosting分類的結果是基於所有分類器的加權求和的結果,所以在boosting中分類器的權重並不相等,每個權重代表的是其對於分類器在上一輪迭代中的成功度。
Boosting方法有多個版本,本節只關注其中一個最流行的版本AdaBoost。
1.3 AdaBoost
AdaBoost是adaptive boosting(自適應boosting)的縮寫,它的理論根植於使用弱分離器和多個實例來構建一個強分類器。這里的“弱”意味着分類器的性能比隨機猜測要略好,但是也不會好太多;而“強”分類器的錯誤率將會低很多。
其運行過程如下:訓練數據中的每個樣本,並賦予其一個權重,這些權重構成了向量D。一開始,這些權重都初始化成相等值。首先在訓練數據上訓練出一個弱分類器並計算該分類器的錯誤率,然后在同一數據集上再次訓練弱分類器。在分類器的第二次訓練當中,將會重新調整每個樣本的權重,其中第一次分對的樣本的權重將會降低,而第一次分錯的樣本的權重將會提高。為了從所有弱分類器中得到最終的分類結果,AdaBoost為每個分類器都分配了一個權重值alpha,這些alpha值是基於每個弱分類器的錯誤率進行計算的。其中,錯誤率ε的定義為:

而alpha的計算公式為:

AdaBoost算法流程如下圖:

計算出alpha值之后,可以對權重向量D進行更新,以使得那些正確分類的樣本的權重降低而錯分樣本的權重升高。
如果某個樣本被正確分類,權重更改為:
而如果被錯分,權重則更改為:
在計算出D之后,AdaBoost又開始進入下一輪迭代,知道訓練錯誤率為0或者弱分類器的數目達到用戶指定值為止。
2、AdaBoost算法的實現
2.1 構建弱分類器
單層決策樹是AdaBoost中最流行的弱分類器。
算法偽代碼↓

1 def buildStump(dataArr,classLabels,D): 2 ''' 3 建立一個單層決策樹 4 輸人為權重向量D, 5 返回具有最小錯誤率的單層決策樹、最小的錯誤率以及估計的類別向量 6 ''' 7 dataMatrix = mat(dataArr); labelMat = mat(classLabels).T 8 m,n = shape(dataMatrix) 9 numSteps = 10.0; bestStump = {}; bestClasEst = mat(zeros((m,1))) 10 minError = inf # 11 for i in range(n):#對數據集中的每一個特征 12 rangeMin = dataMatrix[:,i].min(); rangeMax = dataMatrix[:,i].max(); 13 stepSize = (rangeMax-rangeMin)/numSteps 14 for j in range(-1,int(numSteps)+1):#對每個步長 15 for inequal in ['lt', 'gt']: #對每個不等號 16 threshVal = (rangeMin + float(j) * stepSize) 17 predictedVals = stumpClassify(dataMatrix,i,threshVal,inequal) 18 errArr = mat(ones((m,1))) 19 errArr[predictedVals == labelMat] = 0 20 weightedError = D.T*errArr #計算加權錯誤率 21 #print("split: dim %d, thresh %.2f, thresh ineqal: %s, the weighted error is %.3f" % (i, threshVal, inequal, weightedError)) 22 #如果錯誤率低於minError,則將當前單層決策樹設為最佳單層決策樹 23 if weightedError < minError: 24 minError = weightedError 25 bestClasEst = predictedVals.copy() 26 bestStump['dim'] = i 27 bestStump['thresh'] = threshVal 28 bestStump['ineq'] = inequal 29 return bestStump,minError,bestClasEst 30 31 def stumpClassify(dataMatrix,dimen,threshVal,threshIneq): 32 ''' 33 通過閾值比較對數據進行分類 34 ''' 35 retArray = ones((shape(dataMatrix)[0],1)) 36 if threshIneq == 'lt': 37 retArray[dataMatrix[:,dimen] <= threshVal] = -1.0 38 else: 39 retArray[dataMatrix[:,dimen] > threshVal] = -1.0 40 return retArray
2.2 基於單層決策樹的AdaBoost訓練過程
算法偽代碼↓

1 def loadSimpData(): 2 ''' 3 導入簡單訓練數據 4 ''' 5 datMat = matrix([[ 1. , 2.1], 6 [ 2. , 1.1], 7 [ 1.3, 1. ], 8 [ 1. , 1. ], 9 [ 2. , 1. ]]) 10 classLabels = [1.0, 1.0, -1.0, -1.0, 1.0] 11 return datMat,classLabels 12 13 def adaBoostTrainDS(dataArr,classLabels,numIt=40): 14 ''' 15 基於單層決策樹的AdaBoost訓練過程 16 ''' 17 weakClassArr = [] 18 m = shape(dataArr)[0] 19 D = mat(ones((m,1))/m) #初始化權重向量為1/m 20 aggClassEst = mat(zeros((m,1)))#記錄每個數據點的類別估計累計值 21 for i in range(numIt): 22 #建立一個單層決策樹 23 bestStump,error,classEst = buildStump(dataArr,classLabels,D) 24 print("D:",D.T) 25 #計算alpha,此處分母用max(error,1e-16)以防止error=0 26 alpha = float(0.5*log((1.0-error)/max(error,1e-16))) 27 bestStump['alpha'] = alpha 28 weakClassArr.append(bestStump) 29 print("classEst: ",classEst.T) 30 #計算下一次迭代的D 31 expon = multiply(-1*alpha*mat(classLabels).T,classEst) 32 D = multiply(D,exp(expon)) 33 D = D/D.sum() 34 #以下計算訓練錯誤率,如果總錯誤率為0,則終止循環 35 aggClassEst += alpha*classEst 36 print("aggClassEst: ",aggClassEst.T) 37 aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T,ones((m,1))) 38 errorRate = aggErrors.sum()/m 39 print("total error: ",errorRate) 40 if errorRate == 0.0: break 41 return weakClassArr,aggClassEst
2.3 簡單測試分類效果
1 def adaClassify(datToClass,classifierArr): 2 ''' 3 利用訓練出的多個弱分類器進行分類 4 datToClass:待分類數據 5 classifierArr:訓練的結果 6 ''' 7 dataMatrix = mat(datToClass) 8 m = shape(dataMatrix)[0] 9 aggClassEst = mat(zeros((m,1))) 10 #遍歷classifierArr中的所有弱分類器,並基於stumpClassify對每個分類器得到一個類別的估計值 11 for i in range(len(classifierArr)): 12 classEst = stumpClassify(dataMatrix,classifierArr[i]['dim'],\ 13 classifierArr[i]['thresh'],\ 14 classifierArr[i]['ineq']) 15 aggClassEst += classifierArr[i]['alpha']*classEst 16 print(aggClassEst) 17 return sign(aggClassEst)
按如下指令測試:

3、實例:在馬疝病數據集上應用AdaBoost分類器
前面一個章節中曾利用Logistic回歸來預測患有疝病的馬是否能夠存活,而在本節我們將利用多個單層決策樹和AdaBoost來預測。
1 def loadDataSet(fileName): 2 '''讀取數據函數''' 3 numFeat = len(open(fileName).readline().split('\t')) #獲取列數,默認最后一列為類標簽且類標簽為+1和-1 4 dataMat = []; labelMat = [] 5 fr = open(fileName) 6 for line in fr.readlines(): 7 lineArr =[] 8 curLine = line.strip().split('\t') 9 for i in range(numFeat-1): 10 lineArr.append(float(curLine[i])) 11 dataMat.append(lineArr) 12 labelMat.append(float(curLine[-1])) 13 return dataMat,labelMat 14 15 if __name__ == "__main__": 16 17 '''馬疝病測試''' 18 #導入訓練數據 19 datArr,labelArr = loadDataSet('horseColicTraining2.txt') 20 weakClassArr,aggClassEst = adaBoostTrainDS(datArr,labelArr,10) 21 #導入測試數據 22 testArr,testLabelArr = loadDataSet('horseColicTest2.txt') 23 prediction = adaClassify(testArr,weakClassArr) 24 #計算錯誤率 25 errArr = mat(ones((67,1))) 26 errArr[prediction != mat(testLabelArr).T].sum()/67
將弱分類器的數目設定為1到10000之間的幾個不同數字,並運行上述過程。得到如下結果

在同一數據集上采用Logistic回歸得到的平均錯誤率為0.35,而使用AdaBoost方法,從表中可以看出,僅僅使用50個弱分類器就達到了較高的性能。
THE END.
