1. 程式人生 > >機器學習實戰(六)AdaBoost元演算法

機器學習實戰(六)AdaBoost元演算法

目錄

0. 前言

1. AdaBoost

2. 單層決策樹

3. 非均衡資料

4. 實戰案例

4.1. 馬病死亡案例


學習完機器學習實戰的AdaBoost元演算法,簡單的做個筆記。文中部分描述屬於個人消化後的理解,僅供參考。

所有程式碼和資料可以訪問 我的 github

如果這篇文章對你有一點小小的幫助,請給個關注喔~我會非常開心的~

0. 前言

在分類任務中,我們通常可以不僅僅採用一個演算法,而是多個演算法模型結合使用,綜合每個弱分類器的結果,稱為整合方法(ensemble method)或元演算法(meta-algorithm)。

通常有以下兩種方法:

  • bagging:自舉匯聚法(bootstrap aggregating),從原資料集中選擇若干次與原資料集大小相等的新資料集,每個資料的選擇是隨機的,即新資料集中,資料可能重複,原資料集中有些資料可能也不會出現在新資料集中。
  • boosting:分類器是序列訓練的,新分類器根據已有的分類器效能進行訓練,集中關注被已有分類器分錯的那些資料。

bagging 中,分類器的權重是相等的,boosting 中,分類器的權重不相等。

本篇中,主要介紹 boosting 中的 AdaBoost(adaptive boosting)。

  • 優點:泛化錯誤率低,易編碼
  • 缺點:對離群點敏感
  • 適用資料型別:數值型和標稱型資料

1. AdaBoost

AdaBoost 演算法流程可描述如下:

  1. 對每個訓練樣本設定相等的權重,即資料集構成權重向量 D
  2. 訓練一個加權錯誤率最低的最佳弱分類器
  3. 根據加權錯誤率,計算弱分類器的分類器權重 \alpha
  4. 根據權重向量 D 和弱分類器的分類器權重 \alpha ,更新權重向量 D
  5. 繼續訓練下一個弱分類器......
  6. 直到弱分類器達到指定數量,或者弱分類器的訓練錯誤率為 0
     為止
  7. 預測時,將資料通過每個弱分類器,將其結果加權求和

初始時,權重向量 D 被設定為每個樣本相同,即 \frac{1}{m} 。

弱分類器的加權錯誤率 \varepsilon 定義為錯誤樣本的權重相加。

弱分類器的分類器權重 \alpha 定義如下:

\alpha=\frac{1}{2}\ln(\frac{1-\varepsilon }{\varepsilon })

更新權重向量 D 時,若樣本分類正確,則權重下降,若樣本分類錯誤,則權重上升:

\begin{align*} D_i^{(t+1)} &= \frac{D_i^{(t)}e^{-\alpha}}{\sum_{i=1}^{m}D^{(t)}_i}\ (if\ correct) \\ D_i^{(t+1)} &= \frac{D_i^{(t)}e^{\alpha}}{\sum_{i=1}^{m}D^{(t)}_i}\ (if\ error) \end{align*}

注:AdaBoost 每次訓練的弱分類器,會集中關注那些被分類錯誤的樣本。

2. 單層決策樹

單層決策樹(decision stump,也稱決策樹樁),是僅僅基於單個特徵進行分類的弱分類器。

在本篇中,使用單層決策樹作為弱分類器,使用三重迴圈構建最佳的單層決策樹:

  1. 第一層迴圈:遍歷每一個特徵
  2. 第二層迴圈:遍歷此特徵的每一個閾值(即小於或者大於閾值,屬於一類)
  3. 第三層迴圈:遍歷小於閾值屬於正類大於閾值屬於反類,和小於閾值屬於反類大於閾值屬於正類

每一次都計算加權錯誤率,最後選擇加權錯誤率最小的單層決策樹作為這次的弱分類器。

3. 非均衡資料

對於資料是非均勻的情況,可根據實際情況選擇:

  • Precision、Recall、F-score(F1-measure)
  • TPR、FPR、TNR、FNR、AUC
  • Accuracy

例如,寧可將反類判成正類,也不願將正類判成反類,就可以使用 Recall。

這部分可以詳見 吳恩達機器學習(九)Precision、Recall、F-score、TPR、FPR、TNR、FNR、AUC、Accuracy

或者可對資料進行欠抽樣(undersampling)和過抽樣(oversampling):

  • 欠抽樣:刪除部分樣例,可將離決策邊界較遠的樣例刪除
  • 過抽樣:複製部分樣例,或者可加入與已有樣例相似的點

4. 實戰案例

以下將展示書中案例的程式碼段,所有程式碼和資料可以在github中下載:

4.1. 馬病死亡案例

# coding:utf-8
from numpy import *
import matplotlib.pyplot as plt

"""
馬病死亡案例
"""


# 載入資料集
def loadDataSet(fileName):
    numFeat = len(open(fileName).readline().split('\t'))
    dataMat = []
    labelMat = []
    fr = open(fileName)
    for line in fr.readlines():
        lineArr = []
        curLine = line.strip().split('\t')
        for i in range(numFeat - 1):
            lineArr.append(float(curLine[i]))
        dataMat.append(lineArr)
        labelMat.append(float(curLine[-1]))
    return dataMat, labelMat


# 根據特徵和閾值劃分資料類別
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
    retArray = ones((shape(dataMatrix)[0], 1))
    if threshIneq == 'lt':
        retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
    else:
        retArray[dataMatrix[:, dimen] > threshVal] = -1.0
    return retArray


# 建立單層決策樹
# 第一層迴圈:遍歷每一個特徵
# 第二層迴圈:遍歷每一個閾值
# 第三層迴圈:遍歷小於閾值的是正類還是反類
def buildStump(dataArr, classLabels, D):
    dataMatrix = mat(dataArr)
    labelMat = mat(classLabels).T
    m, n = shape(dataMatrix)
    # 閾值劃分數量
    numSteps = 10.0
    # 最佳的決策樹
    bestStump = {}
    # 最佳決策樹的分類結果
    bestClasEst = mat(zeros((m, 1)))
    # 最小的加權錯誤率
    minError = 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):
            # 第三層迴圈:遍歷小於閾值的是正類還是反類
            for inequal in ['lt', 'gt']:
                # 閾值
                threshVal = (rangeMin + float(j) * stepSize)
                # 根據特徵和閾值劃分資料
                predictedVals = stumpClassify(dataMatrix, i, threshVal, inequal)
                # 計算樣本是否預測錯誤
                errArr = mat(ones((m, 1)))
                errArr[predictedVals == labelMat] = 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


# 構建adaboost分類器
def adaBoostTrainDS(dataArr, classLabels, numIt=40):
    weakClassArr = []
    m = shape(dataArr)[0]
    # 初始化樣本權重向量
    D = mat(ones((m, 1)) / m)
    # 加權的每個樣本分類結果
    aggClassEst = mat(zeros((m, 1)))
    # 迭代
    for i in range(numIt):
        # 建立單層決策樹
        bestStump, error, classEst = buildStump(dataArr, classLabels, D)
        print("D:", D.T)
        # 計算弱分類器權重
        alpha = float(0.5 * log((1.0 - error) / max(error, 1e-16)))
        bestStump['alpha'] = alpha
        weakClassArr.append(bestStump)
        print("classEst: ", classEst.T)
        # 更新樣本權重向量
        expon = multiply(-1 * alpha * mat(classLabels).T, classEst)
        D = multiply(D, exp(expon)) / D.sum()
        # 更新每個樣本的加權分類結果
        aggClassEst += alpha * classEst
        print("aggClassEst: ", aggClassEst.T)
        # 計算當前加權的錯誤率
        aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T, ones((m, 1)))
        errorRate = aggErrors.sum() / m
        print("total error: ", errorRate)
        if errorRate == 0.0: break
    return weakClassArr, aggClassEst


# 分類函式
def adaClassify(datToClass, classifierArr):
    dataMatrix = mat(datToClass)
    m = shape(dataMatrix)[0]
    # 加權的預測結果
    aggClassEst = mat(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
        print(aggClassEst)
    return sign(aggClassEst)


# 畫ROC曲線
def plotROC(predStrengths, classLabels):
    cur = (1.0, 1.0)
    ySum = 0.0
    # 正類數量
    numPosClas = sum(array(classLabels) == 1.0)
    # 1/正類數量
    yStep = 1 / float(numPosClas)
    # 1/反類數量
    xStep = 1 / float(len(classLabels) - numPosClas)
    # 按照從小到大,索引排序
    sortedIndicies = predStrengths.argsort()
    fig = plt.figure()
    fig.clf()
    ax = plt.subplot(111)
    # 遍歷排序後的索引
    # 表示屬於正類的概率
    # 因排序,屬於正類的概率越來越大,條件越來越苛刻
    # 由初始 TPF->1 FPR->1
    # 最終 TPR->0 FPR->0
    for index in sortedIndicies.tolist()[0]:
        if classLabels[index] == 1.0:
            delX = 0
            delY = yStep
        else:
            delX = xStep
            delY = 0
            ySum += cur[1]
        ax.plot([cur[0], cur[0] - delX], [cur[1], cur[1] - delY], c='b')
        # 更新cur
        cur = (cur[0] - delX, cur[1] - delY)
    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()
    # 微積分算AUC面積
    # 對多個小長方塊求面積之和
    # 小長方塊的寬為 xStep
    # 每一次的長為 cur[1]_i
    # 即 cur[1]_1 * xStep + ... + cur[1]_n * xStep
    # 即 ySum * xStep
    print("the Area Under the Curve is: ", ySum * xStep)


if __name__ == '__main__':
    datArr, labelArr = loadDataSet('horseColicTraining2.txt')
    classifierArr, aggClassEst = adaBoostTrainDS(datArr, labelArr, 50)
    testArr, testLabelArr = loadDataSet('horseColicTest2.txt')
    pred = adaClassify(testArr, classifierArr)
    errArr = mat(ones((67, 1)))
    errNum = errArr[pred != mat(testLabelArr).T].sum()
    print(float(errNum) / len(testLabelArr))
    plotROC(aggClassEst.T, labelArr)

如果這篇文章對你有一點小小的幫助,請給個關注喔~我會非常開心的~