Привет! Ты уже знаком с машинным обучением и scikit-learn. Но есть один важнейший шаг, который нельзя пропускать — разделение данных на обучающую и тестовую выборки. Без этого ты никогда не узнаешь, насколько хорошо на самом деле работает твоя модель.
В этой статье мы разберём:
- Зачем нужно разделять данные
- Функция
train_test_split() - Параметры:
test_size,train_size,random_state - Стратификация для несбалансированных данных
- Как избежать утечки данных
- Что нужно знать перед началом
- Основная часть
- Зачем разделять данные?
- Функция train_test_split()
- Параметры train_test_split()
- test_size и train_size
- random_state (фиксация случайности)
- stratify (стратификация)
- Полный пример: Iris
- Работа с несбалансированными данными
- Разделение для временных рядов
- train_test_split в реальном проекте
- Задачи для закрепления
- Нюансы и подводные камни
- Утечка данных (Data Leakage)
- Очень маленькие наборы данных
- Частые ошибки и как их избежать
- Ошибка 1: Оценка модели на train-выборке
- Ошибка 2: Неправильный порядок переменных
- Ошибка 3: Забыл stratify для классификации
- Шпаргалка
- Заключение
- КВИЗ
- Что дальше?
Что нужно знать перед началом
Для этого урока тебе понадобится:
- Установленный scikit-learn (
pip install scikit-learn) - Базовое понимание машинного обучения (что такое модель, признаки, целевая переменная)
- Понимание, что такое overfitting (переобучение)
Совет: Разделение данных — это не просто формальность. Это критически важный этап, который влияет на качество всей модели.
Основная часть
Зачем разделять данные?
Представь, что ты учишься к экзамену. Если ты будешь решать только те задачи, которые уже видел, ты не узнаешь, готов ли ты к новым. Так и в ML: если мы обучаем модель и проверяем её на тех же данных, мы не узнаем, как она справится с новыми данными.
Именно поэтому данные делят на две части:
| Выборка | Назначение |
|---|---|
| Обучающая (train) | На этих данных модель учится (подбирает веса) |
| Тестовая (test) | На этих данных проверяем качество (модель их не видела) |
Совет: Классическое соотношение — 80% на обучение, 20% на тест. Для маленьких наборов данных можно 70/30 или даже 60/40.
Функция train_test_split()
В scikit-learn есть готовая функция для разделения данных:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2, # 20% на тест
random_state=42 # Фиксируем случайность
)Что возвращает функция?
| Переменная | Размер | Что содержит |
|---|---|---|
X_train | 80% | Признаки для обучения |
X_test | 20% | Признаки для проверки |
y_train | 80% | Ответы для обучения |
y_test | 20% | Ответы для проверки |
Совет: Порядок переменных важен! Сначала
X_train, потомX_test, потомy_train, потомy_test. Запомни последовательность: X_train, X_test, y_train, y_test.
Параметры train_test_split()
test_size и train_size
# test_size = 0.3 → 30% на тест, 70% на обучение
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)
# train_size = 0.7 → 70% на обучение, 30% на тест
X_train, X_test, y_train, y_test = train_test_split(
X, y, train_size=0.7, random_state=42
)Совет: Указывай только один параметр — либо
test_size, либоtrain_size. Если не указать — по умолчаниюtest_size=0.25.
random_state (фиксация случайности)
# Без random_state — каждое запуск даст разное разделение
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# С random_state — одинаковое разделение при каждом запуске
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)Совет: Всегда используй
random_stateв учебных проектах. Это позволяет воспроизводить результаты.
stratify (стратификация)
Когда классы в данных неравномерны (например, 90% кошек и 10% собак), простая случайная выборка может дать выборку, где вообще нет собак. Стратификация сохраняет пропорцию классов.
# Без стратификации
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# Со стратификацией (сохраняет пропорции классов)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)Совет: Всегда используй
stratify=yдля задач классификации. Это особенно важно при несбалансированных данных.
Полный пример: Iris
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# Загружаем данные
iris = load_iris()
X, y = iris.data, iris.target
# Разделяем с сохранением пропорций классов
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print(f"Размер обучающей выборки: {len(X_train)}")
print(f"Размер тестовой выборки: {len(X_test)}")
print(f"Пропорция классов в y_train: {sum(y_train == 0)} / {sum(y_train == 1)} / {sum(y_train == 2)}")
# Обучаем модель
model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)
# Оцениваем качество
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Точность на тестовой выборке: {accuracy:.2%}")Результат:
Размер обучающей выборки: 120
Размер тестовой выборки: 30
Пропорция классов в y_train: 40 / 40 / 40
Точность на тестовой выборке: 100.00%Совет: Видишь, как красиво сработала стратификация? По 40 образцов каждого класса в train и по 10 в test.
Работа с несбалансированными данными
from sklearn.datasets import make_classification
# Создаём несбалансированный датасет
X, y = make_classification(
n_samples=1000,
weights=[0.9, 0.1], # 90% класса 0, 10% класса 1
random_state=42
)
print(f"Всего: {sum(y == 0)} класса 0, {sum(y == 1)} класса 1")
# Без стратификации
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
print(f"Без стратификации: в train {sum(y_train == 1)} экз. класса 1")
# Со стратификацией
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print(f"Со стратификацией: в train {sum(y_train == 1)} экз. класса 1")Совет: Без стратификации редкий класс может вообще не попасть в тестовую выборку!
Разделение для временных рядов
Для временных рядов случайное разделение не подходит! Нужно использовать временное разделение.
import pandas as pd
from sklearn.model_selection import train_test_split
# Создаём временные данные
dates = pd.date_range('2024-01-01', periods=100)
data = pd.DataFrame({
'date': dates,
'value': range(100)
})
# Разделяем по времени (первые 80% — train, последние 20% — test)
split_idx = int(len(data) * 0.8)
train_data = data.iloc[:split_idx]
test_data = data.iloc[split_idx:]
X_train = train_data[['date']]
y_train = train_data['value']
X_test = test_data[['date']]
y_test = test_data['value']Совет: Для временных рядов нельзя использовать shuffle! Порядок должен сохраняться.
train_test_split в реальном проекте
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
# 1. Загружаем данные
df = pd.read_csv('customer_data.csv')
# 2. Разделяем признаки и целевую переменную
X = df.drop('churn', axis=1) # churn — целевая переменная
y = df['churn']
# 3. Разделяем на train/test
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
random_state=42,
stratify=y # важно для несбалансированных данных
)
# 4. Проверяем, что пропорции сохранились
print(f"Пропорция churn в train: {sum(y_train == 1) / len(y_train):.2%}")
print(f"Пропорция churn в test: {sum(y_test == 1) / len(y_test):.2%}")
# 5. Обучаем и оцениваем
model = RandomForestClassifier(random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print(classification_report(y_test, y_pred))Задачи для закрепления
Задача 1. Раздели данные на train/test с параметрами test_size=0.3, random_state=123.
Задача 2. Что делает параметр stratify в train_test_split()?
Задача 3. Раздели данные, где признаки — это список из 100 элементов, а ответы — тоже список из 100 элементов. Используй test_size=0.25.
Задача 4. Зачем нужен random_state и что будет без него?
Задача 5. Почему нельзя использовать случайное разделение для временных рядов?
Ответы:
Задача 1.
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=123
)Задача 2.
stratify сохраняет пропорции классов в обучающей и тестовой выборках. Это важно для несбалансированных данных.
Задача 3.
X = list(range(100))
y = list(range(100, 200))
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42
)
print(f"X_train: {len(X_train)}, X_test: {len(X_test)}")Задача 4.
random_state фиксирует случайность. Без него каждый запуск даёт разные выборки. Для воспроизводимости результатов всегда указывай random_state.
Задача 5.
Для временных рядов важна последовательность. Нельзя перемешивать данные, потому что будущее зависит от прошлого. Нужно разделять по времени (первые 80% — train, последние 20% — test).
Нюансы и подводные камни
Утечка данных (Data Leakage)
Никогда не разделяй данные после предобработки (масштабирования, кодирования)!
# Плохо — утечка данных
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X) # Используем все данные
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y)
# Хорошо — сначала разделяем, потом масштабируем
X_train, X_test, y_train, y_test = train_test_split(X, y)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # Учимся на train
X_test_scaled = scaler.transform(X_test) # Применяем к testОчень маленькие наборы данных
Если данных мало, используй кросс-валидацию вместо train/test:
from sklearn.model_selection import cross_val_score
scores = cross_val_score(model, X, y, cv=5)
print(f"Средняя точность: {scores.mean():.2%}")Частые ошибки и как их избежать
Ошибка 1: Оценка модели на train-выборке
# Так нельзя — это не отражает реальное качество
model.fit(X_train, y_train)
accuracy = model.score(X_train, y_train) # Ошибка! Оценка на тех же данных
# Правильно — оцениваем на тестовой
accuracy = model.score(X_test, y_test)Ошибка 2: Неправильный порядок переменных
# Неправильный порядок
X_train, y_train, X_test, y_test = train_test_split(X, y)
# Правильно: X_train, X_test, y_train, y_test
X_train, X_test, y_train, y_test = train_test_split(X, y)Ошибка 3: Забыл stratify для классификации
# Может привести к несбалансированным выборкам
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# Всегда используй stratify для классификации
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y
)Шпаргалка
| Что нужно | Как пишется |
|---|---|
| Базовое разделение | train_test_split(X, y, test_size=0.2) |
| Зафиксировать случайность | random_state=42 |
| Сохранить пропорции классов | stratify=y |
| Соотношение 70/30 | test_size=0.3 или train_size=0.7 |
| Разделить без перемешивания | shuffle=False |
| Для временных рядов | shuffle=False + раздел по времени |
Заключение
Сегодня мы узнали:
- Зачем разделять данные на train и test
- Как использовать
train_test_split - Параметры:
test_size,random_state,stratify - Стратификация для несбалансированных данных
- Как избежать утечки данных и других ошибок
Запомни главное:
- Всегда разделяй данные до предобработки
- Используй
stratify=yдля классификации - Никогда не оценивай модель на train-выборке
- Фиксируй
random_stateдля воспроизводимости




