天天看點

《scikit-learn》SVM(三)ROC曲線

當我們越來越追求較高的recall的時候,precision會下降,就是說随着越來越多的少數類被捕獲出來,就會伴随着更多的多數類被判斷錯誤,我們想知道随着追求的recall的增加,模型把多數類判斷錯誤的現象的将如何變化;或者說啊,我們每多找出一些少數類,就會把多少多數類樣本給判斷錯誤。是以我們可以使用Recall和假正率FPR之間的平衡,來代替Recall和precision之間的平衡,

《scikit-learn》SVM(三)ROC曲線
《scikit-learn》SVM(三)ROC曲線

是以這種,衡量模型在盡量捕獲少數類的時候,造成對多數類樣本錯誤判斷情況的變化曲線,我們稱之為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()
           
《scikit-learn》SVM(三)ROC曲線

第二步,構造邏輯回歸模型,且得到預測的資料和真實資料都保留。

# 第二步:使用邏輯回歸
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()
           
《scikit-learn》SVM(三)ROC曲線

下面我們使用帶上機率估計的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()
           
《scikit-learn》SVM(三)ROC曲線

圖中的黑色虛線表示是一個斜率等于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)
           
《scikit-learn》SVM(三)ROC曲線

一般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

繼續閱讀