ROC曲線及facenet中的使用

ROC(Receiver Operating Characteristic)曲線,以及AUC(Area Under Curve),常用來評價一個二值分類器的優劣,ROC的橫軸爲false positive rate,FPR,也就是誤判爲正確的比例,縱軸是true positive rate,也就是正確的判斷爲正確的比例,定義如下:
這裏寫圖片描述
接下來我們考慮ROC曲線圖中的四個點和一條線。第一個點,(0,1),即FPR=0, TPR=1,這意味着FN(false negative)=0,並且FP(false positive)=0。Wow,這是一個完美的分類器,它將所有的樣本都正確分類。第二個點,(1,0),即FPR=1,TPR=0,類似地分析可以發現這是一個最糟糕的分類器,因爲它成功避開了所有的正確答案。第三個點,(0,0),即FPR=TPR=0,即FP(false positive)=TP(true positive)=0,可以發現該分類器預測所有的樣本都爲負樣本(negative)。類似的,第四個點(1,1),分類器實際上預測所有的樣本都爲正樣本。經過以上的分析,我們可以斷言,ROC曲線越接近左上角,該分類器的性能越好。
這裏寫圖片描述
下面考慮ROC曲線圖中的虛線y=x上的點。這條對角線上的點其實表示的是一個採用隨機猜測策略的分類器的結果,例如(0.5,0.5),表示該分類器隨機對於一半的樣本猜測其爲正樣本,另外一半的樣本爲負樣本。

如何畫ROC曲線

對於一個特定的分類器和測試數據集,顯然只能得到一個分類結果,即一組FPR和TPR結果,而要得到一個曲線,我們實際上需要一系列FPR和TPR的值,這又是如何得到的呢?我們先來看一下Wikipedia上對ROC曲線的定義:

In signal detection theory, a receiver operating characteristic (ROC), or simply ROC curve, is a graphical plot which illustrates the performance of a binary classifier system as its discrimination threshold is varied.

問題在於“as its discrimination threashold is varied”。如何理解這裏的“discrimination threashold”呢?我們忽略了分類器的一個重要功能“概率輸出”,即表示分類器認爲某個樣本具有多大的概率屬於正樣本(或負樣本)。通過更深入地瞭解各個分類器的內部機理,我們總能想辦法得到一種概率輸出。通常來說,是將一個實數範圍通過某個變換映射到(0,1)區間[^3]。
假如我們已經得到了所有樣本的概率輸出(屬於正樣本的概率),現在的問題是如何改變“discrimination threashold”?我們根據每個測試樣本屬於正樣本的概率值從大到小排序。每次選取一個不同的threshold,我們就可以得到一組FPR和TPR,即ROC曲線上的一點。
當我們將threshold設置爲1和0時,分別可以得到ROC曲線上的(0,0)和(1,1)兩個點。將這些(FPR,TPR)對連接起來,就得到了ROC曲線。當threshold取值越多,ROC曲線越平滑。

其實,我們並不一定要得到每個測試樣本是正樣本的概率值,只要得到這個分類器對該測試樣本的“評分值”即可(評分值並不一定在(0,1)區間)。評分越高,表示分類器越肯定地認爲這個測試樣本是正樣本,而且同時使用各個評分值作爲threshold。我認爲將評分值轉化爲概率更易於理解一些。

AUC值的計算

AUC(Area Under Curve)被定義爲ROC曲線下的面積,顯然這個面積的數值不會大於1。又由於ROC曲線一般都處於y=x這條直線的上方,所以AUC的取值範圍在0.5和1之間。使用AUC值作爲評價標準是因爲很多時候ROC曲線並不能清晰的說明哪個分類器的效果更好,而作爲一個數值,對應AUC更大的分類器效果更好。

爲什麼使用ROC曲線

既然已經這麼多評價標準,爲什麼還要使用ROC和AUC呢?因爲ROC曲線有個很好的特性:當測試集中的正負樣本的分佈變化的時候,ROC曲線能夠保持不變。在實際的數據集中經常會出現類不平衡(class imbalance)現象,即負樣本比正樣本多很多(或者相反),而且測試數據中的正負樣本的分佈也可能隨着時間變化。下圖是ROC曲線和Precision-Recall曲線[^5]的對比:
這裏寫圖片描述
在上圖中,(a)和(c)爲ROC曲線,(b)和(d)爲Precision-Recall曲線。(a)和(b)展示的是分類其在原始測試集(正負樣本分佈平衡)的結果,(c)和(d)是將測試集中負樣本的數量增加到原來的10倍後,分類器的結果。可以明顯的看出,ROC曲線基本保持原貌,而Precision-Recall曲線則變化較大

下面給出facenet中的計算roc代碼:

#embeddings:輸入端的特徵向量,由於這份代碼是針對lfw數據的,因此特徵是每兩個特徵之間是一對
#actual_issame,實際上是不是同一類,或者說是不是同一個人
#nrof_folds,交叉驗證的fold數
#下面有個thresholds,是從0開始,間隔0.01,一直到4,總共400個閾值,這裏就對應了前面提到的,多個閾值下,得到多個TPR和FPR值,從而可以畫出一個ROC曲線
#返回的tpr,fpr,即可用於繪製ROC曲線
def evaluate(embeddings, actual_issame, nrof_folds=10):
    # Calculate evaluation metrics
    thresholds = np.arange(0, 4, 0.01)
    embeddings1 = embeddings[0::2]
    embeddings2 = embeddings[1::2]
    tpr, fpr, accuracy = facenet.calculate_roc(thresholds, embeddings1, embeddings2,
        np.asarray(actual_issame), nrof_folds=nrof_folds)
    thresholds = np.arange(0, 4, 0.001)
    val, val_std, far = facenet.calculate_val(thresholds, embeddings1, embeddings2,
        np.asarray(actual_issame), 1e-3, nrof_folds=nrof_folds)
    return tpr, fpr, accuracy, val, val_std, far



def calculate_roc(thresholds, embeddings1, embeddings2, actual_issame, nrof_folds=10):
    assert(embeddings1.shape[0] == embeddings2.shape[0])
    assert(embeddings1.shape[1] == embeddings2.shape[1])
    #lfw中點對的個數
    nrof_pairs = min(len(actual_issame), embeddings1.shape[0])
    #這裏thresholds是一個長度爲400的數組,從0開始,間隔0.01
    nrof_thresholds = len(thresholds)
    k_fold = KFold(n_splits=nrof_folds, shuffle=False)
    #tprs是一個10x400的數組
    tprs = np.zeros((nrof_folds,nrof_thresholds))
    fprs = np.zeros((nrof_folds,nrof_thresholds))
    accuracy = np.zeros((nrof_folds))

    #求點對之間的差值
    diff = np.subtract(embeddings1, embeddings2)
    #求點對之間的差值的平方和,再相加,相當於是求完歐式距離
    dist = np.sum(np.square(diff),1)
    indices = np.arange(nrof_pairs)

    #將數據分成10份,一份是測試,9份是訓練
    for fold_idx, (train_set, test_set) in enumerate(k_fold.split(indices)):

        # Find the best threshold for the fold
        acc_train = np.zeros((nrof_thresholds))
        #在0到4,每間隔0.01的數字作爲閾值,然後再選擇其中值最大的作爲閾值
        for threshold_idx, threshold in enumerate(thresholds):
            #在當前閾值下,求訓練數據的準確度
            _, _, acc_train[threshold_idx] = calculate_accuracy(threshold, dist[train_set], actual_issame[train_set])
        #獲取0-4之間閾值下的最佳值
        best_threshold_index = np.argmax(acc_train)
        #獲取0-4之間閾值下的測試集的TPR和FPR
        for threshold_idx, threshold in enumerate(thresholds):
            tprs[fold_idx,threshold_idx], fprs[fold_idx,threshold_idx], _ = calculate_accuracy(threshold, dist[test_set], actual_issame[test_set])
        #根據上面獲取的最佳閾值,求測試數據集的準確度,作爲最終的準確度
        _, _, accuracy[fold_idx] = calculate_accuracy(thresholds[best_threshold_index], dist[test_set], actual_issame[test_set])
        #由於還有交叉驗證,因此還需要求10次tpr和fpr均值
        tpr = np.mean(tprs,0)
        fpr = np.mean(fprs,0)
    return tpr, fpr, accuracy

def calculate_accuracy(threshold, dist, actual_issame):
    predict_issame = np.less(dist, threshold)
    #判斷爲同一個人的,與實際上也爲同一個人的個數,真真率
    tp = np.sum(np.logical_and(predict_issame, actual_issame))
    #判斷爲同一個人的,實際上不是同一個人的個數,真假率
    fp = np.sum(np.logical_and(predict_issame, np.logical_not(actual_issame)))
    #判斷不爲同一個人的個數,和實際上也不是同一個的個數,假假率
    tn = np.sum(np.logical_and(np.logical_not(predict_issame), np.logical_not(actual_issame)))
    #判斷不爲同一個人,實際上是同一個人的個數,假真率
    fn = np.sum(np.logical_and(np.logical_not(predict_issame), actual_issame))

    tpr = 0 if (tp+fn==0) else float(tp) / float(tp+fn)
    fpr = 0 if (fp+tn==0) else float(fp) / float(fp+tn)
    acc = float(tp+tn)/dist.size
    return tpr, fpr, acc

圖片及部分文字來源:
http://alexkong.net/2013/06/introduction-to-auc-and-roc/

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