8 如何评价分类算法

前面我们所讲的对分类算法的评价,使用的是准确率的方式来衡量算法的好坏,公式为:sum(y_true == y_predict) / len(y_true),但是这种衡量方式有一种弊端,当数据特别极限偏斜的情况下,这种衡量方式就不太准确了,那么具体有什么陷阱呢?且听我慢慢道来。

1 精准率和召回率

1.1 准确率方式的陷阱

准确率公式:sum(y_true == y_predict) / len(y_true)

结论:当数据极度倾斜的情况下,准确率来衡量是不准确的。

举个例子?

我们做一个癌症预测系统,输入体检信息,可以判断是否有癌症。如果我们的算法预测准确率是99.9%,那么这个系统是好?还是坏? 答:不一定好。如果癌症产生的概率只有0.1%。假如我们系统预测所有的人都是健康的,这样我们的预测准确率也是99.9%,也就是白忙活一场。而如果癌症产生的概率只有0.01%,那我们的算法还不如个傻子预测的准。

那么该用什么来衡量分类算法的好坏呢?首先我们先介绍几个概念。

1.2 混淆矩阵

对于二分类的问题,我们构造这样的一个矩阵。

  • 行代表真实值,列代表预测值。
  • TN:实际为0,预测也为0,True Negative
  • FP:实际为0,但是预测为1,False Positive
  • FN:实际为1,但是预测为0,False Negative
  • TP:实际为1,预测也为1,True Positive

举例:假设癌症系统预测10000个人,混淆矩阵如下所示:

  • 10000个人中,9978个人本没患癌症,同时算法预测也没患癌症。
  • 同时有12个人本来没患癌症,但是预测算法预测患了癌症。
  • 有2个人确实患了癌症,但是算法预测他没有患癌症。
  • 有8个人患了癌症,同时算法也预测他患了癌症。

1.3 精准率和召回率

在实际的有偏数据中,1通常是我们关注的那个事件

因此精准率就是:预测我们关注的那个事件,有多准

举例:

癌症系统:有偏数据中,1是患癌症的事件,在预测有癌症的人群中,有40%的人是预测对的。 金融系统:有偏数据中,1是股票上涨,我们预测了20个股票上涨,有8个是预测对的。

表示:我们关注的那个事件,真实的发生了的这些数据中,我们成功预测了多少。

举例:

癌症系统:真实患癌症的人中,我们预测成功了80%。真实有10个人患癌症的人,我们召回了8个人。 金融系统:实际股票上涨的10只股票,我们预测对了8只,即召回了8只。

通过介绍上面两个评价方式,此时再回到开始提到的准确率有陷阱的问题中:10000个人预测都是健康的,实际患癌症的概率为0.1%。此时准确率为99.9%

1.4 代码实现

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)]
    ])


def precision_score(y_true, y_predict):
    """精准率"""
    assert len(y_true) == len(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):
    """召回率"""
    assert len(y_true) == len(y_predict)
    tp = TP(y_true, y_predict)
    fn = FN(y_true, y_predict)
    try:
        return tp / (tp + fn)
    except:
        return 0.0

复制代码

2 精准率与召回率平衡

有的精准率高,召回率低,有些精准率低,召回率高,我们该如何取舍呢?

对于我们刚刚举的2个例子,预测癌症与预测股票。

预测股票的时候,我们对精准率更加关注些,此时我们不用太关注召回率,所有涨的股票中,漏掉几个也无伤大雅,反正也没投入,也没亏钱。反而如果预测的股票中,也就是我们投钱的股票中,跌的多,涨的少,那我们就亏钱了。因此精准率越高,我们就越赚钱。

预测癌症与之相反,我们对召回率关注更高。一些人本来没患癌症,我预测了你们患了癌症,这些人可能多参加几个检查就好了,也没什么生命风险,因此精准率低点无所谓。然而如果病人实际患了癌症,我预测他没患,那可能就会耽误病人的病情,可能会害了人家性命,那是万万不行的,因此对召回率有更高的要求。

但是还有一些场景,对精准率和召回率并不像上面两个例子那么极端,希望同时关注精准率和召回率,此时该怎么办呢?

2.1 F1 Score

二者都兼顾,就使用precision和recall的调和平均值—— F1 Score

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

那么为什么使用调和平均值,而不直接使用二者的平均值呢?

答:因为调和平均值有一个性质,当二者一个非常小一个非常大时,F1也会很小。只有当二者都很大时,F1才会大。

def f1_score(y_true, y_predict):
    precision = precision_score(y_true, y_predict)
    recall = recall_score(y_true, y_predict)

    try:
        return 2. * precision * recall / (precision + recall)
    except:
        return 0.
复制代码

2.2 Precision-Recall的平衡

在逻辑回归中,决策边界为\theta^T.x_b = 0,那么如果将决策边界进行平移,\theta^T.x_b = threshold,如果大于threshold分类为1,小于threshold分类为0

举例说明:其中五角星实际分类为1,圆圈实际分类为0,对于决策边界的不同,对应的精准率与召回率也不相同。

可以看到这样的特性,随着threshold增大,精准率是逐渐增大的,召回率逐渐降低,那么此时可以找到这样一个超参数threshold,使得Precision-Recall平衡

log_reg = LogisticRegression()
log_reg.fit(X_train, y_train)
y_predict = log_reg.predict(X_test)

#通过此方法,可以获取决策分数值
decision_scores = log_reg.decision_function(X_test) # array([-22.05700185, -33.02943631, -16.21335414, -80.37912074,-48.25121102, -24.54004847, -44.39161228, -25.0429358 ,-0.97827574, -19.71740779])

# 设置threshold = 5
y_predict_2 = np.array(decision_scores >= 5, dtype='int')

precision_score(y_test, y_predict_2)
recall_score(y_test, y_predict_2)
复制代码

2.3 Precision-Recall曲线

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
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score


digits = datasets.load_digits() # 数字识别数据集
X = digits.data
y = digits.target.copy()

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)
decision_scores = log_reg.decision_function(X_test)

# 绘制曲线
precisions = []
recalls = []
thresholds = np.arange(np.min(decision_scores), np.max(decision_scores), 0.1)
for threshold in thresholds:
    y_predict = np.array(decision_scores >= threshold, dtype='int')
    precisions.append(precision_score(y_test, y_predict))
    recalls.append(recall_score(y_test, y_predict))

plt.plot(thresholds, precisions)
plt.plot(thresholds, recalls)
plt.show()
复制代码

也可以这样绘制

plt.plot(precisions, recalls)
plt.show()
复制代码

此时箭头处是一个急剧下降的点,那么此处可能就是Precision-Recall平衡的点。

scikit-learn中提供更简便的方式

from sklearn.metrics import precision_recall_curve
# 一行代码就返回了三个值
precisions, recalls, thresholds = precision_recall_curve(y_test, decision_scores)
复制代码

那么如果我们通过两次调参,分别获得如下的曲线,那么说明外侧曲线的模型更好一些,也可以通过面积来看,不过通常会使用下面ROC曲线的面积,来衡量我们的算法。

3 ROC曲线

3.1 TPR与FPR

先来看几个定义:

TPR: 与召回率相同

FPR: 预测为1,可惜预测错了,真实值不为1的概率

TPR与FPR呈现相一致的趋势

举例说明:其中五角星为真实分类为1,圆圈真实分类为0

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.
复制代码

3.2 ROC曲线绘制

log_reg = LogisticRegression()
log_reg.fit(X_train, y_train)
decision_scores = log_reg.decision_function(X_test)

fprs = []
tprs = []
thresholds = np.arange(np.min(decision_scores), np.max(decision_scores), 0.1)
for threshold in thresholds:
    y_predict = np.array(decision_scores >= threshold, dtype='int')
    fprs.append(FPR(y_test, y_predict))
    tprs.append(TPR(y_test, y_predict))

plt.plot(fprs, tprs)
plt.show()
复制代码

scikit-learn中的ROC

from sklearn.metrics import roc_curve
# 也是一行搞定
fprs, tprs, thresholds = roc_curve(y_test, decision_scores)
复制代码

3.3 ROC AUC

其中ROC下面的面积取值是在0~1之间,可以使用ROC下的面积来衡量算法的好坏

from sklearn.metrics import roc_auc_score
# 传入的参数是decision_scores,内部已经封装好了,可以理解为先求出ROC曲线,然后再求面积
roc_auc_score(y_test, decision_scores) # 0.98304526748971188
复制代码

ROC-ACU对有偏数据并不敏感,ROC主要应用场合是比较两个模型孰优孰劣,面积大的模型更好一些。

4 多分类的问题

from sklearn.metrics import precision_score

# 默认是二分类的,多类别会报错
precision_score(y_test, y_predict)

#合理设置一些参数,可以解决多分类的问题
precision_score(y_test, y_predict, average="micro")

from sklearn.metrics import confusion_matrix
confusion_matrix(y_test, y_predict)
复制代码

直观的方式,用图像绘制出来,数值越大,图片越亮

cfm = confusion_matrix(y_test, y_predict)
plt.matshow(cfm, cmap=plt.cm.gray)  # 绘制矩阵,映射成灰度值
plt.show()
复制代码

关注预测正确的是没有意义的,我们要看错误的地方

row_sums = np.sum(cfm, axis=1) # 每一行求和
err_matrix = cfm / row_sums       # 错误占每行的百分比
np.fill_diagonal(err_matrix, 0)     #正确的不关注

plt.matshow(err_matrix, cmap=plt.cm.gray)
plt.show()
复制代码

上图说明(1,9)和(8,1)很亮,说明这两个犯得错误更多一些, 此时在算法层面上,可以单独调整一下这两个分类的模型, 另外也可以分析下样本数据,是否数据上有什么问题。



声明:此文章为本人学习笔记,课程来源于慕课网:python3入门机器学习经典算法与应用。在此也感谢bobo老师精妙的讲解。

如果您觉得有用,欢迎关注我的公众号,我会不定期发布自己的学习笔记、AI资料、以及感悟,欢迎留言,与大家一起探索AI之路。

转载于:https://juejin.im/post/5d010f3e6fb9a07efb697efc

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值