Scikit-learn: 建议:从 plot_confusion_matrix 中删除预测并只传递预测标签

创建于 2019-12-13  ·  61评论  ·  资料来源: scikit-learn/scikit-learn

plot_confusion_matrix的签名目前是:

sklearn.metrics.plot_confusion_matrix(estimator, X, y_true, labels=None, sample_weight=None, normalize=None, display_labels=None, include_values=True, xticks_rotation='horizontal', values_format=None, cmap='viridis', ax=None)

该函数采用估计器和原始数据,不能与已经预测的标签一起使用。 这有一些缺点:

  • 如果应该绘制混淆矩阵但也应该在其他地方使用预测(例如计算accuracy_score),则必须多次执行估计。 如果估计器是随机的,这需要更长的时间并且可能导致不同的值。
  • 如果没有可用的估计器(例如从文件加载的预测),则根本无法使用该图。

建议:允许将预测标签y_pred传递给plot_confusion_matrix ,用于代替estimatorX 。 在我看来,最干净的解决方案是从函数中删除预测步骤并使用类似于accuracy_score的签名,例如(y_true, y_pred, labels=None, sample_weight=None, ...) 。 但是,为了保持向后兼容性,可以添加y_pred作为可选关键字参数。

model_selection

所有61条评论

我们绝对应该保持向后兼容,但是添加一个y_pred关键字 arg 对我来说听起来很合理。 如果 y_pred 被传递但 X 或 estimator 也被传递,我们应该引发错误。

您想提交 PR @jhennrich吗?

我提交了一个PR,但是我认为目前CI有问题所以还没有通过。

我同意我们应该支持plot_XXX(y_true, y_pred)以避免多次计算预测。
我们在 plot_roc_curve 和 plot_precision_recall_curve 中也有类似的问题。
添加 y_pred 似乎可以接受,但老实说,我认为这不是一个好的解决方案。
对于那些接受 **kwargs 的函数(例如,plot_precision_recall_curve),似乎不可能保持向后兼容?

为什么不能保持向后兼容? 在我看来#15883中的提议是可以的

为什么不能保持向后兼容? 在我看来#15883中的提议是可以的

因为我们不支持 plot_confusion_matrix 中的 **kwargs。 @尼古拉斯拥抱

为什么 kwargs 是一个问题?

嗯,还有一件烦人的事情,我们在 plot_roc_curve 和 plot_precision_recall_curve(和 plot_partial_dependence)中支持 **kwargs,但我们在 plot_confusion_matrix 中不支持它

为什么 kwargs 是一个问题?

如果我们在 **kwargs 之前添加新参数,我们可以保持向后兼容性,对吧?

我的 PR 中的更改向后兼容,仍然可以添加 **kwargs。 但我同意@qinhanmin2014 ,一个更estimatorX并使用与大多数一致的位置参数(y_true,y_pred,...)其他 sklearn 的东西。

如果我们在 **kwargs 之前添加新参数,我们可以保持向后兼容性,对吧?

是的

一个更清洁的解决方案......

不幸的是,这需要一个弃用周期(除非我们在错误修复版本中让它变得非常快,但我对此表示怀疑......)

@thomasjpfan ,有什么理由将估算器作为输入而不是预测传递?

谢谢,让我们先添加 y_pred,**kwags 是另一个问题。

不幸的是,这需要一个弃用周期(除非我们在错误修复版本中让它变得非常快,但我对此表示怀疑......)

这似乎不可能,叹息

@thomasjpfan ,有什么理由将估算器作为输入而不是预测传递?

我同意我们需要重新考虑我们的 API 设计。 也尝试 ping @amueller

如果用户想提供自己的绘图部分并提供自己的混淆矩阵:

from sklearn.metrics import ConfusionMatrixDisplay
confusion_matrix = confusion_matrix(...)
display_labels = [...]

disp = ConfusionMatrixDisplay(confusion_matrix=confusion_matrix,
                              display_labels=display_labels)
disp.plot(...)

对于其他度量绘图函数,可以类似地执行此操作。

plot_confusion_matrix的设计有点像计分器,能够很好地处理估算器的输出。 换句话说,它是一个方便的包装器,用于与ConfusionMatrixDisplay和估计器进行交互。

通过首先接受估计器,绘图函数有一个统一的接口。 例如, plot_partial_dependence执行创建部分依赖图所需的所有计算并将其传递给PartialDependenceDisplay 。 用户仍然可以自己创建PartialDependenceDisplay ,但在这种情况下会更加复杂。

虽然,我愿意拥有一条“快速路径”,允许将y_pred传递到与指标相关的绘图函数中,该函数将直接传递给confusion_matrix并让它处理验证。

构建 PDP 所需的预测计算非常复杂。 此外,这些预测在例如记分器或度量中通常是不可用的。 它们仅用于绘制 PDP。 所以在这种情况下只接受 plot_partial_dependence 中的估计器是有意义的。

OTOH 对于混淆矩阵,预测实际上只是est.predict(X)

我不认为我们想要一个统一的界面。 这是两个非常不同的输入用例

编辑:此外,基于树的 PDP 甚至根本不需要预测

如果没有估算器,我们还会遇到其他事情。 例如,如果plot_precision_recall_curve接受y_pred ,它将需要pos_label因为它不能再被推断。 在这种情况下,我更愿意直接使用PrecisionRecallDisplay并让用户计算重建绘图所需的参数。

这归结为我们用这个 API 回答什么样的问题。 当前的接口围绕评估一个估计量,因此使用估计量作为参数。 它的动机是回答“这个经过训练的模型如何处理这个输入数据?”

如果我们接受y_pred, y_true ,现在问题变成了“这个指标如何处理这些数据?” 这些数据可能由也可能不是由模型生成。

在这种特定情况下, @jhennrich 确实可以直接使用 ConfusionMatrixDisplay。

一个缺点是您需要指定display_labels因为它没有默认值。

@thomasjpfan您认为我们通常可以为 Display 对象提供合理的默认值,从而仍然可以直接使用 Display 对象吗?

对于某些参数,例如display_labels ,有一个合理的默认值。 其他Display对象参数也可以有合理的默认值。 必须提供一些参数。 例如, confusion_matrix必须提供ConfusionMatrixDisplayprecisionrecallPrecisionRecallDisplay

这种事情的一个经典模式是定义:

ConfusionMatrixDisplay.from_estimator(...)
ConfusionMatrixDisplay.from_predictions(...)

但这对 scikit-learn 来说不是很惯用。

我开始感到困惑。 当前API的目标是避免用户想要多次绘制时进行多次计算,但是如果我们接受y_true和y_pred,用户仍然不需要多次计算吗? (我知道在 PDP 中情况有所不同)

@jnothman那个 API 看起来很漂亮!

@qinhanmin2014传递estimator, X, yy_true, y_pred可以满足“不计算多次”API。 在这两种情况下,都会计算混淆矩阵并将其存储到Display对象中。

它们之间的区别在于混淆矩阵的计算从哪里开始。 可以将 pass y_pred视为估算器的“预计算”值。

所以我认为y_true, y_predestimator, X, y (当然不是在 PDP 中),因为有时(通常?)用户不仅想绘制预测,他们还想分析预测。 使用当前的 API,他们需要多次计算预测。

对于指标,我可以看到使用y_true, y_pred不是estimator, X, y的偏好。 想象一下,如果指标绘图仅支持y_true, y_pred

est = # fit estimator

plot_partial_dependence(est, X, ...)

# if plot_confusion_matrix accepts `y_true, y_pred`
y_pred = est.predict(X)
plot_confusion_matrix(y_true, y_pred, ...)

# if plot_roc_curve supports `y_true, y_score`
y_score = est.predict_proba(X)[: , 1]
plot_roc_curve(y_true, y_score, ...)
plot_precision_recall_curve(y_true, y_score, ...)

目前的 API 看起来像:

est = # fit estimator
plot_partial_dependence(est, X, ...)
plot_confusion_matrix(est, X, y, ...)
plot_roc_curve(est, X, y, ...)

# this will call `predict_proba` again
plot_precision_recall_curve(est, X, y, ...)

我更喜欢有一个支持这两个选项的 API(不知何故)。

对于指标,我可以看到使用 y_true, y_pred 超过 estimator, X, y 的偏好。 想象一下,如果指标绘图仅支持 y_true, y_pred

是的,这就是我的意思。

我更喜欢有一个支持这两个选项的 API(不知何故)。

我认为这是一个实用的解决方案。 一个烦人的事情是我们只能在最后加上y_pred(即plot_confusion_matrix(estimator, X, y_true, ..., y_pred))

是的,它会在最后,API 将如下所示:

plot_confusion_matrix(y_true=y_true, y_pred=y_pred, ...)

我觉得我没问题。 这本质上是 PR https://github.com/scikit-learn/scikit-learn/pull/15883

是的,它会在最后,API 看起来像这样 plot_confusion_matrix(y_true=y_true, y_pred=y_pred, ...)

我猜你的意思是我们应该添加 y_true 并删除 est & X ,对吧? 我想这是不可能的? (因为我们只能在最后加上 y_pred)

我们想在 0.22.1 中解决这个问题吗? @NicolasHug @thomasjfox我认为将它放在 0.22.1 中是值得的,但与此同时,这似乎是一个新功能。

不,不要把它放在 0.22.1 中。 这明显违反了semver

@qinhanmin2014y_pred或删除est, X似乎是属于下一个版本的新功能。

我猜你的意思是我们应该添加 y_true 并删除 est & X ,对吧? 我想这是不可能的?

最后,我更愿意支持同时拥有这两个接口,因为它们的用例略有不同。

  1. est, X更容易进行快速分析,因为该函数处理选择响应函数、切片结果并将其传递给度量。
  2. y_true, y_pred适用于了解如何使用基础指标并已保存预测的用户。

https://github.com/scikit-learn/scikit-learn/issues/15880#issuecomment -565489619 有什么问题?

我还没有阅读整篇文章,但如果我们允许这里的接口,我们还需要为plot_roc_curve这样做,其中提供预测和提供估计器之间的接口将大不相同(一个需要 pos_label 另一个不需要't)。
所以我认为在同一个界面中允许两者是一个坏主意(有人会在传递 estimator 时传递 pos_label 并得到他们不期望的结果)。

ConfusionMatrixDisplay.from_estimator(...)
ConfusionMatrixDisplay.from_predictions(...)

可以工作,但它基本上会使plot_confusion_matrix变得多余,因此我们将再次删除函数并更改类和函数之间的职责(我们说类不进行计算)。

如果我们要添加一个from_predictionsplot_roc_curve它需要基本反映roc_curve接口完美。 所以我觉得让用户直接调用roc_curve函数,然后把结果传给Display对象也不算太坏。

设计显示对象的全部目的是允许@jhennrich提到的用例以及我们为什么将计算与函数分开。 我还没有看到关于为什么我们应该放弃那个决定的争论。

@amueller从技术上讲,您是对的,我的问题的当前解决方案是仅使用ConfusionMatrixDisplay 。 但是使用起来真的很笨拙:

  • 你必须明确地传递标签
  • 你必须先计算混淆矩阵
  • 你必须创建一个类的对象,然后仍然调用plot方法

对于所有我能想到的应用plot_confusion_matrix与签名(y_true, y_pred, ...)将大大超过我们目前有更多的方便。 在我看来,有更多的用例需要明确计算预测(尽管我确信我的观点是有偏见的)。

如果您有plot_confusion_matrix(y_true, y_pred)签名,并且您确实想在estimatorxy数据上使用它,则只需编写很少的额外代码: plot_confusion_matrix(y, estimator.predict(x))
相比之下,如果您有当前的签名并且想要从y_truey_pred进行绘图,则必须编写更多的代码。

在我看来, plot_confusion_matrix(y_true, y_pred)签名应该是默认的,另一个需要estimatorxy函数应该建立在上面。

最后但并非最不重要的是,老实说,我并不真正理解ConfusionMatrixDisplay类背后的想法。 该函数只有一个构造函数和一个方法,因此无论何时使用它,您最终都会创建一个实例并调用plot函数。 我不明白为什么这应该是一个类而不仅仅是一个函数。 还有其他*Display类(PrecisionRecall,ROC,...),但是它们的构造函数和plot()签名完全不同,因此无论如何它们都不能换出。
也许这超出了这个问题的范围。

@jhennrich

如果您有一个 plot_confusion_matrix(y_true, y_pred) 签名并且您实际上想在 estimator, x, y 数据上使用它,那么只需编写很少的额外代码:plot_confusion_matrix(y, estimator.predict(x))。

对于混淆矩阵的情况,如果我们有一个y_true, y_pred接口,很容易传入estimator.predict 。 另一方面,对于plot_roc_auc ,用户需要进行切片:

y_pred = est.predict_proba(X)
plot_roc_curve(y_true, y_pred[:, 1])

# or
y_pred = est.decision_function(X)
plot_roc_curve(y_true, y_pred[:, 1])

最后但并非最不重要的一点是,老实说,我并不真正理解 ConfusionMatrixDisplay 类背后的想法。 该函数只有一个构造函数和一个方法,因此无论何时使用它,您最终都会创建一个实例并调用 plot 函数。 我不明白为什么这应该是一个类而不仅仅是一个函数。

Display对象的目的是存储计算值,允许用户多次调用plot而无需重新计算。 这可以通过使用plot_partial_dependence看到:

# Does expensive computation
disp = plot_partial_dependence(est, ...)

# change line color without needing to recompute partial dependence
disp.plot(line_kw={"c": "red"})

老实说,我对这个问题持观望态度。 我是 +0.1 转向复制指标界面以绘制指标并删除est, X, y界面。 :/

对于混淆矩阵情况,如果我们有 y_true, y_pred 接口,则很容易传入 estimator.predict。 另一方面,对于 plot_roc_auc,用户需要进行切片:

是的,但通过这样做,我们避免了多次计算预测(尽管预测通常不是那么昂贵)

也许是一个实用的解决方案,它在 0.23 中支持 plot_XXX(如果适用)中的y_true, y_pred

@jhennrich你将如何在不明确传递标签的情况下做到这一点? 如果标签可以从给定的confusion_matrix推断出来,将为您做到这一点。

但确实你是对的,它是三行而不是一行。

在混淆_矩阵的情况下,我倾向于同意更常见的情况可能是传递y_truey_pred
界面目前之所以如此,是为了与其他度量绘图功能保持一致。 正如@thomasjpfan所说,绘制 roc 曲线不太明显。

现在绘制混淆矩阵和绘制 roc 曲线的代码是相同的。 根据您建议的更改,它们将不再相同,并且不会有简单的方法使它们相同。

问题是在这种情况下,使用一致的接口还是使用简单的接口更好。
@jhennrich对我来说真正的问题是plot_roc_curve的正确界面是什么。 你对此有什么想法吗?

@thomasjpfan你也倾向于用y_store来绘制 roc auc 吗?

使用 scorer 接口而不是使用 metric 接口肯定有利有弊。 但是对于更复杂的事情,使用 scorer 界面要安全得多。

@qinhanmin2014
我认为将y_predplot_confusion_matrix会很好。 问题是我们是否要将y_scoreplot_roc_curveplot_precision_recall_curve 。 如果我们这样做,那么我们还必须添加pos_label就像我上面所说的那样,事情会变得更加复杂。

我看到了三种解决方法:
a)只增加y_predplot_confusion_matrix ,但不要加y_scoreplot_roc_curve等缺点:调用的问题predict_proba这些指标多次保持存在。
b) 让直接使用Display对象更容易(虽然我真的不知道如何)。
c) 添加另一个反映度量接口的方法或函数。 缺点:更大的 API 表面。

我不认为让plot_X函数同时反映计分器和度量界面是一个好主意。

我认为以某种方式解决这个问题会很棒@adrinjalali你想在下次会议上讨论它吗?

我有时会做关于这个问题的噩梦。 也许我们可以添加一个静态方法,直接获取度量的输出:

result = confusion_matrix(...)
ConfusionMatrixDisplay.from_metric(result).plot()

对于 roc 曲线:

result = roc_curve(...)
RocCurveDisplay.from_metric(*result).plot()

顺便说一句,从代码库来看,我认为更多用户熟悉指标界面而不是分数界面。

我有时会做关于这个问题的噩梦。

不好了 :(

顺便说一句,从代码库来看,我认为更多用户熟悉指标界面而不是分数界面。

我认为这绝对是真的。 但是我也很确定人们在应该使用y_score y_pred时使用

我不确定您提出的静态方法与构造函数有何不同,但也许我忽略了一些东西。

嗨,我刚刚对这个问题进行了投票 - 作为 sklearn 的长期用户,我发现plot_confusion_matrix的当前 API 非常......好吧,令人困惑。 我真的很喜欢它的添加(更少的复制粘贴),但是度量函数总是使用 (y_true, y_pred) 方案,它更灵活,而且我已经习惯了。

在我的情况下,传入估计器没有意义,因为它是一个非常慢的模型,我宁愿从文件中加载预测,也不愿在每次我想分析结果时重新运行它。 我很高兴在此线程中发现使用 *Display 对象的解决方法,但它的可发现性不是很好 - 我建议至少将其添加到plot_confusion_matrix文档或混淆矩阵用户指南?

就我而言,传递估算器没有意义,因为它是一个非常慢的模型,我宁愿加载预测

感谢您的输入。 如果当前的 API 令人困惑,那么转向更像指标的 API 接口并经历痛苦的​​弃用周期会更有意义。

我们对使用指标界面的最大担忧是:

但是我也很确定人们在应该使用 y_score 时使用 y_pred 并且得到错误的结果,因为界面没有告诉您需要做一些不同的事情并且没有人阅读文档。

@pzelasko您对此事有何看法?

@thomasjpfan我理解这个问题,这是一个艰难的问题。 也许一个合理的妥协是只允许此函数的关键字参数(现在您不必再支持 Python 2)? 喜欢: def plot_confusion_matrix(*, y_true, y_pred, ...) 。 它仍然与其他指标不同,但 1) 它有充分的理由,2) 它至少使用与其他函数相同类型的输入。

无论如何,我知道您为什么对进行任何 API 更改犹豫不决,这就是为什么我建议至少在文档中提及解决方法。 (我实际上已经阅读了很多次,我真的很感激它们!)

当前使用y_truey_pred方法如下所示: https: //scikit-learn.org/stable/auto_examples/miscellaneous/plot_display_object_visualization.html#create -confusionmatrixdisplay

我知道我在这里伸展,但是这个呢:

plot_confusion_matrix(estimator='precomputed', y_true, y_pred, ...)

其中第二个位置接受y_true作为预测,如果estimator='precomputed

如果你想更进一步,我更喜欢plot_confusion_matrix((estimator, X, y), ...)plot_confusion_matrix((y_true, y_pred), ...)但我不确定它是否解决了@amueller提出的关于类似度量的 API 的问题

有一些新的绘图实用程序允许metric API 真正有意义:

我理解@amueller提到的需要传递pos_label等的问题,但这对于任何上述函数都不是问题。

我们是否可以同时为这两个 API 提供评分器和指标 API? 我们不需要担心那里的向后兼容性。

我仍然支持使用precomputed ,我们通常在估算器中使用它。 在这种情况下,签名将是:

plot_confusion_matrix(estimator='precomputed', y_true, y_pred, ..., metric_kwargs=None)

我将把 PR 放在一起,看看这是什么样子。

我还没有真正讨论 API,我只是问我们是否可以支持新 PR 的两个选项。

(但关于 API,我认为“预先计算”没有多大帮助:我们如何处理X ?我认为我们应该通过正确错误来保持 (y_pred) 和 (estimator, X) 互斥.另外,预先计算估算器意味着什么?)

或者estimator='none'estimator='predictions'estimator='precomputed_predictions' ,然后X变成y_predy_score 。 这几乎就像我们如何在估计器中使用X处理预先计算的距离。

我们是否可以同时为这两个 API 提供评分器和指标 API?

我们将如何支持这两种选择? 有两个功能?

我也喜欢:

CalibrationDisplay.from_estimator(...)
CalibrationDisplay.from_predictions(...)

这将是两种方法。

Guillaume 建议使用元组https://github.com/scikit-learn/scikit-learn/issues/15880#issuecomment -670590882 是一种选择。 我认为如果我们从一开始就从那里开始,那将是最好的选择。 但是我担心使用元组会破坏与现有实用程序的一致性。

带有互斥的plot_XYZ(estimator=None, X=None, y=None, y_pred=None)是另一种选择,也是我目前提倡的选择。

我喜欢CalibrationDisplay.from_estimator(...) ,但正如 Andy 指出的那样,我们需要删除plot_XYZ函数。 可能值得考虑。

我认为我们可以转向元组并弃用当前的行为。 (只要我们同意使用元组)

所以这似乎是在讨论命名空间,对吧?
无论我们有一个函数和一个构造函数,还是两个类方法,或两个函数,它们的功能和代码基本相同。

@pzelasko @jhennrich你对有两个类方法或两个函数感觉如何? 或者你更喜欢单个函数,这在 python 中有点混乱。

如果您更喜欢两个函数或两个类方法,尽管可发现性,您是否看到任何好处? 可发现性可能足以成为使用 classmethods 的理由,但我认为拥有两个函数的理由并不充分。

我们可以在这里添加拦截器标签吗? 它似乎阻止了 #18020 和 #17443 (cc @cmarmo) 的进展

拦截器标签用于发布拦截器(在发布之前绝对需要修复的东西),而不是公关拦截器

啊,很高兴知道。

@pzelasko @jhennrich你对有两个类方法或两个函数感觉如何? 或者你更喜欢单个函数,这在 python 中有点混乱。

如果您更喜欢两个函数或两个类方法,尽管可发现性,您是否看到任何好处? 可发现性可能足以成为使用 classmethods 的理由,但我认为拥有两个函数的理由并不充分。

我最喜欢二类方法,尤其是from_xxx模式 - 就像@thomasjpfan提出的那样:

CalibrationDisplay.from_estimator(...)
CalibrationDisplay.from_predictions(...)

看起来没有强烈反对使用 2 类方法,所以让我们这样做。 我们需要:

  • 介绍当前存在的图的类方法:

    • ConfusionMatrixDisplay
    • PrecisionRecallDisplay
    • RocCurveDisplay
    • DetCurveDisplay
    • PartialDependenceDisplay 。 对于这个,我们不想引入from_predictions类方法,因为它没有意义,我们只想要from_estimator
  • 对于上面列出的所有 Display,弃用它们对应的plot_...函数。 我们不需要弃用plot_det_curve因为它还没有发布,我们可以删除它。

  • 对于像#17443 和#18020 这样的新 PR,我们可以立即实现类方法,而不是引入plot函数。

这是一些工作,但我认为我们可以在 0.24 之前完成这项工作,以便 #17443 和 #18020 已经可以向前推进。

任何反对意见@thomasjpfan @jnothman @amueller @glemaitre

@jhennrich @pzelasko ,您是否有兴趣提交 PR 以在 Display 对象之一中引入类方法?

感谢您做出决定@NicolasHug! 我会进入#17443(等待反对意见后)

我没有异议。

也没有异议。

然后我会照顾其他课程并推进我停滞不前的 PR。
@lucyleeow ,如果我没有做所有这些,而你正在寻找一些 PR,请联系我 :)

我很想做出贡献,但目前我参与的项目太多。 感谢您听取建议!

听起来不错 :)

此页面是否有帮助?
0 / 5 - 0 等级