Почему без train/test split качество модели почти ничего не значит
Одна из самых опасных ошибок новичка в ML — радоваться метрике, посчитанной на тех же данных, на которых модель училась. На train-части модель видит ответы и подстраивается под них. Поэтому высокая точность здесь вообще не гарантирует, что она умеет работать на новых данных. Train/test split нужен именно для того, чтобы разорвать этот самообман и задать честный вопрос: как модель ведет себя там, где ответы не использовались в обучении.
Смысл split не в ритуале, а в логике проверки. Мы всегда хотим приблизиться к реальному будущему. В будущем модель получает новые объекты, для которых правильные ответы заранее неизвестны. Значит, и в учебном процессе нужна часть данных, которая играет роль этого будущего.
Что именно происходит при разделении выборки
Мы берем исходный набор наблюдений и делим его на две части. Train используется для обучения: на нем модель видит признаки и правильные ответы, подбирает параметры, минимизирует ошибку. Test или validation часть откладывается в сторону. На ней мы уже ничего не доучиваем, а только оцениваем, как модель переносит свои закономерности на новые данные.
$$ D = D_{train} \cup D_{test}, \quad D_{train} \cap D_{test} = \varnothing $$Исходный датасет разбивается на две непересекающиеся части: одна идет в обучение, другая остается для честной проверки качества.
D— полный набор данныхD_{train}— часть данных для обученияD_{test}— часть данных для проверки\varnothing— отсутствие пересечения между поднаборами
Эта запись важна не как формальность. Она подчеркивает, что проверка должна быть независимой. Если train и test пересекаются или если информация из test просочилась в preprocessing, честность оценки ломается.
Где split особенно часто делают неправильно
Первая ошибка — случайно допустить data leakage: масштабировать все данные до split, построить признак по полной выборке или использовать признаки, которые содержат будущую информацию. Вторая — игнорировать временную структуру. Для временных рядов случайное перемешивание часто недопустимо, потому что модель не должна «видеть будущее». Третья — работать без стратификации там, где классы сильно несбалансированы.
import pandas as pd
from sklearn.model_selection import train_test_split
data = pd.DataFrame({
'sessions': [1, 2, 3, 4, 5, 6, 7, 8],
'avg_time': [2, 3, 4, 5, 6, 7, 8, 9],
'target': [0, 0, 0, 0, 1, 1, 1, 1]
})
X = data[['sessions', 'avg_time']]
y = data['target']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42, stratify=y
)
print({'train_size': len(X_train), 'test_size': len(X_test)})
print({'train_target_rate': y_train.mean(), 'test_target_rate': y_test.mean()})Это и есть правильная интуиция split: не просто разрезать массив, а сохранить смысл будущей проверки. Если этот навык поставлен, дальше уже можно уверенно переходить к cross-validation и более сложным схемам оценки.