內容概要¶
- 模型評估的目的及一般評估流程
- 分類准確率的用處及其限制
- 混淆矩陣(confusion matrix)是如何表示一個分類器的性能
- 混淆矩陣中的度量是如何計算的
- 通過改變分類閾值來調整分類器性能
- ROC曲線的用處
- 曲線下面積(Area Under the Curve, AUC)與分類准確率的不同
1. 回顧¶
模型評估可以用於在不同的模型類型、調節參數、特征組合中選擇適合的模型,所以我們需要一個模型評估的流程來估計訓練得到的模型對於非樣本數據的泛化能力,並且還需要恰當的模型評估度量手段來衡量模型的性能表現。
對於模型評估流程而言,之前介紹了K折交叉驗證的方法,針對模型評估度量方法,回歸問題可以采用平均絕對誤差(Mean Absolute Error)、均方誤差(Mean Squared Error)、均方根誤差(Root Mean Squared Error),而分類問題可以采用分類准確率和這篇文章中介紹的度量方法。
2. 分類准確率(Classification accuracy)¶
這里我們使用Pima Indians Diabetes dataset,其中包含健康數據和糖尿病狀態數據,一共有768個病人的數據。
# read the data into a Pandas DataFrame
import pandas as pd url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/pima-indians-diabetes/pima-indians-diabetes.data' col_names = ['pregnant', 'glucose', 'bp', 'skin', 'insulin', 'bmi', 'pedigree', 'age', 'label'] pima = pd.read_csv(url, header=None, names=col_names)
# print the first 5 rows of data
pima.head()
上面表格中的label一列,1表示該病人有糖尿病,0表示該病人沒有糖尿病
# define X and y
feature_cols = ['pregnant', 'insulin', 'bmi', 'age'] X = pima[feature_cols] y = pima.label
# split X and y into training and testing sets
from sklearn.cross_validation import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
# train a logistic regression model on the training set
from sklearn.linear_model import LogisticRegression logreg = LogisticRegression() logreg.fit(X_train, y_train)
# make class predictions for the testing set
y_pred_class = logreg.predict(X_test)
# calculate accuracy
from sklearn import metrics print metrics.accuracy_score(y_test, y_pred_class)
分類准確率分數是指所有分類正確的百分比。
空准確率(null accuracy)是指當模型總是預測比例較高的類別,那么其正確的比例是多少
# examine the class distribution of the testing set (using a Pandas Series method)
y_test.value_counts()
# calculate the percentage of ones
y_test.mean()
# calculate the percentage of zeros
1 - y_test.mean()
# calculate null accuracy(for binary classification problems coded as 0/1)
max(y_test.mean(), 1-y_test.mean())
我們看到空准確率是68%,而分類准確率是69%,這說明該分類准確率並不是很好的模型度量方法,分類准確率的一個缺點是其不能表現任何有關測試數據的潛在分布。
# calculate null accuracy (for multi-class classification problems)
y_test.value_counts().head(1) / len(y_test)
比較真實和預測的類別響應值:
# print the first 25 true and predicted responses
print "True:", y_test.values[0:25] print "Pred:", y_pred_class[0:25]
從上面真實值和預測值的比較中可以看出,當正確的類別是0時,預測的類別基本都是0;當正確的類別是1時,預測的類別大都不是1。換句話說,該訓練的模型大都在比例較高的那項類別的預測中預測正確,而在另外一中類別的預測中預測失敗,而我們沒法從分類准確率這項指標中發現這個問題。
分類准確率這一衡量分類器的標准比較容易理解,但是它不能告訴你響應值的潛在分布,並且它也不能告訴你分類器犯錯的類型。接下來介紹的混淆矩陣可以識別這個問題。
3. 混淆矩陣¶
# IMPORTANT: first argument is true values, second argument is predicted values
print metrics.confusion_matrix(y_test, y_pred_class)
- 真陽性(True Positive,TP):指被分類器正確分類的正例數據
- 真陰性(True Negative,TN):指被分類器正確分類的負例數據
- 假陽性(False Positive,FP):被錯誤地標記為正例數據的負例數據
- 假陰性(False Negative,FN):被錯誤地標記為負例數據的正例數據
# save confusion matrix and slice into four pieces
confusion = metrics.confusion_matrix(y_test, y_pred_class) TP = confusion[1, 1] TN = confusion[0, 0] FP = confusion[0, 1] FN = confusion[1, 0] print "TP:", TP print "TN:", TN print "FP:", FP print "FN:", FN
4. 基於混淆矩陣的評估度量¶
准確率、識別率(Classification Accuracy):分類器正確分類的比例
print (TP+TN) / float(TP+TN+FN+FP) print metrics.accuracy_score(y_test, y_pred_class)
錯誤率、誤分類率(Classification Error):分類器誤分類的比例
print (FP+FN) / float(TP+TN+FN+FP) print 1-metrics.accuracy_score(y_test, y_pred_class)
考慮類不平衡問題,其中感興趣的主類是稀少的。即數據集的分布反映負類顯著地占多數,而正類占少數。故面對這種問題,需要其他的度量,評估分類器正確地識別正例數據的情況和正確地識別負例數據的情況。
靈敏性(Sensitivity),也稱為真正例識別率、召回率(Recall):正確識別的正例數據在實際正例數據中的百分比
print TP / float(TP+FN) recall = metrics.recall_score(y_test, y_pred_class) print metrics.recall_score(y_test, y_pred_class)
特效性(Specificity),也稱為真負例率:正確識別的負例數據在實際負例數據中的百分比
print TN / float(TN+FP)
假陽率(False Positive Rate):實際值是負例數據,預測錯誤的百分比
print FP / float(TN+FP) specificity = TN / float(TN+FP) print 1 - specificity
精度(Precision):看做精確性的度量,即標記為正類的數據實際為正例的百分比
print TP / float(TP+FP) precision = metrics.precision_score(y_test, y_pred_class) print precision
F度量(又稱為F1分數或F分數),是使用精度和召回率的方法組合到一個度量上
F F度量是精度和召回率的調和均值,它賦予精度和召回率相等的權重。
F β Fβ度量是精度和召回率的加權度量,它賦予召回率權重是賦予精度的β β倍。
print (2*precision*recall) / (precision+recall) print metrics.f1_score(y_test, y_pred_class)
總結
混淆矩陣賦予一個分類器性能表現更全面的認識,同時它通過計算各種分類度量,指導你進行模型選擇。
使用什么度量取決於具體的業務要求:
- 垃圾郵件過濾器:優先優化精度或者特效性,因為該應用對假陽性(非垃圾郵件被放進垃圾郵件箱)的要求高於對假陰性(垃圾郵件被放進正常的收件箱)的要求
- 欺詐交易檢測器:優先優化靈敏度,因為該應用對假陰性(欺詐行為未被檢測)的要求高於假陽性(正常交易被認為是欺詐)的要求
5. 調整分類的閾值¶
# print the first 10 predicted responses
logreg.predict(X_test)[0:10]
y_test.values[0:10]
# print the first 10 predicted probabilities of class membership
logreg.predict_proba(X_test)[0:10, :]
上面的輸出中,第一列顯示的是預測值為0的百分比,第二列顯示的是預測值為1的百分比。
# print the first 10 predicted probabilities for class 1
logreg.predict_proba(X_test)[0:10, 1]
我們看到,預測為1的和實際的類別號差別很大,所以這里有50%作為分類的閾值顯然不太合理。於是我們將所有預測類別為1的百分比數據用直方圖的方式形象地表示出來,然后嘗試重新設置閾值。
# store the predicted probabilities for class 1
y_pred_prob = logreg.predict_proba(X_test)[:, 1]
# allow plots to appear in the notebook
%matplotlib inline import matplotlib.pyplot as plt
# histogram of predicted probabilities
plt.hist(y_pred_prob, bins=8) plt.xlim(0, 1) plt.title('Histogram of predicted probabilities') plt.xlabel('Predicted probability of diabetes') plt.ylabel('Frequency')
我們發現在20%-30%之間的數高達45%,故以50%作為分類閾值時,只有很少的一部分數據會被認為是類別為1的情況。我們可以將閾值調小,以改變分類器的靈敏度和特效性。
# predict diabetes if the predicted probability is greater than 0.3
from sklearn.preprocessing import binarize y_pred_class = binarize(y_pred_prob, 0.3)[0]
# print the first 10 predicted probabilities
y_pred_prob[0:10]
# print the first 10 predicted classes with the lower threshold
y_pred_class[0:10]
y_test.values[0:10]
從上面兩組數據對比來看,效果確實改善不少
# previous confusion matrix (default threshold of 0.5)
print confusion
# new confusion matrix (threshold of 0.3)
print metrics.confusion_matrix(y_test, y_pred_class)
# sensitivity has increased (used to be 0.24)
print 46 / float(46 + 16) print metrics.recall_score(y_test, y_pred_class)
# specificity has decreased (used to be 0.91)
print 80 / float(80 + 50)
總結:
- 0.5作為閾值時默認的情況
- 調節閾值可以改變靈敏性和特效性
- 靈敏性和特效性是一對相反作用的指標
- 該閾值的調節是作為改善分類性能的最后一步,應更多去關注分類器的選擇或構建更好的分類器
6. ROC曲線和AUC¶
ROC曲線指受試者工作特征曲線/接收器操作特性(receiver operating characteristic,ROC)曲線, 是反映靈敏性和特效性連續變量的綜合指標,是用構圖法揭示敏感性和特異性的相互關系,它通過將連續變量設定出多個不同的臨界值,從而計算出一系列敏感性和特異性。
ROC曲線是根據一系列不同的二分類方式(分界值或決定閾),以真正例率(也就是靈敏度)(True Positive Rate,TPR)為縱坐標,假正例率(1-特效性)(False Positive Rate,FPR)為橫坐標繪制的曲線。
ROC觀察模型正確地識別正例的比例與模型錯誤地把負例數據識別成正例的比例之間的權衡。TPR的增加以FPR的增加為代價。ROC曲線下的面積是模型准確率的度量。
# IMPORTANT: first argument is true values, second argument is predicted probabilities
fpr, tpr, thresholds = metrics.roc_curve(y_test, y_pred_prob) plt.plot(fpr, tpr) plt.xlim([0.0, 1.0]) plt.ylim([0.0, 1.0]) plt.title('ROC curve for diabetes classifier') plt.xlabel('False Positive Rate (1 - Specificity)') plt.ylabel('True Positive Rate (Sensitivity)') plt.grid(True)
ROC曲線上的每一個點對應於一個threshold,對於一個分類器,每個threshold下會有一個TPR和FPR。 比如Threshold最大時,TP=FP=0,對應於原點;Threshold最小時,TN=FN=0,對應於右上角的點(1,1)
正如上面所述,TPR的增加以FPR的增加為代價,所以ROC曲線可以幫助我們選擇一個可以平衡靈敏性和特效性的閾值。通過ROC曲線我們沒法看到響應閾值的對應關系,所以我們用下面的函數來查看。
# define a function that accepts a threshold and prints sensitivity and specificity
def evaluate_threshold(threshold): print 'Sensitivity:', tpr[thresholds > threshold][-1] print 'Specificity:', 1 - fpr[thresholds > threshold][-1]
evaluate_threshold(0.5)
evaluate_threshold(0.3)
AUC(Area Under Curve)被定義為ROC曲線下的面積,也可以認為是ROC曲線下面積占單位面積的比例,顯然這個面積的數值不會大於1。又由於ROC曲線一般都處於y=x這條直線的上方,所以AUC的取值范圍在0.5和1之間。
對應AUC更大的分類器效果更好。所以AUC是衡量分類器性能的一個很好的度量,並且它不像分類准確率那樣,在類別比例差別很大的情況下,依然是很好的度量手段。在欺詐交易檢測中,由於欺詐案例是很小的一部分,這時分類准確率就不再是一個良好的度量,而可以使用AUC來度量。
# IMPORTANT: first argument is true values, second argument is predicted probabilities
print metrics.roc_auc_score(y_test, y_pred_prob)
# calculate cross-validated AUC
from sklearn.cross_validation import cross_val_score cross_val_score(logreg, X, y, cv=10, scoring='roc_auc').mean()
參考資料¶
- scikit-learn documentation: Model evaluation
- ROC曲線-閾值評價標准