Stratified k-Fold Cross-Validation (Стратифицированная k-блочная перекрестная проверка) — это улучшенная версия стандартной k-Fold перекрестной проверки, которая специально разработана для работы с несбалансированными данными.
Основная идея
В отличие от обычной k-Fold, которая просто случайно разбивает данные, Stratified k-Fold гарантирует, что в каждом фолде сохраняется примерное то же соотношение классов, что и в исходном наборе данных.
Проще говоря: “Каждый фолд должен быть мини-копией всего датасета по распределению целевой переменной”.
Почему это важно? Проблема обычной k-Fold
Представьте, что у вас есть данные о диагностике редкого заболевания:
- Всего пациентов: 1000
- Больных (положительный класс): 50 (5%)
- Здоровых (отрицательный класс): 950 (95%)
При обычной k-Fold (k=5) может случиться так, что в одном из фолдов окажется:
- Тестовый фолд: 200 пациентов, из которых только 2 больных (1%)
- Обучающие фолды: 800 пациентов, из которых 48 больных (6%)
Проблемы:
- Модель не научится нормально распознавать больных, так как в обучающей выборке их непропорционально много.
- Оценка на тестовом фолде будет нерепрезентативной, так как в нем практически нет больных.
Как работает Stratified k-Fold?
Алгоритм:
- Для каждого класса отдельно данные разбиваются на k фолдов.
- Затем фолды объединяются так, чтобы в каждом конечном фолде было примерно одинаковое процентное соотношение всех классов.
Визуализация:
Исходные данные: [0,0,0,0,0,0,0,0,0,0,1,1,1,1] # 10 нулей, 4 единицы
Обычная k-Fold (k=3):
Fold 1: [0,0,0,0,1] # 4 нуля, 1 единица (20% единиц)
Fold 2: [0,0,0,0,1,1] # 4 нуля, 2 единицы (33% единиц)
Fold 3: [0,0,1,1] # 2 нуля, 2 единицы (50% единиц)
Stratified k-Fold (k=3):
Fold 1: [0,0,0,0,1,1] # 4 нуля, 2 единицы (33% единиц)
Fold 2: [0,0,0,0,1,1] # 4 нуля, 2 единицы (33% единиц)
Fold 3: [0,0,1,1] # 2 нуля, 2 единицы (50% единиц)
# (максимально близко к исходному 29%)
Практический пример
Задача: Классификация спам-писем
- Всего писем: 10,000
- Спам: 1,000 (10%)
- Не спам: 9,000 (90%)
Stratified k-Fold (k=5) гарантирует, что в каждом фолде будет:
- Тестовый фолд: 2,000 писем ≈ 200 спама (10%) + 1,800 не спама (90%)
- Обучающие фолды: 8,000 писем ≈ 800 спама (10%) + 7,200 не спама (90%)
Таким образом, модель всегда учится и тестируется на репрезентативных данных.
Преимущества Stratified k-Fold
- Репрезентативность: Каждый фолд представляет исходное распределение классов.
- Снижение дисперсии оценки: Оценки качества модели более стабильны от фолда к фолду.
- Надежность для несбалансированных данных: Критически важен, когда один класс значительно преобладает над другим.
- Более точная оценка метрик: Таких как Precision, Recall, F1-score, которые чувствительны к распределению классов.
Когда использовать?
Stratified k-Fold рекомендуется использовать ВСЕГДА для задач классификации, потому что:
✅ Обязательно использовать:
- Несбалансированные данные
- Мультиклассовая классификация
- Когда важны метрики Precision, Recall, F1
- При маленьком размере датасета
⚠️ Можно использовать обычную k-Fold:
- Сбалансированные данные (но Stratified все равно не повредит)
- Задачи регрессии (для регрессии существует Stratified версия на основе квантилей)
Реализация в Python
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification
import numpy as np
# Создаем несбалансированные данные
X, y = make_classification(n_samples=1000, n_classes=2,
weights=[0.9, 0.1], # 90% класса 0, 10% класса 1
random_state=42)
print("Распределение классов в исходных данных:")
print(f"Класс 0: {np.sum(y == 0)} ({np.sum(y == 0)/len(y)*100:.1f}%)")
print(f"Класс 1: {np.sum(y == 1)} ({np.sum(y == 1)/len(y)*100:.1f}%)")
# Создаем стратифицированную перекрестную проверку
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# Проверяем распределение в фолдах
for fold, (train_idx, test_idx) in enumerate(skf.split(X, y)):
y_train, y_test = y[train_idx], y[test_idx]
print(f"\nФолд {fold + 1}:")
print(f" Обучающая: класс 1 = {np.sum(y_train == 1)} ({np.sum(y_train == 1)/len(y_train)*100:.1f}%)")
print(f" Тестовая: класс 1 = {np.sum(y_test == 1)} ({np.sum(y_test == 1)/len(y_test)*100:.1f}%)")
# Запускаем перекрестную проверку
model = RandomForestClassifier(random_state=42)
scores = cross_val_score(model, X, y, cv=skf, scoring='f1_macro')
print(f"\nF1-score по фолдам: {scores}")
print(f"Средний F1-score: {scores.mean():.3f} ± {scores.std():.3f}")Вывод программы:
Распределение классов в исходных данных:
Класс 0: 900 (90.0%)
Класс 1: 100 (10.0%)
Фолд 1:
Обучающая: класс 1 = 80 (10.0%)
Тестовая: класс 1 = 20 (10.0%)
Фолд 2:
Обучающая: класс 1 = 80 (10.0%)
Тестовая: класс 1 = 20 (10.0%)
...
Как видно, распределение классов сохраняется в каждом фолде!
Сравнение с обычной k-Fold
from sklearn.model_selection import KFold
# Обычная k-Fold
kf = KFold(n_splits=5, shuffle=True, random_state=42)
print("Обычная k-Fold распределение:")
for fold, (train_idx, test_idx) in enumerate(kf.split(X, y)):
y_test = y[test_idx]
print(f"Фолд {fold + 1}: класс 1 = {np.sum(y_test == 1)} ({np.sum(y_test == 1)/len(y_test)*100:.1f}%)")Вывод (может варьироваться):
Обычная k-Fold распределение:
Фолд 1: класс 1 = 25 (12.5%)
Фолд 2: класс 1 = 15 (7.5%)
Фолд 3: класс 1 = 22 (11.0%)
...
Видно, что распределение классов сильно “прыгает” между фолдами.
Краткий итог
- Stratified k-Fold — это улучшенная версия k-Fold для задач классификации
- Гарантирует сохранение пропорций классов в каждом фолде
- Критически важен для несбалансированных данных
- Дает более надежные и стабильные оценки качества модели
- Является стандартом де-факто для перекрестной проверки в классификации
Правило: Для классификации всегда используйте StratifiedKFold, если нет веских причин использовать обычный KFold.