我花了將近一周的時間,才算搞懂了adaboost的原理。這根骨頭終究還是被我啃下來了。
Adaboost是boosting系的解決方案,類似的是bagging系,bagging系是另外一個話題,還沒有深入研究。Adaboost是boosting系非常流行的算法。但凡是介紹boosting的書籍無不介紹Adaboosting,也是因為其學習效果很好。
Adaboost首先要建立一個概念:
弱分類器,也成為基礎分類器,就是分類能力不是特別強,正確概率略高於50%的那種,比如只有一層的決策樹。boosting的原理就是整合弱分類器,使其聯合起來變成一種"強分類器"。
在Adaboost中,就是通過訓練出多個弱分類器,然后為他們賦權重;最后形成了一組弱分類器+權重的模型,
那么,關鍵來了怎么選擇弱分類器,怎么來分配權重?據說弱分類器可以是svm,可以是邏輯回歸;但是我看到資料和描述都是以決策樹為藍本的。
要想要搞懂adaboost,還要搞懂他的兩個層級的權重,第一個權重是上面我們講到的分類器的權重,成為alpha,是一個浮點型的值;

另外一個是樣本權重,稱之為D,是一個列向量,和每個樣本對應。首先講一下樣本權重,在一個分類器訓練出來之后,將會重新設置樣本權重,首個分類器他的樣本權重是一樣的,都是1/sample_count,然后每每次訓練完,都會調整這個這個樣本權重,為什么?我們繼續沿用決策樹說事。調整的策略就是增大預測錯誤的樣本的權重,為什么?樣本權重只有一個作用,就是計算錯誤權重,錯誤權重errorWeight=D.T * errorArr,errorArr是預測錯誤的列向量,預測正確的樣本對應值0,預測錯誤的為1,樣本權重D就是做件事情,那么對於決策樹模型而言,本輪某個特征判斷錯了,那么樣本其實就上了黑名單,用數學表示就是這個這個樣本的權重將會增加,

樣本權重增加導致了什么?其實不會導致什么,即使說明了某個樣本的錯誤比重要增加。所謂錯誤權重,都是判斷錯誤,如果歷史某個樣本已經判讀出錯過一次,那么這個樣本如果再錯,它的錯誤權重就要增加,這種權重的改變(D中元素wi的總和不變,保持為1),將會導致weighterror值更加有意義,判定最小weighterror也會更加准確。如果判斷對了,會相應的減小樣本的權重。

兩層的權重介紹完了,基本算法也就明了了。
下面即使就兩層權重來說明一下兩層算法。內層的算法是遍歷樣本中的每個特征,然后再從特征值的最小值開始嘗試進行分類,逐漸按照等量增加特征值不斷地嘗試分類一直達到最大值,走完了一輪特征,換下一個特征,在逐次增加特征值...計算下來每次嘗試的錯誤權重,記錄下來最小的錯誤權重的信息,信息包括:特征列索引,特征值以及邏輯比較(大於還是小於),當把所有的特征跑完一遍,到此,一個分類器就橫空出世了,設么是分類器?本質就是最小錯誤權重的信息,就是分類器。
1 # 分類,滿足指定取值范圍的分類為-1,不滿足的為1 2 def stumpClassifier(dataMat, dim, threshValue, inequal): 3 #print("stumpClassifier-dataMat: ") 4 #print(dataMat) 5 retArr = ones((shape(dataMat)[0], 1)) 6 if(inequal=="lt"): 7 retArr[dataMat[:,dim]<=threshValue] = -1 8 else: 9 retArr[dataMat[:,dim]>threshValue] = -1 10 return retArr 11 12 # 返回一個弱分類器,本質上就是一個一層決策樹,這棵樹包括: 13 # 1.錯誤權重最低的區分特征以及特征值信息 14 # 2.最小錯誤權重 15 # 3.最小錯誤權重對應的預測分類 16 def buildstump(dataArr, labelArr, D): 17 datamat = mat(dataArr) 18 labelmat = mat(labelArr).T 19 m,n = shape(datamat) 20 errorArr = mat(zeros((m,1))) 21 beststump = {} 22 bestClassEst = mat(zeros((m, 1))) 23 minError = inf 24 numstep = 10.0 # 這里設置了一個float類型 25 # 遍歷每一列,尋求最分度最大的特征(維度)自己特征值 26 for i in range(n): 27 minvalue = datamat[:,i].min() 28 maxvalue = datamat[:,i].max() 29 stepsize = (maxvalue - minvalue)/numstep 30 for j in range(-1, int(numstep) + 1): 31 for ineq in ["lt", "gt"]: 32 threshValue = minvalue + int(j) * stepsize 33 predictValue = stumpClassifier(datamat, i, threshValue, ineq) 34 errorArr = mat(ones((m, 1))) # 一個列向量 35 # 這里設置分類正確的矩陣值為0,這樣在參與計算錯誤權重的時候,正確分類就不參與計算,只有錯誤分類錯誤的樣本才會參與計算 36 errorArr[predictValue==labelmat] = 0 37 # 這里注意,雖然是行向量和列向量相乘結果是累加值,但是形式還是一個矩陣,結果獲取方需要通過float等函數進行處理 38 # D在初始化的時候是1/m,之后將會根據學習情況,提高錯誤樣本的權重,減少正確樣本點的權重 39 weightError = D.T * errorArr 40 #print("split: dim %d, thresh %.2f, thresh ineqal: %s, the weighted error is %.3f" % (i, threshValue, ineq, weightError)) 41 if(weightError < minError): 42 minError = weightError 43 bestClassEst = predictValue.copy() 44 beststump["dim"] = i 45 beststump["threshVal"] = threshValue 46 beststump["ineq"] = ineq 47 48 return beststump, minError, bestClassEst
作為外層算法,是一個循環調用內層算法的過程,每當獲得了一個分類器,都要為他計算權重,權重alpha的計算公式上面已經給出,總之和最小錯誤權重有關系,錯誤權重越小,分類器的權重越高,說明是優質分類器(相對的),反之亦然;然后就是累加權重alpha*預測值classEst,累加的目標就是sign和真實的分類器一致,注意是符號一致,真實分類器只有-1,1兩種值。如果一致了,退出循環;不一致,說明還要再引入分類器,此時再來計算D值(參見上文公式),然后基於D值再來調用內層算法。
這樣不斷獲得分類器,直到分類一致(或者循環次數達到指定次數)。外層算分目的是獲取到一組分類器,這組分類器是經過訓練,實現了全來一遍,就可以保證累加權重預測值之和的符號(sign)和真實的分類一致。
1 from numpy import multiply 2 from numpy import exp 3 from numpy import zeros 4 from numpy import log 5 6 # adaboost本質其實就是將多個分類器按照訓練的權重進行整合,每個分類器都提供了區分度最高的特征以及特征值區分范圍(包括值以及lt/gt) 7 # 在使用的時候因為是二元分類,所以每個分類器都是關注自己的識別出來的關鍵特征來進行分類,最后通過權重來進行整合。demo中的數據因為特征 8 # 比較少,所以三輪下來就OK了,如果是多特征的需要更多分類器來進行是別的。 9 def adaboostTransDS(dataArr, labelArr, itNum=41): 10 dataMat = mat(dataArr) 11 m = shape(dataMat)[0] 12 D = mat(ones((m, 1))/m) 13 weakClassArr=[] 14 aggClassEst=mat(zeros((m,1))) 15 for i in range(itNum): 16 # classESt代表的是本輪學習中,錯誤率最低的樣本預測分類情況 17 # 獲取一個分類器 18 bestStump, error, classEst = buildstump(dataMat, labelArr, D) 19 # 計算該分類器的alpha值,注意這里必須要通過float進行強壯,否則alpha將會是矩陣值形式,無法參與后續的計算。具體原因參見buildstump 20 # 注意alpha是分類器級別的權重,D則是分類器中樣本的權重;而且error越小,alpha越大,分類器的權重也越大 21 alpha = float(0.5 * log((1 - error)/ max(error, 1e-16))) 22 bestStump["alpha"] = alpha 23 # 添加該分類器的信息 24 weakClassArr.append(bestStump) 25 26 # 計算下一個分類器中樣本的D值(權重),D值是 27 labelMat = mat(labelArr).T 28 alphaLabel = -1 * alpha * labelMat 29 # labelMat和classEst要么-1,要么1,這里的乘法就是要解決一個問題:判斷正確的是-alpha,判斷錯誤的是alpha,這個邏輯是 30 # 根據D的公式來的,通過這個符號實現如果是判斷正確,則減輕權重,判斷錯誤則家中權重; 31 # 之所以要加重錯誤列的權重是為了讓下一個分類器注意了,如果下一個分類器還是在此特征上面預測錯誤,那么錯誤權重會增加,這個 32 # 特征就作為最小錯誤權重(weihterror)特征出現的可能性就降低了。 33 expon = multiply((-1)*alpha * labelMat, classEst) 34 D = multiply(D ,exp(expon)) 35 # D是如何保證D集合的值之和是1的呢?因為D/D.sum(),就像1/6,2/6,3/6之和為1的道理一樣的 36 D = D/D.sum() 37 print("alpha is: " + str(alpha)) 38 #print("D is: ") 39 #print(D) 40 print("classEst:") 41 print(classEst) 42 # 計算Error值,這里其實是累加每個分類器的預測值,直到他們的累加值的符號和目標分類目標一致,所以錯誤率並不是每個分類器的錯誤率 43 # 而是分類器的分類預估*alpha的累加列向量vs原始分類列向量的錯誤率。 44 # 之類classEst是由-1和1組成,所以是帶有符號的 45 aggClassEst += alpha * classEst 46 #print("aggClassEst") 47 #print(aggClassEst) 48 errorMat = sign(aggClassEst) != labelMat 49 #print("errorMat") 50 #print(errorMat) 51 aggErrors = multiply(errorMat, ones((m,1))) 52 errorRate = aggErrors.sum() / m 53 print("error rate: %f" % errorRate) 54 55 if(errorRate == 0.0): 56 break 57 58 return weakClassArr
有了這組分類器,即adaboost classifieies,那么就可以進行分類了。
首先是獲取訓練數據集,然后通過外層算法獲取到adaboost classifieies(組分類器),這個是訓練過程;獲得了組分類器之后,在獲取測試數據集,然后遍歷分類器,讓每個分類器都對這批測試數據進行分類,每個分類器都會使用自己最好的分類方式(權重錯誤最低)來進行分類,即根據指定的特征,利用指定特征值進行比較分類;得到的分類結果(-1,1集合)將會乘以他們的權重(alpha),成為權重分類向量(weightClassEst);然后將各個分類器的權重分類向量進行累加(aggClassEst),最后取aggClassEst的符號作為分類結果。到此,分類結束。
1 def adaClassifier(dataToClass, classifierArr): 2 dataTestMat = mat(dataToClass) 3 print("handler dataset is: ") 4 print(dataTestMat) 5 classifer_count = shape(classifierArr)[0] 6 data_count = shape(dataTestMat)[0] 7 aggClassEst = mat(zeros((data_count, 1))) 8 for i in range(classifer_count): 9 classifier = classifierArr[i] 10 print(classifier) 11 classEst = stumpClassifier(dataTestMat, classifier["dim"], classifier["threshVal"], classifier["ineq"]) 12 aggClassEst += classifier["alpha"]*classEst 13 print("aggClassEst") 14 print(aggClassEst) 15 return sign(aggClassEst) 16 17 # 開始分類 18 dataMat, labelArr = loadSimpData() 19 classifierArr = adaboostTransDS(dataMat, labelArr, 9) 20 adaClassifier([[0,0],[5,5]], classifierArr)
