分类算法的评价

一、分类算法评价指标

1.分类准确度的问题

分类算法如果用分类准确度来衡量好坏将会存在问题。例如一个癌症预测系统,输入体检信息,可以判断是否有癌症,预测准确度可以达到99.9%,看起来预测系统还可以,但是如果癌症的产生概率只有0.1%,那么系统只要预测所有人都是健康的就可以达到99.9%的准确率,因此虽然准确率很高,但是预测系统实际上没有发挥什么作用。更加极端的如果癌症概率只有0.01%,那么预测所有人都是健康的概率是99.99%,比预测系统的结果还要好。因此可以得到结论:在存在极度偏斜的数据中,应用分类准确度来评价分类算法的好坏是远远不够的。

2.混淆矩阵

对于二分类问题。可以得到如下的混淆矩阵。

通过混淆矩阵可以得到精准率和召回率,用这两个指标评价分类算法将会有更好的效果。

3.精准率和召回率

精准率:分类正确的正样本个数占分类器判定为正样本的样本个数的比例(预测分类为1,相应的预测对的概率)。

    对应于检索中的查准率检索出相关文档数/检索出的文档总数

之所以使用1的分类来计算精准率是因为,在实际生活中,1代表着受关注的对象,例如:癌症预测系统中,1就代表着患癌症,40%意味着,系统做出100次病人患有癌症的预测结论,其中有40%结论是准确的。

召回率:分类正确的正样本个数占真正的正样本个数的比例(真实分类为1,相应的预测对的概率)。

    对应于检索中的查全率检索出相关文档数/文档库中相关文档总数

召回率意味着如果有10个癌症患者,将会有8个被预测到。

 

区别精确率和召回率主要记住他们是分母不同就好了,召回率是对应测试集中的正类数据而言,而准确率是对应预测结果为正类的数据而言。

另一种图解:

 

 

现在假设有10000个人,预测所有的人都是健康的,假设有10个患病,则有如下的混淆矩阵:

对于准确率:9990/10000=99.9%。对于精准率:0/0没有意义。对于召回率:0/10=0。可以看出模型对于预测疾病其实并不好。

4.代码实现

4.1 自己编写代码动手实现以下精准率与召回率:

采用digits数据集,为了达到极度偏斜的效果,我们将所有数据设为如果是9为1,不是9为0

import numpy as np
from sklearn import datasets

digits = datasets.load_digits()
X = digits.data
y = digits.target.copy() #如果直接引用那么y变了,target也会变
y[digits.target==9] = 1
y[digits.target!=9] = 0

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
# 逻辑回归预测一哈
from sklearn.linear_model import LogisticRegression 
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):
    assert len(y_true) == len(y_predict)
    return np.sum((y_true == 0) & (y_predict == 0))

def FP(y_true, y_predict):
    assert len(y_true) == len(y_predict)
    return np.sum((y_true == 0) & (y_predict == 1))

def FN(y_true, y_predict):
    assert len(y_true) == len(y_predict)
    return np.sum((y_true == 1) & (y_predict == 0))


def TP(y_true, y_predict):
    assert len(y_true) == len(y_predict)
    return np.sum((y_true == 1) & (y_predict == 1))


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)

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

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
print("score:" , log_reg.score(X_test, y_test))
print("precision_score", precision_score(y_test, y_log_predict))
print("recall_score", recall_score(y_test, y_log_predict))

 结果:

score: 0.9755555555555555
precision_score 0.9473684210526315
recall_score 0.8
View Code

 

4.2 sklearn代码实现

由于计算逻辑比较简单,所以直接给出sklearn中封装好的实现。

#因为是与指标相关,所以都是metrics
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test,y_log_predict)
from sklearn.metrics import precision_score #sklearn中的精准率
precision_score(y_test,y_log_predict)
from sklearn.metrics import recall_score    #sklearn中的召回率
recall_score(y_test,y_log_predict)

 结果:0.8

5.对精准率和召回率的分析

对于一个模型得到了精准率和召回率,那么应该如何通过这两个指标对模型进行评价,又或者是一个模型经过调参后,得到不同的精准率和召回率,应该选取哪个参数对应的精准率和召回率才好。这个需要根据不同的场景进行选择。

例如:对于股票预测,更多的应该是关注精准率,假设关注股票上升的情况,高精准率意味着TP值高(正确地预测到股票会升),这个时候可以帮助人们调整投资,增加收入,如果这一指标低,就以为FP值高(错误地认为股票会升),也就是说股票其实是降的,而预测成升了,这将会使用户亏钱。而召回率低只是意味着在股票上升的情况中,对几个股票上升的情况没有被预测到,这对于投资者来说也是可以接受的,毕竟没有亏钱,因此低召回率对用户影响不是很大。

例如:对于疾病预测领域,更多的应该关注召回率,因为高召回率意味着能够更多地将得病的病人预测出来,这个对于患病者非常重要。而精准率低意味着错误地预测病人患病,而这个时候只要被预测患病的人再去检查一下即可,实际上是可以接受的,因此低精准率对用户影响不大。

而某些情况可能需要同时考虑到两个指标,以达到一个均衡。这个时候就需要F1 score。这个称为精准率和召回率的调和平均值。可以发现只有两个值都比较高的时候,F1才会比较高。如果两个值某一个很高,另一个很低,F1值也不会高。

from sklearn.metrics import f1_score
f1_score(y_test,y_log_predict)

 

二、精准率和召回率

精准率和召回率之间是互相矛盾的,如果提高召回率,精准率就不可避免的下降,如果精准率提高,召回率就不可避免的下降。

分类阈值的取值的影响

在逻辑回归中,我们让决策边界是 θ* X = 0作为决策边界,即大于0的分类1,小于0的分类0,我们可以不让他是0,而是一个常数threshold.下图是threshold取不同的值可能产生的变化

如图所示,五角星为1,圆形为0。划的竖线代表决策边界。可以看到,当精准率提高时,召回率就下降,当召回率提高时,精准率就下降,二者是个矛盾。

log_reg.decision_function(X_test)[:10]
# 这个函数就是逻辑回归模型里那个threshold的值,模型的默认值都是0
'''调用函数输出结果
array([-22.05700185, -33.02943631, -16.21335414, -80.37912074,
       -48.25121102, -24.54004847, -44.39161228, -25.0429358 ,
        -0.97827574, -19.71740779])'''
np.min(decision_scores) #分类标准阈值的最大值
np.max(decision_scores)
y_predict_2 = np.array(decision_scores >= 5, dtype='int') #这样就改变了分类标准的阈值,大于5的才是1

 

Precision-Recall-Curve(PR曲线)

随着分类阈值的不断变化,precision和recall值也会不断变化,那么我们可以画出其相应的曲线,可以在曲线中找到一个F1 Score最高的分类阈值。通常在recall要急剧下降的那个位置。

实现自己的PR曲线

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

digits = datasets.load_digits()
X = digits.data
y = digits.target.copy()
y[digits.target==9] = 1
y[digits.target!=9] = 0

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

from sklearn.linear_model import LogisticRegression
log_reg = LogisticRegression()
log_reg.fit(X_train, y_train)
decision_scores = log_reg.decision_function(X_test)

from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
precisions = [] 
recalls = []
thresholds = np.arange(np.min(decision_scores), np.max(decision_scores), 0.1) #以0.1为步长获取分类阈值最大值到最小值里的点
for threshold in thresholds:
    y_predict = np.array(decision_scores >= threshold, dtype='int') # 每个点求一次 precision_score和recall_score
    precisions.append(precision_score(y_test, y_predict))
    recalls.append(recall_score(y_test, y_predict))
#画出对应的值
plt.plot(thresholds,precisions,label="precision")
plt.plot(thresholds,recalls,label="recall")
plt.legend()
plt.show()

 

绘制precision和recall曲线。

plt.plot(precisions,recalls)
plt.show() #快速下降的点,可能就是比较平衡的点

 

scikit-learn 中的PR曲线

from sklearn.metrics import precision_recall_curve
precisions, recalls, thresholds = precision_recall_curve(y_test, decision_scores)
#注意通过这个函数得到的precision和recall的最后一个值分别为1和0,没有对应的Score
plt.plot(thresholds, precisions[:-1])  #所以不取最后一个
plt.plot(thresholds, recalls[:-1])
plt.show()


plt.plot(precisions, recalls)
plt.show()

 

 注意:

 

ROC曲线

ROC曲线是用来描述TPR与FPR之间的曲线之间的关系.

TPR(True-Positive-Rate): 代表预测为1并且预测对了的样本数量占真实值为1的百分比为多少

FPR(False-Positive-Rate):代表预测为1但预测错了的样本数量占真实值为0的百分比是多少

 

分类阈值改变,TPR和FPR的变化:

可以看到FPR和TPR呈现相一致的关系,这个也容易理解。当召回率提高时,说明会尽量将1样本都包含进去(竖线左移),而这个时候就会增加错分的0样本(更多的0被包含进去),因此TN减小,FP增大,必然导致FPR的增大。

会发现当阈值变低,TPR与FPR值都变高,阈值变低那么预测值更容易变成1,所以预测值为1的样本也多了,真实值为1的总量是不变的,所以TPR变高,同理预测错了的样本也会变多。

 

代码实现 FPR 和 TPR,并绘制 ROC 曲线:

  封装:

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.

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.

   求 TPR 和 FPR:

import numpy as np
from sklearn import datasets

digits = datasets.load_digits()
X = digits.data
y = digits.target.copy()
y[digits.target==9] = 1
y[digits.target!=9] = 0

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

from sklearn.linear_model import LogisticRegression
log_reg = LogisticRegression()
log_reg.fit(X_train, y_train)
decision_scores = log_reg.decision_function(X_test)


from playML.metrics import FPR, TPR

fprs = []
tprs = []
thresholds = np.arange(np.min(decision_scores), np.max(decision_scores), 0.1)

for threshold in thresholds:
    # dtype=‘int‘:将数据类型从 bool 型转为 int 型;
    y_predict = np.array(decision_scores >= threshold, dtype=‘int‘)
    fprs.append(FPR(y_test, y_predict))
    tprs.append(TPR(y_test, y_predict))

  绘制 ROC 曲线:

import matplotlib.pyplot as plt
plt.plot(fprs, tprs)
plt.show()

 

  • 分析:

  1. ROC 曲线与图形边界围成的面积,作为衡量模型优劣的标准,面积越大,模型越优;
  2. 可以是同样算法不同超参数所得的不同模型,也可以是不同算法所得的不同模型。

scikit-learn中的ROC曲线:

from sklearn.linear_model import LogisticRegression
log_reg = LogisticRegression()
log_reg.fit(X_train, y_train)
decision_scores = log_reg.decision_function(X_test) #获取逻辑回归每个样本的decision_scores

from sklearn.metrics import roc_curve
fprs, tprs, thresholds = roc_curve(y_test, decision_scores) #获取tpr与fpr的值

plt.plot(fprs, tprs)
plt.show()

 

ROC曲线的面积越大,那么我们的模型就越好,因为如果绘制一个FPR-TPR的曲线,在同样的FPR(犯错率)之下,TPR的值越高,模型越好,相应的它的面积越大。

  • 计算 ROC 曲线与坐标轴围成的面积:称 ROC 的 auc;

  • 面积越大,模型越优;

 

from sklearn.metrics import roc_auc_score #求面积, 面积最大就为1,因为TPR与FPR最大值都为1
roc_auc_score(y_test, decision_scores)# 结果:0.98304526748971188

这个将会得到比较高的值,说明auc对于有偏数据并不是很敏感,auc一般用于比较两个模型之间的好坏。

ROC对有偏数据并不敏感,主要用来比较两个模型谁更好一些,比如一个算法不同参数,或者两个不同的算法,都可以用这个来比较,如果他们的数据不是极度偏移的话。

 

 

三、多分类评价

1.多分类问题中的混淆矩阵

这里只研究多分类混淆矩阵,其他的以后在详细研究一下。

import numpy as np
import matplotlib.pyplot as plt

from sklearn import datasets
digits = datasets.load_digits() 
X = digits.data #数据并没有进行切割,所以这是10分类问题
y = digits.target

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.8, random_state=666)

from sklearn.linear_model import LogisticRegression
log_reg = LogisticRegression()
log_reg.fit(X_train, y_train)
log_reg.score(X_test, y_test)

y_predict = log_reg.predict(X_test)

from sklearn.metrics import precision_score
precision_score(y_test, y_predict) #如果直接获取矩阵,会报错

 ValueError: Target is multiclass but average='binary'. Please choose another average setting.

precision_score(y_test, y_predict, average="micro") #根据错误提示,需要更改参数
#为了求出多分类的精准度和召回率,必须传入参数average='micro'
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test, y_predict) #跟2分类的混淆矩阵的含义一样,行代表真实值列代表预测值

 结果:

array([[147,   0,   1,   0,   0,   1,   0,   0,   0,   0],
       [  0, 123,   1,   2,   0,   0,   0,   3,   4,  10],
       [  0,   0, 134,   1,   0,   0,   0,   0,   1,   0],
       [  0,   0,   0, 138,   0,   5,   0,   1,   5,   0],
       [  2,   5,   0,   0, 139,   0,   0,   3,   0,   1],
       [  1,   3,   1,   0,   0, 146,   0,   0,   1,   0],
       [  0,   2,   0,   0,   0,   1, 131,   0,   2,   0],
       [  0,   0,   0,   1,   0,   0,   0, 132,   1,   2],
       [  1,   9,   2,   3,   2,   4,   0,   0, 115,   4],
       [  0,   1,   0,   5,   0,   3,   0,   2,   2, 134]], dtype=int64)
View Code

其中对角线的代表该分类预测正确的数量,其他位置的值代表预测错误所对应的值。为了直观,现在绘制出图像。

cfm = confusion_matrix(y_test, y_predict)
plt.matshow(cfm, cmap=plt.cm.gray) #一个矩阵的绘制参数,第一个参数是矩阵,第二个参数是颜色
plt.show()

 

from sklearn.metrics import confusion_matrix
cfm = confusion_matrix(y_test,y_predict)
row_sums = np.sum(cfm,axis=1)#将每行相加,得到每行的样本总数
err_matrix = cfm/row_sums#绘制比例
np.fill_diagonal(err_matrix,0)# 因为对角线肯定对的多,将对角线的元素全部置为0
plt.matshow(err_matrix,cmap=plt.cm.gray) #越亮代表值越大,也就是错分的越多
plt.show()#这样就看出把什么看成什么的错误多了

 

 

 



 

posted @ 2018-10-04 21:43  耐烦不急  阅读(3434)  评论(1编辑  收藏  举报