Честная проверка модели

Утечка цели и неправильное разделение

Содержание курса

Удаление признака-нарушителя до разделения данных

Обнаружив утечку, нужно убрать признак из X — и сделать это до вызова train_test_split. Порядок важен.

import pandas as pd
from sklearn.model_selection import train_test_split

df = pd.DataFrame({
    'hours_studied':  [10, 5, 8, 3, 7],
    'attendance_pct': [90, 60, 80, 40, 75],
    'final_score':    [88, 55, 78, 42, 72],
    'passed':         [1, 0, 1, 0, 1]
})

# Шаг 1: убираем цель и признак-нарушитель
X = df.drop(columns=['passed', 'final_score'])  # утечка удалена
y = df['passed']

# Шаг 2: только после этого делим
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

print(list(X.columns))  # ['hours_studied', 'attendance_pct']

Почему именно до train_test_split? Разберём, что пошло бы не так, если бы мы оставили final_score в X и попытались исправить ситуацию уже после разделения — только для обучающей части.

Представим, что X сформирован с final_score и уже разбит на X_train и X_test. Тогда попытка удалить столбец только из train и обучить модель выглядела бы так:

# Плохой сценарий — только для иллюстрации, не повторять
X_bad = df.drop(columns=['passed'])          # final_score оставлен
X_train_bad, X_test_bad, y_train, y_test = train_test_split(
    X_bad, y, test_size=0.2, random_state=42
)

X_train_fixed = X_train_bad.drop(columns=['final_score'])  # убрали только из train

from sklearn.tree import DecisionTreeClassifier
model = DecisionTreeClassifier()
model.fit(X_train_fixed, y_train)    # fit видел 2 признака
model.predict(X_test_bad)            # X_test_bad содержит 3 признака — несовпадение схем

Scikit-learn запоминает имена и количество столбцов при fit. Когда predict получает набор с другими столбцами, вызов завершается исключением — модель не может сделать ни одного предсказания. Это не тихое искажение оценки, а полная блокировка работы.

Можно, конечно, удалить final_score из обеих частей после разделения — технической ошибки тогда не будет. Но логика становится запутанной: нужно не забыть обработать каждую часть отдельно, и риск пропустить одну из них вполне реален. Гораздо надёжнее сформировать корректный X один раз, до любого деления.

Ещё опаснее сценарий, когда final_score остаётся в X целиком и попадает в fit. Насколько сильно просядет ошибка, зависит от модели и от того, насколько точно признак кодирует цель. Но даже если ошибка снизится лишь умеренно, оценка становится недостоверной: в реальном запросе final_score будет недоступен, и модель окажется бесполезной с первого же предсказания. Утечку обнаруживают не по величине ошибки, а задав вопрос о доступности признака в момент прогноза — именно этот вопрос позволяет отделить допустимые признаки от недопустимых.

Правило простое: X должен содержать только те столбцы, которые будут доступны в момент настоящего прогноза. Всё остальное — вон до split.