對於一個只有兩個類別的分類問題中,若你希望抓出的那個類別只占 1%,而另外一類占了 99%,則就算你的模型全部猜答案是較多的那一個類別,準確度也會有驚人的 99%,但它顯然是個沒有用處的模型,因此顯然不適合用準確度來評估模型效果,而該輪到 precision、recall 和 F-score 上場。
在定義上,你希望抓出的類別叫做正類別(P, positive),而不希望抓出的是負類別(N, negative);答案是 P 而模型也說是 P,稱為 true positive;答案是 N 而模型也說是 N,稱為 true negative;答案是 P 但模型說是 N,稱為 false negative (模型說是 N,但是錯了) 或 type 2 error;答案是 N 但模型說是 P,稱為 false positive (模型說是 P,但是錯了) 或 type 1 error。我們可以把四種情況畫成一個矩陣,叫做混淆矩陣(confusion matrix)。兩類別的混淆矩陣範例如下
答案 \ 預測 N P N TN FP (type 1 error) P FN (type 2 error) TP 上述矩陣只是一個通常的填法,每個 row 或 column 不一定要先放 N 再放 P,也不一定要 row 是答案,column 是預測。另外,多類別也可以繪製混淆矩陣,但通常未必會去談論它的 precision/recall。
知道了混淆矩陣以後,就可以來看 precision 和 recall 的定義。Precision 是模型認為是 P 的資料中,有多少是正確的,即 TP / (TP + FP);recall 則是真正是 P 的當中,有多少被抓出來,即 TP / (FN + TP)。舉個例子,假設有三人結夥搶劫,為了躲避警察追捕而混進了另外七人當中,所以總共有十人要被警察盤問並看看是不是要帶回警局;假設警察 A 說全部帶走,則抓出十人的裡面有三人是對的,precision 是 3 / 10 = 30%,而三名真正的犯人都有被抓出來,所以 recall 是 3 / 3 = 100%;而警察 B 經過仔細的推理以後,帶走了其中一位真正的犯人,則 precision 是 1 / 1 = 100%,recall 是 1 / 3 = 33.33%。如果你希望在模型評估時,可以在 precision 和 recall 中間取一個平衡,則可以計算 precision 和 recall 的調和平均數,稱為 F-score。另外,實務上也會根據 FN 和 FP 所帶來的損失不同,而在偏重的指標甚至計算公式上有所變化,例如醫療診斷不能漏掉真正的患者, 即 FN 的代價高,則可能較重視 recall;而垃圾郵件過濾不能誤刪重要郵件,即 FP 的代價高,則可能較重視 precision。如果各位將來處理的相關應用有這樣的特性,請記得調整適合的評估方式。
以下的範例,會隨機產生一些二分類問題的資料,來示範 precision 以及 recall 的計算。資料都會顯示出來,你可以手動計算看看來驗證:
import numpy as np groundtruth = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]) prediction = np.array([ np.random.randint(2) if np.random.rand(1) >= 0.4 else ans \ for ans in groundtruth ]) print('Groundtruth:', groundtruth) print('Prediction:', prediction) print('Accuracy (%):', np.mean(groundtruth == prediction) * 100) conf_mat = np.zeros((2, 2)) for ans, pred in zip(groundtruth, prediction): conf_mat[ans, pred] += 1 print(conf_mat) tn = conf_mat[0, 0] tp = conf_mat[1, 1] fp = conf_mat[0, 1] fn = conf_mat[1, 0] print('Precision (%):', tp / (tp + fp) * 100) print('Recall (%):', tp / (fn + tp) * 100)在上述範例中,採用了自行撰寫的程式碼,來計算出二分類問題的混淆矩陣。若要應對多類別的分類問題,或者就是懶得自己寫,也可以使用 sklearn.metrics 的 confusion_matrix 來進行,相關說明請自行參考文件。
此外,雖然上述範例的預測資料,是用「60% 亂猜,40% 抄答案」來直接產生二分類的結果,但實際運作的模型,其行為通常會是產生一個預測的信心度或機率值,並另外由某種方式決定一個門檻(threshold),來轉換成二分類的結果。如果你希望觀察不同門檻對於模型表現的影響,或者想找出最佳的門檻值,則可以用 sklearn.metrics 的 roc_curve 來繪製 ROC 曲線(Receiver Operating Characteristic curve)。ROC 曲線的橫軸是 False Positive Rate (FPR = FP / (FP + TN)),縱軸是 True Positive Rate (TPR = TP / (TP + FN),也就是 Recall)。曲線下方的面積稱為 AUC(Area Under Curve),AUC 越接近 1 表示模型的分類能力越好,而 AUC = 0.5 則相當於隨機猜測。以下範例展示如何繪製 ROC 曲線:
import numpy as np import matplotlib.pyplot as plt from sklearn.metrics import roc_curve groundtruth = np.random.randint(0, 2, 100) scores = np.random.rand(groundtruth.size) prediction = scores >= 0.4 fpr, tpr, thresholds = roc_curve(groundtruth, scores) plt.plot(fpr, tpr) plt.xlabel('False Positive Rate') plt.ylabel('True Positive Rate') plt.show()