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'])
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')
四、精準率與召回率
精準率:
即精準率為8/(8+12)=40%朗恳。所謂的精準率是:分母為所有預測為1的個數(shù)得糜,分子是其中預測對了的個數(shù),即預測值為1繁扎,且預測對了的比例幔荒。
為什么管它叫精準率呢?在有偏的數(shù)據(jù)中梳玫,我們通常更關(guān)注值為1的特征爹梁,比如“患病”,比如“有風險”提澎。在100次結(jié)果為患病的預測姚垃,平均有40次預測是對的。即精準率為我們關(guān)注的那個事件盼忌,預測的有多準积糯。
召回率:
即精準率為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)和平均值牧挣。
什么是調(diào)和平均值?為什么要取調(diào)和平均值醒陆?調(diào)和平均值的特點是如果二者極度不平衡瀑构,如某一個值特別高、另一個值特別低時刨摩,得到的F1 Score值也特別低寺晌;只有二者都非常高,F(xiàn)1才會高澡刹。這樣才符合我們對精準率和召回率的衡量標準呻征。
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ù)百分比蹦掐。很好理解技羔,就是召回率。
6.1.3卧抗、FPR
FPR:預測為1堕阔,但預測錯了的數(shù)量,占真實值不為1的數(shù)據(jù)百分比颗味。與TPR相對應超陆,F(xiàn)PR除以真實值為0的這一行所有的數(shù)字和 。
預測值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