混淆矩阵——评估指标

885 阅读8分钟

相关文章

承接上文《混淆矩阵》,本文通过混淆矩阵获取几个常见的评估指标准确率(Accuracy)、精确度(Precision)、召回率(Recall)、F1(F-score)。使用sklearn、tensorflow和手搓混淆矩阵这3种方式进行指标的计算 🦆🦆🦆

import sklearn
import numpy as np
import pandas as pd
import tensorflow as tf
from matplotlib import pyplot as plt
from sklearn.datasets import make_classification
from sklearn.preprocessing import label_binarize
from sklearn.multiclass import OneVsRestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split

准确率(Accuracy)

准确率,最基础的评估指标。反映了模型对于正确分类样本占所有样本的比例

但准确率会受类别分布的影响,如果模型对某个类别的预测特别准,反而对其他类别的预测很差,准确率也可能看起来也不错,而造成模型优异的假象。哼,我还以为是个六边形全能战神,却道是个偏科学霸😂😂😂

# 假设有6个类别
# 设置预测结果
pred = [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5]
# 设置正确标签
true = [0, 1, 2, 3, 1, 5, 0, 1, 2, 3, 1, 5, 0, 1, 2, 3, 4, 5]

sklearn.metrics.accuracy_score 计算

accuracy = sklearn.metrics.accuracy_score(y_true=true, y_pred=pred)
print(accuracy)
==============================
输出:
0.8888888888888888

tf.keras.metrics.Accuracy 计算

accuracy = tf.keras.metrics.Accuracy()
accuracy.update_state(y_true=true, y_pred=pred)

print(accuracy.result().numpy())
==============================
输出:
0.8888889

精确率(Precision)

精确率,是对于某一个类别而言的。举个栗子🌰,有10个类别,对于类别 cat🐱 而言,精确率反映了模型分类为类别 cat🐱 的样本中有多少是真正的cat🐱。毕竟,模型可能正确地把 cat🐱 分类为 cat🐱,也可能错误地把 dog🐶 分类为cat🐱

准确率看的是总体(所有类别)的准确性,而精确率则是细化到单个类别的分类准确与否,某个类别的精确率越高,说明模型对这个类别预测的越准确,每个类别的精确率都很高,说明六边形全能战神已成,即模型的预测能力已蛮准确

sklearn.metrics.precision_score 计算

precision = sklearn.metrics.precision_score(y_true=true, y_pred=pred, average='macro')

print(precision)
==============================
输出:
0.8888888888888888

tf.keras.metrics.Precision 计算

precision = tf.keras.metrics.Precision()
precision.update_state(y_true=tf.one_hot(true,6), y_pred=tf.one_hot(pred,6))

print(precision.result().numpy())
==============================
输出:
0.8888889

召回率(Recall)

召回率,类似精确率,也是对于某个类别而言的。在实际应用中,比如疾病诊断、欺诈检测或安全预警...要尽可能准确地识别出所有真正属于正类的样本。因为漏掉任何一个真正的正类样本(例如,漏诊一个患者的情况或者错过一个潜在的安全威胁)都可能带来严重的后果

假设我们有100个病人,其中50个是患病的(正类),另外50个是健康的(负类)。使用模型对这100个病人进行分类,得到以下结果:

  • 40个病人测试结果为阳性(模型预测为正类)
  • 60个病人测试结果为阴性(模型预测为负类)

但是,在这40个阳性结果中,实际上有30个是真正的患病者(真阳性,TP),而另外10个是健康的人但被错误地诊断为患病(假阳性,FP)。在60个阴性结果中,实际上有20个是真正的患病者但被错误地诊断为健康(假阴性,FN),而另外40个是真正的健康人(真阴性,TN)

无标题画板.jpg

现在,我们可以计算召回率:Recall=TPTP+FN=3030+20=3050=0.6Recall = \frac{TP}{TP+FN} = \frac{30}{30+20} = \frac{30}{50} = 0.6

我们想要的是真病人,模型实际上判断出了30人,还落下了20人(即假健康人),倘若Recall为1.0,则意味对于病人,模型都可以判断出来(但不保证模型会把健康人错误的分类为病人(即假病人),这也是Recall与Precision之间的区别)

一句话概括就是一个衡量不落下我们想要类别的指标

sklearn.metrics.recall_score 计算

recall = sklearn.metrics.recall_score(y_true=true, y_pred=pred, average='macro')

print(recall)
==============================
输出:
0.9333333333333332

tf.keras.metrics.Recall 计算

recall = tf.keras.metrics.Recall()
recall.update_state(y_true=tf.one_hot(true,6), y_pred=tf.one_hot(pred,6))

print(recall.result().numpy())
==============================
输出:
0.8888889

F1 分数

F1 分数,也称F1-score,F-1measure。F1 分数是Precision和Recall的调和平均数,用于同时考虑这两个指标的表现。F1 分数的取值范围在0到1,0表示最差性能,1表示最佳性能。

sklearn.metrics.f1_score

f1 = sklearn.metrics.f1_score(y_true=true, y_pred=pred, average='macro')

print(f1)
==============================
输出:
0.875

tf.keras.metrics.F1Score

f1 = tf.keras.metrics.F1Score(average='macro')
f1.update_state(y_true=tf.one_hot(true,6), y_pred=tf.one_hot(pred,6))

print(f1.result().numpy())
==============================
输出:
0.875

绘制表格

# 绘制 召回率、精确度、F1分数 表格
data = sklearn.metrics.classification_report(y_true=true, y_pred=pred, output_dict=True)
t = pd.DataFrame(data=data).T
t

image.png

手搓混淆矩阵

对于计算得到的混淆矩阵,手搓计算准确率(Accuracy)、精确度(Precision、召回率(Recall)、F1(F-score)

别问为啥手搓的指标和sklearn、tensorflow计算的有出入,笔者也不知道呀😅😅😅

构造混淆矩阵

先得出混淆矩阵,对各个类别计算TP、TN、FP、FN,进一步的去计算这些杂七杂八的指标

构造混淆矩阵这里就看懵的,先去look look《混淆矩阵》吧😁

cm = sklearn.metrics.confusion_matrix(y_true=true, y_pred=pred)
print(cm)

# 计算混淆矩阵的总和
total = np.sum(cm)
# 计算那条深色斜线的总和
line = np.sum([cm[i, i] for i in range(len(cm))])
# 储存每个类别的 TP TF NP NF
classes_list = []
for i in range(len(cm)):
    TP = cm[i, i]
    TN = line - TP
    FP = sum(cm[:, i]) - TP
    FN = total - TP - TN - FP
    classes_list.append({i: {'tp': TP, 'tn': TN, 'fp': FP, 'fn': FN}})
classes_list
==============================
输出:
[[3 0 0 0 0 0]
 [0 3 0 0 2 0]
 [0 0 3 0 0 0]
 [0 0 0 3 0 0]
 [0 0 0 0 1 0]
 [0 0 0 0 0 3]]

[{0: {'tp': 3, 'tn': 13, 'fp': 0, 'fn': 2}},
 {1: {'tp': 3, 'tn': 13, 'fp': 0, 'fn': 2}},
 {2: {'tp': 3, 'tn': 13, 'fp': 0, 'fn': 2}},
 {3: {'tp': 3, 'tn': 13, 'fp': 0, 'fn': 2}},
 {4: {'tp': 1, 'tn': 15, 'fp': 2, 'fn': 0}},
 {5: {'tp': 3, 'tn': 13, 'fp': 0, 'fn': 2}}]

手搓 Accuracy

Accuracy=TP+TNTP+TN+FP+FNAccuracy = \frac{TP+TN}{TP+TN+FP+FN}

方式1

mean = 0
for element in classes_list:
    # 获取各个类别的 TP TN FP FN
    tp, tn, fp, fn = list(element.values())[0].values()
    # 计算 Accuracy
    print(f'类别{list(element.keys())[0]}   Accuracy={(tp+tn)/(tp+tn+fp+fn)}')
    mean+=(tp+tn)/(tp+tn+fp+fn)

print(f'\n平均     Accuracy={mean/6}')
==============================
输出:
类别0 Accuracy=0.8888888888888888
类别1 Accuracy=0.8888888888888888
类别2 Accuracy=0.8888888888888888
类别3 Accuracy=0.8888888888888888
类别4 Accuracy=0.8888888888888888 
类别5 Accuracy=0.8888888888888888
平均  Accuracy=0.888888888888889

方式2

# 获取样本总数
total = len(pred)
correct = tf.math.count_nonzero(tf.equal(pred, true)).numpy()
accuracy = correct/total

print(accuracy)
==============================
输出:
0.8888888888888888

手搓 Precision

Precision=TPTP+FPPrecision= \frac{TP}{TP+FP}

mean = 0
for element in classes_list:
    # 获取各个类别的 TP TN FP FN
    tp, tn, fp, fn = list(element.values())[0].values()
    # 计算 Precision
    print(f'类别{list(element.keys())[0]}   Precision={(tp)/(tp+fp)}')
    mean += (tp)/(tp+fp)

print(f'平均     Precision={mean/6}')
==============================
输出:
类别0 Precision=1.0
类别1 Precision=1.0
类别2 Precision=1.0
类别3 Precision=1.0
类别4 Precision=0.3333333333333333
类别5 Precision=1.0
平均  Precision=0.8888888888888888

手搓 Recall

Recall=TPTP+FNRecall = \frac{TP}{TP+FN}

mean = 0
for element in classes_list:
    # 获取各个类别的 TP TN FP FN
    tp, tn, fp, fn = list(element.values())[0].values()
    # 计算 Recall
    print(f'类别{list(element.keys())[0]}   Recall={(tp)/(tp+fn)}')
    mean += (tp)/(tp+fn)

print(f'平均     Recall={mean/6}')
==============================
输出:
类别0 Recall=0.6
类别1 Recall=0.6
类别2 Recall=0.6
类别3 Recall=0.6
类别4 Recall=1.0
类别5 Recall=0.6
平均  Recall=0.6666666666666666

手搓 F1 分数

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

mean = 0
for element in classes_list:
    # 获取各个类别的 TP TN FP FN
    tp, tn, fp, fn = list(element.values())[0].values()
    # 计算 Precision、Recall
    precision = (tp)/(tp+fp)
    recall = (tp)/(tp+fn)
    print(f'类别{list(element.keys())[0]}   F1={2*(precision*recall)/(precision+recall)}')
    mean+=2*(precision*recall)/(precision+recall)
print(f'平均     F1={mean/6}')
==============================
输出:
类别0 F1=0.7499999999999999
类别1 F1=0.7499999999999999
类别2 F1=0.7499999999999999
类别3 F1=0.7499999999999999
类别4 F1=0.5
类别5 F1=0.7499999999999999
平均  F1=0.7083333333333331

ROC-AUC 曲线

  • make_classification
    • 生成分类数据集
    • n_samples:数据的数量
    • n_features:每个数据的特征数
    • n_informative:每个数据的有效特征数
    • n_classes:数据集中的类别数
    • class_sep:各类别之间的区分难易度
n_classes = 6
x, y = make_classification(n_samples=1000, n_features=32, n_informative=16, n_classes=n_classes, class_sep=2)
y = label_binarize(y, classes=range(n_classes))

train_x, valid_x, train_y, valid_y = train_test_split(x, y, test_size=0.3)

model = OneVsRestClassifier(LogisticRegression())
output = model.fit(train_x, train_y).decision_function(valid_x)
pred = model.predict(valid_x)
print('准确率: ', accuracy_score(valid_y, pred))
==============================
输出:
准确率: 0.62

ROC 曲线,它以真正例率(TPR)为纵轴,假正例率(FPR)为横轴,绘制出模型在不同阈值下的分类结果

在ROC曲线上,横轴表示FPR,即被错误地判断为正例的负例样本占所有负例样本的比例。纵轴表示TPR,即被正确地判断为正例的正例样本占所有正例样本的比例。ROC曲线的形状和位置可以反映出模型在不同阈值下的分类性能

理想情况下,ROC 曲线应该尽可能地靠近左上角,表示模型在最佳阈值下能够实现较高的TPR和较低的FPR。ROC 曲线下面积就是 AUC,AUC 是一个常用的评估指标,用于衡量模型的整体分类性能。AUC 的取值范围在0.5到1之间,越接近1表示模型的性能越好

fpr = {}
tpr = {}
roc_auc = {}
for i in range(n_classes):
    # 计算 ROC
    fpr[i], tpr[i], _ = roc_curve(valid_y[:, i], output[:, i])
    # 计算 AUC
    roc_auc[i] = auc(fpr[i], tpr[i])

fig, ax = plt.subplots()
ax.plot([0, 1], '--', linewidth=2)
for i in range(n_classes):
    # 绘制 ROC 曲线,显示 ACU
    display = RocCurveDisplay(fpr=fpr[i], tpr=tpr[i], roc_auc=roc_auc[i], estimator_name=f'Class {i}')
    display.plot(ax=ax)

plt.title('ROC-AUC')
plt.show()

76052a09-5e8e-466c-85f8-3b5e59040a60.png