2025, Sep 30 03:16

Как сравнивать преобразования признаков и цели в регрессии с Pipeline и GridSearchCV

Пошагово сравниваем преобразования признаков и целевой переменной в регрессии с помощью scikit-learn: Pipeline, GridSearchCV, TransformedTargetRegressor.

Оценка разных вариантов преобразований признаков и целевой переменной в одной и той же регрессионной постановке — частая задача. Самый простой путь — накидать несколько конвейеров, обучить их по очереди и сравнить метрики. Это работает, но как только пространство вариантов расширяется, хочется чего‑то в духе GridSearchCV: одного унифицированного механизма, который перебирает допустимые комбинации и выбирает лучший вариант.

Базовый подход: ручной цикл по заранее заданным конвейерам

Шаблон ниже создаёт несколько конвейеров, обучает каждый на одинаковых обучающих данных и записывает MSE и R2, чтобы понять, какая версия показывает лучший результат.

import numpy as np
import pandas as pd
from sklearn.preprocessing import FunctionTransformer, PowerTransformer
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from sklearn.metrics import mean_squared_error
from sklearn.compose import TransformedTargetRegressor

model_space = {
    'linear': LinearRegression(),
    'power': make_pipeline(PowerTransformer(), LinearRegression()),
    'log': make_pipeline(FunctionTransformer(np.log, np.exp), LinearRegression()),
    'log_sqrt': TransformedTargetRegressor(
        regressor=make_pipeline(FunctionTransformer(np.log, np.exp), LinearRegression()),
        func=np.sqrt,
        inverse_func=np.square
    )
}

scores_df = pd.DataFrame()
for label, algo in model_space.items():
    algo.fit(X_train, y_train)
    y_pred_eval = algo.predict(X_eval)
    y_pred_train = algo.predict(X_train)
    r2_val = algo.score(X_train, y_train)
    scores_df.at[label, 'MSE'] = mean_squared_error(y_train, y_pred_train)
    scores_df.at[label, 'R2'] = r2_val

best_key = scores_df['R2'].idxmax()

Подход простой и прозрачный: вы точно знаете, какие конвейеры пробовали, можете обратиться к любому обученному объекту по его ключу и сохранить любые нужные диагностики.

Ограничения подхода

Стоит захотеть более систематический перебор преобразований — и ручной цикл начинает мешать. Вы можете захотеть исключить конкретные сочетания, применять преобразование цели только с некоторыми преобразованиями признаков и при этом сохранить единые настройки оценки. Ровно под такой сценарий и создан GridSearchCV в scikit-learn: он позволяет перечислить допустимые варианты и выбрать из этого пространства лучший оцениватель.

Переход к GridSearchCV с несколькими сетками параметров

Удобнее всего организовать поиск так: задать обобщённый Pipeline из двух шагов и передать список сеток параметров. Несколько сеток позволяют легко исключать «лишние» или некорректные сочетания — достаточно их не указывать. Такой подход также без проблем вмещает TransformedTargetRegressor, когда требуется преобразовать целевую переменную.

from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import FunctionTransformer, PowerTransformer
from sklearn.linear_model import LinearRegression
from sklearn.compose import TransformedTargetRegressor
import numpy as np

base_flow = Pipeline([
    ('transformer', None),
    ('estimator', LinearRegression())
])

param_maps = [
    {'transformer': [FunctionTransformer(np.log, np.exp), PowerTransformer()]},
    {
        'transformer': [FunctionTransformer(np.log, np.exp)],
        'estimator': [
            TransformedTargetRegressor(
                regressor=LinearRegression(),
                func=np.sqrt,
                inverse_func=np.square
            )
        ]
    }
]

searcher = GridSearchCV(base_flow, param_maps, cv=5)
searcher.fit(X_mat, y_vec)
chosen = searcher.best_estimator_

В этой конфигурации оцениваются только те комбинации, которые вы явно разрешили. Первая сетка перебирает преобразования признаков, которые сразу подаются в LinearRegression. Вторая добавляет путь, где признаки логарифмируются, а целевая преобразуется через TransformedTargetRegressor. За счёт такой структуры нежелательные сочетания не возникают, а пространство поиска остаётся наглядным.

Минус по сравнению с «ручным» словарём — доступ к отдельным обученным вариантам. В цикле каждая модель у вас под рукой. В GridSearchCV вы получаете best_estimator_ и можете изучать cv_results_, но отдельные обученные модели так явно не выдаются.

Зачем всё это

Последовательное сравнение преобразований снижает долю ситуативных решений и держит конфигурацию под контролем. Один объект поиска объединяет логику оценки, задаёт единые настройки кросс‑валидации и позволяет рассматривать преобразование цели наравне с конвейерами признаков.

Итоги

Если вы проверяете несколько преобразований для регрессии, начните с общего Pipeline с именованными шагами и перечислите в списке сеток только те комбинации, которые реально хотите рассматривать. Обучите через GridSearchCV и заберите победивший оцениватель из best_estimator_. Если важно сохранить все обученные варианты для ручного анализа, схема «словарь + цикл» остаётся рабочей альтернативой. Выбирайте путь, который соответствует нужному уровню контроля и автоматизации на этапе оценки.

Статья основана на вопросе на StackOverflow от p1xel и ответе от p1xel.