機器學習基礎(chǔ)-如何評價分類結(jié)果的優(yōu)劣?

How to measure the quality of classification results丽已?

在剛接觸機器學習的時候蚌堵,我們可以自己動手完成一個簡單的KNN算法,但是我們不禁會疑惑沛婴,它的效果怎樣吼畏?在機器學習中如何評價一個算法的好壞?我們在機器學習過程中還有需要注意那些其他的問題呢嘁灯?

我們?nèi)匀灰砸粋€著名的鳶尾花數(shù)據(jù)集來學習機器學習中判斷模型的有些的各種指標泻蚊。

一、數(shù)據(jù)準備

對于算法優(yōu)劣的度量丑婿,通常的做法是將原始數(shù)據(jù)中的一部分作為訓練數(shù)據(jù)性雄、另一部分作為測試數(shù)據(jù)。使用訓練數(shù)據(jù)訓練模型枯冈,再用測試數(shù)據(jù)對模型進行驗證毅贮。

1.1、導入鳶尾花數(shù)據(jù)集

import numpy as np
from sklearn import datasets
import matplotlib.pyplot as plt
import pandas as pd

iris = datasets.load_iris()
X = iris.data
y = iris.target

X.shape
y.shape
(150,)

可以看出數(shù)據(jù)集保存在X中尘奏,分類的結(jié)果保存在了y中

1.2滩褥、拆分數(shù)據(jù)

一般情況下我們按照0.8:0.2的比例進行拆分,但是有時候我們不能簡單地把前n個數(shù)據(jù)作為訓練數(shù)據(jù)集炫加,后n個作為測試數(shù)據(jù)集瑰煎,比如鳶尾花數(shù)據(jù)集中的數(shù)據(jù)是有序排列的,如下:

y
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])

從以上的數(shù)據(jù)可知俗孝,要想使拆分數(shù)據(jù)集變得有意義酒甸,為了解決這個問題,我們可以將數(shù)據(jù)集打亂赋铝,做一個shuffle操作插勤。但是本數(shù)據(jù)集的特征和標簽是分開的,也就是說我們分別亂序后革骨,原來的對應關(guān)系就不存在了农尖。有兩種方法解決這一問題:

1.將X和y合并為同一個矩陣,然后對矩陣進行shuffle良哲,之后再分解

2.對y的索引進行亂序盛卡,根據(jù)索引確定與X的對應關(guān)系,最后再通過亂序的索引進行賦值

為了鞏固基礎(chǔ)筑凫,下面我們自己動手來完成數(shù)據(jù)集的拆分

第一種方式

# 使用concatenate函數(shù)進行拼接滑沧,因為傳入的矩陣必須具有相同的形狀并村。
# 因此需要對label進行reshape操作,reshape(-1,1)表示行數(shù)自動計算滓技,1列哩牍。
# axis=1表示縱向拼接。
tempConcat = np.concatenate((X, y.reshape(-1,1)), axis=1)
# 拼接好后殖属,直接進行亂序操作
np.random.shuffle(tempConcat)
# 再將shuffle后的數(shù)組使用split方法拆分
shuffle_X,shuffle_y = np.split(tempConcat, [4], axis=1)
# 設置劃分的比例
test_ratio = 0.2
test_size = int(len(X) * test_ratio)
X_train = shuffle_X[test_size:]
y_train = shuffle_y[test_size:]
X_test = shuffle_X[:test_size]
y_test = shuffle_y[:test_size]

第二種方式

# 將x長度這么多的數(shù)姐叁,返回一個新的打亂順序的數(shù)組,注意洗显,數(shù)組中的元素不是原來的數(shù)據(jù)外潜,而是混亂的索引
shuffle_index = np.random.permutation(len(X))
# 指定測試數(shù)據(jù)的比例
test_ratio = 0.2
test_size = int(len(X) * test_ratio)
test_index = shuffle_index[:test_size]
train_index = shuffle_index[test_size:]
X_train = X[train_index]
X_test = X[test_index]
y_train = y[train_index]
y_test = y[test_index]

將拆分方法封裝成與sklearn中同名的函數(shù)

import numpy as np

def train_test_split_temp(X, y, test_ratio=0.2, seed=None):
    """將矩陣X和標簽y按照test_ration分割成X_train, X_test, y_train, y_test"""
    assert X.shape[0] == y.shape[0],         "the size of X must be equal to the size of y"
    assert 0.0 <= test_ratio <= 1.0,         "test_train must be valid"

    if seed:    # 是否使用隨機種子,使隨機結(jié)果相同挠唆,方便debug
        np.random.seed(seed)    # permutation(n) 可直接生成一個隨機排列的數(shù)組处窥,含有n個元素
    shuffle_index = np.random.permutation(len(X))

    test_size = int(len(X) * test_ratio)
    test_index = shuffle_index[:test_size]
    train_index = shuffle_index[test_size:]
    X_train = X[train_index]
    X_test = X[test_index]
    y_train = y[train_index]
    y_test = y[test_index]    
    return X_train, X_test, y_train, y_test
X_train, X_test, y_train, y_test = train_test_split_temp(X, y)

sklearn中的train_test_split

我們自己寫的train_test_split其實也是在模仿sklearn風格,更多的時候我們可以直接調(diào)用玄组。

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=666)

print(X_train.shape)
print(X_test.shape)
print(y_train.shape)
print(y_test.shape)
(120, 4)
(30, 4)
(120,)
(30,)
from sklearn.neighbors import KNeighborsClassifier
# 創(chuàng)建kNN_classifier實例
kNN_classifier = KNeighborsClassifier(n_neighbors=3)
# kNN_classifier做一遍fit(擬合)的過程滔驾,沒有返回值,
# 模型就存儲在kNN_classifier實例中
kNN_classifier.fit(X_train, y_train)

# kNN進行預測predict俄讹,需要傳入一個矩陣哆致,而不能是一個數(shù)組。reshape()成一個二維數(shù)組患膛,第一個參數(shù)是1表示只有一個數(shù)據(jù)摊阀,第二個參數(shù)-1,numpy自動決定第二維度有多少
y_predict = kNN_classifier.predict(X_test)
y_predict
array([1, 2, 1, 2, 0, 1, 1, 2, 1, 1, 1, 0, 0, 0, 2, 1, 0, 2, 2, 2, 1, 0,
       2, 0, 1, 1, 0, 1, 2, 2])

二踪蹬、分類準確度

在劃分出測試數(shù)據(jù)集后胞此,我們就可以驗證其模型準確率了。在這了引出一個非常簡單且常用的概念:accuracy(分類準確度)

accuracy_score:函數(shù)計算分類準確率跃捣,返回被正確分類的樣本比例(default)或者是數(shù)量(normalize=False)
在多標簽分類問題中漱牵,該函數(shù)返回子集的準確率,對于一個給定的多標簽樣本疚漆,如果預測得到的標簽集合與該樣本真正的標簽集合嚴格吻合酣胀,則subset accuracy =1.0否則是0.0

因accuracy定義清洗、計算方法簡單娶聘,因此經(jīng)常被使用灵临。但是它在某些情況下并不一定是評估模型的最佳工具。精度(查準率)和召回率(查全率)等指標對衡量機器學習的模型性能在某些場合下要比accuracy更好趴荸。

當然這些指標在后續(xù)都會介紹。在這里我們就使用分類精準度宦焦,并將其作用于一個新的手寫數(shù)字識別分類算法上发钝。

對于上面的鳶尾花分類顿涣,準確度=分類正確的數(shù)量/全部測試樣本的數(shù)量

print(sum(y_test == y_predict)/len(y_test))
1.0

再通過一個例子來鞏固一下

2.1、數(shù)據(jù)探索

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
# 手寫數(shù)字數(shù)據(jù)集酝豪,封裝好的對象涛碑,可以理解為一個字段
digits = datasets.load_digits()
# 可以使用keys()方法來看一下數(shù)據(jù)集的詳情
digits.keys()

dict_keys(['data', 'target', 'target_names', 'images', 'DESCR'])

我們可以看一下sklearn.datasets提供的數(shù)據(jù)描述:

5620張圖片,每張圖片有64個像素點即特征(8*8整數(shù)像素圖像)孵淘,每個特征的取值范圍是1~16(sklearn中的不全)蒲障,對應的分類結(jié)果是10個數(shù)字print(digits.DESCR)

下面我們根據(jù)datasets提供的方法,進行簡單的數(shù)據(jù)探索瘫证。

# 特征的shape
X = digits.data
X.shape

(1797, 64)
# 標簽的shape
y = digits.target
y.shape
(1797,)
# # 標簽分類
digits.target_names
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# # 去除某一個具體的數(shù)據(jù)揉阎,查看其特征以及標簽信息
some_digit = X[0]
some_digit
array([ 0.,  0.,  5., 13.,  9.,  1.,  0.,  0.,  0.,  0., 13., 15., 10.,
       15.,  5.,  0.,  0.,  3., 15.,  2.,  0., 11.,  8.,  0.,  0.,  4.,
       12.,  0.,  0.,  8.,  8.,  0.,  0.,  5.,  8.,  0.,  0.,  9.,  8.,
        0.,  0.,  4., 11.,  0.,  1., 12.,  7.,  0.,  0.,  2., 14.,  5.,
       10., 12.,  0.,  0.,  0.,  0.,  6., 13., 10.,  0.,  0.,  0.])
# # 也可以這條數(shù)據(jù)進行可視化
some_digmit_image = some_digit.reshape(8, 8)
plt.imshow(some_digmit_image, cmap = matplotlib.cm.binary)
plt.show()

2.2、自己實現(xiàn)分類準確度

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=666)
print(X_train.shape)
print(X_test.shape)
print(y_train.shape)
print(y_test.shape)
(1437, 64)
(360, 64)
(1437,)
(360,)
np.ceil(np.log2(X.shape[0]))
11.0
knn = KNeighborsClassifier(n_neighbors=11)
knn.fit(X_train, y_train)
y_predict = knn.predict(X_test)
print(sum(y_test == y_predict)/len(y_test))
0.9861111111111112

2.3背捌、sklearn中的準確度

from sklearn.metrics import accuracy_score

accuracy_score(y_test, y_predict)
0.9861111111111112

三毙籽、混淆矩陣

討論混淆矩陣之前,我們先思考這樣一個問題:

對于一個癌癥預測系統(tǒng)毡庆,輸入檢查指標坑赡,判斷是否患有癌癥,預測準確度99.9%么抗。這個系統(tǒng)是好是壞呢毅否?

如果癌癥產(chǎn)生的概率是0.1%,那其實根本不需要任何機器學習算法蝇刀,只要系統(tǒng)預測所有人都是健康的螟加,即可達到99.9%的準確率。也就是說對于極度偏斜(Skewed Data)的數(shù)據(jù)熊泵,只使用分類準確度是不能衡量仰迁。

這是就需要使用混淆矩陣(Confusion Matrix)做進一步分析。

3.1顽分、什么是混淆矩陣徐许?

對于二分類問題來說,所有的問題被分為0和1兩類卒蘸,混淆矩陣是2*2的矩陣:

預測值0 預測值1
真實值0 TN FP
真實值1 FN TP
  • TN:真實值是0雌隅,預測值也是0,即我們預測是negative缸沃,預測正確了恰起。
  • FP:真實值是0,預測值是1趾牧,即我們預測是positive检盼,但是預測錯誤了。
  • FN:真實值是1翘单,預測值是0吨枉,即我們預測是negative蹦渣,但預測錯誤了。
  • TP:真實值是1貌亭,預測值是1柬唯,即我們預測是positive,預測正確了圃庭。

現(xiàn)在假設有1萬人進行預測锄奢,填入混淆矩陣如下:

預測值0 預測值1
真實值0 9978 12
真實值1 2 8

對于1萬個人中,有9978個人本身并沒有癌癥剧腻,我們的算法也判斷他沒有癌癥拘央;有12個人本身沒有癌癥,但是我們的算法卻錯誤地預測他有癌癥恕酸;有2個人確實有癌癥堪滨,但我們算法預測他沒有癌癥;有8個人確實有癌癥蕊温,而且我們也預測對了袱箱。

因為混淆矩陣表達的信息比簡單的分類準確度更全面,因此可以通過混淆矩陣得到一些有效的指標义矛。

3.2发笔、混淆矩陣的代碼實現(xiàn)

實現(xiàn)一個邏輯回歸算法

import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

digits = datasets.load_digits()
X = digits.data
y = digits.target.copy()

# 要構(gòu)造偏斜數(shù)據(jù),將數(shù)字9的對應索引的元素設置為1凉翻,0~8設置為0
y[digits.target==9]=1
y[digits.target!=9]=0

# 使用邏輯回歸做一個分類
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

log_reg = LogisticRegression()
log_reg.fit(X_train,y_train)
# 得到X_test所對應的預測值
y_log_predict = log_reg.predict(X_test)
log_reg.score(X_test, y_test)

0.9755555555555555

定義混淆矩陣的四個指標:TN

def TN(y_true, y_predict):
    assert len(y_true) == len(y_predict)
    # (y_true == 0):向量與數(shù)值按位比較了讨,得到的是一個布爾向量
    # 向量與向量按位與,結(jié)果還是布爾向量
    # np.sum 計算布爾向量中True的個數(shù)(True記為1制轰,F(xiàn)alse記為0)
    return np.sum((y_true == 0) & (y_predict == 0))  
    # 向量與向量按位與前计,結(jié)果還是向量
TN(y_test, y_log_predict)
403

定義混淆矩陣的四個指標:FP

def FP(y_true, y_predict):
    assert len(y_true) == len(y_predict)
    # (y_true == 0):向量與數(shù)值按位比較,得到的是一個布爾向量
    # 向量與向量按位與垃杖,結(jié)果還是布爾向量
    # np.sum 計算布爾向量中True的個數(shù)(True記為1男杈,F(xiàn)alse記為0)
    return np.sum((y_true == 0) & (y_predict == 1))  # 向量與向量按位與,結(jié)果還是向量
FP(y_test, y_log_predict)
2

定義混淆矩陣的四個指標:FN

def FN(y_true, y_predict):
    assert len(y_true) == len(y_predict)
    # (y_true == 0):向量與數(shù)值按位比較调俘,得到的是一個布爾向量
    # 向量與向量按位與伶棒,結(jié)果還是布爾向量
    # np.sum 計算布爾向量中True的個數(shù)(True記為1,F(xiàn)alse記為0)
    return np.sum((y_true == 1) & (y_predict == 0))  # 向量與向量按位與彩库,結(jié)果還是向量
FN(y_test, y_log_predict)
9

定義混淆矩陣的四個指標:TP

def TP(y_true, y_predict):
    assert len(y_true) == len(y_predict)
    # (y_true == 0):向量與數(shù)值按位比較肤无,得到的是一個布爾向量
    # 向量與向量按位與,結(jié)果還是布爾向量
    # np.sum 計算布爾向量中True的個數(shù)(True記為1骇钦,F(xiàn)alse記為0)
    return np.sum((y_true == 1) & (y_predict == 1))  # 向量與向量按位與宛渐,結(jié)果還是向量
TP(y_test, y_log_predict)
36

輸出混淆矩陣

import pandas as pd
def confusion_matrix(y_true, y_predict):
    return pd.DataFrame(np.array([
        [TN(y_true, y_predict), FP(y_true, y_predict)],
        [FN(y_true, y_predict), TP(y_true, y_predict)]]) 
                        ,index=['實際值0', '實際值1'] 
                        ,columns=["預測值0", '預測值1'])

confusion_matrix(y_test, y_log_predict)
# pd.DataFrame(confusion_matrix(y_test, y_log_predict)
#              ,index=['實際值0', '實際值1']
#              ,columns=["預測值0", '預測值1'])
pandas輸出混淆矩陣

3.3、scikit-learn中的混淆矩陣

from sklearn.metrics import confusion_matrix

# cm = confusion_matrix(y_test, y_log_predict)

cm = pd.crosstab(y_log_predict,y_test)
# cm

# 導入第三方模塊
import seaborn as sns

# 將混淆矩陣構(gòu)造成數(shù)據(jù)框,并加上字段名和行名稱,用于行或列的含義說明
cm = pd.DataFrame(cm)
# 繪制熱力圖
sns.heatmap(cm, annot = True,cmap = 'GnBu', fmt='g')
# 添加x軸和y軸的標簽
plt.xlabel(' Real Lable')
plt.ylabel(' Predict Lable')
# 圖形顯示
Text(33,0.5,' Predict Lable')
seaborn構(gòu)建的混淆矩陣

四、精準率與召回率

精準率:precision= \frac{TP}{TP+FP}

即精準率為8/(8+12)=40%朗恳。所謂的精準率是:分母為所有預測為1的個數(shù)得糜,分子是其中預測對了的個數(shù),即預測值為1繁扎,且預測對了的比例幔荒。

為什么管它叫精準率呢?在有偏的數(shù)據(jù)中梳玫,我們通常更關(guān)注值為1的特征爹梁,比如“患病”,比如“有風險”提澎。在100次結(jié)果為患病的預測姚垃,平均有40次預測是對的。即精準率為我們關(guān)注的那個事件盼忌,預測的有多準积糯。

召回率:recall= \frac{TP}{TP+FN}

即精準率為8/(8+2)=80%。所謂召回率是:所有真實值為1的數(shù)據(jù)中谦纱,預測對了的個數(shù)看成。每當有100個癌癥患者,算法可以成功的預測出8個 跨嘉。也就是我們關(guān)注的那個事件真實的發(fā)生情況下川慌,我們成功預測的比例是多少

那么為什么需要精準率和召回率呢祠乃?還是下面的這個例子梦重,有10000個人,混淆矩陣如下:

預測值0 預測值1
真實值0 9978 12
真實值1 2 8

如果我們粗暴的認為所有人都是健康的亮瓷,那算法的準確率是99.78%琴拧,但這是毫無意義的。如果算精準率則是40%寺庄,召回率是80%艾蓝。

4.1、精準率的代碼實現(xiàn)

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
precision_score(y_test, y_log_predict)
0.9473684210526315

4.2斗塘、召回率的代碼實現(xiàn)

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
recall_score(y_test, y_log_predict)
0.8

4.3赢织、scikit-learn中的精準率與召回率

from sklearn.metrics import precision_score

precision_score(y_test, y_log_predict)
0.9473684210526315
from sklearn.metrics import recall_score

recall_score(y_test, y_log_predict)
0.8

4.4、精準率與召回率的關(guān)系與側(cè)重點

  • 精準率(查準率):預測值為1馍盟,且預測對了的比例于置,即:我們關(guān)注的那個事件,預測的有多準贞岭。

  • 召回率(查全率):所有真實值為1的數(shù)據(jù)中八毯,預測對了的個數(shù)搓侄,即:我們關(guān)注的那個事件真實的發(fā)生情況下,我們成功預測的比例是多少话速。

有的時候讶踪,對于一個算法而言,精準率與召回率之間呈現(xiàn)負相關(guān)的關(guān)系泊交。精準率高一些乳讥,召回率就低一些;或者召回率高一些廓俭,精準率就低一些云石。那么如何取舍呢?

其實在衡量機器學習的其他指標中研乒,我們也需要進行取舍汹忠,通常只需要把握一個原則:

視場景而定。

比如我們做了一個股票預測系統(tǒng)雹熬,未來股票是??還是??這樣一個二分類問題宽菜。很顯然“漲”才是我們關(guān)注的焦點,那么我們肯定希望:系統(tǒng)預測上漲的股票中橄唬,真正上漲的比例越大越好赋焕,這就是希望查準率高。那么我們是否關(guān)注查全率呢仰楚?在大盤中有太多的真實上漲股票隆判,雖然我們漏掉了一些上升周期,但是我們沒有買進僧界,也就沒有損失侨嘀。但是如果查準率不高,預測上漲的結(jié)果下跌了捂襟,那就是實實在在的虧錢了咬腕。所以在這個場景中,查準率更重要葬荷。

當然也有追求召回率的場景涨共,在醫(yī)療領(lǐng)域做疾病診斷,如果召回率低宠漩,意味著本來有一個病人得病了举反,但是沒有正確預測出來,病情就惡化了扒吁。我們希望盡可能地將所有有病的患者都預測出來火鼻,而不是在看在預測有病的樣例中有多準。

五、F1 Score

5.1魁索、F1 Score 的定義

在實際業(yè)務場景中融撞,也有很多沒有這么明顯的選擇。那么在同時需要關(guān)注精準率和召回率粗蔚,如何在兩個指標中取得平衡呢尝偎?在這種情況下,我們使用一種新的指標:F1 Score鹏控。

如果要我們綜合精準率和召回率這兩個指標冬念,我們可能會想到取平均值這樣的方法。F1 Score的思想也差不多:

F1 Score 是精準率和召回率的調(diào)和平均值牧挣。

F1= \frac{2*precision*recall}{precision+recall}

什么是調(diào)和平均值?為什么要取調(diào)和平均值醒陆?調(diào)和平均值的特點是如果二者極度不平衡瀑构,如某一個值特別高、另一個值特別低時刨摩,得到的F1 Score值也特別低寺晌;只有二者都非常高,F(xiàn)1才會高澡刹。這樣才符合我們對精準率和召回率的衡量標準呻征。

\frac{1}{F1}= \frac{1}{2}(\frac{1}{precision} + \frac{1}{recall})

5.2、代碼演示

def f1_score(precision, recall):
    try:
        return 2 * precision * recall / (precision + recall)
    except:
        return 0.0

precision = 0.5
recall = 0.5
f1_score(precision, recall)
0.5

假設精準率和召回率同時為0.5罢浇,則二者的算數(shù)平均值為0.5陆赋,計算F1 Score:

precision = 0.5
recall = 0.5
f1_score(precision, recall)
0.5

假設精準率為0.9,召回率同時為0.1嚷闭,則二者的算數(shù)平均值為0.5攒岛,計算F1 Score:

precision = 0.5
recall = 0.5
f1_score(precision, recall)
0.5

六、ROC曲線

6.1胞锰、分類閾值灾锯、TPR和FPR

在了解ROC曲線之前,先看三個概念:分類閾值嗅榕、TPR和FPR

6.1.1顺饮、分類閾值

分類閾值,即設置判斷樣本為正例的閾值threshold凌那,

如果某個邏輯回歸模型對某封電子郵件進行預測時返回的概率為 0.9995兼雄,則表示該模型預測這封郵件非常可能是垃圾郵件案怯。相反君旦,在同一個邏輯回歸模型中預測分數(shù)為 0.0003 的另一封電子郵件很可能不是垃圾郵件。可如果某封電子郵件的預測分數(shù)為 0.6 呢金砍?為了將邏輯回歸值映射到二元類別局蚀,您必須指定分類閾值(也稱為判定閾值)。如果值高于該閾值恕稠,則表示“垃圾郵件”琅绅;如果值低于該閾值,則表示“非垃圾郵件”鹅巍。人們往往會認為分類閾值應始終為 0.5千扶,但閾值取決于具體問題,因此必須對其進行調(diào)整骆捧。

在sklearn中有一個方法叫:decision_function澎羞,即返回分類閾值

decision_scores = log_reg.decision_function(X_test)
y_predict = np.array(decision_scores >= 5, dtype='int')
# decision_scores
# X_test
array([[ 0.,  0.,  4., ...,  3.,  0.,  0.],
       [ 0.,  0.,  4., ...,  2.,  0.,  0.],
       [ 0.,  2., 11., ..., 10.,  0.,  0.],
       ...,
       [ 0.,  0.,  1., ...,  0.,  0.,  0.],
       [ 0.,  0.,  0., ...,  0.,  0.,  0.],
       [ 0.,  0., 12., ...,  8.,  0.,  0.]])

我們知道,精準率和召回率這兩個指標有內(nèi)在的聯(lián)系敛苇,并且相互沖突妆绞。precision隨著threshold的增加而降低,recall隨著threshold的增大而減小枫攀。如果某些場景需要precision括饶,recall都保持在80%,可以通過這種方式求出threshold

6.1.2来涨、TPR

預測值0 預測值1
真實值0 TN FP
真實值1 FN TP

TPR:預測為1图焰,且預測對了的數(shù)量,占真實值為1的數(shù)據(jù)百分比蹦掐。很好理解技羔,就是召回率。
TPR = recall= \frac{TP}{TP+FN}

6.1.3卧抗、FPR

FPR:預測為1堕阔,但預測錯了的數(shù)量,占真實值不為1的數(shù)據(jù)百分比颗味。與TPR相對應超陆,F(xiàn)PR除以真實值為0的這一行所有的數(shù)字和 。
FPR = \frac{FP}{TN+FP}

預測值0 預測值1
真實值0 9978 12
真實值1 2 8

TPR和FPR之間是成正比的浦马,TPR高时呀,F(xiàn)PR也高。ROC曲線就是刻畫這兩個指標之間的關(guān)系晶默。

6.2谨娜、 什么是ROC曲線

ROC曲線(Receiver Operation Characteristic Cureve),描述TPR和FPR之間的關(guān)系磺陡。x軸是FPR趴梢,y軸是TPR漠畜。

我們已經(jīng)知道,TPR就是所有正例中坞靶,有多少被正確地判定為正憔狞;FPR是所有負例中,有多少被錯誤地判定為正彰阴。 分類閾值取不同值瘾敢,TPR和FPR的計算結(jié)果也不同,最理想情況下尿这,我們希望所有正例 & 負例 都被成功預測 TPR=1簇抵,F(xiàn)PR=0,即 所有的正例預測值 > 所有的負例預測值射众,此時閾值取最小正例預測值與最大負例預測值之間的值即可碟摆。

TPR越大越好,F(xiàn)PR越小越好叨橱,但這兩個指標通常是矛盾的焦履。為了增大TPR,可以預測更多的樣本為正例雏逾,與此同時也增加了更多負例被誤判為正例的情況。

6.2.1郑临、代碼實現(xiàn)

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

digits = datasets.load_digits()
X = digits.data
y = digits.target.copy()

# 要構(gòu)造偏斜數(shù)據(jù)栖博,將數(shù)字9的對應索引的元素設置為1,0~8設置為0
y[digits.target==9]=1
y[digits.target!=9]=0

# 使用邏輯回歸做一個分類
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

log_reg = LogisticRegression()
log_reg.fit(X_train,y_train)
# 計算邏輯回歸給予X_test樣本的決策數(shù)據(jù)值
# 通過decision_function可以調(diào)整精準率和召回率
decision_scores = log_reg.decision_function(X_test)
# print(decision_scores)
# TPR
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.0

# FPR
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.0

fprs = []    
tprs = []

# 以0.1為步長厢洞,遍歷decision_scores中的最小值到最大值的所有數(shù)據(jù)點仇让,將其作為閾值集合
thresholds = np.arange(np.min(decision_scores), np.max(decision_scores), 0.1)
print(thresholds, len(thresholds))
for threshold in thresholds:
    # decision_scores >= threshold 是布爾型向量,用dtype設置為int
    # 大于等于閾值threshold分類為1躺翻,小于為0丧叽,用這種方法得到預測值
    y_predict = np.array(decision_scores >= threshold, dtype=int)
    #print(y_predict)
    # print(y_test)
    #print(FPR(y_test, y_predict))
    # 對于每個閾值,所得到的FPR和TPR都添加到相應的隊列中
    fprs.append(FPR(y_test, y_predict))
    tprs.append(TPR(y_test, y_predict))
    
# 繪制ROC曲線公你,x軸是fpr的值踊淳,y軸是tpr的值
plt.plot(fprs, tprs)
plt.show()
[-85.68608523 -85.58608523 -85.48608523 ...  19.61391477  19.71391477
  19.81391477] 1056

可以看到曲線每次都是一個“爬坡”,遇到正例往上爬一格陕靠,錯了往右爬一格迂尝,顯然往上爬對于算法性能來說是最好的。

sklearn中的ROC曲線:

from sklearn.metrics import roc_curve

fprs, tprs, thresholds = roc_curve(y_test, decision_scores)
plt.plot(fprs, tprs)
plt.show()

6.2.2 分析

ROC曲線距離左上角越近剪芥,證明分類器效果越好垄开。如果一條算法1的ROC曲線完全包含算法2,則可以斷定性能算法1>算法2税肪。這很好理解溉躲,此時任做一條 橫線(縱線)榜田,任意相同TPR(FPR) 時,算法1的FPR更低(TPR更高)锻梳,故顯然更優(yōu)箭券。

從上面ROC圖中的幾個標記點,我們可以做一些直觀分析:

我們可以看出,左上角的點(TPR=1,FPR=0)唱蒸,為完美分類邦鲫,也就是這個醫(yī)生醫(yī)術(shù)高明,診斷全對神汹。點A(TPR>FPR)庆捺,說明醫(yī)生A的判斷大體是正確的。中線上的點B(TPR=FPR)屁魏,也就是醫(yī)生B全都是蒙的滔以,蒙對一半,蒙錯一半氓拼;下半平面的點C(TPR<FPR)你画,這個醫(yī)生說你有病,那么你很可能沒有病桃漾,醫(yī)生C的話我們要反著聽坏匪,為真庸醫(yī)。

很多時候兩個分類器的ROC曲線交叉撬统,無法判斷哪個分類器性能更好适滓,這時可以計算曲線下的面積AUC,作為性能度量恋追。

七凭迹、AUC

一般在ROC曲線中,我們關(guān)注是曲線下面的面積苦囱, 稱為AUC(Area Under Curve)嗅绸。這個AUC是橫軸范圍(0,1 ),縱軸是(0,1)所以總面積是小于1的撕彤。

ROC和AUC的主要應用:比較兩個模型哪個好鱼鸠?主要通過AUC能夠直觀看出來。

ROC曲線下方由梯形組成羹铅,矩形可以看成特征的梯形瞧柔。因此,AUC的面積可以這樣算:(上底+下底)* 高 / 2睦裳,曲線下面的面積可以由多個梯形面積疊加得到造锅。AUC越大,分類器分類效果越好廉邑。

  • AUC = 1哥蔚,是完美分類器倒谷,采用這個預測模型時,不管設定什么閾值都能得出完美預測糙箍。絕大多數(shù)預測的場合渤愁,不存在完美分類器。
  • 0.5 < AUC < 1深夯,優(yōu)于隨機猜測抖格。這個分類器(模型)妥善設定閾值的話,能有預測價值咕晋。
  • AUC = 0.5雹拄,跟隨機猜測一樣,模型沒有預測價值掌呜。
  • AUC < 0.5滓玖,比隨機猜測還差;但只要總是反預測而行质蕉,就優(yōu)于隨機猜測势篡。

可以在sklearn中求出AUC值

from sklearn.metrics import roc_auc_score
roc_auc_score(y_test, decision_scores)
0.9830452674897119

八、總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末模暗,一起剝皮案震驚了整個濱河市禁悠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兑宇,老刑警劉巖碍侦,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異顾孽,居然都是意外死亡,警方通過查閱死者的電腦和手機比规,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門若厚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜒什,你說我怎么就攤上這事测秸。” “怎么了灾常?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵霎冯,是天一觀的道長。 經(jīng)常有香客問我钞瀑,道長沈撞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任雕什,我火速辦了婚禮缠俺,結(jié)果婚禮上显晶,老公的妹妹穿的比我還像新娘。我一直安慰自己壹士,他們只是感情好磷雇,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著躏救,像睡著了一般唯笙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盒使,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天崩掘,我揣著相機與錄音,去河邊找鬼忠怖。 笑死呢堰,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的凡泣。 我是一名探鬼主播枉疼,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鞋拟!你這毒婦竟也來了骂维?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤贺纲,失蹤者是張志新(化名)和其女友劉穎航闺,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猴誊,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡潦刃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了懈叹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乖杠。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖澄成,靈堂內(nèi)的尸體忽然破棺而出胧洒,到底是詐尸還是另有隱情,我是刑警寧澤墨状,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布卫漫,位于F島的核電站,受9級特大地震影響肾砂,放射性物質(zhì)發(fā)生泄漏列赎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一镐确、第九天 我趴在偏房一處隱蔽的房頂上張望粥谬。 院中可真熱鬧肛根,春花似錦、人聲如沸漏策。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掺喻。三九已至芭届,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間感耙,已是汗流浹背褂乍。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留即硼,地道東北人逃片。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像只酥,于是被迫代替她去往敵國和親褥实。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345