機器學習筆記(十二)——集成學習方法之AdaBoost

集成學習方法

本文參考於《機器學習實戰》和《機器學習》

在此之前一共介紹了五種分類算法,分別爲KNN、決策樹、樸素貝葉斯、邏輯迴歸、支持向量機,可以看到每一種算法都有各自的優缺點,以及適合的數據集。集成學習方法可以將不同分類算法構建的分類器組合在一起,更加高效準確的分類。

使用集成學習方法時可以有多種形式:可以是不同算法的集成,也可以是同一算法在不同設置下的集成,還可以是數據集不同部分分配給不同分類器之後的集成。

對於集成方法中分類器的組合也可以將集成分爲“同質”和“異質”。例如“決策樹集成”中所有的分類器都爲決策樹,同質集成中的個體分類器可以稱作“基分類器”;而“異質”就是集成中包含不同分類算法,異質集成中的個體分類器常稱爲“組件分類器”。

在這裏插入圖片描述

集成學習通過將若干個弱學習器組合成一個強分類器,這個強分類器可以獲得比單一學習器更加顯著的準確率。弱分類器常指準確率略優於隨機猜測的學習器,比如在二分類問題上準確率略高於50%的分類器,在同質集成學習中基分類器有時也可以直接稱爲弱分類器。

集成學習方法大致可分爲Bagging和Boosting兩種方法,這兩種方法的思想都是將多種分類和迴歸算法通過某種方式組合在一起,從而得到一個強分類器。下面分別介紹這兩種方法,比較兩種方法的異同。

Bagging

可以將Bagging方法的主要原理總結成以下幾點:

  • 抽樣:在原始數據中有放回的抽取N次得到一個新的採樣數據集。新數據集與原始數據集大小相等,由於是有放回抽樣,所以允許採樣數據集中有重複值。

  • 訓練:總共選取M個採樣數據集,每一個數據集對應一種學習算法,從而可以得到M個分類器,利用這些分類器對數據進行分類。

  • 票選:M個分類器會得到M個分類結果,所以最後通過投票將分類結果中最多的類別作爲最終的分類結果。

隨機森林就是Bagging方法的典型代表,它是由很多棵決策樹與Bagging方法思想相結合而成。

Boosting

Boosting方法與Bagging方法很類似,但前者主要是通過將權重與串行迭代結合的方式進行學習,Boosting更加關注每次分類結束後被分錯的樣本,在下次分類之前,Boosting會增大分錯樣本的權重,這樣下一個分類器在訓練時就會更加註重對分錯樣本的訓練。

在決定最終的分類時,由於每一輪分類權重都會發生改變,所以Boosting方法通過加權求和的方式得到分類結果。Boosting方法的典型代表爲Adaboost,它是由很多棵決策樹與Boosting方法思想相結合而成,也被稱作提升樹,除此之外還有GBDT和XGBoost。下文會給出以AdaBoost的流程圖,其原理就是Boosting的主要思想。

兩種方法區別

兩種算法雖然思想上都是通過將若干個弱分類器組合成一個強分類器,讓最終的分類準確率得到提升,但是二者也是有很大區別的,具體如下:

  • Bagging在原始數據集中以隨機放回抽樣的方式得到新採樣集;而Boosting每一輪的訓練集都爲原始數據集,只是訓練集中每個樣本的權重在發生變化。

  • Bagging是採用並行的方式學習,每個分類器學習時互不影響;而Boosting是採用串行的方式學習,上一次訓練結果會影響下一次訓練集中樣本的權重。

  • Bagging由於每個分類器權重都一致,所以在判定分類最終結果時採用投票式;而Boosting每個分類器都有相應的權重,所以在判定分類最終結果時採用加權求和的方式。

AdaBoost算法介紹

算法原理

AdaBoost的全稱爲adaptive boosting(自適應boosting),可以將其運行過程歸結爲以下幾點:

  • 訓練數據集中的每個樣本都會被賦予一個相同的權重值,這些權重會構成一個向量,暫稱爲D。

  • 分類器在訓練數據集之後會計算出該分類器的錯誤率,即分錯樣本的概率,並且依據錯誤率更新分類器的權重。

  • 在下一個分類器訓練之前,會依據上一個分類器的權重調整每一個樣本的權重,上一次分對的樣本權重降低,分錯的樣本權重提高。

  • 迭代次數是人爲設置的,在達到迭代次數或者滿足某個條件後,以加權求和的方式確定最終的分類結果。

相關公式

1、樣本權重

對於一個含有N個樣本的數據集,在初始化訓練數據的權重分佈式,每個樣本的權重都應該是相等的。
在這裏插入圖片描述
2、錯誤率

如果我們將基於不同權重D(M)D(M)訓練出的分類器稱作hM(x)h_M(x),每個樣本實際對應類別爲f(x)f(x),則錯誤率ϵ\epsilon的定義如下:ϵ=\epsilon=\frac{未正確分類的樣本數目}{所有樣本數目} ϵM=P(hM(x)f(x))\epsilon_M=P(h_M(x)\neq f(x))

3、分類器權重

除樣本權重外,每一個分類器也會有一個權重αM\alpha_M: αM=12ln(1ϵMϵM)\alpha_M=\frac{1}{2}ln(\frac{1-\epsilon_M}{\epsilon_M})
可以看下面這張流程圖:
在這裏插入圖片描述
左邊是數據集,黑條的長度代表每個樣本的權重,每一個分類器訓練之後後,都會得到一個分類器權重α\alpha,最後所有分類器加權結果(三角形)會在圓形中求和,以得到最終的分類結果。

4、更新權重D

在下次訓練之前,爲了使正確分類的樣本權重降低而錯分樣本的權重提高,需要對權重向量D更新:
在這裏插入圖片描述
在兩個式子合併時,這裏一般針對二分類問題,即類別標籤爲+1和-1。當分類器預測結果和真實類別相同時,二者乘積爲+1,不同時,二者乘積爲-1。其中ZtZ_t稱作規範化因子,它的作用就是確保DM+1D_{M+1}是一個概率分佈。

5、最終分類器輸出

可以利用每個弱分類器的線性組合達到加權求和的目的,並且通過符號函數確定最終的分類結果。H(x)=sign(m=1Mαmhm(x))H(x)=sign(\sum_{m=1}^M\alpha_m h_m(x))

結合策略

可以看到AdaBoost的流程圖中有一個“結合策略”,顧名思義,就是通過某種策略將所有的弱分類器的分類結果結合在一起,繼而判斷出最終的分類結果。結合策略可分爲三類:平均法、投票法和學習法。

  • 平均法

對於數值型的迴歸預測問題,最常見的結合策略是使用平均法,平均法又可以分爲簡單平均法和加權平均法。

假設有n個弱分類器(h1(x),h2(x),...,hn(x))(h_1(x),h_2(x),...,h_n(x)),簡單平均法公式如下: H(x)=1ni=1nhi(x)H(x)=\frac{1}{n}\sum_{i=1}^nh_i(x)

加權平均法公式如下:H(x)=i=1nwihi(x)H(x)=\sum_{i=1}^nw_ih_i(x)
其中wiw_i是弱分類器hi(x)h_i(x)的權重,通常要求wi0w_i\geq0i=1nwi=1\sum_{i=1}^nw_i=1。可以看出簡單平均法是加權平均法令wi=1nw_i=\frac{1}{n}的特例。

  • 投票法

對分類的預測,通常使用的是投票法,投票法可以分爲相對多數投票法、絕對多數投票法和加權投票法。

相對多數投票法就是常提及的少數服從多數,在預測結果中選擇出現次數最多的類別作爲最終的分類類別,如果不止一個類別獲得最高票數,則在其中隨機選取一個做最終類別。

絕對多數投票法就是在相對多數投票法的基礎上進行改進,不光要求要獲得最高票數,並且票數還要超過50%,否則拒絕預測。

加權投票法與加權平均法類似,即每個若分類器得到的分類票數要乘以一個權重,最終將各個類別的票數加權求和,最大值對應類別作爲最終分類類別。

  • 學習法

學習法相對於前兩種方法較爲複雜,但得到的效果可能會更優,學習法的代表是Stacking,其整體思想是在弱分類器之後再加上一個分類器,將弱分類器的輸出當成輸入再次訓練。

我們將弱分類器稱作初級分類器,將用於結合的分類器稱作次級分類器,利用學習法結合策略時,先通過初級分類器對數據集訓練,然後再通過次級分類器在訓練一次,纔會得到最終的預測結果。

AdaBoost代碼實現

單層決策樹

單層決策樹是一種簡單的決策樹,它僅僅基於單個特徵來做決策,也就是得到一個信息增益之後會捨棄其他特徵,所以單層決策樹只有一次分裂過程,實際上也是一個樹樁。

之所以用單層決策樹來構建弱分類器,是因爲在下面這份數據集中,單層決策樹能更好的體現"弱"這個性質。
在這裏插入圖片描述爲什麼這麼說呢?在某個座標軸上選擇一個值,如果要利用垂直座標軸並且經過該值的一條直線將兩類樣本分隔開,這顯然是不可能的。但是可以通過多次劃分,即通過使用多棵單層決策樹構建出一個完全正確分類的分類器。

我們暫且將上圖樣本的分類規定爲正類和負類,那麼在劃分數據集時候可以給定相應的閾值,如下圖:
在這裏插入圖片描述
假如選取X1特徵的閾值爲1.4,可以規定小於這個閾值的樣本爲負類,相應大於這個閾值的樣本爲正類;但規定小於閾值的樣本爲正類,大於爲負類也同樣合理,所以對於一個特徵有兩種情況,這也是下面這個函數中唯一比較彆扭的點。

def buildStump(dataMatrix, classLabels, D):
    labelMat = np.mat(classLabels).T # 標籤列表矩陣化並轉置
    m, n = np.shape(dataMatrix) # 獲取輸入數據矩陣行數和列數
    numSteps = 10.0 # 閾值改變次數
    bestStump = {} # 用於存樹的空字典
    bestClasEst = np.mat(np.zeros((m, 1)))
    minError = float('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): # 閾值改變次數
            # lt(less than)指小於該閾值,分類爲-1
            # gt(greater than)指大於該閾值,分類爲-1
            for inequal in ['lt', 'gt']: # 兩種情況,都需要遍歷
                threshVal = (rangeMin + float(j) * stepSize) # 計算閾值
                predictedVals = stumpClassify(dataMatrix, i, threshVal, inequal) # 得到分類結果
                errArr = np.mat(np.ones((m, 1))) # 創建一個存誤差值的矩陣
                errArr[predictedVals == labelMat] = 0 # 將分類正確的樣本賦值爲0
                weightedError = D.T * errArr # 利用權重計算出誤差
                if weightedError < minError: # 如果本輪誤差比上一次小,則替換
                    minError = weightedError
                    bestClasEst = predictedVals.copy()
                    # 將單層決策樹存入字典
                    bestStump['dim'] = i
                    bestStump['thresh'] = threshVal
                    bestStump['ineq'] = inequal
    return bestStump, minError, bestClasEst

這部分代碼中需要了解以下點:numSteps就是遍歷某個特徵所有取值的次數;bestStump這個字典用來存儲最佳單層決策樹的相關信息;minError爲最小誤差,暫定正無窮大,每次循環都會更新。

三層for循環是關鍵部分,首先第一層是遍歷所有特徵,並且設置了每次閾值改變的長度;第二層是以步長爲基礎,遍歷特徵上所有可取值;第三層則是小於和大於之間切換不等式。

在第三層循環內調用了一個函數,基於給定變量,會返回分類預測結果。首先創建了一個全爲1的矩陣,如果預測結果和真實結果一致則對應位置調整爲0,並且利用這個矩陣和權重矩陣的轉置相乘可得到一個新的誤差,接着會通過比較更新最小誤差,並儲存最佳單層決策樹的相關信息,即特徵、閾值和(lt or gt)。

第三層循環調用函數如下:

def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
    # 創建一個與輸入矩陣行數相同、列數爲1的矩陣
    retArray = np.ones((np.shape(dataMatrix)[0], 1))
    # 如果小於閾值,分類爲-1
    if threshIneq == 'lt':
        retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
    # 如果大於閾值,分類爲-1
    else:
        retArray[dataMatrix[:, dimen] > threshVal] = -1.0
    return retArray

這個函數非常簡單,就是之前提及兩種情況,小於閾值將其分類爲-1或者大於閾值時將其分類爲-1都是合理情況,所以需要遍歷考慮這兩種情況。

在這裏插入圖片描述

可以看到對於第一個特徵來說最小誤差爲0.2,也就是說垂直於x軸劃分,最優情況下會分錯一個樣本,下面利用AdaBoost方法將多個單側決策樹結合在一起,看一下樣本是否能全部分類正確。

結合AdaBoost方法

前文已經給出了AdaBoost運行流程及所需公式,只需要設定弱分類器的個數或者稱爲迭代次數,每個弱分類器都實現一遍流程即可,代碼部分如下:

def adaBoostTrainDS(dataArr, classLabels, numIt = 40):
    weakClassArr = []
    m = np.shape(dataArr)[0]
    D = np.mat(np.ones((m, 1)) / m) # 初始化權重
    aggClassEst = np.mat(np.zeros((m,1))) #初始化一個值爲0的行向量
    for i in range(numIt): # 在給定次數內迭代
        bestStump, error, classEst = buildStump(dataArr, classLabels, D) # 構建單層決策樹
        alpha = float(0.5 * np.log((1.0 - error) / max(error, 1e-16))) # 計算弱分類器的權重
        bestStump['alpha'] = alpha # 將弱分類器權重存入樹中
        weakClassArr.append(bestStump) # 將最佳單層決策樹存入一個列表
        # 更新樣本權重
        expon = np.multiply(-1 * alpha * np.mat(classLabels).T, classEst)
        D = np.multiply(D, np.exp(expon))
        D = D / D.sum()
        # 計算AdaBoost的誤差
        aggClassEst += alpha * classEst                       
        aggErrors = np.multiply(np.sign(aggClassEst) != np.mat(classLabels).T, np.ones((m,1)))
        errorRate = aggErrors.sum() / m
        # 如果誤差爲0,則退出循環
        if errorRate == 0.0:
            break
    return weakClassArr, aggClassEst

首先對所有樣本初始化一個權重D,在設置的迭代次數內,得到存儲最佳單層決策樹相關信息的字典、最小誤差、預測結果。在最小誤差的基礎上更新分類器權重alpha,依據公式當最小誤差爲0時,程序會發生報錯,即除數爲0,利用max方法即可防止這種情況發生。

然後依據alpha更新樣本的權重,而expon中的classEst也就是上文公式中弱分類器預測結果hM(x)h_M(x),classLabels也就是真實結果f(x)f(x)

因爲我們無法猜得一個數據集需要幾棵單層決策樹才能完全正確分類,如果迭代次數過多,則會發生過擬合的現象,分類反而不準了,所以上述代碼最後這部分就是爲了防止迭代次數過多,在誤差爲0時即使未達到迭代次數也退出迭代。

這裏運用了上文公式的H(x)=sign(m=1Mαmhm(x))H(x)=sign(\sum_{m=1}^M\alpha_m h_m(x))
先計算每個弱分類器預測類別的估計值,累加後再利用符號函數進行分類。這裏科普一下sign方法,當值大於0時返回1.0,當值小於0時返回-1.0,當值等於0時返回0,知道了sign方法,這部分代碼就很容易理解。


最後AdaBoost方法通過三個最佳單層決策樹實現了對數據集的完全正確分類,比如下面這種切分方式:
在這裏插入圖片描述
可能表達的比較抽象,但是在理解的時候你要拋開一個刀就能劃分的思想,我們利用的是集成方法求解分類問題,這是三刀組合在一起纔得到的完全正確分類結果。
在這裏插入圖片描述

測試算法

算法模型已經建成了,所以需要對該模型進行測試,代碼的思路比較簡單,就是遍歷所有訓練得到的弱分類器,計算每個弱分類器預測類別的估計值,累加後再通過符號函數對類別進行判斷,代碼如下:

def adaClassify(datToClass,classifierArr):
    dataMatrix = np.mat(datToClass)
    m = np.shape(dataMatrix)[0]
    aggClassEst = np.mat(np.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
    return np.sign(aggClassEst)

本身我們的訓練數據集就非常小,所以不會計算模型準確率,只是輸入兩個樣本對其類別進行判斷,這裏樣本選擇[[5,5],[0,0]],運行截圖如下:

在這裏插入圖片描述

可以看到,隨着迭代次數的增加,類別的估計值的絕對值越來越大(離0越來越遠),因爲最後是利用符號函數對分類結果進行判斷,所以這也代表着輸入樣本的分類結果越來越強。

總結

AdaBoost的優點就是分類的準確率高,與其他複雜算法相比更容易編碼,並且可以應用在大部分分類器上,沒有什麼參數需要調整。缺點就是對離羣點敏感,因爲離羣點一般都是錯誤數據,往往應該被捨棄,對於這類樣本分類器很容易分錯,而分錯的樣本的下次迭代時權重會增加,分類器對這類樣本會更加重視,就可能會導致一錯再錯。

關注公衆號【奶糖貓】後臺回覆"AdaBoost"可獲取源碼供參考,感謝閱讀。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章