Найдите шаг этого pandas-конвейера, который обучается до разделения выборки.
Конвейер ниже показывает подозрительно высокую метрику. Один шаг предобработки видит данные, которых видеть не должен.
Ограничения:
- Сохраните то же поведение импутации и масштабирования.
- Не меняйте долю разделения и зерно генератора.
- Обе части должны быть преобразованы одними и теми же обученными статистиками.
import pandas as pd
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
def prepare(df: pd.DataFrame, target: str):
X, y = df.drop(columns=[target]), df[target]
X = pd.DataFrame(SimpleImputer(strategy="median").fit_transform(X), columns=X.columns)
X = pd.DataFrame(StandardScaler().fit_transform(X), columns=X.columns)
return train_test_split(X, y, test_size=0.2, random_state=42)
Найдите и исправьте ошибку.
Скейлер и импутер обучаются на полном фрейме, поэтому статистики теста утекают в обучающие признаки, а метрика завышена. Сначала разделите выборку, вызовите fit только на train, затем примените transform к обеим частям этими статистиками.
- ✗Вызывать fit_transform на тестовой части вместо transform обученными на train статистиками
- ✗Думать, что утечка требует обращения к таргету, тогда как статистики признаков текут сами
- ✗Разделить выборку, но затем заново обучить скейлер отдельно на каждой части
- →Как помощник sklearn под названием Pipeline делает такую ошибку структурно невозможной?
- →Где та же утечка возникает снова при кросс-валидации вместо одного разделения?
Оба шага предобработки вызывают fit_transform на полном X, то есть медианы и статистики масштабирования считаются вместе с отложенными строками. Разделять надо первым действием.
import pandas as pd
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
def prepare(df: pd.DataFrame, target: str):
X, y = df.drop(columns=[target]), df[target]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
imputer = SimpleImputer(strategy="median").fit(X_train)
scaler = StandardScaler().fit(imputer.transform(X_train))
def apply(part):
return pd.DataFrame(scaler.transform(imputer.transform(part)), columns=X.columns, index=part.index)
return apply(X_train), apply(X_test), y_train, y_test
Ключевое правило: fit — только на train, transform — на обеих частях. Тот же порядок обязателен внутри каждого фолда кросс-валидации; sklearn.pipeline.Pipeline внутри cross_val_score соблюдает его автоматически.