統計學習方法之 adaBoost

轉載:http://www.hankcs.com/ml/adaboost.html


本文是《統計學習方法》第8章提升方法的筆記,整合了《機器學習實戰》中的提升樹Python代碼,並添加了註解和PR值計算代碼。《方法》重理論,但不易理解,《實戰》重實踐,但缺乏理論基礎,特別是AdaBoost算法的解釋、提升樹與加法模型的關係等。兩相結合,應該能獲得較爲全面的知識。

提升方法AdaBoost算法

提升方法的思路是綜合多個分類器,得到更準確的分類結果。    adaboost.png

AdaBoost算法的歸類

《統計學習方法》稱AdaBoost是提升算法的代表,所謂提升算法,指的是一種常用的統計學習方法,應用廣泛且有效。在分類問題中,它通過改變訓練樣本的權重,學習多個分類器,並將這些分類器進行線性組合,提髙分類的性能。

《機器學習實戰》稱AdaBoost是最流行的元算法,所謂元算法,指的是“學習算法的算法”。

AdaBoost算法的基本思想

  1. 多輪訓練,多個分類器

  2. 每輪訓練增加錯誤分類樣本的權值,降低正確分類樣本的權值

  3. 降低錯誤率高的分類器的權值,增加正確率高的分類器的權值

AdaBoost算法

給定一個二類分類的訓練數據集

屏幕快照 2016-04-05 下午9.45.50.png

其中,每個樣本點由實例與標記組成。實例屏幕快照 2016-04-05 下午9.48.06.png,標記屏幕快照 2016-04-05 下午9.49.10.png屏幕快照 2016-04-05 下午9.50.08.png是實例空間,屏幕快照 2016-04-05 下午9.50.48.png是標記集合。AdaBoost利用以下算法,從訓練數據中學習一系列弱分類器或基本分類器,並將這些弱分類器線性組合成爲一個強分類器。

AdaBoost算法

輸入:訓練數據集屏幕快照 2016-04-05 下午9.45.50.png:弱學習算法;

輸出:最終分類器屏幕快照 2016-04-05 下午9.55.46.png

(1)初始化訓練數據的權值分佈

屏幕快照 2016-04-05 下午9.57.10.png

每個w的下標由兩部分構成,前一個數表示當前迭代次數,與D的下標保持一致,後一個數表示第幾個權值,與位置保持一致。初始情況下,每個權值都是均等的。

(2)屏幕快照 2016-04-05 下午9.57.59.png(這裏的M原著未做解釋,其實是表示訓練的迭代次數,是由用戶指定的。每輪迭代產生一個分類器,最終就有M個分類器):

(a)使用具有權值分佈的訓練數據集學習,得到基本分類器

屏幕快照 2016-04-05 下午10.04.06.png

(b)計算屏幕快照 2016-04-05 下午10.04.40.png在訓練數據集上的分類誤差率

屏幕快照 2016-04-05 下午10.05.04.png

分類誤差率這個名字可能產生誤解,這裏其實是個加權和。

(c)計算屏幕快照 2016-04-05 下午10.04.40.png的係數

屏幕快照 2016-04-05 下午10.06.18.png

這裏的對數是自然對數。屏幕快照 2016-04-06 上午9.08.00.png表示屏幕快照 2016-04-05 下午10.04.40.png在最終分類器中的重要性。由式屏幕快照 2016-04-05 下午10.06.18.png可知,當屏幕快照 2016-04-06 上午9.09.34.png時,屏幕快照 2016-04-06 上午9.09.54.png,並且屏幕快照 2016-04-06 上午9.08.00.png隨着屏幕快照 2016-04-06 上午9.10.19.png的減小而增大,所以分類誤差率越小的基本分類器在最終分類器中的作用越大。

爲什麼一定要用這個式子呢?這與前向分步算法的推導有關,在後面的章節會介紹。

(d)更新訓練數據集的權值分佈

屏幕快照 2016-04-05 下午10.09.44.png

y只有正負一兩種取值,所以上式可以寫作:

屏幕快照 2016-04-06 上午9.12.53.png

這裏,屏幕快照 2016-04-05 下午10.12.44.png是規範化因子

屏幕快照 2016-04-05 下午10.13.05.png

它使屏幕快照 2016-04-05 下午10.13.35.png成爲一個概率分佈。

由此可知,被基本分類器誤分類樣本的權值得以擴大,而被正確分類樣本的權值卻得以縮小。兩相比較,誤分類樣本的權值被放大屏幕快照 2016-04-06 上午9.14.37.png倍。因此,誤分類樣本在下一輪學習中起更大的作用。不改變所給的訓練數據,而不斷改變訓練數據權值的分佈,使得訓練數據在基本分類器的學習中起不同的作用,這是AdaBoost的一個特點。

(3)構建基本分類器的線性組合

屏幕快照 2016-04-05 下午10.14.24.png

得到最終分類器

屏幕快照 2016-04-05 下午10.14.51.png

AdaBoost算法的解釋

AdaBoost算法還有另一個解釋,即可以認爲AdaBoost算法是模型爲加法模型、損失函數爲指數函數、學習算法爲前向分步算法時的二類分類學習方法。

爲什麼還要學習前向分步算法呢?直接給我AdaBoost的代碼不就好了嗎?因爲只有理解了前向分步算法,才能理解AdaBoost爲什麼能跟決策樹組合起來。

前向分步算法

考慮加法模型(additive model)

屏幕快照 2016-04-06 上午10.30.25.png

其中,屏幕快照 2016-04-06 上午10.31.45.png爲基函數,屏幕快照 2016-04-06 上午10.43.40.png爲基函數的參數,屏幕快照 2016-04-06 上午10.44.10.png爲基函數的係數。顯然,屏幕快照 2016-04-05 下午10.14.24.png是一個加法模型。

在給定訓練數據及損失函數的條件下,學習加法模型屏幕快照 2016-04-06 下午4.26.02.png成爲經驗風險極小化即損失函數極小化問題:

屏幕快照 2016-04-06 下午4.28.18.png

通常這是一個複雜的優化問題。前向分步算法(forward stage wise algorithm)求解這一優化問題的想法是:因爲學習的是加法模型,如果能夠從前向後,每一步只學習一個基函數及其係數,逐步逼近優化目標函數式屏幕快照 2016-04-06 下午4.28.18.png(L應該是loss的縮寫,表示一個損失函數,輸入正確答案yi和模型預測值,輸出損失值),那麼就可以簡化優化的複雜度。具體地,每步只需優化如下損失函數:

屏幕快照 2016-04-06 下午4.31.17.png

也就是說,原來有M個分類器,現在只專注優化一個。

給定訓練數據集屏幕快照 2016-04-06 下午4.35.00.png。損失函數屏幕快照 2016-04-06 下午4.35.38.png和基函數的集合屏幕快照 2016-04-06 下午4.35.59.png,學習加法模型屏幕快照 2016-04-06 下午4.26.02.png的前向分步算法如下:

算法(前向分步算法)

輸入:訓練數據集屏幕快照 2016-04-06 下午4.35.00.png,損失函數屏幕快照 2016-04-06 下午4.35.38.png,基函數集屏幕快照 2016-04-06 下午4.35.59.png;

輸出:加法模型屏幕快照 2016-04-06 下午4.26.02.png

(1)初始化屏幕快照 2016-04-06 下午4.39.23.png

(2)屏幕快照 2016-04-06 下午4.39.46.png

(a極小化損失函數

屏幕快照 2016-04-06 下午4.40.17.png

得到參數屏幕快照 2016-04-06 下午4.41.11.png

 (b)更新

屏幕快照 2016-04-06 下午4.41.45.png

(3)得到加法模型

屏幕快照 2016-04-06 下午4.42.39.png

這樣,前向分步算法將同時求解從m=1到M所有參數屏幕快照 2016-04-06 下午4.41.11.png的優化問題簡化爲逐次求解各個屏幕快照 2016-04-06 下午4.41.11.png的優化問題。

前向分步算法與AdaBoost

由前向分步算法可以推導出AdaBoost,用定理敘述這一關係。

定理 AdaBoost算法是前向分歩加法算法的特例。這時,模型是由基本分類器組成的加法模型,損失函數是指數函數。

證明 前向分步算法學習的是加法模型,當基函數爲基本分類器時,該加法模型等價於AdaBoost的最終分類器

由基本分類器屏幕快照 2016-04-06 下午4.49.34.png及其係數屏幕快照 2016-04-06 下午4.49.56.png組成,m=1,2,…,M。前向分步算法逐一學習基函數,這一過程與AdaBoost算法逐一學習基本分類器的過程一致。下面證明前向分步算法的損失函數是指數損失函數(exponential loss function)

屏幕快照 2016-04-06 下午4.50.55.png

時,其學習的具體操作等價於AdaBoost算法學習的具體操作。

假設經過m-1輪迭代前向分步算法已經得到屏幕快照 2016-04-06 下午4.51.36.png:

屏幕快照 2016-04-06 下午4.51.59.png

在第m輪迭代得到屏幕快照 2016-04-06 下午4.58.19.png屏幕快照 2016-04-06 下午4.58.52.png

屏幕快照 2016-04-06 下午4.59.10.png

目標是使前向分步算法得到的屏幕快照 2016-04-06 下午4.58.19.png使屏幕快照 2016-04-06 下午4.58.52.png在訓練數據集T上的指數損失最小,即

屏幕快照 2016-04-06 下午5.06.49.png

上式可以表示爲

屏幕快照 2016-04-06 下午5.07.28.png

其中,屏幕快照 2016-04-06 下午5.08.44.png(指數中的加法可以拿出來做乘法)。因爲屏幕快照 2016-04-06 下午5.12.24.png既不依賴α也不依賴於G,所以與最小化無關。但屏幕快照 2016-04-06 下午5.12.24.png依賴於屏幕快照 2016-04-06 下午4.51.36.png,隨着每一輪迭代而發生改變。

現證使式屏幕快照 2016-04-06 下午5.07.28.png達到最小的屏幕快照 2016-04-06 下午5.15.55.png就是AdaBoost算法所得到的屏幕快照 2016-04-06 下午4.58.19.png

求解式屏幕快照 2016-04-06 下午5.07.28.png可分兩步:

首先,求屏幕快照 2016-04-06 下午5.19.45.png。對任意a>0,使式屏幕快照 2016-04-06 下午5.07.28.png最小的屏幕快照 2016-04-06 下午5.17.39.png由下式得到:

屏幕快照 2016-04-06 下午5.19.04.png

其中,屏幕快照 2016-04-06 下午5.20.20.png

此分類器屏幕快照 2016-04-06 下午5.19.45.png即爲AdaBoost算法的基本分類器屏幕快照 2016-04-06 下午5.21.24.png,因爲它是使第m輪加權訓練數據分類誤差率最小的基本分類器。

之後,求屏幕快照 2016-04-06 下午5.22.56.png屏幕快照 2016-04-06 下午5.07.28.png

屏幕快照 2016-04-06 下午5.23.24.png

這個轉換很簡單,當y和G一致時,指數爲負,反之爲正,第二個等號也是利用這個原理,只不過換成了用指示變量I表述。

將已求得的屏幕快照 2016-04-06 下午5.19.45.png代入式屏幕快照 2016-04-06 下午5.23.24.png,對α求導並使導數爲0,即得到使式屏幕快照 2016-04-06 下午5.07.28.png最小的a。

屏幕快照 2016-04-06 下午7.00.44.png

其中,屏幕快照 2016-04-06 下午7.01.08.png是分類誤差率:

這裏的屏幕快照 2016-04-06 下午7.01.58.png與AdaBoost算法第2(c)步的屏幕快照 2016-04-06 下午7.02.35.png完全一致。

最後來看每一輪樣本權值的更新。由

屏幕快照 2016-04-06 下午7.04.04.png

以及屏幕快照 2016-04-06 下午7.04.22.png,可得

屏幕快照 2016-04-06 下午7.04.51.png

這與AdaBoost算法第2(d)步的樣本權值的更新,只相差規範化因子,因而等價。

提升樹

提升樹是以分類樹或迴歸樹爲基本分類器的提升方法。提升樹被認爲是統計學習中性能最好的方法之一。

提升方法實際採用加法模型(即基函數的線性組合)與前向分步算法。以決策樹爲基函數的提升方法稱爲提升樹(boosting tree)。對分類問題決策樹是二叉分類樹,對迴歸問題決策樹是二叉迴歸樹。在原著例題中看到的基本分類器,可以看作是由一個根結點直接連接兩個葉結點的簡單決策樹,即所謂的決策樹樁(decision stump)。提升樹模型可以表示爲決策樹的加法模型:

屏幕快照 2016-04-06 下午7.11.14.png

其中,屏幕快照 2016-04-06 下午7.12.15.png表示決策樹;屏幕快照 2016-04-06 下午7.12.28.png爲決策樹的參數;M爲樹的個數。

提升樹算法

提升樹算法採用前向分步算法。首先確定初始提升樹/e(x)=0,第m歩的模型是

屏幕快照 2016-04-06 下午7.14.10.png

其中,屏幕快照 2016-04-06 下午7.14.33.png爲當前模型,通過經驗風險極小化確定下一棵決策樹的參數屏幕快照 2016-04-06 下午7.14.55.png

屏幕快照 2016-04-06 下午7.15.09.png

由於樹的線性組合可以很好地擬合訓練數據,即使數據中的輸入與輸出之間的關係很複雜也是如此,所以提升樹是一個髙功能的學習算法。

不同問題有大同小異的提升樹學習算法,其主要區別在於使用的損失函數不同。包括用平方誤差損失函數的迴歸問題,用指數損失函數的分類問題,以及用一般損失函數的一般決策問題。

對於二類分類問題,提升樹算法只需將AdaBoost算法中的基本分類器限制爲二類分類樹即可,可以說這時的提升樹算法是AdaBoost算法的特殊情況,接下來通過《機器學習實戰》中的代碼學習其應用。

提升樹的Python實現

AdaBoost+決策樹=提升樹,來看看具體用Python怎麼實現。

測試數據

老規矩,寫代碼前先看數據,有什麼樣的數據寫什麼樣的代碼。

考慮到學習代碼的最佳方式是單步,而單步的時候,測試數據越簡單越好。所以這裏先上一份簡單的測試數據:

  1. def loadSimpData():
  2.     """
  3. 加載簡單數據集
  4.     :return:
  5.     """
  6.     datMat = matrix([[1., 2.1],
  7.                      [2., 1.1],
  8.                      [1.3, 1.],
  9.                      [1., 1.],
  10.                      [2., 1.]])
  11.     classLabels = [1.0, 1.0, -1.0, -1.0, 1.0]
  12.     return datMat, classLabels

數據很簡單,就是一個二維平面上的5個點。

寫一段可視化代碼:

  1. def plotData(datMat, classLabels):
  2.     xcord0 = []
  3.     ycord0 = []
  4.     xcord1 = []
  5.     ycord1 = []
  6.  
  7.     for i in range(len(classLabels)):
  8.         if classLabels[i]==1.0:
  9.             xcord1.append(datMat[i,0]), ycord1.append(datMat[i,1])
  10.         else:
  11.             xcord0.append(datMat[i,0]), ycord0.append(datMat[i,1])
  12.     fig = plt.figure()
  13.     ax = fig.add_subplot(111)
  14.     ax.scatter(xcord0,ycord0, marker='s', s=90)
  15.     ax.scatter(xcord1,ycord1, marker='o', s=50, c='red')
  16.     plt.title('decision stump test data')
  17.     plt.show()

畫出來是這種效果:

簡單數據集.png

單層決策樹分類

單層決策樹就是一個樹樁,只能利用一個維度的特徵進行分類。

  1. def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):  # just classify the data
  2.     """
  3. 用只有一層的樹樁決策樹對數據進行分類
  4.     :param dataMatrix: 數據
  5.     :param dimen: 特徵的下標
  6.     :param threshVal: 閾值
  7.     :param threshIneq: 大於或小於
  8.     :return: 分類結果
  9.     """
  10.     retArray = ones((shape(dataMatrix)[0], 1))
  11.     if threshIneq == 'lt':
  12.         retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
  13.     else:
  14.         retArray[dataMatrix[:, dimen] > threshVal] = -1.0
  15.     return retArray

dimen決定了這個樹樁能用的唯一特徵。

構建決策樹樁

上個函數到底取什麼參數纔好呢?給定訓練數據集的權重向量,利用該向量計算錯誤率加權和,取最低的參數作爲訓練結果。

  1. def buildStump(dataArr, classLabels, D):
  2.     """
  3. 構建決策樹(一個樹樁)
  4.     :param dataArr: 數據特徵矩陣
  5.     :param classLabels: 標籤向量
  6.     :param D: 訓練數據的權重向量
  7.     :return: 最佳決策樹,最小的錯誤率加權和,最優預測結果
  8.     """
  9.     dataMatrix = mat(dataArr)
  10.     labelMat = mat(classLabels).T
  11.     m, n = shape(dataMatrix)
  12.     numSteps = 10.0
  13.     bestStump = {}
  14.     bestClasEst = mat(zeros((m, 1)))
  15.     minError = inf  # 將錯誤率之和設爲正無窮
  16.     for i in range(n):  # 遍歷所有維度
  17.         rangeMin = dataMatrix[:, i].min()   #該維的最小最大值
  18.         rangeMax = dataMatrix[:, i].max()
  19.         stepSize = (rangeMax - rangeMin) / numSteps
  20.         for j in range(-1, int(numSteps) + 1):  # 遍歷這個區間
  21.             for inequal in ['lt', 'gt']:  # 遍歷大於和小於
  22.                 threshVal = (rangeMin + float(j) * stepSize)
  23.                 predictedVals = stumpClassify(dataMatrix, i, threshVal,
  24.                                               inequal)  # 使用參數 i, j, lessThan 調用樹樁決策樹分類
  25.                 errArr = mat(ones((m, 1)))
  26.                 errArr[predictedVals == labelMat] = 0 # 預測正確的樣本對應的錯誤率爲0,否則爲1
  27.                 weightedError = D.* errArr  # 計算錯誤率加權和
  28.                 # print "split: dim %d, thresh %.2f, thresh ineqal: %s, the weighted error is %.3f" % (i, threshVal, inequal, weightedError)
  29.                 if weightedError < minError:    # 記錄最優樹樁決策樹分類器
  30.                     minError = weightedError
  31.                     bestClasEst = predictedVals.copy()
  32.                     bestStump['dim'] = i
  33.                     bestStump['thresh'] = threshVal
  34.                     bestStump['ineq'] = inequal
  35.     return bestStump, minError, bestClasEst

上面的這一句

  1. weightedError = D.* errArr  # 計算錯誤率加權和

其實對應着這個公式:

屏幕快照 2016-04-05 下午10.05.04.png

AdaBoost訓練

有了上面的基礎,就不難看懂完整的訓練代碼了:

  1. def adaBoostTrainDS(dataArr, classLabels, numIt=40):
  2.     """
  3. 基於單層決策樹的ada訓練
  4.     :param dataArr: 樣本特徵矩陣
  5.     :param classLabels: 樣本分類向量
  6.     :param numIt: 迭代次數
  7.     :return: 一系列弱分類器及其權重,樣本分類結果
  8.     """
  9.     weakClassArr = []
  10.     m = shape(dataArr)[0]
  11.     D = mat(ones((m, 1)) / m)  # 將每個樣本的權重初始化爲均等
  12.     aggClassEst = mat(zeros((m, 1)))
  13.     for i in range(numIt):
  14.         bestStump, error, classEst = buildStump(dataArr, classLabels, D)  # 構建樹樁決策樹,這是一個若分類器,只能利用一個維度做決策
  15.         # print "D:",D.T
  16.         alpha = float(
  17.             0.5 * log((1.0 - error) / max(error, 1e-16)))  # 計算 alpha, 防止發生除零錯誤
  18.         bestStump['alpha'] = alpha
  19.         weakClassArr.append(bestStump)  # 保存樹樁決策樹
  20.         # print "classEst: ",classEst.T
  21.         expon = multiply(-1 * alpha * mat(classLabels).T, classEst)  # 每個樣本對應的指數,當預測值等於y的時候,恰好爲-alpha,否則爲alpha
  22.         D = multiply(D, exp(expon))  # 計算下一個迭代的D向量
  23.         D = D / D.sum() # 歸一化
  24.         # 計算所有分類器的誤差,如果爲0則終止訓練
  25.         aggClassEst += alpha * classEst
  26.         # print "aggClassEst: ",aggClassEst.T
  27.         aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T, ones((m, 1))) # aggClassEst每個元素的符號代表分類結果,如果與y不等則表示錯誤
  28.         errorRate = aggErrors.sum() / m
  29.         print "total error: ", errorRate
  30.         if errorRate == 0.0: break
  31.     return weakClassArr, aggClassEst

其中alpha的計算:

  1.         alpha = float(
  2.             0.5 * log((1.0 - error) / max(error, 1e-16)))  # 計算 alpha, 防止發生除零錯誤

對應着

屏幕快照 2016-04-05 下午10.06.18.png

之後記錄下這個alpha和對應的決策樹樁,然後算法更新訓練數據的權重:

  1.         expon = multiply(-1 * alpha * mat(classLabels).T, classEst)  # 每個樣本對應的指數,當預測值等於y的時候,恰好爲-alpha,否則爲alpha
  2.         D = multiply(D, exp(expon))  # 計算下一個迭代的D向量
  3.         D = D / D.sum() # 歸一化

對應着

屏幕快照 2016-04-05 下午10.09.44.png

更新完畢後就可以利用這個權值分佈訓練下一個決策樹樁了,當然,作爲一個節省時間的策略,可以檢查一下當前錯誤率是否滿足要求,如果滿足,則終止訓練。

提升樹分類

分類很簡單,就是一個多數表決的過程:

  1. def adaClassify(datToClass, classifierArr):
  2.     dataMatrix = mat(datToClass)  
  3.     m = shape(dataMatrix)[0]
  4.     aggClassEst = mat(zeros((m, 1)))
  5.     for i in range(len(classifierArr)):
  6.         classEst = stumpClassify(dataMatrix, classifierArr[i]['dim'], \
  7.                                  classifierArr[i]['thresh'], \
  8.                                  classifierArr[i]['ineq'])  
  9.         aggClassEst += classifierArr[i]['alpha'] * classEst
  10.         print aggClassEst
  11.     return sign(aggClassEst)

調用方法

利用上述簡單數據集進行測試:

  1. datArr, labelArr = loadSimpData()
  2. classifierArr, aggClassEst = adaBoostTrainDS(datArr, labelArr, 30)
  3. print adaClassify([0, 0], classifierArr)

輸出

  1. total error:  0.2
  2. total error:  0.2
  3. total error:  0.0
  4. [[-0.69314718]]
  5. [[-1.66610226]]
  6. [[-2.56198199]]
  7. [[-1.]]

可見前向分步訓練過程中的誤差率是逐漸降低的:

  1. total error:  0.2
  2. total error:  0.2
  3. total error:  0.0

分類過程中,隨着加法模型的不斷疊加,對於(0,0)這個點,其累加結果是“負”得越來越厲害的,最終取符號輸出-1類別。對應訓練數據:

簡單數據集.png

的確應該屬於-1。

複雜數據集

《機器學習實戰》還給出了一個馬疝病數據集:horseColicTraining2.txt

加載代碼:

  1. def loadDataSet(fileName):
  2.     """
  3.  從文件中加載以製表符分割的數據集(浮點數格式)
  4.     :param fileName:
  5.     :return:
  6.     """
  7.     numFeat = len(open(fileName).readline().split('\t'))
  8.     dataMat = []
  9.     labelMat = []
  10.     fr = open(fileName)
  11.     for line in fr.readlines():
  12.         lineArr = []
  13.         curLine = line.strip().split('\t')
  14.         for i in range(numFeat - 1):
  15.             lineArr.append(float(curLine[i]))
  16.         dataMat.append(lineArr)
  17.         labelMat.append(float(curLine[-1]))
  18.     return dataMat, labelMat

訓練代碼:

  1. datArr, labelArr = loadDataSet("horseColicTraining2.txt")
  2. classifierArr, aggClassEst = adaBoostTrainDS(datArr, labelArr, 10)

我們可以直接將aggClassEst打印出來觀察對訓練集的預測結果,但這樣太抽象,而且我們希望計算準確率和召回率,特別是在測試集上的準確率和召回率。

準確率與召回率

對於二分類問題:

屏幕快照 2016-04-06 下午9.56.32.png

準確率的計算方法爲

  1. TP / (TP + FP)

召回率的計算方法爲

  1. TP / (TP + FN)

測試集:horseColicTest2.txt

寫一段代碼計算在不同的數據集下的準確率與召回率:

  1. def evaluate(aggClassEst, classLabels):
  2.     """
  3. 計算準確率與召回率
  4.     :param aggClassEst:
  5.     :param classLabels:
  6.     :return: P, R
  7.     """
  8.     TP = 0.
  9.     FP = 0.
  10.     TN = 0.
  11.     FN = 0.
  12.     for i in range(len(classLabels)):
  13.         if classLabels[i] == 1.0:
  14.             if (sign(aggClassEst[i]) == classLabels[i]):
  15.                 TP += 1.0
  16.             else:
  17.                 FP += 1.0
  18.         else:
  19.             if (sign(aggClassEst[i]) == classLabels[i]):
  20.                 TN += 1.0
  21.             else:
  22.                 FN += 1.0
  23.  
  24.     return TP / (TP + FP), TP / (TP + FN)
  25.  
  26.  
  27. def train_test(datArr, labelArr, datArrTest, labelArrTest, num):
  28.     classifierArr, aggClassEst = adaBoostTrainDS(datArr, labelArr, num)
  29.     prTrain = evaluate(aggClassEst, labelArr)
  30.     aggClassEst = adaClassify(datArrTest, classifierArr)
  31.     prTest = evaluate(aggClassEst, labelArrTest)
  32.     return prTrain, prTest
  33.  
  34.  
  35. datArr, labelArr = loadDataSet("horseColicTraining2.txt")
  36. datArrTest, labelArrTest = loadDataSet("horseColicTest2.txt")
  37. prTrain, prTest = train_test(datArr, labelArr, datArrTest, labelArrTest, 10)
  38. print prTrain, prTest

輸出

  1. (0.8595505617977528, 0.7766497461928934) (0.7872340425531915, 0.8604651162790697)

事實上,通過調整分類器的數量(train_test的最後一個參數),可以得到不同的性能。《機器學習實戰》驗證了1到10000個分類器的錯誤率:

屏幕快照 2016-04-06 下午10.07.14.png

上圖說明分類器並不是越多越好,50是個最好的值,過了這個值,模型發生過擬合,性能越來越低。

ROC曲線

給定分類器在某個數據集上的輸出,我們就能計算出假陽率與真陽率,這兩個值決定一個座標點。以假陽率作爲x軸,真陽率作爲y軸,制定座標系。

完美的分類器應該是左上角那個點,分類器越接近左上角,就越完美。這樣我們就可以較爲直觀地比較兩個不同的分類器了。

在決策函數中,我們使用的是sign來二值化預測強度,以得到最終分類。也就是說,我們取的閾值是0。分類強度離閾值越遠,分類的可信度越高。如果我們改變閾值,我們就能得到同一個分類器在座標系中不同的點,將它們按照閾值大小順序連成線,就能得到一條ROC曲線。完美的分類器的ROC曲線應該是正方形的左上角對應的兩條邊,而隨機猜測的ROC曲線則是連接左下角與右上角的一條直線。ROC曲線下的面積(AUC)反映了分類器的平均性能。

繪製代碼:

  1. def plotROC(predStrengths, classLabels):
  2.     import matplotlib.pyplot as plt
  3.     cur = (1.0, 1.0)  # 中間變量,初始狀態爲右上角
  4.     ySum = 0.0  # variable to calculate AUC
  5.     numPosClas = sum(array(classLabels) == 1.0)  # TP
  6.     yStep = 1 / float(numPosClas)
  7.     xStep = 1 / float(len(classLabels) - numPosClas)
  8.     sortedIndicies = predStrengths.argsort()  # 按元素值排序後的下標,逆序
  9.     fig = plt.figure()
  10.     fig.clf()
  11.     ax = plt.subplot(111)
  12.     # 遍歷所有的值
  13.     for index in sortedIndicies.tolist()[0]:
  14.         if classLabels[index] == 1.0:  # 預測正確
  15.             delX = 0  # 真陽率不變
  16.             delY = yStep  # 假陽率減小
  17.         else:
  18.             delX = xStep
  19.             delY = 0
  20.             ySum += cur[1]  # ROC面積的一個小長條
  21.         # 從 cur 到 (cur[0]-delX,cur[1]-delY) 畫一條線
  22.         ax.plot([cur[0], cur[0] - delX], [cur[1], cur[1] - delY], c='b')
  23.         cur = (cur[0] - delX, cur[1] - delY)
  24.     ax.plot([0, 1], [0, 1], 'b--')  # 隨機猜測的ROC線
  25.     plt.xlabel('False positive rate')
  26.     plt.ylabel('True positive rate')
  27.     plt.title('ROC curve for AdaBoost horse colic detection system')
  28.     ax.axis([0, 1, 0, 1])
  29.     plt.show()
  30.     print "the Area Under the Curve is: ", ySum * xStep
  31. plotROC(aggClassEst.T, labelArr)

效果:

ROC.png

隨機猜測的ROC曲線是一條y=x直線,這是因爲對真陽率和假陽率相等,意味着分類器猜對和猜錯的比率相等,說明該分類器就是在隨機猜。

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