機器學習實戰之adaboost(附python3代碼)

本節代碼存放在github網址上,環境:python3
利用AdaBoost元算法提高分類性能

元算法(meta-algorithm)或者集成方法(ensemble method)是對其他算法進行組合的一種方式
AdaBoost算法
優點:範化錯誤率低,易編碼,可以應用在大部分分類器上,無參數調整
缺點:對離羣點敏感
適用數據類型: 數值型和標稱型數據

bagging和boosting所使用的多個分類器類型都是一致的!

bagging: 基於數據隨機抽樣的分類器構建方法

放回取樣得到S個數據集,將某個學習算法分別作用於每個數據集就得到了S個分類器,要對新數據進行分類時,可以應用這S個分類器進行分類,
選取分類器投票結果中最多的類別作爲最後的分類結果。分類器的權重是相等的。

random forest 也是bagging方法的一種

boosting:通過集中關注被已有分類器錯分的那些數據來獲得新的分類器

不同的分類器是通過串行訓練獲得的,每個新的分類器都根據已訓練出的分類器的性能來訓練。
其分類結果是基於所有分類器的加權求和結果的,分類器權重並不相等,每個權重代表的是其對應分類器在上一輪迭代中的成功度。

AdaBoost一般流程

  1. 收集數據:可以使用任何方法
  2. 準備數據:依賴於所使用的弱分類器類型,本章使用的是單層決策樹,這種分類器可以處理任何數據類型。當然也可以使用任意分類器作爲弱分類器,KNN, 決策樹,樸素貝葉斯,Logistic迴歸,SVM中任一分類器都可以充當弱分類器。作爲弱分類器,簡單分類器的效果最好。 確保類別標籤是+1和-1,而非1和0.
  3. 分析數據:可以使用任一方法分析數據:可以使用任一方法
  4. 訓練算法:AdaBoost的大部分時間都用在訓練上, 分類器將多次在同一數據集上訓練弱分類器。
  5. 測試算法:計算分類的錯誤率測試算法:計算分類的錯誤率
  6. 使用算法:同SVM同,AdaBoost預測兩個類別中的一個。若想把它應用到多分類的場合,需要像多類SVM中的做法一樣對AdaBoost進行修改。使用算法:同SVM同,AdaBoost預測兩個類別中的一個。若想把它應用到多分類的場合,需要像多類SVM中的做法一樣對AdaBoost進行修改。

AdaBoost運行過程

訓練數據中的每個樣本,並賦予其一個權重,這些權重構成了向量D. 一開始,這些權重都初始化成相等值。首先在訓練集上訓練出一個弱分類器,並計算該分類器的錯誤率,然後在同一數據集上再次訓練弱分類器。在分類器的第二次訓練中會調整每個樣本的權重,其中第一次分對的的樣本中的權重將會降低,而第一次分錯的樣本中的權重將會提高。爲了從所有弱分類器中得到最終的分類結果,AdaBoost爲每個弱分類器都分配了一個權重值alpha,這些alpha是基於每個弱分類器的錯誤率進行計算的。
錯誤率ε定義爲

在這裏插入圖片描述

alpha定義爲:

在這裏插入圖片描述

計算出alpha後,可以對權重D進行更新,以使那些正確分類的樣本權重降低而錯分樣本的權重提高。D的計算方法如下:
若某個樣本被正確分類,該樣本權重更改爲:
在這裏插入圖片描述

若某個樣本被錯分,該樣本權重更改爲:

在這裏插入圖片描述

基於單層決策樹構建弱分類器

僞代碼如下:

將最小錯誤率minError設爲+inf
對數據集中每個特徵(第一層循環):
	對每個步長(第二層循環):
		對每個不等號(第三層循環):
			建立一棵單層決策樹並利用加權數據集對它進行測試
			如果錯誤率低於minError,則將當前單層決策樹設爲最佳單層決策樹
返回最佳單層決策樹

python代碼如下:

# -*- coding: utf-8 -*-

__author__ = 'WF'
# import warnings
import numpy as np
# warnings.simplefilter(action='ignore', category=FutureWarning)

# 加載簡單數據集
def loadSimpleData():
    dataMat = np.matrix([[1.,  2.1],
                         [2.,  1.1],
                         [1.3,  1.],
                         [1.,  1.],
                         [2.,  1.]])
    classLabel = [1.0, 1.0, -1.0, -1.0, 1.0]
    return dataMat, classLabel

# 圖形化展示數據
def plot_simpledata():
    import matplotlib.pyplot as plt
    x, y = loadSimpleData()
    y = np.array(y)
    x = np.array(x)
    # print(y)
    # x1 = x[np.nonzero(y)[0]]
    x1 = x[np.where(y == 1.0)[0]]
    # print(np.nonzero(y)[0])
    x0 = x[np.where(y == -1.0)[0]]
    # print(x0[:, 0], x0[:, 1])
    figure = plt.figure()
    ax = figure.add_subplot(111)
    ax.scatter(x0[:, 0], x0[:, 1], marker='<', c='r', s=50)
    ax.scatter(x1[:, 0], x1[:, 1], marker='s', c='g', s=20)
    plt.show()

# 單層決策樹生成函數
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
    '''
    通過閾值比較對數據進行分類,所有在閾值一邊的數據會被分到-1,另一邊的數據被分到1
    :param dataMatrix:數據矩陣
    :param dimen: 維度屬性
    :param threshVal: 閾值
    :param threshIneq:閾值比較符號
    :return:單層決策樹字典,錯誤率,類別估計
    '''
    retArray = np.ones((np.shape(dataMatrix)[0], 1))
    if threshIneq == 'lt':
        retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
    else:
        retArray[dataMatrix[:, dimen] > threshVal] = -1.0
    return retArray

# 遍歷stumpClassify()函數所有的可能輸入值,並找到數據集上最佳的單層決策樹
def buildStump(dataArr, classLabels, D):
    # classLabels = np.reshape(classLabels, (len(classLabels), 1))

    dataMatrix = np.mat(dataArr)
    labelMat = np.mat(classLabels).T
    # print(np.shape(labelMat))   # (5,1)
    m, n = np.shape(dataMatrix)
    numSteps = 10.0
    bestStump = {}
    bestClasEst = np.mat(np.zeros((m, 1)))
    minError = np.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)
                # print('p:', predictedVals)
                # print(np.shape(predictedVals))  #(5, 1)
                errArr = np.mat(np.ones((m, 1)))
                # print(np.where(predictedVals == labelMat)[0])
                # for num in range(len(predictedVals)):
                #     print(predictedVals[num] == classLabels[num])
                #     if float(predictedVals[num]) == float(classLabels[num]):
                #         errArr[num][0] = 0
                # print(errArr)
                errArr[predictedVals == labelMat] =0
                # print(errArr)
                weightedError = D.T * errArr   # 計算加權錯誤率
                print("split:dim %d, thresh %.2f, thresh ineqal: %s, the weighted error is %.3f" \
                      %(i, threshVal, inequal, weightedError))
                if weightedError < minError:
                    minError = weightedError
                    bestClasEst = predictedVals.copy()
                    bestStump['dim'] = i
                    bestStump['thresh'] = threshVal
                    bestStump['ineq'] = inequal
    return bestStump, minError, bestClasEst

**

完整AdaBoost算法實現

**
僞代碼:

對每次迭代:
	利用buildStump()函數找到最佳的單層決策樹
	將最佳的單層決策樹加入到單層決策樹組
	計算alpha
	計算新的權重向量D
	更新累計類別估計值
	如果錯誤率等於0.0, 則退出循環

python代碼如下:

def adaBoostTrainDS(dataArr, classLabels, numIt = 40):
 	'''
    :param dataArr:數據集(不包含label)
    :param classLabels: 類別標籤
    :param numIt: 迭代次數
    :return:
    '''
    weakClassArr = []
    m = np.shape(dataArr)[0]
    D = np.mat(np.ones((m, 1))/m)
    aggClassEst = np.mat(np.zeros((m, 1)))
    for i in range(numIt):
        bestStump, error, classEst = buildStump(dataArr, classLabels, D)
        print('D:', D.T)
        alpha = float(0.5*np.log((1.0-error)/max(error, 1e-16)))
        bestStump['alpha'] = alpha
        weakClassArr.append(bestStump)
        print('classEst:', classEst.T)
        # 爲下一次迭代計算D
        expon = np.multiply(-1*alpha*np.mat(list(map(float, classLabels))).T, classEst)
        D = np.multiply(D, np.exp(expon))
        D = D/D.sum()
        aggClassEst += alpha*classEst
        print('aggClassEst:', aggClassEst.T)
        aggErrors = np.multiply(np.sign(aggClassEst) != np.mat(classLabels).T, np.ones((m, 1)))
        errorRate = aggErrors.sum()/m
        print('errorRate:', errorRate)
        if errorRate == 0.0:
            break
    return weakClassArr, aggClassEst

基於AdaBoost的分類

每個弱分類器的結果以其對應的alpha作爲權重,所有這些弱分類器的結果加權求和就得到最後的結果。程序返回aggClassEst(記錄每個數據點的類別估計累計值)的符號,如果aggClassEst大於0則返回+1, 否則返回-1
python代碼如下:

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
        print('aggClassEst:', aggClassEst)
    return(np.sign(aggClassEst))

完整代碼如下

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

__author__ = 'WF'
# import warnings
import numpy as np
# warnings.simplefilter(action='ignore', category=FutureWarning)

def loadSimpleData():
    dataMat = np.matrix([[1.,  2.1],
                         [2.,  1.1],
                         [1.3,  1.],
                         [1.,  1.],
                         [2.,  1.]])
    classLabel = [1.0, 1.0, -1.0, -1.0, 1.0]
    return dataMat, classLabel


def adaptive_load_data(filename):
    numFeat = len(open(filename).readline().strip().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 plot_simpledata():
    import matplotlib.pyplot as plt
    x, y = loadSimpleData()
    y = np.array(y)
    x = np.array(x)
    # print(y)
    # x1 = x[np.nonzero(y)[0]]
    x1 = x[np.where(y == 1.0)[0]]
    # print(np.nonzero(y)[0])
    x0 = x[np.where(y == -1.0)[0]]
    # print(x0[:, 0], x0[:, 1])
    figure = plt.figure()
    ax = figure.add_subplot(111)
    ax.scatter(x0[:, 0], x0[:, 1], marker='<', c='r', s=50)
    ax.scatter(x1[:, 0], x1[:, 1], marker='s', c='g', s=20)
    plt.show()


def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
    '''
    通過閾值比較對數據進行分類,所有在閾值一邊的數據會被分到-1,另一邊的數據被分到1
    :param dataMatrix:數據矩陣
    :param dimen: 維度屬性
    :param threshVal: 閾值
    :param threshIneq:閾值比較符號
    :return:單層決策樹字典,錯誤率,類別估計
    '''
    retArray = np.ones((np.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):
    # classLabels = np.reshape(classLabels, (len(classLabels), 1))

    dataMatrix = np.mat(dataArr)
    labelMat = np.mat(classLabels).T
    # print(np.shape(labelMat))   # (5,1)
    m, n = np.shape(dataMatrix)
    numSteps = 10.0
    bestStump = {}
    bestClasEst = np.mat(np.zeros((m, 1)))
    minError = np.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)
                # print('p:', predictedVals)
                # print(np.shape(predictedVals))  #(5, 1)
                errArr = np.mat(np.ones((m, 1)))
                # print(np.where(predictedVals == labelMat)[0])
                # for num in range(len(predictedVals)):
                #     print(predictedVals[num] == classLabels[num])
                #     if float(predictedVals[num]) == float(classLabels[num]):
                #         errArr[num][0] = 0
                # print(errArr)
                errArr[predictedVals == labelMat] =0
                # print(errArr)
                weightedError = D.T * errArr   # 計算加權錯誤率
                print("split:dim %d, thresh %.2f, thresh ineqal: %s, the weighted error is %.3f" \
                      %(i, threshVal, inequal, weightedError))
                if weightedError < minError:
                    minError = weightedError
                    bestClasEst = predictedVals.copy()
                    bestStump['dim'] = i
                    bestStump['thresh'] = threshVal
                    bestStump['ineq'] = inequal
    return bestStump, minError, bestClasEst


def adaBoostTrainDS(dataArr, classLabels, numIt = 40):
    '''

    :param dataArr:數據集(不包含label)
    :param classLabels: 類別標籤
    :param numIt: 迭代次數
    :return:
    '''
    weakClassArr = []
    m = np.shape(dataArr)[0]
    D = np.mat(np.ones((m, 1))/m)
    aggClassEst = np.mat(np.zeros((m, 1)))
    for i in range(numIt):
        bestStump, error, classEst = buildStump(dataArr, classLabels, D)
        print('D:', D.T)
        alpha = float(0.5*np.log((1.0-error)/max(error, 1e-16)))
        bestStump['alpha'] = alpha
        weakClassArr.append(bestStump)
        print('classEst:', classEst.T)
        # 爲下一次迭代計算D
        expon = np.multiply(-1*alpha*np.mat(list(map(float, classLabels))).T, classEst)
        D = np.multiply(D, np.exp(expon))
        D = D/D.sum()
        aggClassEst += alpha*classEst
        print('aggClassEst:', aggClassEst.T)
        aggErrors = np.multiply(np.sign(aggClassEst) != np.mat(classLabels).T, np.ones((m, 1)))
        errorRate = aggErrors.sum()/m
        print('errorRate:', errorRate)
        if errorRate == 0.0:
            break
    return weakClassArr, aggClassEst


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
        print('aggClassEst:', aggClassEst)
    return(np.sign(aggClassEst))

def plotROC(predStrengths, classLabels):
    import matplotlib.pyplot as plt
    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()   # 清除當前 figure 的所有axes,但是不關閉這個 window,所以能繼續複用於其他的 plot。
    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]
        ax.plot([cur[0], cur[0]-delX], [cur[1], cur[1]-delY], c='b')
        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()
    print('the Area Under the Curve is:', ySum*xStep)


if __name__ == "__main__":
    dataMat, classLabels = loadSimpleData()
    # plot_simpledata()

    # D = np.mat(np.ones((5, 1))/5)
    # bestStump, minError, bestClasEst = buildStump(dataMat, classLabels, D)
    # print(bestStump, minError, bestClasEst)

    # weakClassArr = adaBoostTrainDS(dataMat, classLabels, 9)
    # print(weakClassArr)
    #
    # print(adaClassify([[0, 0], [5, 5]], weakClassArr))

    datArr, labelArr = adaptive_load_data('horseColicTraining2.txt')
    # print(datArr)
    print(labelArr)
    classifierArr, aggClassEst = adaBoostTrainDS(datArr, labelArr, 10)
    # testArr, testLabelArr = adaptive_load_data('horseColicTest2.txt')
    # prediction10 = adaClassify(testArr, classifierArr)
    # print(prediction10.T)
    # errArr = np.mat(np.ones((67, 1)))
    # errnum = errArr[prediction10 != np.mat(testLabelArr).T].sum()
    # print('errornum:', errnum, 'errorRate:', errnum/67)

    plotROC(aggClassEst.T, labelArr)

分類性能度量指標:準確率、召回率、ROC曲線及AUC

TP(真陽):將正例判定爲正例
FP(假陽):將反例判定爲正例
TN(真陰):將反例判定爲反例
FN(假陰):將正例判定爲反例
錯誤率指在所有測試樣例中錯分的比例。
準確率指在預測爲正例的樣本中真正正例的比例

P = TP/(TP+FP)

召回率查全率)指預測爲正例的真正正例佔所有真實正例的比例

R=TP/(TP+FN)

我們很容易構造一個高準確率或者高召回率的分類器,但是很難保證兩者同時成立。如果將任何樣本都判爲正例,則召回率達到100%,但是準確率很低。

另一個用來度量分類中的非均衡性的工具是ROC曲線(ROC Curve),ROC代表接收者操作特徵(receiver operating characteristic)
ROC曲線中,橫軸是假陽率=FP/(FP+TN),縱軸是真正率=TP/(TP+FN).
ROC曲線給出的是當閾值變化時假陽率和真陽率變化的情況。左下角點是將所有樣例判爲反例的情況。
右上角是將所有樣例判爲正例的情況。
理想情況下,最佳分類器應該儘可能的處於左上角,即分類器在假陽率很低的同時獲得了很高的真陽率。
對不同的ROC曲線進行比較的一個指標是曲線下的面積AUC(Area Under the Curve),其給出的是分類器的平均性能值,一個完美的分類器AUC爲1.0, 隨機猜測的AUC爲0.5.

上述代碼的ROC曲線如下圖:
在這裏插入圖片描述

基於代價函數的分類器決策控制

cost = TP*0 + FP*1 + TN*0 + FN*1
cost = TP*(-5) + FP*50 + TN*0 + FN*1

處理非均衡問題的數據抽樣方法

過抽樣或者欠抽樣
過抽樣是複製樣例,欠抽樣是刪除樣例,選擇離決策邊界較遠的樣例進行刪除。

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