[機器學習] -提升方法AdaBoost

Adaboost是一種集成學習的方法,當採用基於簡單模型的單個分類器對樣本進行分類的效果不理想時,人們希望能夠通過構建並整合多個分類器來提高最終的分類性能。

Boosting方法並不是簡單地對多個分類器的輸出進行投票決策,而是通過一種迭代過程對分類器的輸入和輸出進行加權處理。在不同應用中可以採用不同類型的弱分類器,在每次迭代過程中,根據分類的情況對各個樣本進行加權,而不僅僅是簡單的重採樣。

弱分類器:人們常稱不理想的單個分類器稱爲弱分類器

1 Adaboost算法流程

Boosting算法的訓練過程:
        
  
  目前,最爲廣泛使用的Boosting方法是提出的AdaBoost算法。這裏對這個算法做一個簡單的介紹。
  設給定NN個訓練樣本{x1,x2,....xN}{\{x_1,x_2,....x_N}\},用Gm(x){1,1}(m=1,...,M){G_m}\left( x \right) \in \left\{ { - 1,1} \right\}\left( {m = 1,...,M} \right)表示MM個弱分類器在樣本xx上的輸出,通過AdaBoost算法構造這MM個分類器並進行決策的具體過程如下:
  1. 初始化訓練樣本{x1,x2,....xN}{\{x_1,x_2,....x_N}\}的權重wi=1/N,i=1,....N{w_i} = 1/N,{\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} i = 1,....N
  
  2. 對m=1Mm = 1 \to M,重複使用以下過程:
  (1)利用{wi}\{w_i\}加權後的訓練樣本構造分類器Gm(x){1,1}{G_m}\left( x \right) \in \left\{ { - 1,1} \right\}。(注意構造弱分類器的具體算法可以不同,例如使用線性分類器和決策樹等。)
  (2)計算樣本用{wi}\{w_i\}加權後的分類錯誤率eme_m,並令cm=1/2log((1em)/em){c_m} = 1/2*\log \left( {\left( {1 - {e_m}} \right)/{e_m}} \right)
  (3)更新訓練數據集的權重分佈:
    Zm=i=1Nwmiexp(cmyiGm(x)){Z_m} = \sum\limits_{i = 1}^N {{w_{mi}}} \exp \left( { - {c_m}{y_i}{G_m}\left( x \right)} \right)ZZ是規範化因子。
    wm+1,i=wmiexp(cmyiGm(x))Zmi=1,...N{w_{m + 1,i}} = \frac{{{w_{mi}} \cdot \exp \left( { - {c_m}{y_i}{G_m}\left( x \right)} \right)}}{{{Z_m}}},i=1,...N
    Dm+1=(wm+1,1,wm+1,2,.......wm+1,N)D_{m+1}=({w_{m+1,1},w_{m+1,2},.......w_{m+1,N}}) 新的權值分佈。
  
  3. 構造基本分類器的線性組合
    f(x)=m=1McmGm(x)f\left( x \right) = \sum\limits_{m = 1}^M {{c_m}} {G_m}\left( x \right)
   對於待分類樣本xx,分類器的輸出爲sign[m=1McmGm(x)]sign[\sum\limits_{m = 1}^M {{c_m}} {G_m}\left( x \right)]

這裏補充說明幾點:
(1)MM表示設置迭代的次數,也是AdaBoost算法構造弱分類器的最大個數。
(2)錯誤率eme_m小於一個最小值時停止循環,或者是eme_m==0停止循環也行。eme_mcm{c_m}中作爲分母,設計程序時需要避開em=0e_m=0的情況。

2 最佳的單層決策樹

接下來,只訓練一個弱分類器。

(1)數據集:

{1,2.1,1
1.5,1.6,1
1.3,1,-1
1,1,-1
2,1,1}

(2)圖
        
從圖中發現,訓練一個單層決策樹的弱分類器,無論怎麼劃分,都存在一個點誤分類。

(3)模型測試

# 找出數據集上最佳的單層決策樹
def get_Stump(xMat, yMat, D):
    """
    參數說明:
        xMat:特徵矩陣
        yMat:標籤矩陣
        D:樣本權重
    返回:
        bestStump:最佳單層決策樹信息
        minE:最小誤差
        bestClass:最佳的分類結果
    """
    m, n = xMat.shape  #m爲樣本的個數,n爲特徵數
    Step = 10  # 初始化一個步數
    bestStump = {}  # 用字典形式來存儲樹樁信息
    bestClass = np.mat(np.zeros((m, 1)))  # 初始化分類結果爲1
    minE = np.inf  # 最小誤差初始化爲正無窮大
    for i in range(n):  # 遍歷所有特徵值
        min = xMat[:, i].min()  # 找到特徵的最小值
        max = xMat[:, i].max()  # 找到特徵的最大值
        stepSize = (max - min)/ Step  # 計算步長
        for j in range(-1, int(Step)+1):
            for S in ['lt', 'gt']:  # 大於和小於的情況,均遍歷
                Q = (min + j * stepSize)  # 計算閾值
                re = Classify0(xMat, i, Q, S)  # 計算分類結果
                err = np.mat(np.ones((m,1)))  # 初始化誤差矩陣
                err[re == yMat] = 0  # 分類正確的,賦值爲0
                eca = D.T * err  # 計算誤差
                if eca < minE:  # 找到誤差最小的分類方式
                    minE = eca
                    bestClass = re.copy()
                    bestStump['特徵值'] = i
                    bestStump['閾值'] = Q
                    bestStump['標誌'] = S
    return bestStump, minE, bestClass

(4)訓練結果

bestStump:{'特徵值': 0, '閾值': 1.3, '標誌': 'lt'}

minE:[[0.2]]

bestClass:[[-1.]
            [ 1.]
            [-1.]
            [-1.]
            [ 1.]]

3 基於單層決策樹的Adaboost集成訓練

單層決策樹程序設計的傳入參數有

xMat:特徵矩陣
yMat:標籤矩陣
D:樣本權重

在上述講解的算法步驟中,第二步的第三小步,更新訓練數據集的權重分佈,將更新的權值作爲參數D重新傳入到單層決策樹中,返回誤差minE通過公式cm=1/2log((1em)/em){c_m} = 1/2*\log \left( {\left( {1 - {e_m}} \right)/{e_m}} \right)得到的cm{c_m}作爲新增分類器的權重。

Adaboost集成訓練的程序:

# 基於單層決策樹的Adaboost訓練過程
def Ada_train(xMat, yMat, maxC=40):
    """
    函數功能:基於單層決策樹的Adaboost訓練過程
    參數說明:
        xMat:特徵矩陣
        yMat:標籤矩陣
        maxC:最大迭代次數
    返回:
        weakClass:弱分類器信息
        aggClass:類別估值(更改標籤估值)
    """
    weakClass = []
    m = xMat.shape[0]
    D = np.mat(np.ones((m, 1))/m)  # 初始化權重
    aggClass = np.mat(np.zeros((m,1)))
    for i in range(maxC):
        Stump, error, bestClass = get_Stump(xMat, yMat, D)  # 構造單層決策樹
        alpha = float(0.5*np.log((1-error)/max(error, 1e-6)))  # 計算弱分類器權重alpha
        Stump['alpha'] = np.round(alpha, 2)
        weakClass.append(Stump)  # 存儲單層決策樹
        expon = np.multiply(-1*alpha*yMat, bestClass)  # 計算e的指數項
        D = np.multiply(D, np.exp(expon))
        D = D / D.sum()  # 更新權重
        aggClass += alpha * bestClass  # 計算類別估值(更改標籤估值)
        aggErr = np.multiply(np.sign(aggClass) != yMat, np.ones((m, 1)))  # 統計誤分類樣本數
        errRate = aggErr.sum()/m
        print("total error: ", errRate)
        if errRate == 0:
            break
    return weakClass, aggClass

4 病馬訓練集的測試

完整程序:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date  : 2020/6/30 15:46


"""
1.實現單層決策樹,找出數據集上最佳的單層決策樹
2.實現多層的分類(adaboost)
"""

import numpy as np
import matplotlib.pyplot as plt


# 加載文件
def loadDataSet(path = 'horseColicTraining2.txt'):
    data = list()
    labels = list()
    with open(path) as f:
        lines = f.readlines()
        for line in lines:
            line = line.rstrip().split('\t')
            lineArr = []
            for i in range(len(line)-1):
                lineArr.append(float(line[i]))
            data.append(lineArr)
            labels.append(float(line[-1]))
        xMat = np.array(data)
        yMat = np.array(labels).reshape(-1, 1)
    return xMat, yMat


# 按照閾值分類結果
def Classify0(xMat, i, Q, S):
    """
    xMat:數據矩陣
    Q:閾值
    S:標誌
    """
    re = np.ones((xMat.shape[0], 1))
    if S == 'lt':
        re[xMat[:, i] <= Q] = -1  # 如果小於閾值,則賦值爲-1
    else:
        re[xMat[:, i] > Q] = 1  # 如果大於閾值,則賦值爲1
    return re


# 找出數據集上最佳的單層決策樹
def get_Stump(xMat, yMat, D):
    """
    參數說明:
        xMat:特徵矩陣
        yMat:標籤矩陣
        D:樣本權重
    返回:
        bestStump:最佳單層決策樹信息
        minE:最小誤差
        bestClass:最佳的分類結果
    """
    m, n = xMat.shape  #m爲樣本的個數,n爲特徵數
    Step = 10  # 初始化一個步數
    bestStump = {}  # 用字典形式來存儲樹樁信息
    bestClass = np.mat(np.zeros((m, 1)))  # 初始化分類結果爲1
    minE = np.inf  # 最小誤差初始化爲正無窮大
    for i in range(n):  # 遍歷所有特徵值
        min = xMat[:, i].min()  # 找到特徵的最小值
        max = xMat[:, i].max()  # 找到特徵的最大值
        stepSize = (max - min)/ Step  # 計算步長
        for j in range(-1, int(Step)+1):
            for S in ['lt', 'gt']:  # 大於和小於的情況,均遍歷
                Q = (min + j * stepSize)  # 計算閾值
                re = Classify0(xMat, i, Q, S)  # 計算分類結果
                err = np.mat(np.ones((m,1)))  # 初始化誤差矩陣
                err[re == yMat] = 0  # 分類正確的,賦值爲0
                eca = D.T * err  # 計算誤差
                if eca < minE:  # 找到誤差最小的分類方式
                    minE = eca
                    bestClass = re.copy()
                    bestStump['特徵值'] = i
                    bestStump['閾值'] = Q
                    bestStump['標誌'] = S
    return bestStump, minE, bestClass


# 基於單層決策樹的Adaboost訓練過程
def Ada_train(xMat, yMat, maxC=40):
    """
    函數功能:基於單層決策樹的Adaboost訓練過程
    參數說明:
        xMat:特徵矩陣
        yMat:標籤矩陣
        maxC:最大迭代次數
    返回:
        weakClass:弱分類器信息
        aggClass:類別估值(更改標籤估值)
    """
    weakClass = []
    m = xMat.shape[0]
    D = np.mat(np.ones((m, 1))/m)  # 初始化權重
    aggClass = np.mat(np.zeros((m,1)))
    for i in range(maxC):
        Stump, error, bestClass = get_Stump(xMat, yMat, D)  # 構造單層決策樹
        alpha = float(0.5*np.log((1-error)/max(error, 1e-6)))  # 計算弱分類器權重alpha
        Stump['alpha'] = np.round(alpha, 2)
        weakClass.append(Stump)  # 存儲單層決策樹
        expon = np.multiply(-1*alpha*yMat, bestClass)  # 計算e的指數項
        D = np.multiply(D, np.exp(expon))
        D = D / D.sum()  # 更新權重
        aggClass += alpha * bestClass  # 計算類別估值(更改標籤估值)
        aggErr = np.multiply(np.sign(aggClass) != yMat, np.ones((m, 1)))  # 統計誤分類樣本數
        errRate = aggErr.sum()/m
        print("total error: ", errRate)
        if errRate == 0:
            break
    return weakClass, aggClass


# 開始對待預測的數據進行分類
def AdaClassify(xMat, weakClass):
    m = xMat.shape[0]  # 待分類數據集的長度
    aggClass = np.mat(np.zeros((m, 1)))

    for i in range(len(weakClass)):  # 遍歷所有分類器進行分類
        classEst = Classify0(xMat,
                             weakClass[i]['特徵值'],
                             weakClass[i]['閾值'],
                             weakClass[i]['標誌'],
                             )
        aggClass += classEst * weakClass[i]['alpha']
    return np.sign(aggClass)


# ROC圖像
def plotROC(predStrengths, classLabels):
    """
    輸入:
        predStrengths : Adaboost預測的結果(行)
        classLabels : 原本訓練數據的標籤(列)
    """
    cur = (1.0, 1.0)  # 光標
    ySum = 0.0  # 變量來計算AUC
    numPosClas = sum(np.array(classLabels) == 1.0)
    yStep = 1 / float(numPosClas)  # 向上的步長
    xStep = 1 / float(len(classLabels) - numPosClas)  # 向右的步長
    sortedIndicies = predStrengths.argsort()  # 得到排序索引,它是反向的
    fig = plt.figure()
    fig.clf()
    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)  # 重新更新cur的起始點

    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)


if __name__ == '__main__':
    xMat, yMat = loadDataSet(path='horseColicTraining2.txt')  # 訓練數據

    # 測試單層決策樹
    m = xMat.shape[0]
    D = np.mat(np.ones((m, 1))/m)
    bestStump, minE, bestClass = get_Stump(xMat, yMat, D)

    # 測試單層決策樹的Adaboost訓練過程
    weakClass, aggClass = Ada_train(xMat, yMat, maxC=10)  # 返回弱分類器的集合,以及弱分類的標籤值
    print('分類器的個數:', len(weakClass))
    testArr, testLabelArr = loadDataSet(path='horseColicTest2.txt')  # 測試數據
    pre = AdaClassify(testArr, weakClass)  # 返回預測值

    # 計算準確度
    errArr = np.mat(np.ones((len(pre), 1)))  # 一共有m個預測樣本
    cnt = errArr[pre != testLabelArr].sum()
    print('誤分類點在總體預測樣本中的比例爲:', cnt / len(pre))

    # 繪畫出ROCplotROC(aggClass.T, yMat)

結果(1):

分類器爲: 
[{'特徵值': 2, '閾值': 36.72, '標誌': 'lt', 'alpha': 0.27}, 
{'特徵值': 0, '閾值': 1.0, '標誌': 'lt', 'alpha': 0.15}, 
{'特徵值': 18, '閾值': 0.0, '標誌': 'lt', 'alpha': 0.14}, 
{'特徵值': 18, '閾值': 53.400000000000006, '標誌': 'lt', 'alpha': 0.16}, 
{'特徵值': 0, '閾值': 0.9, '標誌': 'lt', 'alpha': 0.06}, 
{'特徵值': 12, '閾值': 1.2, '標誌': 'lt', 'alpha': 0.05}, 
{'特徵值': 0, '閾值': 1.0, '標誌': 'lt', 'alpha': 0.04},
{'特徵值': 18, '閾值': 53.400000000000006, '標誌': 'lt', 'alpha': 0.03}, 
{'特徵值': 0, '閾值': 0.9, '標誌': 'lt', 'alpha': 0.04}, 
{'特徵值': 4, '閾值': 57.599999999999994, '標誌': 'lt', 'alpha': 0.04}]

結果(2):
ROC曲線圖爲:
        

結果(3):

誤分類點在總體預測樣本中的比例爲: 0.3283582089552239
the Area Under the Curve is:  0.6757359086266125

參考資料
1 https://www.bilibili.com/video/BV1it411q7wy?from=search&seid=15656450157548541974
2 《統計學習方法》- 李航

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