Пропуски до train/test split: как среднее из теста попадает в обучение
В табличной задаче часть значений чека отсутствует. Аналитик сначала заполняет пропуски, затем делит готовую матрицу на обучение и тест. В ноутбуке всё работает, но оценка модели отвечает на неверный вопрос: преобразование уже посмотрело на распределение будущего теста. Даже когда оно не использует метки, оно передаёт в обучение информацию о тех объектах, которые должны быть невидимы.
Посмотрим на числовой столбец. В обучающей части известны 10 и 12, поэтому среднее для заполнения равно 11. В тесте внезапно появляется 1000: это может быть новая категория клиента или ошибка источника. Если SimpleImputer обучить на объединённой таблице, значение для пропусков становится совершенно другим. Код показывает величину сдвига.
import numpy as np
from sklearn.impute import SimpleImputer
train = np.array([[10.], [12.], [np.nan]])
test = np.array([[1000.], [np.nan]])
wrong = SimpleImputer(strategy="mean").fit(np.vstack([train, test]))
right = SimpleImputer(strategy="mean").fit(train)
print("среднее с тестом:", wrong.statistics_[0])
print("среднее без теста:", right.statistics_[0])
print("пропуск в тесте:", right.transform(test)[1, 0])
assert right.statistics_[0] == 11
assert wrong.statistics_[0] == 340.6666666666667Среднее, подсчитанное с тестом, равно примерно 340,667; по одному train — 11. Пропуск в тестовой строке корректно заполняется числом 11, потому что только оно было известно при обучении. Одна новая крупная запись наглядно показывает, как просмотр теста меняет параметр. Для медианы действует то же правило: её считают лишь по train.
Разделение должно предшествовать любому fit
Сначала определите границу оценки: случайный split для независимых объектов, временной для будущих дат или групповой для повторных клиентов. Затем вызывайте fit всех преобразований только на обучающей части. К тесту применяйте только transform. Это касается не только заполнения пропусков, но и масштабирования, отбора признаков, кодирования категорий и снижения размерности. Статистика теста не должна участвовать в построении обучающих признаков.
На практике удобно собрать Pipeline: шаг заполнения и модель хранятся вместе. При кросс-валидации инструмент заново обучит весь пайплайн на обучающей части каждого фолда. Если же вы заранее вызвали fit_transform на полной таблице и лишь после этого передали матрицу в cross_val_score, пайплайн уже не сможет убрать утечку: параметр был вычислен с участием будущих проверочных строк.
Схему заполнения выбирайте по смыслу поля. Нулём можно обозначить «не было покупок», если именно так определён показатель; нулём нельзя автоматически заменить неизвестный возраст. Медиана устойчивее к крупным значениям, но может скрыть закономерность самого пропуска. Когда факт отсутствия значения важен, добавьте индикатор пропуска и проверьте эффект на отдельной валидации. Список пропущенных колонок тоже должен определяться только по train, иначе набор выходных признаков меняется от теста.
У полностью пустой обучающей колонки есть отдельная ловушка. Кодировщик не может вывести из неё медиану или среднее, а сохранение либо удаление колонки влияет на размер входа модели. Не выбирайте правило после просмотра теста, где значения вдруг появились. Определите его заранее: удалить поле по train, задать константу с предметным смыслом или сохранить пустую колонку с постоянным заполнителем. Затем испытайте именно эту схему на новых данных.
Сравнивайте стратегии на тех же фолдах. Если один вариант оценивается на случайном разбиении, а другой на временном, различие баллов нельзя приписывать заполнению пропусков. Помимо итоговой метрики сохраните число пропусков по каждому полю отдельно для train и test. Резкий рост пропусков в новом периоде может быть проблемой выгрузки, которую подбор статистики для заполнения не решит. Сначала восстановите смысл данных, затем решайте, обучать ли новую модель.
Проверка перед сохранением модели
В итоговом эксперименте сохраните числовые статистики после fit и убедитесь, что они воспроизводятся из обучающего среза. Подайте в уже готовый пайплайн строку с пропуском и новую строку с экстремальным значением; число для заполнения не должно пересчитываться. Это важная проверка перед API: сервис получает одиночные запросы и не может узнавать среднее по завтрашним клиентам.
Если в данных есть повторные объекты, сначала решите и задачу разбиения. Разбор GroupKFold для клиентских записей показывает, почему одна и та же сущность в train и test тоже делает оценку слишком оптимистичной. После честного разделения можно сравнивать варианты заполнения по той же метрике, по которой бизнес будет оценивать модель.
Курс «Data Scientist: от данных до деплоя моделей» проводит от проверки исходных данных и feature engineering через выбор валидации к batch-инференсу и API. Вы сможете встроить обработку пропусков в воспроизводимый контур и проверить её как часть модели. Посмотрите программу курса.
Продолжить на практике
- Data Scientist: от данных до деплоя моделей ↗
Практический курс по Data Science: от постановки ML-задачи и работы с данными до inference API, CI/CD и мониторинга моделей в продакшне. Вы пройдёте EDA, feature engineering, обучение и оценку моделей, experiment tracking, error analysis и deployment — чтобы собирать полноценный рабочий контур вокруг ML-решения.
