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%)

Проблемы:

  1. Модель не научится нормально распознавать больных, так как в обучающей выборке их непропорционально много.
  2. Оценка на тестовом фолде будет нерепрезентативной, так как в нем практически нет больных.

Как работает Stratified k-Fold?

Алгоритм:

  1. Для каждого класса отдельно данные разбиваются на k фолдов.
  2. Затем фолды объединяются так, чтобы в каждом конечном фолде было примерно одинаковое процентное соотношение всех классов.

Визуализация:

Исходные данные: [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

  1. Репрезентативность: Каждый фолд представляет исходное распределение классов.
  2. Снижение дисперсии оценки: Оценки качества модели более стабильны от фолда к фолду.
  3. Надежность для несбалансированных данных: Критически важен, когда один класс значительно преобладает над другим.
  4. Более точная оценка метрик: Таких как 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.