Введение в валидацию модели: hold-out, cross-validation и утечки данных

На чтение
6 мин
Дата обновления
05.11.2026

Качество модели нужно оценивать на данных, которые не участвовали в соответствующем обучении. Иначе результат может показывать, насколько хорошо алгоритм запомнил уже известные ответы, а не насколько полезными будут новые предсказания. Именно поэтому валидацию планируют до перебора моделей, признаков и параметров.

Ниже разберём авторский учебный эксперимент на небольшом наборе Wine из scikit-learn. Он содержит 178 объектов, 13 признаков и три класса. Для примера автор выполнил локальный учебный запуск в scikit-learn 1.9.1. Ниже приведены воспроизводимый код и его численный результат; это собственный эксперимент автора, а не опубликованное внешнее исследование. Числа относятся только к этому разбиению и не обещают такого же качества на рабочих данных.

Три роли данных

Обучающая часть нужна для подгонки параметров модели и обучаемых преобразований. Валидационная помогает выбирать настройки: например, силу регуляризации или набор признаков. Тестовая нужна для итоговой оценки уже выбранного решения. Она перестаёт быть независимой, если по её результату продолжать менять модель.

Названия файлов не защищают от смешения ролей. Если вы десять раз посмотрели test и после каждого просмотра меняли настройки, он фактически стал частью процедуры выбора. Лучше заранее определить, когда открывается итоговая оценка, какие решения до этого разрешены и как фиксируется конфигурация эксперимента.

При hold-out роли реализуются отдельными частями данных. При кросс-валидации обучающий массив поочерёдно делится на внутренние train и validation. Финальный test всё равно полезно оставить отдельно: он не участвует в выборе лучшего варианта по фолдам.

Когда выбрать hold-out, а когда CV

СитуацияРазумная отправная точкаОграничение
Большой массив независимых наблюденийОтдельные train, validation, testОдин разрез может оказаться неудачным
Небольшая обычная классификационная выборкаСтратифицированная CV внутри train и отдельный testБольше обучений; редкий класс ограничивает число фолдов
Несколько записей одного человека или объектаРазделение с учётом группЗаписи одной группы не должны пересекать нужную границу
Прогноз будущих событийРазделение по времени и подходящая временная CVСлучайное перемешивание может раскрыть будущее

Это рекомендации по выбору конструкции эксперимента, а не правило «CV всегда лучше». Способ разбиения должен напоминать будущую задачу. Если предсказываете поведение нового клиента, проверяйте перенос на новых клиентов; если завтрашний спрос — перенос вперёд во времени.

В нашем небольшом примере автор оставляет 25% объектов для test, а остальное использует в пятифолдовой стратифицированной CV. Число фолдов и доля выбраны для демонстрации. Они не являются универсальным оптимумом.

Утечка бывает до вызова fit

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

Менее заметная утечка возникает при подготовке данных. Если вычислить средние и масштабы StandardScaler по всему массиву, тестовая часть повлияет на параметры преобразования. В руководстве scikit-learn Common pitfalls рекомендуется сначала разделять данные и не обучать преобразования на тестовой части.

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

Зачем нужен единый Pipeline

В Pipeline помещают последовательность обучаемых преобразований и итоговый оценщик. Когда такая конструкция участвует в CV, преобразования обучаются в рамках соответствующей обучающей части. Это помогает избежать ситуации, когда масштабирование случайно было выполнено заранее на всех объектах.

Pipeline не распознаёт смысл ваших признаков и не исправляет неправильное разделение по времени или клиентам. Он организует процедуру, но не отменяет проверки происхождения данных.

from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split, GridSearchCV, StratifiedKFold
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import f1_score, accuracy_score

X, y = load_wine(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=.25, stratify=y, random_state=11)
pipe = Pipeline([('scale', StandardScaler()),
                 ('model', LogisticRegression(max_iter=2000))])
search = GridSearchCV(pipe, {'model__C': [.01, .1, 1]},
    cv=StratifiedKFold(n_splits=5, shuffle=True, random_state=13),
    scoring='f1_macro', refit=True)
search.fit(X_train, y_train)
pred = search.predict(X_test)
print(search.best_params_, search.best_score_)
print(f1_score(y_test, pred, average='macro'))
print(accuracy_score(y_test, pred))

Получилось 133 обучающих и 45 тестовых объектов. По внутренней CV выбрано C=1, среднее macro-F1 составило около 0,9920. После переобучения выбранной конструкции на всём X_train итоговый macro-F1 на test — около 0,9765, accuracy — 0,9778. Test не использовался для выбора C.

Разница между CV и test сама по себе не доказывает ошибку: оценки зависят от конкретных объектов и процедуры. На такой маленькой выборке один дополнительный промах заметно меняет число. Не превращайте четыре знака после запятой в обещание точности на новой аудитории.

Метрика должна соответствовать решению

В классификации accuracy показывает долю правильных ответов. Авторский арифметический пример: среди ста объектов 95 отрицательных и пять положительных. Предсказание «все отрицательные» даёт 95 правильных ответов, то есть accuracy 95%, при этом не находит ни одного положительного объекта. Такая таблица ошибок объясняет, зачем дополнительно разбирать результаты по классам, а не выбирать модель только по одной доле правильных ответов.

F1 объединяет precision и recall. Вариант macro усредняет показатель по классам без учёта их численности; именно он выбран в нашем многоклассовом примере. Это не означает, что macro-F1 подходит любой бизнес-задаче. Правило выбора метрики задают до сравнения вариантов.

Для регрессии полезны MAE — средняя абсолютная ошибка — и RMSE, корень из средней квадратичной ошибки. Они выражаются в единицах целевой величины. Авторское сравнение по формулам: абсолютные ошибки 1, 1 и 4 дают MAE=2 и RMSE=√6≈2,45; ошибки 2, 2 и 2 дают MAE=2 и RMSE=2. При одинаковом MAE первый набор содержит более крупный отдельный промах и получает больший RMSE. Сравнивайте модели на одном разбиении и одной метрике; отдельно проверяйте, какие ошибки скрывает среднее.

Что проверить перед финальным test

  • Каждый признак доступен в момент будущего предсказания.
  • Группы и время учтены в разбиении.
  • Преобразования и выбор параметров выполняются только внутри разрешённых обучающих данных.
  • Зафиксированы метрика, сетка параметров и способ CV.
  • Сохранены версия библиотек, исходное разбиение и конфигурация запуска.
  • После итогового test вы не продолжаете подгонку под увиденное число.

Если после финальной оценки обнаружилась содержательная ошибка, её следует исправить и честно пересмотреть процедуру проверки. Скрывать предыдущий результат или многократно выбирать удобный random_state хуже, чем признать, что исходный эксперимент был некорректен.