Примечание
Перейти в конец, чтобы скачать полный код примера или запустить этот пример в браузере через JupyterLite или Binder.
Разработка оценщиков, совместимых с маршрутизацией метаданных
В этом документе показано, как использовать механизм маршрутизации метаданных в scikit-learn для создания оценщиков, которые передают метаданные другим оценщикам, функциям оценки и разделителям CV, способным использовать метаданные.
Чтобы лучше понять дальнейшее изложение, нужно ввести два понятия: маршрутизаторы и потребители. Маршрутизатор — это объект, который передаёт заданные данные и метаданные другим объектам. В большинстве случаев маршрутизатор — это метаоценщик, то есть оценщик, принимающий другой оценщик в качестве параметра. Функция, такая как sklearn.model_selection.cross_validate, которая принимает оценщик в качестве параметра и передаёт данные и метаданные, также является маршрутизатором.
Потребитель, с другой стороны, — это объект, который принимает и использует заданные метаданные. Например, оценщик, учитывающий sample_weight в методе fit, является потребителем sample_weight.
Объект может быть одновременно и маршрутизатором, и потребителем. Например, метаоценщик может учитывать sample_weight в некоторых вычислениях, но также может передавать их базовому оценщику.
Сначала импортируем несколько модулей и подготовим случайные данные для оставшейся части скрипта.
# 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,
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)
Маршрутизация метаданных доступна, только если она включена явно:
set_config(enable_metadata_routing=True)
Эта вспомогательная функция служит для проверки передачи метаданных:
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__}.")
Вспомогательная функция для удобного вывода информации о маршрутизации объекта:
def print_routing(obj):
pprint(obj.get_metadata_routing()._serialize())
Оценщик-потребитель
Здесь мы покажем, как оценщик может предоставить необходимый API для поддержки маршрутизации метаданных в качестве потребителя. Представим простой классификатор, принимающий sample_weight в качестве метаданных в fit и groups в методе predict:
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))
Теперь у приведённого выше оценщика есть всё необходимое для использования метаданных. Это обеспечивается некоторой магией, реализованной в BaseEstimator. Теперь приведённый выше класс предоставляет три метода: set_fit_request, set_predict_request и get_metadata_routing. Также имеется set_score_request для sample_weight, поскольку ClassifierMixin реализует метод score, принимающий sample_weight. То же самое относится к регрессорам, которые наследуются от RegressorMixin.
По умолчанию метаданные не запрашиваются, что можно увидеть здесь:
print_routing(ExampleClassifier())
{'fit': {'sample_weight': None},
'predict': {'groups': None},
'score': {'sample_weight': None}}
Приведённый выше вывод означает, что sample_weight и groups не запрашиваются объектом ExampleClassifier, и если маршрутизатору передать эти метаданные, он должен выдать ошибку, поскольку пользователь явно не указал, требуются они или нет. То же самое верно для sample_weight в методе score, унаследованном от ClassifierMixin. Чтобы явно задать значения запросов для этих метаданных, можно воспользоваться следующими методами:
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}}
Примечание
Обратите внимание: пока приведённый выше оценщик не используется в метаоценщике, пользователю не нужно задавать запросы для метаданных, а установленные значения игнорируются, поскольку потребитель не проверяет и не маршрутизирует переданные метаданные. Простой пример использования приведённого выше оценщика будет работать как ожидается.
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.])
Метаоценщик-маршрутизатор
Теперь покажем, как спроектировать метаоценщик в качестве маршрутизатора. Для упрощения рассмотрим метаоценщик, который не делает ничего особенного, кроме маршрутизации метаданных.
Чтобы сделать метаоценщик маршрутизатором, нужно лишь:
- определить метод
get_metadata_routing, возвращающий экземплярMetadataRouter, который отвечает за настройку маршрутизации метаданных. - использовать
process_routingвнутри его методов (fit,predict, …), чтобы правильно передавать метаданные от метаоценщика подчинённому оценщику.
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.
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 information on all the metadata that should be routed from here to
# consuming methods.
routed_params = process_routing(self, "fit", **fit_params)
# A sub-estimator is fitted and its classes are attributed to the
# meta-estimator. Since we call the sub-estimator's fit method, we pass the
# the metadata stored in `routed_params.estimator.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 information on all the metadata that should be routed and
# pass the metadata that is stored in `routed_params.estimator.predict` to the
# sub-estimator's predict method.
routed_params = process_routing(self, "predict", **predict_params)
return self.estimator_.predict(X, **routed_params.estimator.predict)
Разберём разные части приведённого выше кода.
В каждом методе мы используем функцию process_routing для создания Bunch вида {"object_name": {"method_name": {"metadata":
value}}}, который передаётся методу базового оценщика. object_name (estimator в routed_params.estimator.fit) — это тот же estimator, который был добавлен в get_metadata_routing. process_routing также проверяет входные метаданные: он гарантирует, что все переданные метаданные запрошены, чтобы избежать незаметных ошибок.
Далее рассмотрим различные варианты поведения и, в частности, типы возникающих ошибок.
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.
Обратите внимание, что в приведённом выше примере наша вспомогательная функция check_metadata() вызывается через ExampleClassifier. Она проверяет, что sample_weight передано ей правильно. Если это не так, как в следующем примере, она выведет, что sample_weight — None:
meta_est.fit(X, y)
sample_weight is None in ExampleClassifier.
Если передать неизвестные метаданные, возникнет ошибка:
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.
Если передать метаданные, которые явно не запрошены:
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.org/stable/metadata_routing.html> for more information.
Также ошибка возникнет, если явно указать, что метаданные не запрашиваются, но передать их:
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.
Ещё одно понятие, которое нужно ввести, — это метаданные с псевдонимами. Это означает, что оценщик запрашивает метаданные под именем переменной, отличным от имени переменной по умолчанию. Например, в ситуации, когда в конвейере есть два оценщика, один может запрашивать sample_weight1, а другой — sample_weight2. Обратите внимание, что это не меняет того, чего ожидает оценщик: оно лишь указывает метаоценщику, как сопоставить переданные метаданные с необходимыми. Вот пример, в котором мы передаём aliased_sample_weight метаоценщику, но метаоценщик понимает, что aliased_sample_weight — это псевдоним для sample_weight, и передаёт их как sample_weight базовому оценщику:
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.
Передача сюда sample_weight приведёт к ошибке, поскольку они запрошены с псевдонимом, а sample_weight с таким именем не запрошены:
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.
Это подводит нас к get_metadata_routing. В scikit-learn маршрутизация работает так: потребители запрашивают необходимые им данные, а маршрутизаторы передают запрос дальше. Кроме того, маршрутизатор сообщает, что требуется ему самому, чтобы его можно было использовать внутри другого маршрутизатора, например конвейер внутри объекта поиска по сетке. Вывод get_metadata_routing, представляющий собой словарь MetadataRouter, содержит полное дерево запрошенных метаданных всех вложенных объектов и соответствующих маршрутов методов, то есть указывает, какой метод подчинённого оценщика используется в каждом методе метаоценщика:
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}}}}
Как видно, для метода fit запрошены только метаданные "sample_weight" с псевдонимом "aliased_sample_weight". Класс ~utils.metadata_routing.MetadataRouter позволяет легко создать объект маршрутизации, который сформирует нужный нам вывод для get_metadata_routing.
Чтобы понять, как работают псевдонимы в метаоценщиках, представим наш метаоценщик внутри другого метаоценщика:
meta_meta_est = MetaClassifier(estimator=meta_est).fit(
X, y, aliased_sample_weight=my_weights
)
Received sample_weight of length = 100 in ExampleClassifier.
В приведённом выше примере метод fit объекта meta_meta_est будет вызывать методы fit своего подчинённого оценщика следующим образом:
# 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):
...
Метаоценщик-потребитель и маршрутизатор
Рассмотрим чуть более сложный пример: метаоценщик, который, как и прежде, передаёт метаданные базовому оценщику, но также использует некоторые метаданные в собственных методах. Такой метаоценщик одновременно является потребителем и маршрутизатором. Его реализация очень похожа на предыдущую, но с несколькими небольшими изменениями.
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
routed_params = process_routing(self, "fit", **fit_params)
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)
routed_params = process_routing(self, "predict", **predict_params)
return self.estimator_.predict(X, **routed_params.estimator.predict)
Главное отличие приведённого выше метаоценщика от предыдущего состоит в том, что он явно принимает sample_weight в fit и включает его в fit_params. Поскольку sample_weight является явным аргументом, можно быть уверенным, что set_fit_request(sample_weight=...) присутствует для этого метода. Метаоценщик одновременно является потребителем и маршрутизатором sample_weight.
В get_metadata_routing мы добавляем self к маршрутизации с помощью add_self_request, указывая, что этот оценщик использует sample_weight и одновременно является маршрутизатором; это также добавляет ключ $self_request в сведения о маршрутизации, как показано ниже. Рассмотрим несколько примеров:
- Метаданные не запрошены
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_weightзапрошены подчинённым оценщиком
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_weightзапрошены метаоценщиком
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}}}}
Обратите внимание на различие между представленными выше вариантами запрошенных метаданных.
- Мы также можем назначить метаданным псевдонимы, чтобы передавать разные значения методам fit метаоценщика и подчинённого оценщика:
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}}}}
Однако fit метаоценщика нужен только псевдоним для подчинённого оценщика, а собственный вес выборки он обозначает как sample_weight, поскольку не проверяет и не маршрутизирует собственные необходимые метаданные:
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.
- Псевдоним только для подчинённого оценщика:
Это полезно, когда мы не хотим, чтобы метаоценщик использовал метаданные, но подчинённый оценщик должен их использовать.
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}}}}
Метаоценщик не может использовать aliased_sample_weight, поскольку ожидает, что они будут переданы ему как sample_weight. Это будет справедливо, даже если для него задано set_fit_request(sample_weight=True).
Простой конвейер
Чуть более сложный вариант использования — метаоценщик, похожий на Pipeline. Ниже приведён метаоценщик, принимающий преобразователь и классификатор. При вызове его метода fit он применяет fit и transform преобразователя, прежде чем запустить классификатор на преобразованных данных. При вызове predict он применяет transform преобразователя, прежде чем выполнить предсказание методом predict классификатора на новых преобразованных данных.
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
)
Обратите внимание на использование MethodMapping для указания того, какие методы дочернего оценщика (вызываемого объекта) используются в методах метаоценщика (вызывающего объекта). Как видно, SimplePipeline использует методы transform и fit преобразователя в fit, а его метод transform — в predict; именно это и реализовано в структуре маршрутизации класса конвейера.
Ещё одно отличие приведённого выше примера от предыдущих — использование process_routing, которая обрабатывает входные параметры, выполняет необходимую проверку и возвращает routed_params, созданный в предыдущих примерах. Это уменьшает объём шаблонного кода, который разработчику нужно писать в каждом методе метаоценщика. Разработчикам настоятельно рекомендуется использовать эту функцию, если нет веской причины поступить иначе.
Чтобы проверить приведённый выше конвейер, добавим пример преобразователя.
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)
Обратите внимание, что в приведённом выше примере мы реализовали fit_transform, который вызывает fit и transform с соответствующими метаданными. Это необходимо только в том случае, если transform принимает метаданные, поскольку реализация fit_transform по умолчанию в TransformerMixin не передаёт метаданные в transform.
Теперь можно проверить наш конвейер и убедиться, что метаданные передаются правильно. В этом примере используются наши SimplePipeline, ExampleTransformer и RouterConsumerClassifier, который использует наш ExampleClassifier.
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.])
Устаревание / изменение значения по умолчанию
В этом разделе показано, как следует обрабатывать ситуацию, когда маршрутизатор также становится потребителем, особенно если он использует те же метаданные, что и его подчинённый оценщик, или когда потребитель начинает использовать метаданные, которые он не использовал в предыдущем выпуске. В этом случае в течение некоторого времени следует выводить предупреждение, чтобы сообщить пользователям об изменении поведения по сравнению с предыдущими версиями.
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
Как объяснялось выше, это допустимый вариант использования, если my_weights не должны передаваться как sample_weight в MetaRegressor:
reg = MetaRegressor(estimator=LinearRegression().set_fit_request(sample_weight=True)) reg.fit(X, y, sample_weight=my_weights)
Теперь представим, что мы продолжили разработку MetaRegressor и теперь он также использует 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
Приведённая выше реализация почти не отличается от MetaRegressor, и из-за значения запроса по умолчанию, заданного в __metadata_request__fit, при обучении выводится предупреждение.
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.
Когда оценщик начинает использовать метаданные, которые ранее не использовал, для предупреждения пользователей можно применить следующий подход.
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.
В конце отключим флаг конфигурации маршрутизации метаданных:
set_config(enable_metadata_routing=False)
Разработка сторонними разработчиками и зависимость от scikit-learn
Как было показано выше, информация передаётся между классами с помощью MetadataRequest и MetadataRouter. Мы настоятельно не рекомендуем, но при желании можно включить в свой код инструменты, связанные с маршрутизацией метаданных, чтобы получить оцениватель, совместимый со scikit-learn, но не зависящий от пакета scikit-learn. Если выполнены все следующие условия, вам НЕ нужно вносить изменения в код:
- ваш оцениватель наследуется от
BaseEstimator - параметры, используемые методами вашего оценивателя, например
fit, явно указаны в сигнатуре метода, а не представлены как*argsили*kwargs. - ваш оцениватель не передаёт метаданные базовым объектам, то есть не является маршрутизатором.
Общее время выполнения скрипта: (0 минут 0.040 секунд)
Связанные примеры
© 2007–2026 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.9/auto_examples/miscellaneous/plot_metadata_routing.html