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)
该函数采用估计器和原始数据,不能与已经预测的标签一起使用。 这有一些缺点:
建议:允许将预测标签y_pred
传递给plot_confusion_matrix
,用于代替estimator
和X
。 在我看来,最干净的解决方案是从函数中删除预测步骤并使用类似于accuracy_score
的签名,例如(y_true, y_pred, labels=None, sample_weight=None, ...)
。 但是,为了保持向后兼容性,可以添加y_pred
作为可选关键字参数。
我们绝对应该保持向后兼容,但是添加一个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 ,一个更estimator
和X
并使用与大多数一致的位置参数(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
必须提供ConfusionMatrixDisplay
或precision
和recall
为PrecisionRecallDisplay
。
这种事情的一个经典模式是定义:
ConfusionMatrixDisplay.from_estimator(...)
ConfusionMatrixDisplay.from_predictions(...)
但这对 scikit-learn 来说不是很惯用。
我开始感到困惑。 当前API的目标是避免用户想要多次绘制时进行多次计算,但是如果我们接受y_true和y_pred,用户仍然不需要多次计算吗? (我知道在 PDP 中情况有所不同)
@jnothman那个 API 看起来很漂亮!
@qinhanmin2014传递estimator, X, y
或y_true, y_pred
可以满足“不计算多次”API。 在这两种情况下,都会计算混淆矩阵并将其存储到Display
对象中。
它们之间的区别在于混淆矩阵的计算从哪里开始。 可以将 pass y_pred
视为估算器的“预计算”值。
所以我认为y_true, y_pred
比estimator, 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
@qinhanmin2014在y_pred
或删除est, X
似乎是属于下一个版本的新功能。
我猜你的意思是我们应该添加 y_true 并删除 est & X ,对吧? 我想这是不可能的?
最后,我更愿意支持同时拥有这两个接口,因为它们的用例略有不同。
est, X
更容易进行快速分析,因为该函数处理选择响应函数、切片结果并将其传递给度量。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_predictions
至plot_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)
签名,并且您确实想在estimator
、 x
、 y
数据上使用它,则只需编写很少的额外代码: plot_confusion_matrix(y, estimator.predict(x))
。
相比之下,如果您有当前的签名并且想要从y_true
和y_pred
进行绘图,则必须编写更多的代码。
在我看来, plot_confusion_matrix(y_true, y_pred)
签名应该是默认的,另一个需要estimator
、 x
、 y
函数应该建立在上面。
最后但并非最不重要的是,老实说,我并不真正理解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_true
和y_pred
。
界面目前之所以如此,是为了与其他度量绘图功能保持一致。 正如@thomasjpfan所说,绘制 roc 曲线不太明显。
现在绘制混淆矩阵和绘制 roc 曲线的代码是相同的。 根据您建议的更改,它们将不再相同,并且不会有简单的方法使它们相同。
问题是在这种情况下,使用一致的接口还是使用简单的接口更好。
@jhennrich对我来说真正的问题是plot_roc_curve
的正确界面是什么。 你对此有什么想法吗?
@thomasjpfan你也倾向于用y_store
来绘制 roc auc 吗?
使用 scorer 接口而不是使用 metric 接口肯定有利有弊。 但是对于更复杂的事情,使用 scorer 界面要安全得多。
@qinhanmin2014
我认为将y_pred
到plot_confusion_matrix
会很好。 问题是我们是否要将y_score
到plot_roc_curve
和plot_precision_recall_curve
。 如果我们这样做,那么我们还必须添加pos_label
就像我上面所说的那样,事情会变得更加复杂。
我看到了三种解决方法:
a)只增加y_pred
至plot_confusion_matrix
,但不要加y_score
至plot_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_true
和y_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 真正有意义:
plot_prediction_error
在https://github.com/scikit-learn/scikit-learn/pull/18020plot_calibration_curve
在https://github.com/scikit-learn/scikit-learn/pull/17443 (CC @lucyleeow )我理解@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_pred
或y_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,请联系我 :)
我很想做出贡献,但目前我参与的项目太多。 感谢您听取建议!
听起来不错 :)