一、分類算法評價指標
1.分類准確度的問題
分類算法如果用分類准確度來衡量好壞將會存在問題。例如一個癌症預測系統,輸入體檢信息,可以判斷是否有癌症,預測准確度可以達到99.9%,看起來預測系統還可以,但是如果癌症的產生概率只有0.1%,那么系統只要預測所有人都是健康的就可以達到99.9%的准確率,因此雖然准確率很高,但是預測系統實際上沒有發揮什么作用。更加極端的如果癌症概率只有0.01%,那么預測所有人都是健康的概率是99.99%,比預測系統的結果還要好。因此可以得到結論:在存在極度偏斜的數據中,應用分類准確度來評價分類算法的好壞是遠遠不夠的。
2.混淆矩陣
對於二分類問題。可以得到如下的混淆矩陣。
通過混淆矩陣可以得到精准率和召回率,用這兩個指標評價分類算法將會有更好的效果。
3.精准率和召回率
精准率:分類正確的正樣本個數占分類器判定為正樣本的樣本個數的比例(預測分類為1,相應的預測對的概率)。
對應於檢索中的查准率,檢索出相關文檔數/檢索出的文檔總數
之所以使用1的分類來計算精准率是因為,在實際生活中,1代表着受關注的對象,例如:癌症預測系統中,1就代表着患癌症,40%意味着,系統做出100次病人患有癌症的預測結論,其中有40%結論是准確的。
召回率:分類正確的正樣本個數占真正的正樣本個數的比例(真實分類為1,相應的預測對的概率)。
對應於檢索中的查全率。檢索出相關文檔數/文檔庫中相關文檔總數
召回率意味着如果有10個癌症患者,將會有8個被預測到。
區別精確率和召回率主要記住他們是分母不同就好了,召回率是對應測試集中的正類數據而言,而准確率是對應預測結果為正類的數據而言。
另一種圖解:
現在假設有10000個人,預測所有的人都是健康的,假設有10個患病,則有如下的混淆矩陣:
對於准確率:9990/10000=99.9%。對於精准率:0/0沒有意義。對於召回率:0/10=0。可以看出模型對於預測疾病其實並不好。
4.代碼實現
4.1 自己編寫代碼動手實現以下精准率與召回率:
采用digits數據集,為了達到極度偏斜的效果,我們將所有數據設為如果是9為1,不是9為0
import numpy as np from sklearn import datasets digits = datasets.load_digits() X = digits.data y = digits.target.copy() #如果直接引用那么y變了,target也會變 y[digits.target==9] = 1 y[digits.target!=9] = 0 from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666) # 邏輯回歸預測一哈 from sklearn.linear_model import LogisticRegression log_reg = LogisticRegression() log_reg.fit(X_train, y_train) log_reg.score(X_test, y_test) y_log_predict = log_reg.predict(X_test) # 獲取預測的答案 def TN(y_true, y_predict): assert len(y_true) == len(y_predict) return np.sum((y_true == 0) & (y_predict == 0)) def FP(y_true, y_predict): assert len(y_true) == len(y_predict) return np.sum((y_true == 0) & (y_predict == 1)) def FN(y_true, y_predict): assert len(y_true) == len(y_predict) return np.sum((y_true == 1) & (y_predict == 0)) def TP(y_true, y_predict): assert len(y_true) == len(y_predict) return np.sum((y_true == 1) & (y_predict == 1)) def confusion_matrix(y_true, y_predict): return np.array([ [TN(y_true, y_predict), FP(y_true, y_predict)], [FN(y_true, y_predict), TP(y_true, y_predict)] ]) confusion_matrix(y_test, y_log_predict) def precision_score(y_true, y_predict): tp = TP(y_true, y_predict) fp = FP(y_true, y_predict) try: return tp / (tp + fp) except: return 0.0 def recall_score(y_true, y_predict): tp = TP(y_true, y_predict) fn = FN(y_true, y_predict) try: return tp / (tp + fn) except: return 0.0 print("score:" , log_reg.score(X_test, y_test)) print("precision_score", precision_score(y_test, y_log_predict)) print("recall_score", recall_score(y_test, y_log_predict))
結果:

score: 0.9755555555555555 precision_score 0.9473684210526315 recall_score 0.8
4.2 sklearn代碼實現
由於計算邏輯比較簡單,所以直接給出sklearn中封裝好的實現。
#因為是與指標相關,所以都是metrics
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test,y_log_predict)
from sklearn.metrics import precision_score #sklearn中的精准率
precision_score(y_test,y_log_predict)
from sklearn.metrics import recall_score #sklearn中的召回率
recall_score(y_test,y_log_predict)
結果:0.8
5.對精准率和召回率的分析
對於一個模型得到了精准率和召回率,那么應該如何通過這兩個指標對模型進行評價,又或者是一個模型經過調參后,得到不同的精准率和召回率,應該選取哪個參數對應的精准率和召回率才好。這個需要根據不同的場景進行選擇。
例如:對於股票預測,更多的應該是關注精准率,假設關注股票上升的情況,高精准率意味着TP值高(正確地預測到股票會升),這個時候可以幫助人們調整投資,增加收入,如果這一指標低,就以為FP值高(錯誤地認為股票會升),也就是說股票其實是降的,而預測成升了,這將會使用戶虧錢。而召回率低只是意味着在股票上升的情況中,對幾個股票上升的情況沒有被預測到,這對於投資者來說也是可以接受的,畢竟沒有虧錢,因此低召回率對用戶影響不是很大。
例如:對於疾病預測領域,更多的應該關注召回率,因為高召回率意味着能夠更多地將得病的病人預測出來,這個對於患病者非常重要。而精准率低意味着錯誤地預測病人患病,而這個時候只要被預測患病的人再去檢查一下即可,實際上是可以接受的,因此低精准率對用戶影響不大。
而某些情況可能需要同時考慮到兩個指標,以達到一個均衡。這個時候就需要F1 score。這個稱為精准率和召回率的調和平均值。可以發現只有兩個值都比較高的時候,F1才會比較高。如果兩個值某一個很高,另一個很低,F1值也不會高。
from sklearn.metrics import f1_score f1_score(y_test,y_log_predict)
二、精准率和召回率
精准率和召回率之間是互相矛盾的,如果提高召回率,精准率就不可避免的下降,如果精准率提高,召回率就不可避免的下降。
分類閾值的取值的影響
在邏輯回歸中,我們讓決策邊界是 θ* X = 0作為決策邊界,即大於0的分類1,小於0的分類0,我們可以不讓他是0,而是一個常數threshold.下圖是threshold取不同的值可能產生的變化
如圖所示,五角星為1,圓形為0。划的豎線代表決策邊界。可以看到,當精准率提高時,召回率就下降,當召回率提高時,精准率就下降,二者是個矛盾。
log_reg.decision_function(X_test)[:10] # 這個函數就是邏輯回歸模型里那個threshold的值,模型的默認值都是0 '''調用函數輸出結果 array([-22.05700185, -33.02943631, -16.21335414, -80.37912074, -48.25121102, -24.54004847, -44.39161228, -25.0429358 , -0.97827574, -19.71740779])''' np.min(decision_scores) #分類標准閾值的最大值 np.max(decision_scores) y_predict_2 = np.array(decision_scores >= 5, dtype='int') #這樣就改變了分類標准的閾值,大於5的才是1
Precision-Recall-Curve(PR曲線)
隨着分類閾值的不斷變化,precision和recall值也會不斷變化,那么我們可以畫出其相應的曲線,可以在曲線中找到一個F1 Score最高的分類閾值。通常在recall要急劇下降的那個位置。
實現自己的PR曲線
import numpy as np import matplotlib.pyplot as plt from sklearn import datasets digits = datasets.load_digits() X = digits.data y = digits.target.copy() y[digits.target==9] = 1 y[digits.target!=9] = 0 from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666) from sklearn.linear_model import LogisticRegression log_reg = LogisticRegression() log_reg.fit(X_train, y_train) decision_scores = log_reg.decision_function(X_test) from sklearn.metrics import precision_score from sklearn.metrics import recall_score precisions = [] recalls = [] thresholds = np.arange(np.min(decision_scores), np.max(decision_scores), 0.1) #以0.1為步長獲取分類閾值最大值到最小值里的點 for threshold in thresholds: y_predict = np.array(decision_scores >= threshold, dtype='int') # 每個點求一次 precision_score和recall_score precisions.append(precision_score(y_test, y_predict)) recalls.append(recall_score(y_test, y_predict)) #畫出對應的值 plt.plot(thresholds,precisions,label="precision") plt.plot(thresholds,recalls,label="recall") plt.legend() plt.show()
繪制precision和recall曲線。
plt.plot(precisions,recalls) plt.show() #快速下降的點,可能就是比較平衡的點
scikit-learn 中的PR曲線
from sklearn.metrics import precision_recall_curve precisions, recalls, thresholds = precision_recall_curve(y_test, decision_scores) #注意通過這個函數得到的precision和recall的最后一個值分別為1和0,沒有對應的Score plt.plot(thresholds, precisions[:-1]) #所以不取最后一個 plt.plot(thresholds, recalls[:-1]) plt.show() plt.plot(precisions, recalls) plt.show()
注意:
ROC曲線
ROC曲線是用來描述TPR與FPR之間的曲線之間的關系.
TPR(True-Positive-Rate): 代表預測為1並且預測對了的樣本數量占真實值為1的百分比為多少
FPR(False-Positive-Rate):代表預測為1但預測錯了的樣本數量占真實值為0的百分比是多少
分類閾值改變,TPR和FPR的變化:
可以看到FPR和TPR呈現相一致的關系,這個也容易理解。當召回率提高時,說明會盡量將1樣本都包含進去(豎線左移),而這個時候就會增加錯分的0樣本(更多的0被包含進去),因此TN減小,FP增大,必然導致FPR的增大。
會發現當閾值變低,TPR與FPR值都變高,閾值變低那么預測值更容易變成1,所以預測值為1的樣本也多了,真實值為1的總量是不變的,所以TPR變高,同理預測錯了的樣本也會變多。
代碼實現 FPR 和 TPR,並繪制 ROC 曲線:
封裝:
def TPR(y_true, y_predict): tp = TP(y_true, y_predict) fn = FN(y_true, y_predict) try: return tp / (tp + fn) except: return 0. def FPR(y_true, y_predict): fp = FP(y_true, y_predict) tn = TN(y_true, y_predict) try: return fp / (fp + tn) except: return 0.
求 TPR 和 FPR:
import numpy as np from sklearn import datasets digits = datasets.load_digits() X = digits.data y = digits.target.copy() y[digits.target==9] = 1 y[digits.target!=9] = 0 from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666) from sklearn.linear_model import LogisticRegression log_reg = LogisticRegression() log_reg.fit(X_train, y_train) decision_scores = log_reg.decision_function(X_test) from playML.metrics import FPR, TPR fprs = [] tprs = [] thresholds = np.arange(np.min(decision_scores), np.max(decision_scores), 0.1) for threshold in thresholds: # dtype=‘int‘:將數據類型從 bool 型轉為 int 型; y_predict = np.array(decision_scores >= threshold, dtype=‘int‘) fprs.append(FPR(y_test, y_predict)) tprs.append(TPR(y_test, y_predict))
繪制 ROC 曲線:
import matplotlib.pyplot as plt plt.plot(fprs, tprs) plt.show()
-
分析:
- ROC 曲線與圖形邊界圍成的面積,作為衡量模型優劣的標准,面積越大,模型越優;
- 可以是同樣算法不同超參數所得的不同模型,也可以是不同算法所得的不同模型。
scikit-learn中的ROC曲線:
from sklearn.linear_model import LogisticRegression log_reg = LogisticRegression() log_reg.fit(X_train, y_train) decision_scores = log_reg.decision_function(X_test) #獲取邏輯回歸每個樣本的decision_scores from sklearn.metrics import roc_curve fprs, tprs, thresholds = roc_curve(y_test, decision_scores) #獲取tpr與fpr的值 plt.plot(fprs, tprs) plt.show()
ROC曲線的面積越大,那么我們的模型就越好,因為如果繪制一個FPR-TPR的曲線,在同樣的FPR(犯錯率)之下,TPR的值越高,模型越好,相應的它的面積越大。
-
計算 ROC 曲線與坐標軸圍成的面積:稱 ROC 的 auc;
-
面積越大,模型越優;
from sklearn.metrics import roc_auc_score #求面積, 面積最大就為1,因為TPR與FPR最大值都為1 roc_auc_score(y_test, decision_scores)# 結果:0.98304526748971188
這個將會得到比較高的值,說明auc對於有偏數據並不是很敏感,auc一般用於比較兩個模型之間的好壞。
ROC對有偏數據並不敏感,主要用來比較兩個模型誰更好一些,比如一個算法不同參數,或者兩個不同的算法,都可以用這個來比較,如果他們的數據不是極度偏移的話。
三、多分類評價
1.多分類問題中的混淆矩陣
這里只研究多分類混淆矩陣,其他的以后在詳細研究一下。
import numpy as np import matplotlib.pyplot as plt from sklearn import datasets digits = datasets.load_digits() X = digits.data #數據並沒有進行切割,所以這是10分類問題 y = digits.target from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.8, random_state=666) from sklearn.linear_model import LogisticRegression log_reg = LogisticRegression() log_reg.fit(X_train, y_train) log_reg.score(X_test, y_test) y_predict = log_reg.predict(X_test) from sklearn.metrics import precision_score precision_score(y_test, y_predict) #如果直接獲取矩陣,會報錯
ValueError: Target is multiclass but average='binary'. Please choose another average setting.
precision_score(y_test, y_predict, average="micro") #根據錯誤提示,需要更改參數 #為了求出多分類的精准度和召回率,必須傳入參數average='micro' from sklearn.metrics import confusion_matrix confusion_matrix(y_test, y_predict) #跟2分類的混淆矩陣的含義一樣,行代表真實值列代表預測值
結果:

array([[147, 0, 1, 0, 0, 1, 0, 0, 0, 0], [ 0, 123, 1, 2, 0, 0, 0, 3, 4, 10], [ 0, 0, 134, 1, 0, 0, 0, 0, 1, 0], [ 0, 0, 0, 138, 0, 5, 0, 1, 5, 0], [ 2, 5, 0, 0, 139, 0, 0, 3, 0, 1], [ 1, 3, 1, 0, 0, 146, 0, 0, 1, 0], [ 0, 2, 0, 0, 0, 1, 131, 0, 2, 0], [ 0, 0, 0, 1, 0, 0, 0, 132, 1, 2], [ 1, 9, 2, 3, 2, 4, 0, 0, 115, 4], [ 0, 1, 0, 5, 0, 3, 0, 2, 2, 134]], dtype=int64)
其中對角線的代表該分類預測正確的數量,其他位置的值代表預測錯誤所對應的值。為了直觀,現在繪制出圖像。
cfm = confusion_matrix(y_test, y_predict) plt.matshow(cfm, cmap=plt.cm.gray) #一個矩陣的繪制參數,第一個參數是矩陣,第二個參數是顏色 plt.show()
from sklearn.metrics import confusion_matrix cfm = confusion_matrix(y_test,y_predict) row_sums = np.sum(cfm,axis=1)#將每行相加,得到每行的樣本總數 err_matrix = cfm/row_sums#繪制比例 np.fill_diagonal(err_matrix,0)# 因為對角線肯定對的多,將對角線的元素全部置為0 plt.matshow(err_matrix,cmap=plt.cm.gray) #越亮代表值越大,也就是錯分的越多 plt.show()#這樣就看出把什么看成什么的錯誤多了