讀古今文學網 > 機器學習實戰 > 7.7 非均衡分類問題 >

7.7 非均衡分類問題

在我們結束分類這個主題之前,還必須討論一個問題。在前面六章的所有分類介紹中,我們都假設所有類別的分類代價是一樣的。例如在第5章,我們構建了一個用於檢測患疝病的馬匹是否存活的系統。在那裡,我們構建了分類器,但是並沒有對分類後的情形加以討論。假如某人給我們牽來一匹馬,他希望我們能預測這匹馬能否生存。我們說馬會死,那麼他們就可能會對馬實施安樂死,而不是通過給馬餵藥來延緩其不可避免的死亡過程。我們的預測也許是錯誤的,馬本來是可以繼續活著的。畢竟,我們的分類器只有80%的精確率(accuracy)。如果我們預測錯誤,那麼我們將會錯殺了一個如此昂貴的動物,更不要說人對馬還存在情感上的依戀。

如何過濾垃圾郵件呢?如果收件箱中會出現某些垃圾郵件,但合法郵件永遠不會扔進垃圾郵件夾中,那麼人們是否會滿意呢?癌症檢測又如何呢?只要患病的人不會得不到治療,那麼再找一個醫生來看看會不會更好呢(即情願誤判也不漏判)?

還可以舉出很多很多這樣的例子,坦白地說,在大多數情況下不同類別的分類代價並不相等。在本節中,我們將會考察一種新的分類器性能度量方法,並通過圖像技術來對在上述非均衡問題下不同分類器的性能進行可視化處理。然後,我們考察這兩種分類器的變換算法,它們能夠將不同決策的代價考慮在內。

7.7.1 其他分類性能度量指標:正確率、召回率及ROC曲線

到現在為止,本書都是基於錯誤率來衡量分類器任務的成功程度的。錯誤率指的是在所有測試樣例中錯分的樣例比例。實際上,這樣的度量錯誤掩蓋了樣例如何被分錯的事實。在機器學習中,有一個普遍適用的稱為混淆矩陣(confusion matrix)的工具,它可以幫助人們更好地瞭解分類中的錯誤。有這樣一個關於在房子周圍可能發現的動物類型的預測,這個預測的三類問題的混淆矩陣如表7-2所示。

表7-2 一個三類問題的混淆矩陣

利用混淆矩陣就可以更好地理解分類中的錯誤了。如果矩陣中的非對角元素均為0,就會得到一個完美的分類器。

接下來,我們考慮另外一個混淆矩陣,這次的矩陣只針對一個簡單的二類問題。在表7-3中,給出了該混淆矩陣。在這個二類問題中,如果將一個正例判為正例,那麼就可以認為產生了一個真正例(True Positive,TP,也稱真陽);如果對一個反例正確地判為反例,則認為產生了一個真反例(True Negative,TN,也稱真陰)。相應地,另外兩種情況則分別稱為偽反例(False Negative,FN,也稱假陰)和偽正例(False Positive,FP,也稱假陽)。這4種情況如表7-3所示。

表7-3 一個二類問題的混淆矩陣,其中的輸出採用了不同的類別標籤

在分類中,當某個類別的重要性高於其他類別時,我們就可以利用上述定義來定義出多個比錯誤率更好的新指標。第一個指標是正確率(Precision),它等於TP/(TP+FP),給出的是預測為正例的樣本中的真正正例的比例。第二個指標是召回率(Recall),它等於TP/(TP+FN),給出的是預測為正例的真實正例占所有真實正例的比例。在召回率很大的分類器中,真正判錯的正例的數目並不多。

我們可以很容易構造一個高正確率或高召回率的分類器,但是很難同時保證兩者成立。如果將任何樣本都判為正例,那麼召回率達到百分之百而此時正確率很低。構建一個同時使正確率和召回率最大的分類器是具有挑戰性的。

另一個用於度量分類中的非均衡性的工具是ROC曲線(ROC curve),ROC代表接收者操作特徵(receiver operating characteristic),它最早在二戰期間由電氣工程師構建雷達系統時使用過。圖7-3給出了一條ROC曲線的例子。

圖7-3 利用10個單層決策樹的AdaBoost馬疝病檢測系統的ROC曲線

在圖7-3的ROC曲線中,給出了兩條線,一條虛線一條實線。圖中的橫軸是偽正例的比例(假陽率=FP/(FP+TN)),而縱軸是真正例的比例(真陽率=TP/(TP+FN))。ROC曲線給出的是當閾值變化時假陽率和真陽率的變化情況。左下角的點所對應的是將所有樣例判為反例的情況,而右上角的點對應的則是將所有樣例判為正例的情況。虛線給出的是隨機猜測的結果曲線。

ROC曲線不但可以用於比較分類器,還可以基於成本效益(cost-versus-benefit)分析來做出決策。由於在不同的閾值下,不同的分類器的表現情況可能各不相同,因此以某種方式將它們組合起來或許會更有意義。如果只是簡單地觀察分類器的錯誤率,那麼我們就難以得到這種更深入的洞察效果了。

在理想的情況下,最佳的分類器應該盡可能地處於左上角,這就意味著分類器在假陽率很低的同時獲得了很高的真陽率。例如在垃圾郵件的過濾中,這就相當於過濾了所有的垃圾郵件,但沒有將任何合法郵件誤識為垃圾郵件而放入垃圾郵件的文件夾中。

對不同的ROC曲線進行比較的一個指標是曲線下的面積(Area Unser the Curve,AUC)。AUC給出的是分類器的平均性能值,當然它並不能完全代替對整條曲線的觀察。一個完美分類器的AUC為1.0,而隨機猜測的AUC則為0.5。

為了畫出ROC曲線,分類器必須提供每個樣例被判為陽性或者陰性的可信程度值。儘管大多數分類器都能做到這一點,但是通常情況下,這些值會在最後輸出離散分類標籤之前被清除。樸素貝葉斯能夠提供一個可能性,而在Logistic回歸中輸入到Sigmoid函數中的是一個數值。在AdaBoost和SVM中,都會計算出一個數值然後輸入到sign函數中。所有的這些值都可以用於衡量給定分類器的預測強度。為了創建ROC曲線,首先要將分類樣例按照其預測強度排序。先從排名最低的樣例開始,所有排名更低的樣例都被判為反例,而所有排名更高的樣例都被判為正例。該情況的對應點為<1.0,1.0>。然後,將其移到排名次低的樣例中去,如果該樣例屬於正例,那麼對真陽率進行修改;如果該樣例屬於反例,那麼對假陰率進行修改。

上述過程聽起來有點容易讓人混淆,但是如果閱讀一下程序清單7-5中的代碼,一切就會變得一目瞭然了。打開adaboost.py文件並加入如下代碼。

程序清單7-5 ROC曲線的繪製及AUC計算函數

def plotROC(predStrengths, classLabels):
    import matplotlib.pyplot as plt
    cur = (1.0,1.0)
    ySum = 0.0
    numPosClas = sum(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]
        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   
  

上述程序中的函數有兩個輸入參數,第一個參數是一個NumPy數組或者一個行向量組成的矩陣。該參數代表的則是分類器的預測強度。在分類器和訓練函數將這些數值應用到sign函數之前,它們就已經產生了。儘管很快就可以看到該函數的實際執行效果,但是我們還是要先討論一下這段代碼。函數的第二個輸入參數是先前使用過的classLabels。我們首先導入pyplot,然後構建一個浮點數二元組,並將它初始化為(1,0,1.0)。該元組保留的是繪製光標的位置,變量ySum則用於計算AUC的值。接下來,通過數組過濾方式計算正例的數目,並將該值賦給numPosClas。該值先是確定了在y坐標軸上的步進數目,接著我們在x軸和y軸的0.0到1.0區間上繪點,因此y軸上的步長是1.0/numPosClas。類似地,就可以得到x軸的步長了。

接下來,我們得到了排序索引❶,但是這些索引是按照最小到最大的順序排列的,因此我們需要從點<1.0,1.0>開始繪,一直到<0,0>。跟著的三行代碼則是用於構建畫筆,並在所有排序值上進行循環。這些值在一個NumPy數組或者矩陣中進行排序,Python則需要一個表來進行迭代循環,因此我們需要調用tolist方法。當遍歷表時,每得到一個標籤為1.0的類,則要沿著y軸的方向下降一個步長,即不斷降低真陽率。類似地,對於每個其他類別的標籤,則是在x軸方向上倒退了一個步長(假陰率方向)。上述代碼只關注1這個類別標籤,因此就無所謂是採用1/0標籤還是+1/-1標籤。

為了計算AUC,我們需要對多個小矩形的面積進行累加。這些小矩形的寬度是 xStep,因此可以先對所有矩形的高度進行累加,最後再乘以xStep得到其總面積。所有高度的和(ySum)隨著x軸的每次移動而漸次增加。一旦決定了是在x軸還是y軸方向上進行移動的,我們就可以在當前點和新點之間畫出一條線段。然後,當前點cur更新了。最後,我們就會得到一個像樣的繪圖並將AUC打印到終端輸出。

為瞭解實際運行效果,我們需要將adaboostTrainDS( )的最後一行代碼替換成:

return weakClassArr,aggClassEst
  

以得到aggClassEst的值。然後,在Python提示符下鍵入如下命令:

>>> reload(adaboost)
<module \'adaboost\' from \'adaboost.pyc\'>
>>> datArr,labelArr = adaboost.loadDataSet(\'horseColicTraining2.txt\')
>>> classifierArray,aggClassEst =
     adaboost.adaBoostTrainDS(datArr,labelArr,10)
>>> adaboost.plotROC(aggClassEst.T,labelArr)
the Area Under the Curve is: 0.858296963506 
  

這時,我們也會得到和圖7-3一樣的ROC曲線。這是在10個弱分類器下,AdaBoost算法性能的結果。我們還記得,當初我們在50個弱分類器下得到了最優的分類性能,那麼這種情況下的ROC曲線會如何呢?這時的AUC是不是更好呢?

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

除了調節分類器的閾值之外,我們還有一些其他可以用於處理非均衡分類的代價的方法,其中的一種稱為代價敏感的學習(cost-sensitive learning)。考慮表7-4中的代價矩陣,第一張表給出的是到目前為止分類器的代價矩陣(代價不是0就是1)。我們可以基於該代價矩陣計算其總代價:TP*0+FN*1+FP*1+TN*0。接下來我們考慮下面的第二張表,基於該代價矩陣的分類代價的計算公式為:TP*(-5)+FN*1+FP*50+TN*0。採用第二張表作為代價矩陣時,兩種分類錯誤的代價是不一樣的。類似地,這兩種正確分類所得到的收益也不一樣。如果在構建分類器時,知道了這些代價值,那麼就可以選擇付出最小代價的分類器。

表7-4 一個二類問題的代價矩陣

在分類算法中,我們有很多方法可以用來引入代價信息。在AdaBoost中,可以基於代價函數來調整錯誤權重向量D。在樸素貝葉斯中,可以選擇具有最小期望代價而不是最大概率的類別作為最後的結果。在SVM中,可以在代價函數中對於不同的類別選擇不同的參數C。上述做法就會給較小類更多的權重,即在訓練時,小類當中只允許更少的錯誤。

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

另外一種針對非均衡問題調節分類器的方法,就是對分類器的訓練數據進行改造。這可以通過欠抽樣(undersampling)或者過抽樣(oversampling)來實現。過抽樣意味著複製樣例,而欠抽樣意味著刪除樣例。不管採用哪種方式,數據都會從原始形式改造為新形式。抽樣過程則可以通過隨機方式或者某個預定方式來實現。

通常也會存在某個罕見的類別需要我們來識別,比如在信用卡欺詐當中。如前所述,正例類別屬於罕見類別。我們希望對於這種罕見類別能盡可能保留更多的信息,因此,我們應該保留正例類別中的所有樣例,而對反例類別進行欠抽樣或者樣例刪除處理。這種方法的一個缺點就在於要確定哪些樣例需要進行剔除。但是,在選擇剔除的樣例中可能攜帶了剩餘樣例中並不包含的有價值信息。

上述問題的一種解決辦法,就是選擇那些離決策邊界較遠的樣例進行刪除。假定我們有一個數據集,其中有50例信用卡欺詐交易和5000例合法交易。如果我們想要對合法交易樣例進行欠抽樣處理,使得這兩類數據比較均衡的話,那麼我們就需要去掉4950個樣例,而這些樣例中可能包含很多有價值的信息。這看上去有些極端,因此有一種替代的策略就是使用反例類別的欠抽樣和正例類別的過抽樣相混合的方法。

要對正例類別進行過抽樣,我們可以複製已有樣例或者加入與已有樣例相似的點。一種方法是加入已有數據點的插值點,但是這種做法可能會導致過擬合的問題。