Hinweis
Zum Ende springen, um den vollständigen Beispielcode herunterzuladen oder dieses Beispiel über JupyterLite oder Binder in Ihrem Browser auszuführen.
Metadaten-Routing#
Dieses Dokument zeigt, wie Sie den Metadaten-Routing-Mechanismus in scikit-learn verwenden können, um Metadaten an die sie konsumierenden Estimators, Scorers und CV-Splitter zu leiten.
Um das folgende Dokument besser zu verstehen, müssen wir zwei Konzepte einführen: Router und Konsumenten. Ein Router ist ein Objekt, das übergebene Daten und Metadaten an andere Objekte weiterleitet. In den meisten Fällen ist ein Router ein Meta-Estimator, d. h. ein Estimator, der einen anderen Estimator als Parameter nimmt. Eine Funktion wie sklearn.model_selection.cross_validate, die einen Estimator als Parameter nimmt und Daten und Metadaten weiterleitet, ist ebenfalls ein Router.
Ein Konsument hingegen ist ein Objekt, das übergebene Metadaten akzeptiert und verwendet. Zum Beispiel ist ein Estimator, der sample_weight in seiner fit-Methode berücksichtigt, ein Konsument von sample_weight.
Es ist möglich, dass ein Objekt sowohl ein Router als auch ein Konsument ist. Zum Beispiel kann ein Meta-Estimator sample_weight in bestimmten Berechnungen berücksichtigen, aber er kann es auch an den zugrundeliegenden Estimator weiterleiten.
Zuerst einige Imports und einige zufällige Daten für den Rest des Skripts.
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
import warnings
from pprint import pprint
import numpy as np
from sklearn import set_config
from sklearn.base import (
BaseEstimator,
ClassifierMixin,
MetaEstimatorMixin,
RegressorMixin,
TransformerMixin,
clone,
)
from sklearn.linear_model import LinearRegression
from sklearn.utils import metadata_routing
from sklearn.utils.metadata_routing import (
MetadataRouter,
MethodMapping,
get_routing_for_object,
process_routing,
)
from sklearn.utils.validation import check_is_fitted
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)
Metadaten-Routing ist nur verfügbar, wenn es explizit aktiviert ist
set_config(enable_metadata_routing=True)
Diese Hilfsfunktion ist ein Dummy, um zu prüfen, ob Metadaten übergeben werden
def check_metadata(obj, **kwargs):
for key, value in kwargs.items():
if value is not None:
print(
f"Received {key} of length = {len(value)} in {obj.__class__.__name__}."
)
else:
print(f"{key} is None in {obj.__class__.__name__}.")
Eine Hilfsfunktion, um die Routing-Informationen eines Objekts schön auszugeben
def print_routing(obj):
pprint(obj.get_metadata_routing()._serialize())
Konsumierender Estimator#
Hier demonstrieren wir, wie ein Estimator die erforderliche API bereitstellen kann, um Metadaten-Routing als Konsument zu unterstützen. Stellen Sie sich einen einfachen Klassifikator vor, der sample_weight als Metadaten in seiner fit-Methode und groups in seiner predict-Methode akzeptiert.
class ExampleClassifier(ClassifierMixin, BaseEstimator):
def fit(self, X, y, sample_weight=None):
check_metadata(self, sample_weight=sample_weight)
# all classifiers need to expose a classes_ attribute once they're fit.
self.classes_ = np.array([0, 1])
return self
def predict(self, X, groups=None):
check_metadata(self, groups=groups)
# return a constant value of 1, not a very smart classifier!
return np.ones(len(X))
Der obige Estimator hat nun alles, was er braucht, um Metadaten zu konsumieren. Dies wird durch einige Magie erreicht, die in BaseEstimator geschieht. Es gibt nun drei Methoden, die von der obigen Klasse bereitgestellt werden: set_fit_request, set_predict_request und get_metadata_routing. Es gibt auch eine set_score_request für sample_weight, die vorhanden ist, seit ClassifierMixin eine score-Methode implementiert, die sample_weight akzeptiert. Dasselbe gilt für Regressoren, die von RegressorMixin erben.
Standardmäßig wird kein Metadaten angefordert, was wir wie folgt sehen können:
print_routing(ExampleClassifier())
{'fit': {'sample_weight': None},
'predict': {'groups': None},
'score': {'sample_weight': None}}
Die obige Ausgabe bedeutet, dass sample_weight und groups nicht von ExampleClassifier angefordert werden. Wenn ein Router diese Metadaten erhält, sollte er einen Fehler auslösen, da der Benutzer nicht explizit festgelegt hat, ob sie erforderlich sind oder nicht. Dasselbe gilt für sample_weight in der score-Methode, die von ClassifierMixin geerbt wird. Um die Werte für diese Metadaten explizit festzulegen, können wir diese Methoden verwenden:
est = (
ExampleClassifier()
.set_fit_request(sample_weight=False)
.set_predict_request(groups=True)
.set_score_request(sample_weight=False)
)
print_routing(est)
{'fit': {'sample_weight': False},
'predict': {'groups': True},
'score': {'sample_weight': False}}
Hinweis
Bitte beachten Sie, dass, solange der obige Estimator nicht in einem Meta-Estimator verwendet wird, der Benutzer keine Anforderungen für die Metadaten festlegen muss und die eingestellten Werte ignoriert werden, da ein Konsument übergebene Metadaten nicht validiert oder weiterleitet. Eine einfache Verwendung des obigen Estimators würde wie erwartet funktionieren.
est = ExampleClassifier()
est.fit(X, y, sample_weight=my_weights)
est.predict(X[:3, :], groups=my_groups)
Received sample_weight of length = 100 in ExampleClassifier.
Received groups of length = 100 in ExampleClassifier.
array([1., 1., 1.])
Routing Meta-Estimator#
Nun zeigen wir, wie man einen Meta-Estimator als Router gestaltet. Als vereinfachtes Beispiel hier ein Meta-Estimator, der nicht viel mehr tut, als Metadaten weiterzuleiten.
class MetaClassifier(MetaEstimatorMixin, ClassifierMixin, BaseEstimator):
def __init__(self, estimator):
self.estimator = estimator
def get_metadata_routing(self):
# This method defines the routing for this meta-estimator.
# In order to do so, a `MetadataRouter` instance is created, and the
# routing is added to it. More explanations follow below.
router = MetadataRouter(owner=self).add(
estimator=self.estimator,
method_mapping=MethodMapping()
.add(caller="fit", callee="fit")
.add(caller="predict", callee="predict")
.add(caller="score", callee="score"),
)
return router
def fit(self, X, y, **fit_params):
# `get_routing_for_object` returns a copy of the `MetadataRouter`
# constructed by the above `get_metadata_routing` method, that is
# internally called.
request_router = get_routing_for_object(self)
# Meta-estimators are responsible for validating the given metadata.
# `method` refers to the parent's method, i.e. `fit` in this example.
request_router.validate_metadata(params=fit_params, method="fit")
# `MetadataRouter.route_params` maps the given metadata to the metadata
# required by the underlying estimator based on the routing information
# defined by the MetadataRouter. The output of type `Bunch` has a key
# for each consuming object and those hold keys for their consuming
# methods, which then contain key for the metadata which should be
# routed to them.
routed_params = request_router.route_params(params=fit_params, caller="fit")
# A sub-estimator is fitted and its classes are attributed to the
# meta-estimator.
self.estimator_ = clone(self.estimator).fit(X, y, **routed_params.estimator.fit)
self.classes_ = self.estimator_.classes_
return self
def predict(self, X, **predict_params):
check_is_fitted(self)
# As in `fit`, we get a copy of the object's MetadataRouter,
request_router = get_routing_for_object(self)
# then we validate the given metadata,
request_router.validate_metadata(params=predict_params, method="predict")
# and then prepare the input to the underlying `predict` method.
routed_params = request_router.route_params(
params=predict_params, caller="predict"
)
return self.estimator_.predict(X, **routed_params.estimator.predict)
Lassen Sie uns die verschiedenen Teile des obigen Codes aufschlüsseln.
Zuerst nimmt die Methode get_routing_for_object unseren Meta-Estimator (self) und gibt einen MetadataRouter oder ein MetadataRequest zurück, wenn das Objekt ein Konsument ist, basierend auf der Ausgabe der Methode get_metadata_routing des Estimators.
Dann verwenden wir in jeder Methode die Methode route_params, um ein Dictionary im Format {"object_name": {"method_name": {"metadata": value}}} zu erstellen, das an die Methode des zugrundeliegenden Estimators übergeben wird. Der object_name (estimator im obigen Beispiel routed_params.estimator.fit) ist derselbe wie der in get_metadata_routing hinzugefügte. validate_metadata stellt sicher, dass alle übergebenen Metadaten angefordert werden, um stille Fehler zu vermeiden.
Als Nächstes illustrieren wir die verschiedenen Verhaltensweisen und insbesondere die Art der ausgelösten Fehler.
meta_est = MetaClassifier(
estimator=ExampleClassifier().set_fit_request(sample_weight=True)
)
meta_est.fit(X, y, sample_weight=my_weights)
Received sample_weight of length = 100 in ExampleClassifier.
Beachten Sie, dass das obige Beispiel unsere Hilfsfunktion check_metadata() über ExampleClassifier aufruft. Sie prüft, ob sample_weight korrekt daran übergeben wird. Wenn nicht, wie im folgenden Beispiel, wird gedruckt, dass sample_weight None ist.
meta_est.fit(X, y)
sample_weight is None in ExampleClassifier.
Wenn wir ein unbekanntes Metadatum übergeben, wird ein Fehler ausgelöst.
try:
meta_est.fit(X, y, test=my_weights)
except TypeError as e:
print(e)
MetaClassifier.fit got unexpected argument(s) {'test'}, which are not routed to any object.
Und wenn wir ein Metadatum übergeben, das nicht explizit angefordert wurde
try:
meta_est.fit(X, y, sample_weight=my_weights).predict(X, groups=my_groups)
except ValueError as e:
print(e)
Received sample_weight of length = 100 in ExampleClassifier.
[groups] are passed but are not explicitly set as requested or not requested for ExampleClassifier.predict, which is used within MetaClassifier.predict. Call `ExampleClassifier.set_predict_request({metadata}=True/False)` for each metadata you want to request/ignore. See the Metadata Routing User guide <https://scikit-learn.de/stable/metadata_routing.html> for more information.
Auch wenn wir es explizit als nicht angefordert markieren, aber es bereitgestellt wird
meta_est = MetaClassifier(
estimator=ExampleClassifier()
.set_fit_request(sample_weight=True)
.set_predict_request(groups=False)
)
try:
meta_est.fit(X, y, sample_weight=my_weights).predict(X[:3, :], groups=my_groups)
except TypeError as e:
print(e)
Received sample_weight of length = 100 in ExampleClassifier.
MetaClassifier.predict got unexpected argument(s) {'groups'}, which are not routed to any object.
Ein weiteres Konzept, das wir einführen wollen, sind aliasierte Metadaten. Dies geschieht, wenn ein Estimator ein Metadatum mit einem anderen Variablennamen als dem Standardvariablennamen anfordert. Zum Beispiel könnten in einem Szenario, in dem es zwei Estimators in einer Pipeline gibt, einer sample_weight1 und der andere sample_weight2 anfordern. Beachten Sie, dass dies nicht ändert, was der Estimator erwartet, sondern dem Meta-Estimator nur mitteilt, wie die bereitgestellten Metadaten den Anforderungen zugeordnet werden. Hier ist ein Beispiel, bei dem wir aliased_sample_weight an den Meta-Estimator übergeben, aber der Meta-Estimator versteht, dass aliased_sample_weight ein Alias für sample_weight ist und übergibt es als sample_weight an den zugrundeliegenden Estimator.
meta_est = MetaClassifier(
estimator=ExampleClassifier().set_fit_request(sample_weight="aliased_sample_weight")
)
meta_est.fit(X, y, aliased_sample_weight=my_weights)
Received sample_weight of length = 100 in ExampleClassifier.
Die Übergabe von sample_weight wird hier fehlschlagen, da es mit einem Alias angefordert wird und sample_weight unter diesem Namen nicht angefordert wird.
try:
meta_est.fit(X, y, sample_weight=my_weights)
except TypeError as e:
print(e)
MetaClassifier.fit got unexpected argument(s) {'sample_weight'}, which are not routed to any object.
Das führt uns zu get_metadata_routing. Die Art und Weise, wie das Routing in scikit-learn funktioniert, ist, dass Konsumenten anfordern, was sie brauchen, und Router es weiterleiten. Zusätzlich stellt ein Router offen, was er selbst benötigt, damit er innerhalb eines anderen Routers verwendet werden kann, z. B. eine Pipeline innerhalb eines Grid Search-Objekts. Die Ausgabe von get_metadata_routing, die eine Wörterbuchdarstellung eines MetadataRouter ist, enthält den vollständigen Baum der von allen verschachtelten Objekten angeforderten Metadaten und ihre entsprechenden Methoden-Routings, d. h. welche Methode eines Sub-Estimators in welcher Methode eines Meta-Estimators verwendet wird.
print_routing(meta_est)
{'estimator': {'mapping': [{'callee': 'fit', 'caller': 'fit'},
{'callee': 'predict', 'caller': 'predict'},
{'callee': 'score', 'caller': 'score'}],
'router': {'fit': {'sample_weight': 'aliased_sample_weight'},
'predict': {'groups': None},
'score': {'sample_weight': None}}}}
Wie Sie sehen können, ist das einzige angeforderte Metadatum für die Methode fit "sample_weight" mit "aliased_sample_weight" als Alias. Die Klasse ~utils.metadata_routing.MetadataRouter ermöglicht es uns, das Routing-Objekt einfach zu erstellen, das die Ausgabe erzeugt, die wir für unsere get_metadata_routing benötigen.
Um zu verstehen, wie Aliase in Meta-Estimators funktionieren, stellen wir uns unseren Meta-Estimator innerhalb eines anderen vor.
meta_meta_est = MetaClassifier(estimator=meta_est).fit(
X, y, aliased_sample_weight=my_weights
)
Received sample_weight of length = 100 in ExampleClassifier.
Im obigen Beispiel wird die fit-Methode von meta_meta_est ihre Sub-Estimator-Methoden fit wie folgt aufrufen:
# user feeds `my_weights` as `aliased_sample_weight` into `meta_meta_est`:
meta_meta_est.fit(X, y, aliased_sample_weight=my_weights):
...
# the first sub-estimator (`meta_est`) expects `aliased_sample_weight`
self.estimator_.fit(X, y, aliased_sample_weight=aliased_sample_weight):
...
# the second sub-estimator (`est`) expects `sample_weight`
self.estimator_.fit(X, y, sample_weight=aliased_sample_weight):
...
Konsumierender und Routing-Meta-Estimator#
Für ein etwas komplexeres Beispiel betrachten wir einen Meta-Estimator, der Metadaten an einen zugrundeliegenden Estimator weiterleitet, wie zuvor, aber auch einige Metadaten in seinen eigenen Methoden verwendet. Dieser Meta-Estimator ist gleichzeitig Konsument und Router. Die Implementierung eines solchen ist sehr ähnlich zu dem, was wir zuvor hatten, aber mit einigen Anpassungen.
class RouterConsumerClassifier(MetaEstimatorMixin, ClassifierMixin, BaseEstimator):
def __init__(self, estimator):
self.estimator = estimator
def get_metadata_routing(self):
router = (
MetadataRouter(owner=self)
# defining metadata routing request values for usage in the meta-estimator
.add_self_request(self)
# defining metadata routing request values for usage in the sub-estimator
.add(
estimator=self.estimator,
method_mapping=MethodMapping()
.add(caller="fit", callee="fit")
.add(caller="predict", callee="predict")
.add(caller="score", callee="score"),
)
)
return router
# Since `sample_weight` is used and consumed here, it should be defined as
# an explicit argument in the method's signature. All other metadata which
# are only routed, will be passed as `**fit_params`:
def fit(self, X, y, sample_weight, **fit_params):
if self.estimator is None:
raise ValueError("estimator cannot be None!")
check_metadata(self, sample_weight=sample_weight)
# We add `sample_weight` to the `fit_params` dictionary.
if sample_weight is not None:
fit_params["sample_weight"] = sample_weight
request_router = get_routing_for_object(self)
request_router.validate_metadata(params=fit_params, method="fit")
routed_params = request_router.route_params(params=fit_params, caller="fit")
self.estimator_ = clone(self.estimator).fit(X, y, **routed_params.estimator.fit)
self.classes_ = self.estimator_.classes_
return self
def predict(self, X, **predict_params):
check_is_fitted(self)
# As in `fit`, we get a copy of the object's MetadataRouter,
request_router = get_routing_for_object(self)
# we validate the given metadata,
request_router.validate_metadata(params=predict_params, method="predict")
# and then prepare the input to the underlying ``predict`` method.
routed_params = request_router.route_params(
params=predict_params, caller="predict"
)
return self.estimator_.predict(X, **routed_params.estimator.predict)
Die Schlüsselstellen, an denen sich der obige Meta-Estimator von unserem früheren Meta-Estimator unterscheidet, sind die explizite Annahme von sample_weight in fit und seine Aufnahme in fit_params. Da sample_weight ein expliziter Parameter ist, können wir sicher sein, dass set_fit_request(sample_weight=...) für diese Methode vorhanden ist. Der Meta-Estimator ist sowohl ein Konsument als auch ein Router von sample_weight.
In get_metadata_routing fügen wir self zum Routing hinzu, indem wir add_self_request verwenden, um anzuzeigen, dass dieser Estimator sample_weight konsumiert und auch ein Router ist; dies fügt auch einen $self_request-Schlüssel zu den Routing-Informationen hinzu, wie unten dargestellt. Betrachten wir nun einige Beispiele:
Keine Metadaten angefordert
meta_est = RouterConsumerClassifier(estimator=ExampleClassifier())
print_routing(meta_est)
{'$self_request': {'fit': {'sample_weight': None},
'score': {'sample_weight': None}},
'estimator': {'mapping': [{'callee': 'fit', 'caller': 'fit'},
{'callee': 'predict', 'caller': 'predict'},
{'callee': 'score', 'caller': 'score'}],
'router': {'fit': {'sample_weight': None},
'predict': {'groups': None},
'score': {'sample_weight': None}}}}
sample_weightangefordert von Sub-Estimator
meta_est = RouterConsumerClassifier(
estimator=ExampleClassifier().set_fit_request(sample_weight=True)
)
print_routing(meta_est)
{'$self_request': {'fit': {'sample_weight': None},
'score': {'sample_weight': None}},
'estimator': {'mapping': [{'callee': 'fit', 'caller': 'fit'},
{'callee': 'predict', 'caller': 'predict'},
{'callee': 'score', 'caller': 'score'}],
'router': {'fit': {'sample_weight': True},
'predict': {'groups': None},
'score': {'sample_weight': None}}}}
sample_weightangefordert von Meta-Estimator
meta_est = RouterConsumerClassifier(estimator=ExampleClassifier()).set_fit_request(
sample_weight=True
)
print_routing(meta_est)
{'$self_request': {'fit': {'sample_weight': True},
'score': {'sample_weight': None}},
'estimator': {'mapping': [{'callee': 'fit', 'caller': 'fit'},
{'callee': 'predict', 'caller': 'predict'},
{'callee': 'score', 'caller': 'score'}],
'router': {'fit': {'sample_weight': None},
'predict': {'groups': None},
'score': {'sample_weight': None}}}}
Beachten Sie den Unterschied in den oben dargestellten angeforderten Metadaten.
Wir können die Metadaten auch aliasieren, um unterschiedliche Werte für die fit-Methoden des Meta- und des Sub-Estimators zu übergeben.
meta_est = RouterConsumerClassifier(
estimator=ExampleClassifier().set_fit_request(sample_weight="clf_sample_weight"),
).set_fit_request(sample_weight="meta_clf_sample_weight")
print_routing(meta_est)
{'$self_request': {'fit': {'sample_weight': 'meta_clf_sample_weight'},
'score': {'sample_weight': None}},
'estimator': {'mapping': [{'callee': 'fit', 'caller': 'fit'},
{'callee': 'predict', 'caller': 'predict'},
{'callee': 'score', 'caller': 'score'}],
'router': {'fit': {'sample_weight': 'clf_sample_weight'},
'predict': {'groups': None},
'score': {'sample_weight': None}}}}
Allerdings benötigt die fit-Methode des Meta-Estimators nur den Alias für den Sub-Estimator und adressiert ihr eigenes sample_weight als sample_weight, da sie ihre eigenen erforderlichen Metadaten nicht validiert und weiterleitet.
meta_est.fit(X, y, sample_weight=my_weights, clf_sample_weight=my_other_weights)
Received sample_weight of length = 100 in RouterConsumerClassifier.
Received sample_weight of length = 100 in ExampleClassifier.
Alias nur auf dem Sub-Estimator
Dies ist nützlich, wenn wir nicht möchten, dass der Meta-Estimator die Metadaten verwendet, aber der Sub-Estimator soll es tun.
meta_est = RouterConsumerClassifier(
estimator=ExampleClassifier().set_fit_request(sample_weight="aliased_sample_weight")
)
print_routing(meta_est)
{'$self_request': {'fit': {'sample_weight': None},
'score': {'sample_weight': None}},
'estimator': {'mapping': [{'callee': 'fit', 'caller': 'fit'},
{'callee': 'predict', 'caller': 'predict'},
{'callee': 'score', 'caller': 'score'}],
'router': {'fit': {'sample_weight': 'aliased_sample_weight'},
'predict': {'groups': None},
'score': {'sample_weight': None}}}}
Der Meta-Estimator kann aliased_sample_weight nicht verwenden, da er erwartet, dass es als sample_weight übergeben wird. Dies würde auch dann gelten, wenn set_fit_request(sample_weight=True) darauf gesetzt wäre.
Einfache Pipeline#
Ein etwas komplizierterer Anwendungsfall ist ein Meta-Estimator, der einer Pipeline ähnelt. Hier ist ein Meta-Estimator, der einen Transformer und einen Classifier akzeptiert. Beim Aufruf seiner fit-Methode wendet er die fit- und transform-Methoden des Transformers an, bevor er den Classifier auf den transformierten Daten ausführt. Bei predict wendet er die transform-Methode des Transformers an, bevor er mit der predict-Methode des Classifiers auf den transformierten neuen Daten vorhersagt.
class SimplePipeline(ClassifierMixin, BaseEstimator):
def __init__(self, transformer, classifier):
self.transformer = transformer
self.classifier = classifier
def get_metadata_routing(self):
router = (
MetadataRouter(owner=self)
# We add the routing for the transformer.
.add(
transformer=self.transformer,
method_mapping=MethodMapping()
# The metadata is routed such that it retraces how
# `SimplePipeline` internally calls the transformer's `fit` and
# `transform` methods in its own methods (`fit` and `predict`).
.add(caller="fit", callee="fit")
.add(caller="fit", callee="transform")
.add(caller="predict", callee="transform"),
)
# We add the routing for the classifier.
.add(
classifier=self.classifier,
method_mapping=MethodMapping()
.add(caller="fit", callee="fit")
.add(caller="predict", callee="predict"),
)
)
return router
def fit(self, X, y, **fit_params):
routed_params = process_routing(self, "fit", **fit_params)
self.transformer_ = clone(self.transformer).fit(
X, y, **routed_params.transformer.fit
)
X_transformed = self.transformer_.transform(
X, **routed_params.transformer.transform
)
self.classifier_ = clone(self.classifier).fit(
X_transformed, y, **routed_params.classifier.fit
)
return self
def predict(self, X, **predict_params):
routed_params = process_routing(self, "predict", **predict_params)
X_transformed = self.transformer_.transform(
X, **routed_params.transformer.transform
)
return self.classifier_.predict(
X_transformed, **routed_params.classifier.predict
)
Beachten Sie die Verwendung von MethodMapping, um zu deklarieren, welche Methoden des Kind-Estimators (Aufgerufener) in welchen Methoden des Meta-Estimators (Aufrufer) verwendet werden. Wie Sie sehen können, verwendet SimplePipeline die transform- und fit-Methoden des Transformers in fit und seine transform-Methode in predict, und das sehen Sie auch in der Routing-Struktur der Pipeline-Klasse implementiert.
Ein weiterer Unterschied im obigen Beispiel zu den vorherigen ist die Verwendung von process_routing, die die Eingabeparameter verarbeitet, die erforderliche Validierung durchführt und die routed_params zurückgibt, die wir in früheren Beispielen erstellt haben. Dies reduziert den Boilerplate-Code, den ein Entwickler in jeder Methode eines Meta-Estimators schreiben muss. Entwicklern wird dringend empfohlen, diese Funktion zu verwenden, es sei denn, es gibt einen guten Grund dagegen.
Um die obige Pipeline zu testen, fügen wir einen Beispiel-Transformer hinzu.
class ExampleTransformer(TransformerMixin, BaseEstimator):
def fit(self, X, y, sample_weight=None):
check_metadata(self, sample_weight=sample_weight)
return self
def transform(self, X, groups=None):
check_metadata(self, groups=groups)
return X
def fit_transform(self, X, y, sample_weight=None, groups=None):
return self.fit(X, y, sample_weight).transform(X, groups)
Beachten Sie, dass wir im obigen Beispiel fit_transform implementiert haben, das fit und transform mit den entsprechenden Metadaten aufruft. Dies ist nur erforderlich, wenn transform Metadaten akzeptiert, da die Standardimplementierung von fit_transform in TransformerMixin keine Metadaten an transform übergibt.
Nun können wir unsere Pipeline testen und sehen, ob Metadaten korrekt weitergegeben werden. Dieses Beispiel verwendet unsere SimplePipeline, unseren ExampleTransformer und unseren RouterConsumerClassifier, der unseren ExampleClassifier verwendet.
pipe = SimplePipeline(
transformer=ExampleTransformer()
# we set transformer's fit to receive sample_weight
.set_fit_request(sample_weight=True)
# we set transformer's transform to receive groups
.set_transform_request(groups=True),
classifier=RouterConsumerClassifier(
estimator=ExampleClassifier()
# we want this sub-estimator to receive sample_weight in fit
.set_fit_request(sample_weight=True)
# but not groups in predict
.set_predict_request(groups=False),
)
# and we want the meta-estimator to receive sample_weight as well
.set_fit_request(sample_weight=True),
)
pipe.fit(X, y, sample_weight=my_weights, groups=my_groups).predict(
X[:3], groups=my_groups
)
Received sample_weight of length = 100 in ExampleTransformer.
Received groups of length = 100 in ExampleTransformer.
Received sample_weight of length = 100 in RouterConsumerClassifier.
Received sample_weight of length = 100 in ExampleClassifier.
Received groups of length = 100 in ExampleTransformer.
groups is None in ExampleClassifier.
array([1., 1., 1.])
Veraltung / Änderung des Standardwerts#
In diesem Abschnitt zeigen wir, wie der Fall behandelt werden sollte, wenn ein Router gleichzeitig auch ein Konsument wird, insbesondere wenn er dieselben Metadaten konsumiert wie sein Sub-Estimator, oder wenn ein Konsument Metadaten zu konsumieren beginnt, die er in einer älteren Version nicht konsumierte. In diesem Fall sollte eine Weile eine Warnung ausgegeben werden, um die Benutzer darüber zu informieren, dass sich das Verhalten von früheren Versionen geändert hat.
class MetaRegressor(MetaEstimatorMixin, RegressorMixin, BaseEstimator):
def __init__(self, estimator):
self.estimator = estimator
def fit(self, X, y, **fit_params):
routed_params = process_routing(self, "fit", **fit_params)
self.estimator_ = clone(self.estimator).fit(X, y, **routed_params.estimator.fit)
def get_metadata_routing(self):
router = MetadataRouter(owner=self).add(
estimator=self.estimator,
method_mapping=MethodMapping().add(caller="fit", callee="fit"),
)
return router
Wie oben erklärt, ist dies eine gültige Verwendung, wenn my_weights nicht als sample_weight an MetaRegressor übergeben werden sollen.
reg = MetaRegressor(estimator=LinearRegression().set_fit_request(sample_weight=True))
reg.fit(X, y, sample_weight=my_weights)
Stellen Sie sich nun vor, wir entwickeln MetaRegressor weiter und er *konsumiert* nun auch sample_weight.
class WeightedMetaRegressor(MetaEstimatorMixin, RegressorMixin, BaseEstimator):
# show warning to remind user to explicitly set the value with
# `.set_{method}_request(sample_weight={boolean})`
__metadata_request__fit = {"sample_weight": metadata_routing.WARN}
def __init__(self, estimator):
self.estimator = estimator
def fit(self, X, y, sample_weight=None, **fit_params):
routed_params = process_routing(
self, "fit", sample_weight=sample_weight, **fit_params
)
check_metadata(self, sample_weight=sample_weight)
self.estimator_ = clone(self.estimator).fit(X, y, **routed_params.estimator.fit)
def get_metadata_routing(self):
router = (
MetadataRouter(owner=self)
.add_self_request(self)
.add(
estimator=self.estimator,
method_mapping=MethodMapping().add(caller="fit", callee="fit"),
)
)
return router
Die obige Implementierung ist fast identisch mit MetaRegressor, und aufgrund des in __metadata_request__fit definierten Standard-Anforderungswerts wird beim Fitten eine Warnung ausgegeben.
with warnings.catch_warnings(record=True) as record:
WeightedMetaRegressor(
estimator=LinearRegression().set_fit_request(sample_weight=False)
).fit(X, y, sample_weight=my_weights)
for w in record:
print(w.message)
Received sample_weight of length = 100 in WeightedMetaRegressor.
Support for sample_weight has recently been added to WeightedMetaRegressor(estimator=LinearRegression()) class. To maintain backward compatibility, it is ignored now. Using `set_fit_request(sample_weight={True, False})` on this method of the class, you can set the request value to False to silence this warning, or to True to consume and use the metadata.
Wenn ein Estimator Metadaten konsumiert, die er zuvor nicht konsumierte, kann das folgende Muster verwendet werden, um die Benutzer darüber zu warnen.
class ExampleRegressor(RegressorMixin, BaseEstimator):
__metadata_request__fit = {"sample_weight": metadata_routing.WARN}
def fit(self, X, y, sample_weight=None):
check_metadata(self, sample_weight=sample_weight)
return self
def predict(self, X):
return np.zeros(shape=(len(X)))
with warnings.catch_warnings(record=True) as record:
MetaRegressor(estimator=ExampleRegressor()).fit(X, y, sample_weight=my_weights)
for w in record:
print(w.message)
sample_weight is None in ExampleRegressor.
Support for sample_weight has recently been added to ExampleRegressor() class. To maintain backward compatibility, it is ignored now. Using `set_fit_request(sample_weight={True, False})` on this method of the class, you can set the request value to False to silence this warning, or to True to consume and use the metadata.
Am Ende deaktivieren wir das Konfigurationsflag für Metadaten-Routing.
set_config(enable_metadata_routing=False)
Entwicklung von Drittanbietern und scikit-learn-Abhängigkeit#
Wie oben gesehen, werden Informationen zwischen Klassen mithilfe von MetadataRequest und MetadataRouter kommuniziert. Es wird dringend davon abgeraten, aber es ist möglich, die Werkzeuge im Zusammenhang mit Metadaten-Routing mitzuliefern, wenn Sie ausschließlich einen scikit-learn-kompatiblen Estimator haben möchten, ohne von scikit-learn abhängig zu sein. Wenn alle folgenden Bedingungen erfüllt sind, müssen Sie Ihren Code überhaupt nicht ändern:
Ihr Estimator erbt von
BaseEstimator.Die von den Methoden Ihres Estimators, z. B.
fit, konsumierten Parameter sind in der Methodensignatur explizit definiert, im Gegensatz zu*argsoder*kwargs.Ihr Estimator leitet keine Metadaten an die zugrundeliegenden Objekte weiter, d. h. er ist kein Router.
Gesamtlaufzeit des Skripts: (0 Minuten 0,049 Sekunden)
Verwandte Beispiele
Anpassen eines Elastic Net mit einer voreingestellten Gram-Matrix und gewichteten Stichproben