1. 程式人生 > >機器學習實戰第7章——利用AdaBoost元算法提高分類性能

機器學習實戰第7章——利用AdaBoost元算法提高分類性能

nes 重要性 function mine spl 技術 可能 copy elar

將不同的分類器組合起來,這種組合結果被稱為集成方法或元算法(meta-algorithm)。

使用集成方法時會有多種形式:(1)可以是不同算法的集成(2)可以是同一種算法在不同設置下的集成(3)數據集不同部分分配給不同分類器之後的集成,等等

接下來介紹基於同一種分類器多個不同實例的兩種不同計算方法bagging和boosting

1. bagging

  原理:從原始數據集選擇S次後得到S個新數據集的一種技術。新數據集和原數據集的大小相等。每個數據集都是通過在原始數據集中隨機選擇一個樣本來進行替換而得到的。這裏的替換就意味著可以多次地選擇同一樣本,允許重復。(取出,放回)。將某個學習算法分別作用於每個數據集就得到S個分類器。每個分類器的權值相同

2. boosting

  原理:集中關註被已有分類器錯分的那些數據來獲得新的分類器。是所有分類器加權求和結果,每個分類器的權值不同。

  從弱學習算法出發,反復學習,得到一系列弱分類器(又稱為基本分類器),然後組合這些弱分類器,構成一個強分類器。大多的boosting方法都是改變訓練數據的概率分布(訓練數據的權值分布),針對不同的訓練數據分布調用弱學習算法學習一系列弱分類器。

  那麽對於boosting方法,有兩個問題需要回答:(1) 在每一輪如何改變訓練數據的權值或者概率分布?

                       (2)如何將弱分類器組合成一個強分類器?

  接下來介紹boosting中一個最流行的算法AdaBoost,在AdaBoost是如何解決上面兩個問題的呢?對於第一個問題,AdaBoost的做法是,提高那些被前一輪弱分類器錯誤分類樣本的權值,而降低被正確分類樣本的權值。對於第二個問題,AdaBoost采取加權多數表決的方法,加大分類誤差率小的弱分類器的權值,減小分類誤差率大的弱分類器的權值

3. AdaBoost算法

    技術分享圖片

下面是具體的算法:

    技術分享圖片

  技術分享圖片

  對於(2)中的(d)根據下式得到

      技術分享圖片

具體代碼如下:

import numpy as np
from math import log


def loadSimpData():
    datMat = np.matrix([
        [1. , 2.1],
        [2, 1.1],
        [1.3, 1.],
        [1., 1.],
        [2., 1.]
    ])
    classLabels = [1.0
, 1.0, -1.0, -1.0, 1.0] return datMat, classLabels def loadDataSet(fileName): #general function to parse tab -delimited floats numFeat = len(open(fileName).readline().split(\t)) #get number of fields 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 """ 函數說明:通過閾值比較對數據進行分類 Parameters: dataMatrix -輸入數據 dimen -第幾列 threshVal -閾值 threshIneq -不等號 return: 分類結果 """ def stumpClassify(dataMatrix, dimen, threshVal, threshIneq): 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 """ 函數說明:建立單層決策樹 Parameters: dataArr -輸入數據集 classLabels -分類 D -權重向量 return: bestStump -最佳單層決策樹字典 minError -最小錯誤率 bestClassEst -最佳類別估計值 """ def buildStump(dataArr, classLabels, D): dataMatrix = np.mat(dataArr) labelMat = np.mat(classLabels).T m, n = np.shape(dataMatrix) #numSteps:步數; bestStump:保存最佳單層決策樹的相關信息; bestClassEst:最佳預測結果 numSteps = 10.0; bestStump = {}; bestClassEst = 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) errArr = np.mat(np.ones((m, 1))) errArr[predictedVals == labelMat] = 0 weightError = D.T * errArr #計算加權錯誤率 print("split:dim %d,thresh %.2f,thresh inequal: %s,the weighted error is %.3f"%(i,threshVal,inequal,weightError)) # 找到最小錯誤率的最佳單層決策樹,並保存相關信息 if weightError < minError: minError = weightError bestClassEst = predictedVals.copy() bestStump[dim] = i bestStump[thresh] = threshVal bestStump[ineq] = inequal return bestStump, minError, bestClassEst """ 函數說明:基於單層決策樹的AdaBoost訓練過程 Patameters: dataArr -數據集 classLabels -數據標簽 numIt -叠代次數 return: weakClassArr -所有的最佳分類器決策樹 aggClassEst -累計分類估計 """ def adaBoostTrainDS(dataArr,classLabels,numIt=40): weakClassArr = [] m = np.shape(dataArr)[0] D = np.mat(np.ones((m,1))/m) #init D to all equal aggClassEst = np.mat(np.zeros((m,1))) for i in range(numIt): bestStump,error,classEst = buildStump(dataArr,classLabels,D)#build Stump print("D:",D.T) # alpha公式:alpha=1/2*ln((1-error)/error) # max(error, 1e-16)是為了防止error=0的情況 alpha = float(0.5 * log((1.0 - error) / max(error, 1e-16))) bestStump[alpha] = alpha weakClassArr.append(bestStump) #保存最優單層決策樹 print("classEst: ",classEst.T) #更新D,D是一個概率分布向量,總和為1 # D=(D*exp(-alpha*classLabels*classEst))/D.sum() # classEst是分類器預測的分類結果,classEst是 # np.multiply是矩陣對應元素相乘,返回和矩陣大小一樣,classLabels是m*1,classEst是1*m expon = np.multiply(-1 * alpha * np.mat(classLabels).T,classEst) #exponent for D calc, getting messy D = np.multiply(D, np.exp(expon)) #Calc New D for next iteration D = D / D.sum() #更新累計估計類別 #aggClassEst記錄每個類別的累計估計值,最終分類函數為f(x) = sign(aggClassEst) 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("total error: ",errorRate) #誤差為0時退出循環 if errorRate == 0.0: break return weakClassArr,aggClassEst
""" 函數說明:測試算法,AdaBoost分類函數 Parameters: datToClass -待分類樣例 classifierArr -多個弱分類器組成的數組 return: 類別 """ 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)): #基於每個分類器得到一個類別值 print(classifierArr[i]) classEst = stumpClassify(dataMatrix,classifierArr[i][dim], classifierArr[i][thresh], classifierArr[i][ineq])#call stump classify aggClassEst += classifierArr[i][alpha]*classEst print(aggClassEst) return np.sign(aggClassEst)
if __name__ == __main__: datMat, classLabels = loadSimpData() D = np.mat(np.ones((5,1)) / 5) buildStump(datMat, classLabels, D) classifierArr, aggClassEst = adaBoostTrainDS(datMat, classLabels) print(adaClassify([0,0],classifierArr)) print(adaClassify([[0,0],[1,0],[2,2]],classifierArr)) #將第5章的馬疝病數據應用到AdaBoost算法中 datArr, labelArr = loadDataSet(horseColicTraining2.txt) classifierArr,aggClassEst = adaBoostTrainDS(datArr, labelArr, 10) testArr, testLableArr = loadDataSet(horseColicTest2.txt) prediction = adaClassify(testArr, classifierArr) errArr = np.mat(np.ones((67,1))) errRate = errArr[prediction!= np.mat(testLableArr).T].sum() / 67 print(errRate)

技術分享圖片

4. 非均衡分類問題

  非均衡分類問題是指,在很多情況下不同類別的分類代價並不相等。例如對於垃圾郵件過濾的例子,如果我們把所有的郵件都預測為垃圾郵件,則用戶可能會錯過重要的合法郵件,如果我們把所有的郵件都預測為合法郵件,則會影響到用戶體驗,但是這兩種代價是不一樣的。

性能度量

  性能度量是指衡量模型泛化能力的評價標準。在回歸問題中常用的性能度量是“均方誤差”(mean squared error)。而在分類問題中錯誤率是一種常用的性能度量,錯誤率是指分類錯誤的樣本占樣本總數的比例,實際上這樣的度量錯誤掩蓋了樣例如何被錯分的事實。在機器學習中,有一個普遍使用的稱為混淆矩陣的工具,它可以幫助人們更好的了解分類中的錯誤。

技術分享圖片

   正確率/查準率(precision):預測為正例的樣本中真正正例的比例

            技術分享圖片

   召回率/查全率(recall):預測為正例的真正例占所有正例的比例

            技術分享圖片

  通常查準率和查全率是一對矛盾的度量。一般來說查準率高的時候,查全率會偏低;而查全率高的時候,查準率會偏低。通常只有在一些簡單任務中查準率和查全率都很高。

  P-R圖能直觀的顯示出學習器在樣本總體上的查全率、查準率。

        技術分享圖片

  圖中的平衡點(BEP)是查準率=查全率時的取值,BEP高則更優,對於圖中的三個學習器,A優於B優於C。

  但BEP還是過於簡化了,通常使用的是F1度量

      技術分享圖片

  對於有偏好的情況:

      技術分享圖片

  其中ß>O 度量了查全率對查準率的相對重要性. ß = 1時退化為標準的F1; ß> 1 時查全率有更大影響; ß < 1 時查準率有更大影響

  

  接下來介紹ROC曲線

      技術分享圖片

  縱坐標為真陽率(真正例率):

        技術分享圖片

  橫坐標為假陽率(假正例率):

        技術分享圖片

  AUC(area under the curve):ROC曲線下的面積,對不同的ROC曲線進行比較的一個指標,給出的是分類器的平均性能值。一個完美的分類器的AUC是1,而隨機猜測的AUC則為0.5。若一個學習器的ROC曲線能把另一個學習器的ROC曲線完全包住,則這個學習器的性能比較好。

  實現代碼如下,書中是以(1.0,1.0)點為起點來畫曲線的,我改成了以(0.0,0.0)為起點,這樣更好理解一些。

  繪制過程:給定m+個正例,m-個反例,將分類器的預測強度進行排序,然後把分類閾值設為最大,即把所有的樣例都預測為反例,此時真陽率和假陽率均為0,在坐標(0,0)處標記一個點。然後將分類閾值依次設置為每個樣例的預測值,即依次將每個樣例劃分為正例。設前一個坐標點為(x,y),若當前點為真正例,則它的坐標為(x,y+1/m+),真陽率變大;若當前點為假正例,則坐(x+1/m-,y),假陽率變大。

"""
    函數說明:ROC曲線繪制及AUC計算函數
    Parameters:
        predStrengths   -分類器的預測強度,馬疝病數據集中為1*299
        classLabels     -分類標簽
"""
def plotROC(predStrengths, classLabels):
    import matplotlib.pyplot as plt
    # cur = (1.0,1.0) #繪制光標的位置
    cur = (0.0,0.0) #繪制光標的位置
    ySum = 0.0 #用於計算AUC的值
    numPosClas = sum(np.array(classLabels)==1.0)    #保存正例的數目
    yStep = 1/float(numPosClas); xStep = 1/float(len(classLabels)-numPosClas)   #y軸的步長、x軸的步長
    sortedIndicies = predStrengths.argsort()#獲取排好序的索引,從小到大
    fig = plt.figure()
    fig.clf()
    ax = plt.subplot(111)
    #loop through all the values, drawing a line segment at each point
    for index in sortedIndicies.tolist()[0][::-1]:  #改為從大到小
        if classLabels[index] == 1.0:   #標簽為1修改真陽率
            delX = 0; delY = yStep;
        else:                           #標簽不為1,修改假陽率
            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)
        ax.plot([cur[0],cur[0]+delX],[cur[1],cur[1]+delY], c=b)
        # cur = (cur[0]-delX,cur[1]-delY)
        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)   #AUC,小矩形面積之和


if __name__ == __main__:
    datArr, labelArr = loadDataSet(horseColicTraining2.txt)
    classifierArr,aggClassEst = adaBoostTrainDS(datArr, labelArr, 50)
    #testArr, testLableArr = loadDataSet(horseColicTest2.txt)
    #prediction = adaClassify(testArr, classifierArr)
    #errArr = np.mat(np.ones((67,1)))
    #errRate = errArr[prediction!= np.mat(testLableArr).T].sum() / 67
    #print(errRate)
    plotROC(aggClassEst.T, labelArr)

結果如下:

    技術分享圖片

參考:

《機器學習實戰》

《機器學習》周誌華

《統計學習方法》

機器學習實戰第7章——利用AdaBoost元算法提高分類性能