Как определить оптимальное значение для разбиения данных (train/test/val split)?

Ответ

Я выбираю стратегию разбиения, исходя из размера данных, стабильности оценки и типа задачи.

Мои практические правила:

Размер датасета Типовая схема Обоснование и пример
Очень маленький (< 1k samples) 60/20/20 или даже 70/15/15 Максимизирую обучающую выборку, использую кросс-валидацию для валидации.
Средний (1k - 100k samples) 70/15/15 или 80/10/10 Стандартный баланс между обучением и надёжной оценкой.
Крупный (> 100k samples) 98/1/1 или 99/0.5/0.5 Даже 1% от большого датасета даёт точную оценку, основная часть идёт в обучение.

Код для стандартного случая:

from sklearn.model_selection import train_test_split

# Первое разбиение: на тренировочный и временный (test + val) наборы
X_train, X_temp, y_train, y_temp = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)
# Второе разбиение: отделяем валидацию от теста
X_val, X_test, y_val, y_test = train_test_split(
    X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp
)
# Итог: 70/15/15
print(f"Train: {X_train.shape[0]}, Val: {X_val.shape[0]}, Test: {X_test.shape[0]}")

Критические нюансы, которые я проверяю:

  1. Стратификация. Для классификации всегда использую stratify=y, чтобы распределение целевого класса сохранялось во всех подвыборках.
  2. Временные ряды. Здесь разбиение должно быть хронологическим. Я использовал TimeSeriesSplit из sklearn или просто задавал граничную дату: всё, что до неё — train, после — test.
  3. Стабильность оценки. Я делаю несколько случайных разбиений (с разными random_state) и смотрю, сильно ли колеблется метрика на тесте. Если колебания велики (например, AUC меняется на ±0.03), значит, тестовая выборка слишком мала или данные неоднородны — нужно увеличить её размер.

Ответ 18+ 🔞

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

Вот мои правила, по которым я обычно работаю:

Размер датасета Как обычно делю Почему именно так и пример
Очень маленький (< 1k примеров) 60/20/20 или даже 70/15/15 Тут надо выжать из обучающей выборки всё, что можно. Валидацию часто делаю через кросс-валидацию, чтобы не оставить модель голодной.
Средний (1k - 100k примеров) 70/15/15 или 80/10/10 Золотая середина. Обучаться есть на чём, и на валидации с тестом уже можно что-то понять, а не гадать на кофейной гуще.
Очень крупный (> 100k примеров) 98/1/1 или 99/0.5/0.5 Когда данных овердохуища, даже один процент — это уже толковая выборка для оценки. Зачем отдавать лишнее, если модель и так нажрётся?

Вот код для самого частого, среднего случая:

from sklearn.model_selection import train_test_split

# Сначала откусываем кусок на тест и валидацию вместе
X_train, X_temp, y_train, y_temp = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)
# Потом этот кусок пилим пополам
X_val, X_test, y_val, y_test = train_test_split(
    X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp
)
# В итоге получается 70/15/15
print(f"Train: {X_train.shape[0]}, Val: {X_val.shape[0]}, Test: {X_test.shape[0]}")

А теперь главное, на что я смотрю, чтобы не облажаться:

  1. Стратификация. Если задача классификация, я всегда ставлю stratify=y. Иначе может получиться, что в трейне одни классы, а в тесте — другие. Это пиздец, а не оценка.
  2. Временные ряды. Тут, ёпта, никаких случайных разбиений! Всё должно идти по порядку. Я использовал TimeSeriesSplit или просто брал всё до определённой даты для обучения, а что после — для теста. Иначе ты обманешь модель будущими данными, и она себя покажет как гений, а на деле — хуй с горы.
  3. Стабильность оценки. Я делаю несколько разбиений с разными random_state и смотрю, как прыгает метрика на тесте. Если она скачет, как угорелая (допустим, AUC пляшет на ±0.03), это тревожный звоночек. Значит, или теста маловато, или данные — хитрая жопа. Надо увеличивать тестовую выборку.