2025, Nov 23 21:01
Tags вместо dict в кастомном оценщике scikit-learn: как избежать AttributeError
Как исправить AttributeError из-за dict в __sklearn_tags__: переход на объект Tags, корректные requires_fit и input_tags.allow_nan в оценщике scikit-learn.
Создать собственный оценщик (estimator) в scikit-learn обычно несложно — пока не сталкиваешься с тонкими изменениями API. Частая ловушка в последних версиях scikit-learn — определять теги оценщика обычным словарём Python. В средах, где ожидается новый объект Tags, это приводит к AttributeError о том, что у dict нет атрибутов вроде requires_fit. Ниже — практическое объяснение проблемы и её решения.
Как воспроизвести проблему
Ниже — минимальный пример оценщика: на вид всё корректно, он даже создаётся без ошибок, но падает, когда scikit-learn запрашивает его теги:
from sklearn.utils.validation import check_is_fitted, check_X_y
import numpy as np
from sklearn.base import BaseEstimator, RegressorMixin
class PenalizedRegressor(BaseEstimator, RegressorMixin):
"""
Minimal penalized regression-style estimator.
"""
def __init__(self, tau: float = 0.5):
self.tau = tau
def _calc_params(self, X, y):
self.intercept_ = 0
self.coef_ = self.tau * np.ones(X.shape[0])
def fit(self, X: np.ndarray, y: np.ndarray):
self.feature_names_in_ = None
if hasattr(X, "columns") and callable(getattr(X, "columns", None)):
self.feature_names_in_ = np.asarray(X.columns, dtype=object)
X, y = check_X_y(X, y, accept_sparse=False, y_numeric=True, ensure_min_samples=2)
self.n_features_in_ = X.shape[1]
self._calc_params(X, y)
self.is_fitted_ = True
return self
def predict(self, X: np.ndarray) -> np.ndarray:
check_is_fitted(self, ["coef_", "intercept_", "is_fitted_"])
preds = np.dot(X, self.coef_) + self.intercept
return preds
def __sklearn_tags__(self):
tags = {
"allow_nan": False,
"requires_y": True,
"requires_fit": True,
}
return tags
# Пример использования
from sklearn.datasets import make_regression
X, y, true_coef = make_regression(n_samples=200, n_features=200, n_informative=25, bias=10, noise=5, random_state=42, coef=True)
reg = PenalizedRegressor()
reg.fit(X, y)
При запуске возникает: AttributeError: 'dict' object has no attribute 'requires_fit'. Кажется, что оценщик «показывает» requires_fit, но scikit-learn пытается обратиться к нему как к атрибуту объекта Tags, а не как к ключу словаря.
Почему так происходит
Во внутренних механизмах последних версий scikit-learn теги больше не ожидаются в виде простого словаря. Теперь библиотека использует специализированный класс Tags со структурированными полями, например input_tags, target_tags, а также верхнеуровневыми флагами вроде requires_fit. Если оценщик возвращает dict, код, который обращается к атрибутам (например, tags.requires_fit), падает, потому что у словаря такого атрибута нет. Именно так и проявляется описанный AttributeError.
В scikit-learn 1.6.1 есть совместимостная логика, способная преобразовать теги старого формата в новый (функция _to_new_tags в sklearn/utils/_tags.py), но полагаться на неё не стоит, тем более что в 1.7.0 её удалили. Надёжнее сразу перейти на объект Tags.
Как исправить
Верните корректный экземпляр Tags, делегировав создание super().__sklearn_tags__(), а затем установите нужные поля. Обратите внимание: флаг required заменяет старый requires_y и находится внутри target_tags. Разрешение NaN для входов — в input_tags.allow_nan. И, наконец, requires_fit остаётся верхнеуровневым атрибутом.
from sklearn.utils.validation import check_is_fitted, check_X_y
import numpy as np
from sklearn.base import BaseEstimator, RegressorMixin
class PenalizedRegressor(BaseEstimator, RegressorMixin):
def __init__(self, tau: float = 0.5):
self.tau = tau
def _calc_params(self, X, y):
self.intercept_ = 0
self.coef_ = self.tau * np.ones(X.shape[0])
def fit(self, X: np.ndarray, y: np.ndarray):
self.feature_names_in_ = None
if hasattr(X, "columns") and callable(getattr(X, "columns", None)):
self.feature_names_in_ = np.asarray(X.columns, dtype=object)
X, y = check_X_y(X, y, accept_sparse=False, y_numeric=True, ensure_min_samples=2)
self.n_features_in_ = X.shape[1]
self._calc_params(X, y)
self.is_fitted_ = True
return self
def predict(self, X: np.ndarray) -> np.ndarray:
check_is_fitted(self, ["coef_", "intercept_", "is_fitted_"])
preds = np.dot(X, self.coef_) + self.intercept
return preds
def __sklearn_tags__(self):
tags = super().__sklearn_tags__()
tags.input_tags.allow_nan = False
tags.target_tags.required = True # заменяет `requires_y`
tags.requires_fit = True
return tags
# Пример использования
from sklearn.datasets import make_regression
X, y, true_coef = make_regression(n_samples=200, n_features=200, n_informative=25, bias=10, noise=5, random_state=42, coef=True)
reg = PenalizedRegressor()
reg.fit(X, y)
Такой подход согласует оценщик с актуальной моделью тегов. В средах, где ранее вызов model._get_tags() приводил к ошибке, формирование тегов через super().__sklearn_tags__() устраняет несоответствие.
Почему это важно
Теги управляют ключевыми аспектами поведения во всей экосистеме scikit-learn: проверкой входных данных, требованием наличия y, обработкой NaN и необходимостью обучения оценщика перед использованием. Возвращать dict было допустимо в старых выпусках, но сейчас это конфликтует с атрибутно-ориентированным API. Переходные помощники существуют лишь в отдельных версиях и затем удаляются, поэтому опираться на них рискованно. Реализовав интерфейс Tags напрямую, вы обеспечите корректную интеграцию оценщика в инструменты и версии, ожидающие новую структуру.
Что запомнить
Если видите AttributeError, связанный с тегами в пользовательском оценщике, корень проблемы обычно в реализации тегов через словарь. Делегируйте создание тегов super().__sklearn_tags__(), обновите нужные структурированные поля — input_tags.allow_nan, target_tags.required и requires_fit — и верните объект Tags. Эта небольшая правка сохраняет совместимость вашего оценщика с текущими механизмами проверки и инспекции scikit-learn и предотвращает сбои из‑за устаревших форматов тегов.