1. 程式人生 > >【機器學習實戰系列】讀書筆記之AdaBoost演算法公式推導和例子講解(一)

【機器學習實戰系列】讀書筆記之AdaBoost演算法公式推導和例子講解(一)

最近在看整合演算法AdaBoost,推薦先看李航的統計學習方法第8章,然後再看機器學習實戰第7章,李航的書上的公式推導講的很詳細了,但是很多地方對於初學者來說,還是需要時間去理解和消化的。本文將從以下幾個方面來介紹AdaBoost演算法。

一、AdaBoost演算法公式推導

二、AdaBoost相關例子講解

三、AdaBoost的toy algorithm的python實現

四、啟發和思考“為什麼”,“如何做”

一、AdaBoost演算法公式推導

AdaBoost,是英文"Adaptive Boosting"(自適應增強)的縮寫,由Yoav Freund和Robert Schapire在1995年提出。它的自適應在於:前一個基本分類器分錯的樣本會得到加強,加權後的全體樣本再次被用來訓練下一個基本分類器。同時,在每一輪中加入一個新的弱分類器,直到達到某個預定的足夠小的錯誤率或達到預先指定的最大迭代次數。

    具體說來,整個Adaboost 迭代演算法就3步:

  1. 初始化訓練資料的權值分佈。如果有N個樣本,則每一個訓練樣本最開始時都被賦予相同的權值:1/N。
  2. 訓練弱分類器。具體訓練過程中,如果某個樣本點已經被準確地分類,那麼在構造下一個訓練集中,它的權值就被降低;相反,如果某個樣本點沒有被準確地分類,那麼它的權值就得到提高。然後,權值更新過的樣本集被用於訓練下一個分類器,整個訓練過程如此迭代地進行下去。
  3. 將各個訓練得到的弱分類器組合成強分類器。各個弱分類器的訓練過程結束後,加大分類誤差率小的弱分類器的權重,使其在最終的分類函式中起著較大的決定作用,而降低分類誤差率大的弱分類器的權重,使其在最終的分類函式中起著較小的決定作用。換言之,誤差率低的弱分類器在最終分類器中佔的權重較大,否則較小。

演算法流程:

Adaboost的演算法流程如下:

  • 步驟1. 首先,初始化訓練資料的權值分佈。每一個訓練樣本最開始時都被賦予相同的權值:1/N。

  • 步驟2. 進行多輪迭代,用m = 1,2, ..., M表示迭代的第多少輪

a. 使用具有權值分佈Dm的訓練資料集學習,得到基本分類器(選取讓誤差率最低的閾值來設計基本分類器):

b. 計算Gm(x)在訓練資料集上的分類誤差率
由上述式子可知,Gm(x)在訓練資料集上的誤差率em就是被Gm(x)誤分類樣本的權值之和
c. 計算Gm(x)的係數,am表示Gm(x)在最終分類器中的重要程度(目的:得到基本分類器在最終分類器中所佔的權重):
由上述式子可知,em <= 1/2時,am >= 0,且am隨著em的減小而增大,意味著分類誤差率越小的基本分類器在最終分類器中的作用越大。
d. 更新訓練資料集的權值分佈(目的:得到樣本的新的權值分佈),用於下一輪迭代

使得被基本分類器Gm(x)誤分類樣本的權值增大,而被正確分類樣本的權值減小。就這樣,通過這樣的方式,AdaBoost方法能“重點關注”或“聚焦於”那些較難分的樣本上。

    其中,Zm是規範化因子,使得Dm+1成為一個概率分佈:

  • 步驟3. 組合各個弱分類器

從而得到最終分類器,如下:

二、AdaBoost相關例子講解

下面,給定下列訓練樣本,請用AdaBoost演算法學習一個強分類器。

    

    求解過程:初始化訓練資料的權值分佈,令每個權值W1i = 1/N = 0.1,其中,N = 10,i = 1,2, ..., 10,然後分別對於m = 1,2,3, ...等值進行迭代。

    拿到這10個數據的訓練樣本後,根據 X 和 Y 的對應關係,要把這10個數據分為兩類,一類是“1”,一類是“-1”,根據資料的特點發現:“0 1 2”這3個數據對應的類是“1”,“3 4 5”這3個數據對應的類是“-1”,“6 7 8”這3個數據對應的類是“1”,9是比較孤獨的,對應類“-1”。拋開孤獨的9不講,“0 1 2”、“3 4 5”、“6 7 8”這是3類不同的資料,分別對應的類是1、-1、1,直觀上推測可知,可以找到對應的資料分界點,比如2.5、5.5、8.5 將那幾類資料分成兩類。當然,這只是主觀臆測,下面實際計算下這個具體過程。

迭代過程1

對於m=1,在權值分佈為D1(10個數據,每個資料的權值皆初始化為0.1)的訓練資料上,經過計算可得:

    1. 閾值v取2.5時誤差率為0.3(x < 2.5時取1,x > 2.5時取-1,則6 7 8分錯,誤差率為0.3),
    2. 閾值v取5.5時誤差率最低為0.4(x < 5.5時取1,x > 5.5時取-1,則3 4 5 6 7 8皆分錯,誤差率0.6大於0.5,不可取。故令x > 5.5時取1,x < 5.5時取-1,則0 1 2 9分錯,誤差率為0.4),
    3. 閾值v取8.5時誤差率為0.3(x < 8.5時取1,x > 8.5時取-1,則3 4 5分錯,誤差率為0.3)。

可以看到,無論閾值v取2.5,還是8.5,總得分錯3個樣本,故可任取其中任意一個如2.5,弄成第一個基本分類器為:

上面說閾值v取2.5時則6 7 8分錯,所以誤差率為0.3,更加詳細的解釋是:因為樣本集中

    1. 0 1 2對應的類(Y)是1,因它們本身都小於2.5,所以被G1(x)分在了相應的類“1”中,分對了。
    2. 3 4 5本身對應的類(Y)是-1,因它們本身都大於2.5,所以被G1(x)分在了相應的類“-1”中,分對了。
    3. 但6 7 8本身對應類(Y)是1,卻因它們本身大於2.5而被G1(x)分在了類"-1"中,所以這3個樣本被分錯了。
    4. 9本身對應的類(Y)是-1,因它本身大於2.5,所以被G1(x)分在了相應的類“-1”中,分對了。

從而得到G1(x)在訓練資料集上的誤差率(被G1(x)誤分類樣本“6 7 8”的權值之和e1=P(G1(xi)≠yi) = 3*0.1 = 0.3

然後根據誤差率e1計算G1的係數:

這個a1代表G1(x)在最終的分類函式中所佔的權重,為0.4236。
接著更新訓練資料的權值分佈,用於下一輪迭代:

值得一提的是,由權值更新的公式可知,每個樣本的新權值是變大還是變小,取決於它是被分錯還是被分正確。

即如果某個樣本被分錯了,則yi * Gm(xi)為負,負負得正,結果使得整個式子變大(樣本權值變大),否則變小。

第一輪迭代後,最後得到各個資料的權值分佈D2 = (0.0715, 0.0715, 0.0715, 0.0715, 0.0715,  0.0715, 0.1666, 0.1666, 0.1666, 0.0715)。由此可以看出,因為樣本中是資料“6 7 8”被G1(x)分錯了,所以它們的權值由之前的0.1增大到0.1666,反之,其它資料皆被分正確,所以它們的權值皆由之前的0.1減小到0.0715。

分類函式f1(x)= a1*G1(x) = 0.4236G1(x)。

此時,得到的第一個基本分類器sign(f1(x))在訓練資料集上有3個誤分類點(即6 7 8)。

    從上述第一輪的整個迭代過程可以看出:被誤分類樣本的權值之和影響誤差率,誤差率影響基本分類器在最終分類器中所佔的權重

迭代過程2

對於m=2,在權值分佈為D2 = (0.0715, 0.0715, 0.0715, 0.0715, 0.0715,  0.0715, 0.1666, 0.1666, 0.1666, 0.0715)的訓練資料上,經過計算可得:

    1. 閾值v取2.5時誤差率為0.1666*3(x < 2.5時取1,x > 2.5時取-1,則6 7 8分錯,誤差率為0.1666*3),
    2. 閾值v取5.5時誤差率最低為0.0715*4(x > 5.5時取1,x < 5.5時取-1,則0 1 2 9分錯,誤差率為0.0715*3 + 0.0715),
    3. 閾值v取8.5時誤差率為0.0715*3(x < 8.5時取1,x > 8.5時取-1,則3 4 5分錯,誤差率為0.0715*3)。

所以,閾值v取8.5時誤差率最低,故第二個基本分類器為:

面對的還是下述樣本:

很明顯,G2(x)把樣本“3 4 5”分錯了,根據D2可知它們的權值為0.0715, 0.0715,  0.0715,所以G2(x)在訓練資料集上的誤差率e2=P(G2(xi)≠yi) = 0.0715 * 3 = 0.2143。

計算G2的係數:

更新訓練資料的權值分佈:
D3 = (0.0455, 0.0455, 0.0455, 0.1667, 0.1667,  0.01667, 0.1060, 0.1060, 0.1060, 0.0455)。被分錯的樣本“3 4 5”的權值變大,其它被分對的樣本的權值變小。
f2(x)=0.4236G1(x) + 0.6496G2(x)

此時,得到的第二個基本分類器sign(f2(x))在訓練資料集上有3個誤分類點(即3 4 5)。

迭代過程3

對於m=3,在權值分佈為D3 = (0.0455, 0.0455, 0.0455, 0.1667, 0.1667,  0.01667, 0.1060, 0.1060, 0.1060, 0.0455)的訓練資料上,經過計算可得:

    1. 閾值v取2.5時誤差率為0.1060*3(x < 2.5時取1,x > 2.5時取-1,則6 7 8分錯,誤差率為0.1060*3),
    2. 閾值v取5.5時誤差率最低為0.0455*4(x > 5.5時取1,x < 5.5時取-1,則0 1 2 9分錯,誤差率為0.0455*3 + 0.0715),
    3. 閾值v取8.5時誤差率為0.1667*3(x < 8.5時取1,x > 8.5時取-1,則3 4 5分錯,誤差率為0.1667*3)。

所以閾值v取5.5時誤差率最低,故第三個基本分類器為:

依然還是原樣本:

此時,被誤分類的樣本是:0 1 2 9,這4個樣本所對應的權值皆為0.0455,

所以G3(x)在訓練資料集上的誤差率e3 = P(G3(xi)≠yi) = 0.0455*4 = 0.1820。

計算G3的係數:

更新訓練資料的權值分佈:

D4 = (0.125, 0.125, 0.125, 0.102, 0.102,  0.102, 0.065, 0.065, 0.065, 0.125)。被分錯的樣本“0 1 2 9”的權值變大,其它被分對的樣本的權值變小。

f3(x)=0.4236G1(x) + 0.6496G2(x)+0.7514G3(x)

此時,得到的第三個基本分類器sign(f3(x))在訓練資料集上有0個誤分類點。至此,整個訓練過程結束。

    現在,咱們來總結下3輪迭代下來,各個樣本權值和誤差率的變化,如下所示(其中,樣本權值D中加了下劃線的表示在上一輪中被分錯的樣本的新權值):

  1. 訓練之前,各個樣本的權值被初始化為D1 = (0.1, 0.1,0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1);
  2. 第一輪迭代中,樣本“6 7 8”被分錯,對應的誤差率為e1=P(G1(xi)≠yi) = 3*0.1 = 0.3,此第一個基本分類器在最終的分類器中所佔的權重為a1 = 0.4236。第一輪迭代過後,樣本新的權值為D2 = (0.0715, 0.0715, 0.0715, 0.0715, 0.0715,  0.0715, 0.1666, 0.1666, 0.1666, 0.0715);
  3. 第二輪迭代中,樣本“3 4 5”被分錯,對應的誤差率為e2=P(G2(xi)≠yi) = 0.0715 * 3 = 0.2143,此第二個基本分類器在最終的分類器中所佔的權重為a2 = 0.6496。第二輪迭代過後,樣本新的權值為D3 = (0.0455, 0.0455, 0.0455, 0.1667, 0.1667,  0.01667, 0.1060, 0.1060, 0.1060, 0.0455);
  4. 第三輪迭代中,樣本“0 1 2 9”被分錯,對應的誤差率為e3 = P(G3(xi)≠yi) = 0.0455*4 = 0.1820,此第三個基本分類器在最終的分類器中所佔的權重為a3 = 0.7514。第三輪迭代過後,樣本新的權值為D4 = (0.125, 0.125, 0.125, 0.102, 0.102,  0.102, 0.065, 0.065, 0.065, 0.125)。

    從上述過程中可以發現,如果某些個樣本被分錯,它們在下一輪迭代中的權值將被增大,同時,其它被分對的樣本在下一輪迭代中的權值將被減小。就這樣,分錯樣本權值增大,分對樣本權值變小,而在下一輪迭代中,總是選取讓誤差率最低的閾值來設計基本分類器,所以誤差率e(所有被Gm(x)誤分類樣本的權值之和)不斷降低。

    綜上,將上面計算得到的a1、a2、a3各值代入G(x)中,G(x) = sign[f3(x)] = sign[ a1 * G1(x) + a2 * G2(x) + a3 * G3(x) ],得到最終的分類器為:

G(x) = sign[f3(x)] = sign[ 0.4236G1(x) + 0.6496G2(x)+0.7514G3(x) ]。

三、AdaBoost的toy algorithm的python實現
"""
  6     AdaBoost提升演算法:(自適應boosting)
  7         優點:泛化錯誤率低,易編碼,可以應用在大部分分類器上,無引數調整
  8         缺點:對離群點敏感
  9 
 10     bagging:自舉匯聚法(bootstrap aggregating)
 11         基於資料隨機重抽樣的分類器構建方法
 12         原始資料集中重新選擇S次得到S個新資料集,將磨溝演算法分別作用於這個資料集,
 13         最後進行投票,選擇投票最多的類別作為分類類別
 14 
 15     boosting:類似於bagging,多個分類器型別都是相同的
 16 
 17         boosting是關注那些已有分類器錯分的資料來獲得新的分類器,
 18         bagging則是根據已訓練的分類器的效能來訓練的。
 19 
 20         boosting分類器權重不相等,權重對應與上一輪迭代成功度
 21         bagging分類器權重相等
 22 """
 23 from numpy import*
 24 
 25 
 26 class Adaboosting(object):
 27 
 28     def loadSimpData(self):
 29         datMat = matrix(
 30             [[1., 2.1],
 31              [2., 1.1],
 32              [1.3, 1.],
 33              [1., 1.],
 34              [2., 1.]])
 35         classLabels = [1.0, 1.0, -1.0, -1.0, 1.0]
 36         return datMat, classLabels
 37 
 38     def stumpClassify(self, datMat, dimen, threshVal, threshIneq):
 39         """
 40         通過閾值比較進行分類
 41             dataMat:資料矩陣
 42             dimen:表示列下標
 43             threshVal:閾值
 44             threshIneq:不等號  lt, gt
 45         只是簡單的將資料分為兩類-1,1,初始化了一個全1的矩陣,我們判斷一下閾值第i列小於/大於閾值的就為-1,(因為我們並不清楚這個劃分標準,所以要大於小於都試一次)
 46 
 47         每一個維度的所有資料跟閾值比較,就相當於找到一個點劃分所有資料。
 48 
 49         """
 50         # print "-----data-----"
 51         # print datMat
 52         retArr = ones((shape(datMat)[0], 1))  # m(資料量)行,1列,列向量
 53         if threshIneq == 'lt':
 54             retArr[datMat[:, dimen] <= threshVal] = -1.0  # 小於閾值的列都為-1
 55         else:
 56             retArr[datMat[:, dimen] > threshVal] = -1.0  # 大於閾值的列都為-1
 57         # print "---------retArr------------"
 58         # print retArr
 59         return retArr
 60 
 61     def buildStump(self, dataArr, classLables, D):
 62         """
 63         單層決策樹生成函式
 64         """
 65         dataMatrix = mat(dataArr)
 66         lableMat = mat(classLables).T
 67         m, n = shape(dataMatrix)
 68         numSteps = 10.0  # 步數,影響的是迭代次數,步長
 69         bestStump = {}  # 儲存分類器的資訊
 70         bestClassEst = mat(zeros((m, 1)))  # 最好的分類器
 71         minError = inf  # 迭代尋找最小錯誤率
 72         for i in range(n):
 73             # 求出每一列資料的最大最小值計算步長
 74             rangeMin = dataMatrix[:, i].min()
 75             rangeMax = dataMatrix[:, i].max()
 76             stepSize = (rangeMax - rangeMin) / numSteps
 77             # j唯一的作用用步數去生成閾值,從最小值大最大值都與資料比較一邊了一遍
 78             for j in range(-1, int(numSteps) + 1):
 79                 threshVal = rangeMin + float(j) * stepSize  # 閾值
 80                 for inequal in ['lt', 'gt']:
 81                     predictedVals = self.stumpClassify(
 82                         dataMatrix, i, threshVal, inequal)
 83                     errArr = mat(ones((m, 1)))
 84                     errArr[predictedVals == lableMat] = 0  # 為1的 表示i分錯的
 85                     weightedError = D.T * errArr  # 分錯的個數*權重(開始權重=1/M行)
 86                     # print "split: dim %d, thresh %.2f, thresh ineqal:\
 87 #%s,the weighted error is %.3f" % (i, threshVal, inequal, weightedError)
 88                     if weightedError < minError:  # 尋找最小的加權錯誤率然後儲存當前的資訊
 89                         minError = weightedError
 90                         bestClassEst = predictedVals.copy()  # 分類結果
 91                         bestStump['dim'] = i
 92                         bestStump['thresh'] = threshVal
 93                         bestStump['ineq'] = inequal
 94         # print bestStump
 95         # print minError
 96         # print bestClassEst  # 類別估計
 97         return bestStump, minError, bestClassEst
 98 
 99     def adaBoostingDs(self, dataArr, classLables, numIt=40):
100         """
101         基於單層決策樹的AdaBoosting訓練過程:
102         """
103         weakClassArr = []  # 最佳決策樹陣列
104         m = shape(dataArr)[0]
105         D = mat(ones((m, 1)) / m)
106         aggClassEst = mat(zeros((m, 1)))
107         for i in range(numIt):
108             bestStump, minError, bestClassEst = self.buildStump(
109                 dataArr, classLables, D)
110             print "bestStump:", bestStump
111             print "D:", D.T
112             alpha = float(
113                 0.5 * log((1.0 - minError) / max(minError, 1e-16)))
114             bestStump['alpha'] = alpha
115             weakClassArr.append(bestStump)
116             print "alpha:", alpha
117             print "classEst:", bestClassEst.T  # 類別估計
118 
119             expon = multiply(-1 * alpha * mat(classLables).T, bestClassEst)
120             D = multiply(D, exp(expon))
121             D = D / D.sum()
122 
123             aggClassEst += alpha * bestClassEst
124             print "aggClassEst ;", aggClassEst.T
125             # 累加錯誤率
126             aggErrors = multiply(sign(aggClassEst) !=
127                                  mat(classLables).T, ones((m, 1)))
128             # 錯誤率平均值
129             errorsRate = aggErrors.sum() / m
130             print "total error:", errorsRate, "\n"
131             if errorsRate == 0.0:
132                 break
133         print "weakClassArr:", weakClassArr
134         return weakClassArr
135 
136     def adClassify(self, datToClass, classifierArr):
137         """
138         預測分類:
139         datToClass:待分類資料
140         classifierArr: 訓練好的分類器陣列
141         """
142         dataMatrix = mat(datToClass)
143         m = shape(dataMatrix)[0]
144         aggClassEst = mat(zeros((m, 1)))
145         print
146         for i in range(len(classifierArr)):  # 有多少個分類器迭代多少次
147             # 呼叫第一個分類器進行分類
148             classEst = self.stumpClassify(dataMatrix, classifierArr[i]['dim'],
149                                           classifierArr[i]['thresh'],
150                                           classifierArr[i]['ineq']
151                                           )
152             # alpha 表示每個分類器的權重,
153             print classEst
154             aggClassEst += classifierArr[i]['alpha'] * classEst
155             print aggClassEst
156         return sign(aggClassEst)
157 
158 
159 if __name__ == "__main__":
160     adaboosting = Adaboosting()
161     D = mat(ones((5, 1)) / 5)
162     dataMat, lableMat = adaboosting.loadSimpData()
163     # 訓練分類器
164     classifierArr = adaboosting.adaBoostingDs(dataMat, lableMat, 40)
165     # 預測資料
166     result = adaboosting.adClassify([0, 0], classifierArr)
167     print result

執行結果:可以看到迭代三次加權錯誤率為0


最後有一個對資料[0,0]的預測:weakClassArr表示儲存的三個分類器的資訊,我們用這個分類器對資料進行預測

三個小數對應的是三個分類器前N個分類加權分類結果累加。對應的-1,-1,-1表示三個分類器對這個資料分類是-1,最後一個表示增強分類器對這個資料的加權求和分類結果為-1

四、啟發和思考“為什麼”,“如何做”

問題一:為什麼給錯分的資料的權重越高,也就是對錯分資料越關注,在下一次分類,誤差率就會降低?這個過程是怎麼體現的?

解答:如果錯分資料的權重變高了,那麼在下一次對資料分類時,分類器假如對上一次的錯分資料再一次分錯的話,根據公式,分類誤差率=錯分資料權重之和,我們就會發現這個時候的分類誤差率變高了,而此時分類器的這個‘閾值’肯定不是我們想要的,其他的閾值(分類誤差率最小)才是我們想要的,這樣就會使得分類器更加關注前一次的錯分資料,使這部分資料在本次分類後不會被錯分,從而也達到了我們的目的,這就是演算法邏輯的整個過程。

問題二:閾值如何確定?

解答:考慮到數值型的特徵,我們可以通過計算最小值和最大值來了解需要多大的步長,比如說上面程式碼裡面的numsteps = 10.0,stepsize = (rangemax-rangemin)/numsteps,threshVal = (rangemin+float(j)*stepsize)

Reference:

統計學習方法

機器學習實戰

https://blog.csdn.net/v_july_v/article/details/40718799

https://www.cnblogs.com/NextNight/p/6227526.html