當我們越來越追求較高的recall的時候,precision會下降,就是說随着越來越多的少數類被捕獲出來,就會伴随着更多的多數類被判斷錯誤,我們想知道随着追求的recall的增加,模型把多數類判斷錯誤的現象的将如何變化;或者說啊,我們每多找出一些少數類,就會把多少多數類樣本給判斷錯誤。是以我們可以使用Recall和假正率FPR之間的平衡,來代替Recall和precision之間的平衡,
是以這種,衡量模型在盡量捕獲少數類的時候,造成對多數類樣本錯誤判斷情況的變化曲線,我們稱之為ROC(The Receiver Operating Character Curve)(受試者操作曲線)曲線,
這是一種以不同門檻值下假正率FPR為橫軸,對應的召回率Recall作為縱坐标的曲線。
一:機率和門檻值
我們再邏輯回歸中,天然的以0.5為門檻值,計算出機率大于0.5的被分為1類,機率值小于0.5的被分為0類.
我們先用邏輯回歸來看看,我們不适用predict接口來預測,我們自行按照0.5的機率門檻值來預測。
第一步構造資料
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_blobs
# ===== 第一步:構造一個樣本不均衡的資料集合例子
x, y = make_blobs(n_samples=[40, 24], # 兩個簇,每個簇的樣本個數分别就是元素值
centers=[[1, 1], [3, 3]], # 指定兩個簇的中心
cluster_std=[1, 2], # 指定方差
n_features=2, # 樣本特征是2
random_state=0,
shuffle=False)
print(x.shape)
print(y.shape)
print((y == 1).sum()) # 1類是少數類
plt.scatter(x[:, 0], x[:, 1], c=y, cmap='rainbow', s=10)
plt.show()
第二步,構造邏輯回歸模型,且得到預測的資料和真實資料都保留。
# 第二步:使用邏輯回歸
from sklearn.linear_model import LogisticRegression
clf_lo = LogisticRegression().fit(x, y)
prob = clf_lo.predict_proba(x)
print(prob.shape) # X個樣本,每個樣本兩個列,每個列分别表示每個類别下的機率。
import pandas as pd
prob = pd.DataFrame(prob)
prob.columns = ['proba_0', 'proba_1']
# 我們自己按照0.5為門檻值,根據各自的proba,自己來預測一下
y_pred = np.array(prob.loc[:, 'proba_1'] > 0.5, dtype='uint8')
prob.loc[:, 'y_pred'] = y_pred # 增加一列,名字是預測的結果的y
prob.loc[:, 'y_true'] = y # 增加一列,名字是真實值的y
print(prob.head())
第三步就是計算混淆矩陣資訊,以及Recall和precision,我們使用scikit-learn中自帶的來搞。
# 使用混淆矩陣
from sklearn.metrics import confusion_matrix as CM, precision_score as PS, recall_score as RS
# 獲得混淆矩陣的所有值
cm = CM(y_true=prob.loc[:, 'y_true'], y_pred=prob.loc[:, 'y_pred'], labels=[1, 0]) # label就是把少數類放在前面
A, B, C, D = cm[0][0], cm[0][1], cm[1][0], cm[1][1]
print(cm)
# 獲得精确度
ps = PS(y_true=prob.loc[:, 'y_true'], y_pred=prob.loc[:, 'y_pred'], labels=[1, 0]) # label就是把少數類放在前面
print(ps)
print(A / (A + C))
# 獲得召回率
rs = RS(y_true=prob.loc[:, 'y_true'], y_pred=prob.loc[:, 'y_pred'], labels=[1, 0]) # label就是把少數類放在前面
print(rs)
print(A / (A + B))
# 獲得假正率
print(C / (C + D))
這裡我們隻是學習下,在scikit-learn中使用自帶的類幫我得到這評估參數
我們前面也說了,ROC曲線是在不同的門檻值情況下,觀察FPR和Recall的變化的,是以我們需要使用不同的門檻值來分别觀察,根據實際的ROC曲線走向,我們來觀察且選擇一個合适的門檻值。
我們寫一個循環,使用不同的門檻值來看看曲線效果。
# =========== 根據不同的門檻值,畫出其曲線
thresholds = np.linspace(0.05, 0.95, 20)
FPR = [] # 假正率
recall = [] # 召回率
accuracy = [] # 準确率
for threshold in thresholds:
# 我們自己按照threshold為門檻值,根據各自的proba,自己來預測一下
y_pred = np.array(prob.loc[:, 'proba_1'] > threshold, dtype='uint8')
cm = CM(y_true=y, y_pred=y_pred, labels=[1, 0]) # label就是把少數類放在前面
A, B, C, D = cm[0][0], cm[0][1], cm[1][0], cm[1][1]
FPR.append(C / (C + D))
recall.append(A / (A + B))
accuracy.append((A + D) / (A + B + C + D))
plt.plot(thresholds, FPR, c='red', label='FPR')
plt.plot(thresholds, recall, c='blue', label='recall')
plt.plot(thresholds, accuracy, c='black', label='accuracy')
plt.legend()
plt.show()
下面我們使用帶上機率估計的svm
重要的參數就是probability,預設是FALSE,為TRUE就是啟動機率估計。我們再樣本空間上的點,經過decision_function後傳回的值是置信度,有正負之分,根據正負傳回樣本的類别,完全是由正負來對應。
置信度不是機率,他的取值沒有邊界,我們希望能提供一種機率的方式幫我們看來每個點的分類情況。
我們要畫出ROC曲線,得要每個樣本的分類的機率數值,得要有門檻值,還有有混淆矩陣來計算FPR和Recall。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_blobs
# ===== 第一步:構造一個樣本不均衡的資料集合例子
x, y = make_blobs(n_samples=[500, 50], # 兩個簇,每個簇的樣本個數分别就是元素值
centers=[[2, 2], [4, 4]], # 指定兩個簇的中心
cluster_std=[1.5, 0.5], # 指定方差
n_features=2, # 樣本特征是2
random_state=0,
shuffle=False)
print(x.shape)
print(y.shape)
print((y == 1).sum()) # 類别為1的是少數類
# plt.scatter(x[:, 0], x[:, 1], c=y, cmap='rainbow', s=10)
# plt.show()
# 構模組化型,使用帶有機率估計的svm
clf_proba = SVC(kernel='linear', C=1, probability=True).fit(x, y)
proba = clf_proba.predict_proba(x)
# print(proba)
print(proba.shape)
print((proba[:, 1] > 0.5).sum()) # 跟下面的統計不一緻,看來即便有機率小于0.5,依然會有被标記成1的情況出現啊
print((proba[:, 1] > 0.45).sum()) # 跟下面的統計不一緻,看來即便有機率小于0.5,依然會有被标記成1的情況出現啊
print((proba[:, 1] > 0.46).sum()) # 跟下面的統計不一緻,看來即便有機率小于0.5,依然會有被标記成1的情況出現啊
deci = clf_proba.decision_function(x)
# print(deci)
print(deci.shape)
print((deci > 0).sum())
y_pred = clf_proba.predict(x)
print((y_pred == 1).sum())
# =========== 根據不同的門檻值,畫出其曲線
from sklearn.metrics import confusion_matrix as CM
thresholds = np.linspace(0.05, 0.95, 40)
FPR = [] # 假正率
recall = [] # 召回率
for threshold in thresholds:
# 我們自己按照threshold為門檻值,根據各自的proba,自己來預測一下
y_pred = np.array(proba[:, 1] > threshold, dtype='uint8')
cm = CM(y_true=y, y_pred=y_pred, labels=[1, 0]) # label就是把少數類放在前面
A, B, C, D = cm[0][0], cm[0][1], cm[1][0], cm[1][1]
FPR.append(C / (C + D))
recall.append(A / (A + B))
# plt.plot(thresholds, FPR, c='red', label='FPR')
# plt.plot(thresholds, recall, c='blue', label='recall')
# plt.legend()
# plt.show()
plt.plot(FPR, recall, c='blue', label='recall') # ROC 曲線
plt.plot(np.linspace(0.05, 0.95, 40), np.linspace(0.05, 0.95, 40), c='black', linestyle='--') # 畫一條斜率等于1的參考線
plt.title('ROC')
plt.show()
圖中的黑色虛線表示是一個斜率等于1的線。
我們怎麼了解呢?橫坐标是FPR,縱坐标是Recall,我們希望能找到recall和FPR之間的平衡,我們想盡量找到少數類的情況下,盡量不誤傷多數類。
橫坐标越大,說明誤傷的多數類越多,我們希望FPR不要太大,越小也好,是以希望取值往左邊靠;縱坐标recall代表我們能捕捉少數類的能力,我們希望能盡量多的捕捉少數類,這個值希望越大越好,是以取值越往上越好。是以呢,取左上方的一個合适值就行。而且我們還希望橫坐标緩慢增長(這個值越小越好),縱坐标卻可以急速上升(這個值越大越好),是以我們圖中有個拐點就是我們可以取值的範圍。
這樣的話,我們能看到,當Recall在增加的時候,FPR率卻增加緩慢,這才是我們想要的效果。
光看曲線,我們也隻能定性看到一些資訊,沒法準确且定量看到資訊。是以我們需要一個定量的名額來衡量ROC曲線優劣程度。那就是AUC。
Roc曲線下的面積,介于0.1和1之間。AUC作為數值可以直覺的評價分類器的好壞,值越大越好。
也可以用scikit-learn中自帶的roc曲線來幫我選擇門檻值以及畫ROC曲線,以及計算AUC。
# == 使用自帶的模型來畫,一步到位直接求出,省的自己寫循環了。
from sklearn.metrics import roc_curve
FPR, recall, thresholds = roc_curve(y_true=y, # 真實标簽是
y_score=clf_proba.decision_function(x), # 置信度,也可以是機率值
pos_label=1) # 正樣本标簽是1,也就是少數類。
print(FPR.shape) # 得到了每一個門檻值下的 FPR 數組
print(recall.shape) # 得到了每一個門檻值下的 Recall 數組
print(thresholds.shape) # 如果y_score使用的是距離,那麼thresholds也是距離;如果y_score使用的是機率,那麼thresholds也是機率
plt.figure()
plt.plot(FPR, recall, c='blue', label='ROC curve') # ROC 曲線
plt.title('ROC') # 設定标題
plt.xlim([-0.05, 1.05])
plt.ylim([-0.05, 1.05])
plt.xlabel('FPR')
plt.ylabel('Recall')
plt.legend(loc='lower right')
plt.show()
# 計算AUC面積
from sklearn.metrics import roc_auc_score
auc = roc_auc_score(y_true=y, # 真實标簽是
y_score=clf_proba.decision_function(x)) # 置信度,也可以是機率值
print(auc)
一般ROC的量化評估名額就是AUC值。
現在我們有了ROC的曲線了,我們怎麼擷取一個合适的門檻值呢,我們希望的是,模型在捕獲少數類能力變強的時候,盡量越小越好地,不要傷害多數類。也就是說,随着recall的增大,盡量FPR的變化越小越,我們希望找到的最大合适點就是recall和FPR差距最大的點,這點就是約登指數。
diff = recall - FPR
maxIdx = diff.tolist().index(max(diff))
print('約登指數是:%f' % thresholds[maxIdx])
我們找到了一個最佳門檻值
本文的一些ROC曲線和AUC概念可以參考如下連結:
https://zhuanlan.zhihu.com/p/83782315