1 混淆矩陣(Confusion Matrix)
??對于二分類問題泉粉,預(yù)測模型會(huì)對每一個(gè)樣本預(yù)測一個(gè)得分s或者一個(gè)概率p护盈。 然后灵莲,可以選取一個(gè)閾值t路克,讓得分s>t的樣本預(yù)測為正潮秘,而得分s<t的樣本預(yù)測為負(fù)琼开。 這樣一來,根據(jù)預(yù)測的結(jié)果和實(shí)際的標(biāo)簽可以把樣本分為4類:
--- | 正樣本 | 負(fù)樣本 |
---|---|---|
預(yù)測為正 | TP(真陽例) | FP(假陽例) |
預(yù)測為負(fù) | FN(假陰例) | TN(真陰例) |
??顯然枕荞,混淆矩陣包含四部分的信息:
??True positive(TP)柜候,稱為真陽率,表明實(shí)際是正樣本預(yù)測成正樣本的樣本數(shù)
??False positive(FP)躏精,稱為假陽率渣刷,表明實(shí)際是負(fù)樣本預(yù)測成正樣本的樣本數(shù)
??False negative(FN),稱為假陰率矗烛,表明實(shí)際是正樣本預(yù)測成負(fù)樣本的樣本數(shù)
??True negative(TN)辅柴,稱為真陰率,表明實(shí)際是負(fù)樣本預(yù)測成負(fù)樣本的樣本數(shù)
??對照著混淆矩陣瞭吃,很容易就能把關(guān)系碌嘀、概念理清楚,但是久而久之歪架,也很容易忘記概念股冗。不妨我們按照位置前后分為兩部分記憶,前面的部分是True/False表示真假和蚪,即代表著預(yù)測的正確性止状,后面的部分是positive/negative表示正負(fù)樣本,即代表著預(yù)測的結(jié)果攒霹,所以导俘,混淆矩陣即可表示為正確性-預(yù)測結(jié)果的集合。現(xiàn)在我們再來看上述四個(gè)部分的概念(均代表樣本數(shù)剔蹋,下述省略):
??TN旅薄,預(yù)測是負(fù)樣本,預(yù)測對了
??FP,預(yù)測是正樣本少梁,預(yù)測錯(cuò)了
??FN洛口,預(yù)測是負(fù)樣本,預(yù)測錯(cuò)了
??TP凯沪,預(yù)測是正樣本第焰,預(yù)測對了
??幾乎我所知道的所有評價(jià)指標(biāo),都是建立在混淆矩陣基礎(chǔ)上的妨马,包括準(zhǔn)確率挺举、精準(zhǔn)率、召回率烘跺、F1-score湘纵,當(dāng)然也包括AUC。
1.1 混淆矩陣的評價(jià)指標(biāo)
??AccuracyRate(準(zhǔn)確率): (TP+TN)/(TP+TN+FN+FP)
??ErrorRate(誤分率): (FN+FP)/(TP+TN+FN+FP)
??Recall(召回率滤淳,查全率,擊中概率): TP/(TP+FN), 在所有GroundTruth為正樣本中有多少被識(shí)別為正樣本了;
??Precision(查準(zhǔn)率):TP/(TP+FP),在所有識(shí)別成正樣本中有多少是真正的正樣本梧喷;
??TPR(TruePositive Rate): TP/(TP+FN),實(shí)際就是Recall
??FAR(FalseAcceptance Rate)或FPR(False Positive Rate):FP/(FP+TN), 錯(cuò)誤接收率脖咐,誤報(bào)率铺敌,在所有GroundTruth為負(fù)樣本中有多少被識(shí)別為正樣本了;
??FRR(FalseRejection Rate): FN/(TP+FN),錯(cuò)誤拒絕率屁擅,拒真率偿凭,在所有GroundTruth為正樣本中有多少被識(shí)別為負(fù)樣本了,它等于1-Recall
??調(diào)和平均值F1_score:2PrecisionRecall/(Recall+ Precision)
#準(zhǔn)備數(shù)據(jù)
import numpy as np
from sklearn import datasets
digits = datasets.load_digits()
X = digits['data']
y = digits['target'].copy()
#手動(dòng)讓digits數(shù)據(jù)集9的數(shù)據(jù)偏斜
y[digits['target']==9] = 1
y[digits['target']!=9] = 0
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
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)
log_reg.score(X_test,y_test)
y_log_predict = log_reg.predict(X_test)
def TN(y_true,y_predict):
return np.sum((y_true==0)&(y_predict==0))
TN(y_test,y_log_predict)
def FP(y_true,y_predict):
return np.sum((y_true==0)&(y_predict==1))
FP(y_test,y_log_predict)
def FN(y_true,y_predict):
return np.sum((y_true==1)&(y_predict==0))
FN(y_test,y_log_predict)
def TP(y_true,y_predict):
return np.sum((y_true==1)&(y_predict==1))
TP(y_test,y_log_predict)
#構(gòu)建混淆矩陣
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)
#精準(zhǔ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)
#召回率
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)
#scikitlearn中的精準(zhǔn)率和召回率
#構(gòu)建混淆矩陣
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test,y_log_predict)
#精準(zhǔn)率
from sklearn.metrics import precision_score
precision_score(y_test,y_log_predict)
#f1-score
f1_score(y_test,y_log_predict)
1.2 Precision-Recall的平衡
??一般來說派歌,決策邊界為theta.T*x_b=0弯囊,即計(jì)算出p>0.5時(shí)分類為1,如果我們手動(dòng)改變這個(gè)threshold硝皂,就可以平移這個(gè)決策邊界,改變精準(zhǔn)率和召回率
#該函數(shù)可以得到log_reg的預(yù)測分?jǐn)?shù)作谭,未帶入sigmoid
decsion_scores = log_reg.decision_function(X_test)
#將threshold由默認(rèn)的0調(diào)為5
y_predict2 = decsion_scores>=5.0
precision_score(y_test,y_predict2)
# 0.96
recall_score(y_test,y_predict2)
# 0.5333333333333333
y_predict2 = decsion_scores>=-5.0
precision_score(y_test,y_predict2)
# 0.7272727272727273
recall_score(y_test,y_predict2)
# 0.8888888888888888
1.3 精準(zhǔn)率和召回率曲線
??可以用precisions-recalls曲線與坐標(biāo)軸圍成的面積衡量模型的好壞
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
thresholds = np.arange(np.min(decsion_scores),np.max(decsion_scores))
precisions = []
recalls = []
for threshold in thresholds:
y_predict = decsion_scores>=threshold
precisions.append(precision_score(y_test,y_predict))
recalls.append(recall_score(y_test,y_predict))
import matplotlib.pyplot as plt
plt.plot(thresholds,precisions)
plt.plot(thresholds,recalls)
plt.show()
plt.plot(precisions,recalls)
plt.show()
??使用scikit-learn繪制Precision-Recall曲線
from sklearn.metrics import precision_recall_curve
precisions,recalls,thresholds = precision_recall_curve(y_test,decsion_scores)
#由于precisions和recalls中比thresholds多了一個(gè)元素稽物,因此要繪制曲線,先去掉這個(gè)元素
plt.plot(thresholds,precisions[:-1])
plt.plot(thresholds,recalls[:-1])
plt.show()
2 ROC曲線
??對于某個(gè)二分類分類器來說折欠,輸出結(jié)果標(biāo)簽(0還是1)往往取決于輸出的概率以及預(yù)定的概率閾值贝或,比如常見的閾值就是0.5,大于0.5的認(rèn)為是正樣本锐秦,小于0.5的認(rèn)為是負(fù)樣本咪奖。如果增大這個(gè)閾值,預(yù)測錯(cuò)誤(針對正樣本而言酱床,即指預(yù)測是正樣本但是預(yù)測錯(cuò)誤羊赵,下同)的概率就會(huì)降低但是隨之而來的就是預(yù)測正確的概率也降低;如果減小這個(gè)閾值,那么預(yù)測正確的概率會(huì)升高但是同時(shí)預(yù)測錯(cuò)誤的概率也會(huì)升高昧捷。實(shí)際上闲昭,這種閾值的選取也一定程度上反映了分類器的分類能力。我們當(dāng)然希望無論選取多大的閾值靡挥,分類都能盡可能地正確序矩,也就是希望該分類器的分類能力越強(qiáng)越好,一定程度上可以理解成一種魯棒能力吧跋破。
??為了形象地衡量這種分類能力簸淀,ROC曲線橫空出世!如下圖所示毒返,即為一條ROC曲線(該曲線的原始數(shù)據(jù)第三部分會(huì)介紹)∽饽唬現(xiàn)在關(guān)心的是:
??橫軸:False Positive Rate(假陽率,F(xiàn)PR)
??縱軸:True Positive Rate(真陽率饿悬,TPR)
??假陽率令蛉,簡單通俗來理解就是預(yù)測為正樣本但是預(yù)測錯(cuò)了的可能性,顯然狡恬,我們不希望該指標(biāo)太高珠叔。
??真陽率,則是代表預(yù)測為正樣本但是預(yù)測對了的可能性弟劲,當(dāng)然祷安,我們希望真陽率越高越好。
??顯然兔乞,ROC曲線的橫縱坐標(biāo)都在[0,1]之間汇鞭,自然ROC曲線的面積不大于1。現(xiàn)在我們來分析幾個(gè)特殊情況庸追,從而更好地掌握ROC曲線的性質(zhì):
??(0,0):假陽率和真陽率都為0霍骄,即分類器全部預(yù)測成負(fù)樣本
??(0,1):假陽率為0,真陽率為1淡溯,全部完美預(yù)測正確读整,happy
??(1,0):假陽率為1,真陽率為0咱娶,全部完美預(yù)測錯(cuò)誤米间,悲劇
??(1,1):假陽率和真陽率都為1,即分類器全部預(yù)測成正樣本
??TPR=FPR膘侮,斜對角線屈糊,預(yù)測為正樣本的結(jié)果一半是對的,一半是錯(cuò)的琼了,代表隨機(jī)分類器的預(yù)測效果逻锐。
??于是,我們可以得到基本的結(jié)論:ROC曲線在斜對角線以下,則表示該分類器效果差于隨機(jī)分類器谦去,反之慷丽,效果好于隨機(jī)分類器,當(dāng)然鳄哭,我們希望ROC曲線盡量位于斜對角線以上要糊,也就是向左上角(0,1)凸。
3 AUC(Area under the ROC curve)曲線
??ROC曲線一定程度上可以反映分類器的分類效果妆丘,但是不夠直觀锄俄,我們希望有這么一個(gè)指標(biāo),如果這個(gè)指標(biāo)越大越好勺拣,越小越差奶赠,于是,就有了AUC药有。AUC實(shí)際上就是ROC曲線下的面積毅戈。AUC直觀地反映了ROC曲線表達(dá)的分類能力。
??AUC = 1愤惰,代表完美分類器
??0.5 < AUC < 1苇经,優(yōu)于隨機(jī)分類器
??0 < AUC < 0.5,差于隨機(jī)分類器
3.1 AUC的用處
??AUC最大的應(yīng)用應(yīng)該就是點(diǎn)擊率預(yù)估(CTR)的離線評估宦言。CTR的離線評估在公司的技術(shù)流程中占有很重要的地位扇单,一般來說,ABTest和轉(zhuǎn)全觀察的資源成本比較大奠旺,所以蜘澜,一個(gè)合適的離線評價(jià)可以節(jié)省很多時(shí)間、人力响疚、資源成本鄙信。那么,為什么AUC可以用來評價(jià)CTR呢忿晕?我們首先要清楚兩個(gè)事情:
??(1)CTR是把分類器輸出的概率當(dāng)做是點(diǎn)擊率的預(yù)估值装诡,如業(yè)界常用的LR模型,利用sigmoid函數(shù)將特征輸入與概率輸出聯(lián)系起來杏糙,這個(gè)輸出的概率就是點(diǎn)擊率的預(yù)估值慎王。內(nèi)容的召回往往是根據(jù)CTR的排序而決定的蚓土。
??(2)AUC量化了ROC曲線表達(dá)的分類能力宏侍。這種分類能力是與概率、閾值緊密相關(guān)的蜀漆,分類能力越好(AUC越大)谅河,那么輸出概率越合理,排序的結(jié)果越合理。
??我們不僅希望分類器給出是否點(diǎn)擊的分類信息绷耍,更需要分類器給出準(zhǔn)確的概率值吐限,作為排序的依據(jù)。所以褂始,這里的AUC就直觀地反映了CTR的準(zhǔn)確性(也就是CTR的排序能力).
3.2 AUC求解
??步驟如下:
??(1)得到結(jié)果數(shù)據(jù)诸典,數(shù)據(jù)結(jié)構(gòu)為:(輸出概率,標(biāo)簽真值)
??(2)對結(jié)果數(shù)據(jù)按輸出概率進(jìn)行分組崎苗,得到(輸出概率狐粱,該輸出概率下真實(shí)正樣本數(shù),該輸出概率下真實(shí)負(fù)樣本數(shù))胆数。這樣做的好處是方便后面的分組統(tǒng)計(jì)肌蜻、閾值劃分統(tǒng)計(jì)等
??(3)對結(jié)果數(shù)據(jù)按輸出概率進(jìn)行從大到小排序
??(4)從大到小,把每一個(gè)輸出概率作為分類閾值必尼,統(tǒng)計(jì)該分類閾值下的TPR和FPR
??(5)微元法計(jì)算ROC曲線面積蒋搜、繪制ROC曲線
??代碼如下所示:
import pylab as pl
from math import log,exp,sqrt
import itertools
import operator
def read_file(file_path, accuracy=2):
db = [] #(score,nonclk,clk)
pos, neg = 0, 0 #正負(fù)樣本數(shù)量
#讀取數(shù)據(jù)
with open(file_path,'r') as fs:
for line in fs:
temp = eval(line)
#精度可控
#score = '%.1f' % float(temp[0])
score = float(temp[0])
trueLabel = int(temp[1])
sample = [score, 0, 1] if trueLabel == 1 else [score, 1, 0]
score,nonclk,clk = sample
pos += clk #正樣本
neg += nonclk #負(fù)樣本
db.append(sample)
return db, pos, neg
def get_roc(db, pos, neg):
#按照輸出概率,從大到小排序
db = sorted(db, key=lambda x:x[0], reverse=True)
file=open('data.txt','w')
file.write(str(db))
file.close()
#計(jì)算ROC坐標(biāo)點(diǎn)
xy_arr = []
tp, fp = 0., 0.
for i in range(len(db)):
tp += db[i][2]
fp += db[i][1]
xy_arr.append([fp/neg,tp/pos])
return xy_arr
def get_AUC(xy_arr):
#計(jì)算曲線下面積
auc = 0.
prev_x = 0
for x,y in xy_arr:
if x != prev_x:
auc += (x - prev_x) * y
prev_x = x
return auc
def draw_ROC(xy_arr):
x = [_v[0] for _v in xy_arr]
y = [_v[1] for _v in xy_arr]
pl.title("ROC curve of %s (AUC = %.4f)" % ('clk',auc))
pl.xlabel("False Positive Rate")
pl.ylabel("True Positive Rate")
pl.plot(x, y)# use pylab to plot x and y
pl.show()# show the plot on the screen
??數(shù)據(jù):提供的數(shù)據(jù)為每一個(gè)樣本的(預(yù)測概率判莉,真實(shí)標(biāo)簽)tuple
??數(shù)據(jù)鏈接:https://pan.baidu.com/s/1c1FUzVM豆挽,密碼1ax8
??計(jì)算結(jié)果:AUC=0.747925810016,與Spark MLLib中的roc_AUC計(jì)算值基本吻合
??當(dāng)然骂租,選擇的概率精度越低祷杈,AUC計(jì)算的偏差就越大。
Reference: