如何标准化混淆矩阵?

Posted

技术标签:

【中文标题】如何标准化混淆矩阵?【英文标题】:How to normalize a confusion matrix? 【发布时间】:2014-01-22 12:33:05 【问题描述】:

我使用来自 scikit-learn 的 confusion_matrix() 为我的分类器计算了一个混淆矩阵。混淆矩阵的对角元素表示预测标签与真实标签相等的点的数量,而非对角元素是分类器错误标注的点。

我想标准化我的混淆矩阵,使其仅包含 0 到 1 之间的数字。我想从矩阵中读取正确分类样本的百分比。

我找到了几种方法来规范化矩阵(行和列规范化),但我对数学知之甚少,不确定这是否是正确的方法。

【问题讨论】:

矩阵有不同类型的归一化(实际上,也适用于其他任何东西),您应该使用哪一种取决于您的应用程序。因此,也许您可​​以编辑您的问题以更详细地描述:您究竟想通过规范化矩阵来实现什么?例如,某事物的总和是否应该为 1? 我编辑了这个问题,让它更清楚一点。我基本上只是想将分类样本的数量转换为百分比,以便我可以立即在矩阵的对角线上看到正确分类的样本数量。 老问题,大部分都是老旧的答案;现在 scikit-learn 开箱即用地提供此功能,请参阅下面的答案:***.com/a/66678924/4685471 【参考方案1】:

我假设M[i,j] 代表Element of real class i was classified as j。如果反过来,您将需要转置我所说的所有内容。我还将使用以下矩阵作为具体示例:

1 2 3
4 5 6
7 8 9

基本上你可以做两件事:

找出每个类别的分类方式

你可以问的第一件事是真实类i的元素有多少百分比在这里归类为每个类。为此,我们将一行固定i 并将每个元素除以该行中元素的总和。在我们的示例中,来自第 2 类的对象被分类为第 1 类 4 次,被正确分类为第 2 类 5 次,被分类为第 3 类 6 次。要找到百分比,我们只需将所有内容除以总和 4 + 5 + 6 = 15

4/15 of the class 2 objects are classified as class 1
5/15 of the class 2 objects are classified as class 2
6/15 of the class 2 objects are classified as class 3

查找负责每个分类的类

您可以做的第二件事是查看分类器的每个结果,并询问其中有多少结果来自每个真实类。它与另一种情况类似,但使用列而不是行。在我们的示例中,当原始类为 1 时,我们的分类器返回“1”1 次,当原始类为 2 时返回 4 次,当原始类为 3 时返回 7 次。要找到百分比,我们除以总和 1 + 4 + 7 = 12

1/12 of the objects classified as class 1 were from class 1
4/12 of the objects classified as class 1 were from class 2
7/12 of the objects classified as class 1 were from class 3

--

当然,我提供的两种方法一次只适用于单行列,我不确定以这种形式实际修改混淆矩阵是否是个好主意。但是,这应该会给出您正在寻找的百分比。

【讨论】:

【参考方案2】:

sklearn的confusion_matrix()输出的矩阵是such that

C_i, j 等于已知在组 i 中的观察数 但预计在j组

所以要获得每个类别的百分比(在二元分类中通常称为特异性和敏感性),您需要按行归一化:将一行中的每个元素替换为自身除以该行元素的总和。

请注意,sklearn 有一个可用的汇总函数,可以根据混淆矩阵计算指标:classification_report。它输出的是准确率和召回率,而不是特异性和敏感性,但通常这些通常被认为提供更多信息(特别是对于不平衡的多类分类。)

【讨论】:

这是真的,您需要按行规范化,因为您可能没有任何元素归类到特定类。换句话说,您在该列中有全零。你会如何正常化呢?除以零会导致 NaN 值。因此,这强化了按行规范化是明智之举的想法 你可以除以 max(1, sum(...)) 以免除以零【参考方案3】:

假设

>>> y_true = [0, 0, 1, 1, 2, 0, 1]
>>> y_pred = [0, 1, 0, 1, 2, 2, 1]
>>> C = confusion_matrix(y_true, y_pred)
>>> C
array([[1, 1, 1],
       [1, 2, 0],
       [0, 0, 1]])

然后,要找出每个类别有多少样本获得了正确的标签,您需要

>>> C / C.astype(np.float).sum(axis=1)
array([[ 0.33333333,  0.33333333,  1.        ],
       [ 0.33333333,  0.66666667,  0.        ],
       [ 0.        ,  0.        ,  1.        ]])

对角线包含所需的值。计算这些的另一种方法是意识到您正在计算的是每类的召回率:

>>> from sklearn.metrics import precision_recall_fscore_support
>>> _, recall, _, _ = precision_recall_fscore_support(y_true, y_pred)
>>> recall
array([ 0.33333333,  0.66666667,  1.        ])

同样,如果你除以 axis=0 的总和,你会得到精度(类别的分数-k 具有基本事实标签 k 的预测):

>>> C / C.astype(np.float).sum(axis=0)
array([[ 0.5       ,  0.33333333,  0.5       ],
       [ 0.5       ,  0.66666667,  0.        ],
       [ 0.        ,  0.        ,  0.5       ]])
>>> prec, _, _, _ = precision_recall_fscore_support(y_true, y_pred)
>>> prec
array([ 0.5       ,  0.66666667,  0.5       ])

【讨论】:

C / C.astype(np.float).sum(axis=1) 只有对角线元素才有意义。使用np.transpose( np.transpose(C) / C.astype(np.float).sum(axis=1) ) 使整个矩阵具有有意义的值不是更好吗? 我同意@arun。但是使用转置,您可以使用keepdims 总和,如C / C.astype(np.float).sum(axis=1, keepdims=True) 感谢您的 C / C.astype(np.float).sum(axis=0) 策略帮助我规范了我的混淆矩阵。【参考方案4】:

来自 sklearn 文档(plot example)

cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]

cm 是 sklearn 提供的混淆矩阵。

【讨论】:

这对被零除并不可靠。 完全正确,但如果你的分母为零,这可能表明某事不是很有意义。所以快失败吧。【参考方案5】:

scikit-learn 本身提供了一个用于绘制图形的库。它基于 matplotlib 并且应该已经安装以继续进行。

pip install scikit-plot

现在,只需将 normalize 参数设置为 true

import scikitplot as skplt 
skplt.metrics.plot_confusion_matrix(Y_TRUE, Y_PRED, normalize=True)

【讨论】:

有点晚了,但是您的代码中有一个小错误(或者可能是库的更新)。 Normalize 接受这个列表中的字符串变量 ['true', 'pred', 'all', None],所以你不能给它一个布尔值。因此该值应该是: normalize = 'true'。 @Wazaki 从 Python 3.6 和 Pip 20.0 开始,上述代码运行良好。 有趣。我的环境满足这两个要求,但是当我使用布尔值时显示错误,并且错误包括我之前编写的集合。也许 scikit-plot 的语法与 skplt 不同。【参考方案6】:

使用 Seaborn,您可以使用健康图轻松打印标准化且漂亮的混淆矩阵:

from sklearn.metrics import confusion_matrix
import seaborn as sns

cm = confusion_matrix(y_test, y_pred)
# Normalise
cmn = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
fig, ax = plt.subplots(figsize=(10,10))
sns.heatmap(cmn, annot=True, fmt='.2f', xticklabels=target_names, yticklabels=target_names)
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.show(block=False)

【讨论】:

谢谢!这个很不错【参考方案7】:

我认为最简单的方法是这样做:

c = sklearn.metrics.confusion_matrix(y, y_pred)
normed_c = (c.T / c.astype(np.float).sum(axis=1)).T

【讨论】:

或简单地normed_c = c / np.sum(c, axis=1, keepdims=True) 按行规范化(真实标签)。【参考方案8】:

如今,scikit-learn 的混淆矩阵带有 normalize 参数;来自docs:

标准化:'true', 'pred', 'all', default=None

在真实(行)、预测(列)条件或所有总体上标准化混淆矩阵。如果没有,混淆矩阵 不会被标准化。

因此,如果您希望对所有样本的值进行归一化,您应该使用

confusion_matrix(y_true, y_pred, normalize='all')

【讨论】:

不错!链接到实施。 github.com/scikit-learn/scikit-learn/blob/9b033758e/sklearn/…【参考方案9】:

对于您有 TOTALS 的情况。像这样的:

             0        1         2     Total
0      5434084      567      3460   5438111
1       458896  4717484    115297   5291677
2       189553     8305  13962602  14160460
Total  6082533  4726356  14081359  24890248

我的解决方案是:

cm = (cm.astype('float').T / cm.drop('Total', axis=1).sum(axis=1)).T

【讨论】:

以上是关于如何标准化混淆矩阵?的主要内容,如果未能解决你的问题,请参考以下文章

Matlab在matlab绘制渐变混淆矩阵

遥感软件中混淆矩阵是如何产生的

如何打印大维度的混淆矩阵

Matlab如何在matlab利用plotconfusion中绘制混淆矩阵

如何计算混淆矩阵?

一文详解人工智能分类方面的KPI评价标准:混淆矩阵TPFPFNTNP-R曲线图ROC曲线图