Train, validation и test: где выбирать модель
Содержание курса
Как получить три непересекающиеся части
Наш учебный файл rent.csv содержит 80 строк и числовые поля: площадь квартиры area_m2, число комнат rooms и целевую арендную плату rent_rub. Уникальный номер объявления listing_id мы сразу назначаем индексом таблицы, чтобы отслеживать конкретные квартиры, а не просто обезличенные номера строк.
Стандартная функция train_test_split из scikit-learn за один вызов умеет делить данные только на две части. Чтобы получить пропорцию 60% на 20% на 20%, разбиение выполняется в два последовательных шага. Сначала мы откладываем 20% от всех 80 строк под итоговый test. Это ровно 16 квартир. Оставшиеся 64 строки образуют промежуточный рабочий набор.
import pandas as pd
from sklearn.model_selection import train_test_split
# Выполните код в папке с распакованным rent.csv.
data = pd.read_csv("rent.csv", index_col="listing_id")
X = data[["area_m2", "rooms"]]
y = data["rent_rub"]
X_dev, X_test, y_dev, y_test = train_test_split(
X, y, test_size=0.20, random_state=42
)
Теперь нужно разделить оставшиеся 64 строки на обучающую и валидационную части. Чтобы валидация получила ровно 20% от исходного датасета (те же 16 квартир), от промежуточного набора нужно отрезать ровно четверть. Доля 0.25 применяется именно к 64 строкам, а не ко всему файлу: 64 умножить на 0.25 даёт 16 строк для validation, а оставшиеся 48 строк отходят в train.
X_train, X_val, y_train, y_val = train_test_split(
X_dev, y_dev, test_size=0.25, random_state=42
)
Параметр random_state фиксирует генератор случайных чисел. Если подать на вход тот же порядок строк в той же среде, разбиение воспроизведётся в точности. Но сам по себе числовой seed — это лишь инструмент воспроизводимости кода, а не доказательство высокого качества будущей модели.
После разделения критически важно убедиться, что ни одна квартира не попала одновременно в две выборки и ни одна строка не потерялась. Для этого мы извлекаем индексы X_train, X_val и X_test и превращаем их во множества set. Мы проверяем три попарных условия через метод .isdisjoint(): train не пересекается с val, train не пересекается с test, а val не пересекается с test. Объединение всех трёх множеств через оператор | должно в точности совпадать с полным набором исходных индексов.
train_ids, val_ids, test_ids = map(set, (X_train.index, X_val.index, X_test.index))
assert train_ids.isdisjoint(val_ids) and train_ids.isdisjoint(test_ids)
assert val_ids.isdisjoint(test_ids)
assert train_ids | val_ids | test_ids == set(X.index)
Обратите внимание: мы проверяем именно идентификаторы строк, а не значения площади или комнат. Две совершенно разные квартиры вполне могут иметь одинаковую площадь в 50 квадратных метров, и это нормально.
Чтобы убедиться в повторяемости, можно вызвать функцию разбиения ещё раз и сохранить результат в переменную again. Конструкция zip связывает шесть исходных частей с шестью новыми, а метод .index.equals проверяет строгое совпадение состава и порядка индексов. Обернув это в функцию all и оператор assert, мы получаем строгую проверку: если всё совпало, assert отрабатывает молча, а при любом расхождении немедленно выбрасывает исключение AssertionError.
