1. 元数据路由#

注意

元数据路由 API 处于实验阶段,尚未在所有估计器中实现。请参阅受支持和不受支持的模型列表了解更多信息。它可能会在没有通常的弃用周期的情况下发生更改。默认情况下,此功能未启用。您可以通过将enable_metadata_routing标志设置为True来启用它。

>>> import sklearn
>>> sklearn.set_config(enable_metadata_routing=True)

请注意,如果要将元数据(例如sample_weight)传递给方法,则本文档中介绍的方法和要求才相关。如果您只传递Xy,而没有将其他参数/元数据传递给fittransform等方法,则无需设置任何内容。

本指南演示了如何在 scikit-learn 中路由和传递元数据。如果您正在开发与 scikit-learn 兼容的估计器或元估计器,您可以查看我们相关的开发者指南:元数据路由

元数据是估计器、评分器或 CV 分割器在用户显式地将其作为参数传递时会考虑到的数据。例如,KMeans在其fit()方法中接受sample_weight,并将其考虑在内以计算其质心。classes被一些分类器使用,groups被一些分割器使用,但是除了 X 和 y 之外传递到对象方法的任何数据都可以被视为元数据。在 scikit-learn 1.3 版本之前,如果没有将这些对象与其他对象(例如在GridSearchCV中接受sample_weight的评分器)结合使用,则没有用于传递此类元数据的单个 API。

使用元数据路由 API,我们可以使用元估计器(例如PipelineGridSearchCV)或诸如cross_validate之类的函数将元数据传输到估计器、评分器和交叉验证分割器,这些函数将数据路由到其他对象。为了将元数据传递给诸如fitscore之类的函数,使用元数据的对象必须_请求_它。这是通过set_{method}_request()方法完成的,其中{method}被请求元数据的方法名称替换。例如,在其fit()方法中使用元数据的估计器将使用set_fit_request(),而评分器将使用set_score_request()。这些方法允许我们指定要请求哪些元数据,例如set_fit_request(sample_weight=True)

对于诸如GroupKFold之类的分组分割器,默认情况下会请求groups参数。以下示例对此进行了最佳演示。

1.1. 使用示例#

这里我们提供一些示例来说明一些常见的用例。我们的目标是通过cross_validate传递sample_weightgroups,它将元数据路由到LogisticRegressionCV和使用make_scorer创建的自定义评分器,两者都_可以_在其方法中使用元数据。在这些示例中,我们希望分别设置是否在不同的使用者中使用元数据。

本节中的示例需要以下导入和数据

>>> import numpy as np
>>> from sklearn.metrics import make_scorer, accuracy_score
>>> from sklearn.linear_model import LogisticRegressionCV, LogisticRegression
>>> from sklearn.model_selection import cross_validate, GridSearchCV, GroupKFold
>>> from sklearn.feature_selection import SelectKBest
>>> from sklearn.pipeline import make_pipeline
>>> n_samples, n_features = 100, 4
>>> rng = np.random.RandomState(42)
>>> X = rng.rand(n_samples, n_features)
>>> y = rng.randint(0, 2, size=n_samples)
>>> my_groups = rng.randint(0, 10, size=n_samples)
>>> my_weights = rng.rand(n_samples)
>>> my_other_weights = rng.rand(n_samples)

1.1.1. 加权评分和拟合#

LogisticRegressionCV内部使用的分割器GroupKFold默认请求groups。但是,我们需要通过在LogisticRegressionCV`s `set_fit_request()方法和make_scorer`s `set_score_request方法中指定sample_weight=True,为其和我们的自定义评分器显式请求sample_weight。这两个使用者都知道如何在它们的fit()score()方法中使用sample_weight。然后,我们可以将元数据传递到cross_validate中,它会将其路由到任何活动的使用者。

>>> weighted_acc = make_scorer(accuracy_score).set_score_request(sample_weight=True)
>>> lr = LogisticRegressionCV(
...     cv=GroupKFold(),
...     scoring=weighted_acc
... ).set_fit_request(sample_weight=True)
>>> cv_results = cross_validate(
...     lr,
...     X,
...     y,
...     params={"sample_weight": my_weights, "groups": my_groups},
...     cv=GroupKFold(),
...     scoring=weighted_acc,
... )

请注意,在此示例中,cross_validatemy_weights路由到评分器和LogisticRegressionCV

如果我们将sample_weight传递到cross_validate的参数中,但没有设置任何对象来请求它,则会引发UnsetMetadataPassedError,提示我们需要显式设置路由位置。如果传递了params={"sample_weights": my_weights, ...}(注意错别字,即weights而不是weight),也会出现这种情况,因为sample_weights没有被其任何底层对象请求。

1.1.2. 加权评分和非加权拟合#

当向路由器元估计器或路由函数)传递元数据(例如sample_weight)时,所有sample_weight使用者都需要显式地请求权重或显式地不请求权重(即TrueFalse)。因此,为了执行未加权拟合,我们需要配置LogisticRegressionCV以不请求样本权重,以便cross_validate不会传递权重。

>>> weighted_acc = make_scorer(accuracy_score).set_score_request(sample_weight=True)
>>> lr = LogisticRegressionCV(
...     cv=GroupKFold(), scoring=weighted_acc,
... ).set_fit_request(sample_weight=False)
>>> cv_results = cross_validate(
...     lr,
...     X,
...     y,
...     cv=GroupKFold(),
...     params={"sample_weight": my_weights, "groups": my_groups},
...     scoring=weighted_acc,
... )

如果没有调用linear_model.LogisticRegressionCV.set_fit_requestcross_validate将引发错误,因为传递了sample_weight,但LogisticRegressionCV没有被显式配置为识别这些权重。

1.1.3. 未加权特征选择#

只有当对象的方法知道如何使用元数据时,才能进行元数据路由,在大多数情况下,这意味着它们具有显式参数。只有这样,我们才能使用set_fit_request(sample_weight=True)等方式设置元数据的请求值。这使得对象成为使用者

LogisticRegressionCV不同,SelectKBest无法使用权重,因此不会在其实例上设置sample_weight的请求值,并且sample_weight不会被路由到它。

>>> weighted_acc = make_scorer(accuracy_score).set_score_request(sample_weight=True)
>>> lr = LogisticRegressionCV(
...     cv=GroupKFold(), scoring=weighted_acc,
... ).set_fit_request(sample_weight=True)
>>> sel = SelectKBest(k=2)
>>> pipe = make_pipeline(sel, lr)
>>> cv_results = cross_validate(
...     pipe,
...     X,
...     y,
...     cv=GroupKFold(),
...     params={"sample_weight": my_weights, "groups": my_groups},
...     scoring=weighted_acc,
... )

1.1.4. 不同的评分和拟合权重#

尽管make_scorerLogisticRegressionCV都期望键sample_weight,但我们可以使用别名将不同的权重传递给不同的使用者。在这个例子中,我们将scoring_weight传递给评分器,将fitting_weight传递给LogisticRegressionCV

>>> weighted_acc = make_scorer(accuracy_score).set_score_request(
...    sample_weight="scoring_weight"
... )
>>> lr = LogisticRegressionCV(
...     cv=GroupKFold(), scoring=weighted_acc,
... ).set_fit_request(sample_weight="fitting_weight")
>>> cv_results = cross_validate(
...     lr,
...     X,
...     y,
...     cv=GroupKFold(),
...     params={
...         "scoring_weight": my_weights,
...         "fitting_weight": my_other_weights,
...         "groups": my_groups,
...     },
...     scoring=weighted_acc,
... )

1.2. API接口#

一个使用者是一个对象(估计器、元估计器、评分器、分割器),它在其至少一个方法(例如fitpredictinverse_transformtransformscoresplit)中接受并使用一些元数据。仅将元数据转发给其他对象(子估计器、评分器或分割器)而自身不使用元数据的元估计器不是使用者。(元)估计器将元数据路由到其他对象的是路由器。(元)估计器可以同时是使用者路由器。(元)估计器和分割器为每个至少接受一个元数据的方法公开一个set_{method}_request方法。例如,如果一个估计器在fitscore中支持sample_weight,它会公开estimator.set_fit_request(sample_weight=value)estimator.set_score_request(sample_weight=value)。这里value可以是

  • True:方法请求sample_weight。这意味着如果提供了元数据,它将被使用,否则不会引发错误。

  • False:方法不请求sample_weight

  • None:如果传递了sample_weight,路由器将引发错误。这几乎是在实例化对象时的默认值,并确保在传递元数据时用户显式设置元数据请求。唯一的例外是Group*Fold分割器。

  • "param_name":如果想要向不同的使用者传递不同的权重,则是sample_weight的别名。如果使用别名,元估计器不应该将"param_name"转发给使用者,而是应该转发sample_weight,因为使用者期望一个名为sample_weight的参数。这意味着对象所需的元数据(例如sample_weight)与用户提供的变量名(例如my_weights)之间的映射是在路由器级别完成的,而不是由使用者对象本身完成的。

使用set_score_request以相同的方式请求评分器的元数据。

如果用户传递了元数据(例如sample_weight),则应该由用户设置所有可能使用sample_weight的对象的元数据请求,否则路由器对象将引发错误。例如,以下代码会引发错误,因为它没有明确指定是否应将sample_weight传递给估计器的评分器。

>>> param_grid = {"C": [0.1, 1]}
>>> lr = LogisticRegression().set_fit_request(sample_weight=True)
>>> try:
...     GridSearchCV(
...         estimator=lr, param_grid=param_grid
...     ).fit(X, y, sample_weight=my_weights)
... except ValueError as e:
...     print(e)
[sample_weight] are passed but are not explicitly set as requested or not
requested for LogisticRegression.score, which is used within GridSearchCV.fit.
Call `LogisticRegression.set_score_request({metadata}=True/False)` for each metadata
you want to request/ignore.

可以通过显式设置请求值来修复此问题。

>>> lr = LogisticRegression().set_fit_request(
...     sample_weight=True
... ).set_score_request(sample_weight=False)

在“使用示例”部分的结尾,我们禁用了元数据路由的配置标志。

>>> sklearn.set_config(enable_metadata_routing=False)

1.3. 元数据路由支持状态#

所有使用者(即仅使用元数据而不路由它们的简单估计器)都支持元数据路由,这意味着它们可以在支持元数据路由的元估计器中使用。但是,对元估计器的元数据路由支持的开发正在进行中,这里列出了支持和尚不支持元数据路由的元估计器和工具。

支持元数据路由的元估计器和函数

尚不支持元数据路由的元估计器和工具