4. 元数据路由#
注意
元数据路由 API 处于实验阶段,尚未为所有估计器实现。有关更多信息,请参阅支持和不支持的模型列表。它可能会在没有通常的弃用周期的情况下发生变化。默认情况下,此功能未启用。您可以通过将 enable_metadata_routing 标志设置为 True 来启用它。
>>> import sklearn
>>> sklearn.set_config(enable_metadata_routing=True)
请注意,本文档中介绍的方法和要求仅在您想要将元数据(例如 sample_weight)传递给方法时才相关。如果您只将 X 和 y 传递给诸如 fit、transform 等方法,而没有传递其他参数/元数据,则无需设置任何内容。
本指南演示了如何在 scikit-learn 中的对象之间路由和传递元数据。如果您正在开发与 scikit-learn 兼容的估计器或元估计器,您可以查看我们的相关开发者指南:元数据路由。
元数据是估计器、评分器或 CV 分割器在用户显式将其作为参数传递时考虑的数据。例如,KMeans 在其 fit() 方法中接受 sample_weight,并考虑它来计算其质心。某些分类器使用 classes,某些分割器使用 groups,但除了 X 和 y 之外传递给对象方法的任何数据都可以被视为元数据。在 scikit-learn 1.3 版本之前,如果这些对象与其他对象结合使用,例如在 GridSearchCV 中接受 sample_weight 的评分器,则没有用于传递此类元数据的统一 API。
借助元数据路由 API,我们可以使用元估计器(例如 Pipeline 或 GridSearchCV)或诸如 cross_validate 之类的函数将元数据传输到估计器、评分器和 CV 分割器,这些函数将数据路由到其他对象。为了将元数据传递给诸如 fit 或 score 之类的方法,使用元数据的对象必须请求它。这是通过 set_{method}_request() 方法完成的,其中 {method} 被替换为请求元数据的方法名称。例如,在其 fit() 方法中使用元数据的估计器将使用 set_fit_request(),而评分器将使用 set_score_request()。这些方法允许我们指定请求哪些元数据,例如 set_fit_request(sample_weight=True)。
对于分组分割器,例如 GroupKFold,默认情况下会请求 groups 参数。以下示例最好地演示了这一点。
4.1. 使用示例#
这里我们提供一些示例来展示一些常见的用例。我们的目标是通过 cross_validate 传递 sample_weight 和 groups,它将元数据路由到 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)
4.1.1. 加权评分和拟合#
在 LogisticRegressionCV 内部使用的分割器 GroupKFold 默认请求 groups。但是,我们需要通过在 LogisticRegressionCV 的 set_fit_request() 方法和 make_scorer 的 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,
... use_legacy_attributes=False,
... ).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_validate 将 my_weights 路由到评分器和 LogisticRegressionCV。
如果我们将在 cross_validate 的参数中传递 sample_weight,但未设置任何对象来请求它,则会引发 UnsetMetadataPassedError,提示我们需要显式设置路由位置。如果传递了 params={"sample_weights": my_weights, ...}(请注意拼写错误,即 weights 而不是 weight),也会发生同样的情况,因为其任何底层对象都没有请求 sample_weights。
4.1.2. 加权评分和未加权拟合#
当将 sample_weight 等元数据传递给路由器(元估计器或路由函数)时,所有 sample_weight 消费者都要求显式请求或显式不请求权重(即 True 或 False)。因此,要执行未加权拟合,我们需要配置 LogisticRegressionCV 以不请求样本权重,这样 cross_validate 就不会传递权重。
>>> weighted_acc = make_scorer(accuracy_score).set_score_request(sample_weight=True)
>>> lr = LogisticRegressionCV(
... cv=GroupKFold(), scoring=weighted_acc, use_legacy_attributes=False
... ).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_request,cross_validate 将会引发错误,因为 sample_weight 已传递,但 LogisticRegressionCV 未显式配置以识别权重。
4.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, use_legacy_attributes=False
... ).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,
... )
4.1.4. 不同的评分和拟合权重#
尽管 make_scorer 和 LogisticRegressionCV 都期望键 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, use_legacy_attributes=False
... ).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,
... )
4.2. API 接口#
消费者是接受并在其至少一个方法中(例如 fit、predict、inverse_transform、transform、score、split)使用某些元数据的对象(估计器、元估计器、评分器、分割器)。仅将元数据转发给其他对象(子估计器、评分器或分割器)而不使用元数据本身的元估计器不是消费者。(元)估计器可以将元数据路由到其他对象,这些对象是路由器。(元)估计器可以同时是消费者和路由器。(元)估计器和分割器为接受至少一个元数据的每个方法公开一个 set_{method}_request 方法。例如,如果一个估计器在其 fit 和 score 中支持 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. See the Metadata Routing User guide
<https://scikit-learn.cn/stable/metadata_routing.html> for more information.
可以通过显式设置请求值来解决此问题。
>>> lr = LogisticRegression().set_fit_request(
... sample_weight=True
... ).set_score_request(sample_weight=False)
在使用示例部分末尾,我们禁用了元数据路由的配置标志。
>>> sklearn.set_config(enable_metadata_routing=False)
4.3. 元数据路由支持状态#
所有消费者(即仅使用元数据而不路由它们的简单估计器)都支持元数据路由,这意味着它们可以在支持元数据路由的元估计器中使用。然而,对元估计器元数据路由的支持正在开发中,这里列出了支持和尚未支持元数据路由的元估计器和工具。
支持元数据路由的元估计器和函数
尚未支持元数据路由的元估计器和工具