Разделение данных на train/test: train_test_split

Разделение данных на train/test: train_test_split Scikit-learn

Привет! Ты уже знаком с машинным обучением и scikit-learn. Но есть один важнейший шаг, который нельзя пропускать — разделение данных на обучающую и тестовую выборки. Без этого ты никогда не узнаешь, насколько хорошо на самом деле работает твоя модель.

В этой статье мы разберём:

  • Зачем нужно разделять данные
  • Функция train_test_split()
  • Параметры: test_sizetrain_sizerandom_state
  • Стратификация для несбалансированных данных
  • Как избежать утечки данных

Что нужно знать перед началом

Для этого урока тебе понадобится:

  • Установленный 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_train80%Признаки для обучения
X_test20%Признаки для проверки
y_train80%Ответы для обучения
y_test20%Ответы для проверки

Совет: Порядок переменных важен! Сначала 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.3random_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/30test_size=0.3 или train_size=0.7
Разделить без перемешиванияshuffle=False
Для временных рядовshuffle=False + раздел по времени

Заключение

Сегодня мы узнали:

  • Зачем разделять данные на train и test
  • Как использовать train_test_split
  • Параметры: test_sizerandom_statestratify
  • Стратификация для несбалансированных данных
  • Как избежать утечки данных и других ошибок

Запомни главное:

  1. Всегда разделяй данные до предобработки
  2. Используй stratify=y для классификации
  3. Никогда не оценивай модель на train-выборке
  4. Фиксируй random_state для воспроизводимости

КВИЗ

Что дальше?

Оцените статью
IMI-DS - PYTHON LERNEN
Содержание
Оглавление ×