SMOTE: объяснение алгоритма c практическими примерами на Python

В задачах машинного обучения, особенно в классификации, часто возникает проблема несбалансированных классов. Когда один класс (обычно доминирующий) представлен существенно больше, чем другой (минорный класс), алгоритмы могут смещать внимание в сторону более частого класса.

SMOTE: объяснение алгоритма c практическими примерами на Python
Краткое содержание

Это приводит к завышенной общей точности, но низкой чувствительности к редким классам. SMOTE (Synthetic Minority Over-sampling Technique) — один из наиболее популярных методов преодоления этой проблемы за счёт создания синтетических образцов минорного класса, что улучшает обобщающие способности моделей.

2. Проблема дисбаланса классов

2.1 Влияние на модель

  • Смещённая оценка качества: Модель может показывать высокую точность, если большая часть предсказаний приходится на доминирующий класс, игнорируя меньшинство.
  • Плохая обобщаемость: Алгоритмы обучаются на недостаточном объёме данных для редкого класса, что ухудшает способность распознавания «редких» объектов.
  • Примеры из практики: В задачах обнаружения мошенничества, диагностики заболеваний или анализа отказов оборудования объекты минорного класса критически важны.

2.2 Стандартные подходы к решению

  • Undersampling: Удаление части объектов доминирующего класса. Недостаток – возможная потеря важной информации.
  • Oversampling: Простое копирование объектов минорного класса. Недостаток – риск переобучения модели из-за избыточной корреляции между примерами.

3. Основы SMOTE

SMOTE решает задачу oversampling’а, не просто дублируя данные, а создавая синтетические примеры, генерируя их на основе линейной интерполяции между соседними точками в пространстве признаков.

3.1 Основная идея и алгоритм

  1. Выбор исходного объекта: Из выборки минорного класса случайным образом выбирается объект xix_ixi​.
  2. Поиск соседей: Для объекта xix_ixi​ вычисляются kkk ближайших соседей (обычно k=5k = 5k=5) среди других объектов этого же класса с использованием метрики (например, Евклидовой).
  3. Повторение: Процесс повторяется до создания требуемого числа синтетических примеров, что позволяет сбалансировать выборку.

3.2 Математическая интерполяция

Интерполяция здесь означает, что новый пример – это линейная комбинация выбранного примера и его соседа. Такая генерация помогает «размыть» границы классов, создавая более гладкое распределение данных в пространстве признаков.

4. Основные параметры SMOTE и их влияние

4.1 k_neighbors

  • Описание: Число ближайших соседей, используемых для генерации синтетических примеров.
  • Влияние:
    • Малое значение (kkk) может привести к недостаточному разнообразию синтетических данных.
    • Слишком большое значение может включить нежелательные точки, особенно если данные имеют сложную структуру или присутствуют выбросы.

4.2 sampling_strategy

  • Описание: Определяет, сколько синтетических примеров нужно создать для каждого класса. Может задаваться как отношение (например, 0.5 означает, что количество объектов минорного класса после применения должно составлять 50% от числа объектов мажоритарного класса) или как словарь, где явно указываются размеры классов.
  • Влияние: Корректный выбор этого параметра позволяет гибко управлять степенью балансировки.

4.3 random_state

  • Описание: Параметр для воспроизводимости результатов. При фиксированном значении генерация синтетических примеров будет детерминированной.

5. Модификации SMOTE

В реальных задачах иногда требуется более тонкая настройка генерации синтетических данных. Существуют модификации стандартного SMOTE:

5.1 Borderline-SMOTE

  • Идея: Фокусируется на объектах, расположенных у границы между классами. Генерируемые примеры создаются для точек, которые имеют много соседей из другого класса, что помогает лучше разделять классы.
  • Варианты: Обычно различают Borderline-SMOTE1 и Borderline-SMOTE2, отличающиеся методом выбора точек для генерации.

5.2 SVM-SMOTE

  • Идея: Использует алгоритм SVM для определения границ между классами и создания синтетических примеров вдоль линии раздела.
  • Преимущество: Может быть эффективен при наличии сложных границ между классами.

5.3 ADASYN (Adaptive Synthetic Sampling)

  • Идея: Подход, похожий на SMOTE, но с адаптивной стратегией: чем труднее классифицировать объект, тем больше синтетических примеров генерируется в его окрестности.
  • Преимущество: Помогает сосредоточиться на «трудных» для классификации участках данных.

6. Практические рекомендации по подготовке данных

6.1 Масштабирование и нормализация

Поскольку SMOTE использует расстояния для определения соседей, важно привести данные к единому масштабу (например, с помощью StandardScaler или MinMaxScaler). Без масштабирования разница в порядках величин признаков может исказить результаты поиска ближайших соседей.

6.2 Анализ выбросов

Перед применением SMOTE необходимо проанализировать данные на наличие выбросов. Выбросы могут привести к генерации нехарактерных синтетических примеров, ухудшающих качество модели.

6.3 Интеграция в пайплайн

Использование SMOTE в составе пайплайна (например, с помощью imblearn.pipeline.Pipeline) позволяет обеспечить корректное применение метода только к обучающей выборке, предотвращая утечку информации при кросс-валидации.

7. Примеры кода на Python

Ниже представлены расширенные примеры кода, демонстрирующие базовое применение SMOTE, его интеграцию в пайплайн и использование модификаций.

7.1 Пример 1: Базовое применение SMOTE и визуализация

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from imblearn.over_sampling import SMOTE

# Создаем искусственный несбалансированный набор данных
X, y = make_classification(n_samples=200, n_features=2, n_redundant=0, n_informative=2,
                           n_clusters_per_class=1, weights=[0.1, 0.9], flip_y=0,
                           random_state=10)

print("Распределение классов до SMOTE:", np.bincount(y))

# Применяем SMOTE для создания синтетических примеров
sm = SMOTE(random_state=42, k_neighbors=5)
X_res, y_res = sm.fit_resample(X, y)

print("Распределение классов после SMOTE:", np.bincount(y_res))

# Визуализируем результаты до и после SMOTE
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.title("До SMOTE")
plt.scatter(X[:, 0], X[:, 1], c=y, cmap='viridis', edgecolor='k')
plt.xlabel("Признак 1")
plt.ylabel("Признак 2")

plt.subplot(1, 2, 2)
plt.title("После SMOTE")
plt.scatter(X_res[:, 0], X_res[:, 1], c=y_res, cmap='viridis', edgecolor='k')
plt.xlabel("Признак 1")
plt.ylabel("Признак 2")

plt.tight_layout()
plt.show()

Пояснения:

  • Функция make_classification генерирует двумерный набор данных с явно выраженным дисбалансом (10% против 90%).
  • SMOTE создаёт новые объекты минорного класса с использованием 5 ближайших соседей.
  • Графическая визуализация помогает увидеть, как синтетические точки распределены между исходными объектами.

7.2 Пример 2: Интеграция SMOTE в пайплайн с классификатором

from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
from imblearn.pipeline import Pipeline
from imblearn.over_sampling import SMOTE
from sklearn.datasets import make_classification

# Создадим искусственный набор данных с большим числом признаков
X, y = make_classification(n_samples=1000, n_features=20, n_redundant=2, n_informative=10,
                           n_clusters_per_class=2, weights=[0.2, 0.8], flip_y=0, random_state=42)

# Разделение данных на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# Создаем пайплайн: сначала SMOTE для балансировки, затем RandomForestClassifier
pipeline = Pipeline([
    ('smote', SMOTE(random_state=42, sampling_strategy=0.5)),  # Меньшинственный класс составит 50% от мажоритарного
    ('classifier', RandomForestClassifier(random_state=42))
])

# Обучаем модель на сбалансированных данных
pipeline.fit(X_train, y_train)

# Предсказываем и оцениваем модель
y_pred = pipeline.predict(X_test)
print(classification_report(y_test, y_pred))

Пояснения:

  • Пайплайн гарантирует, что SMOTE применяется только к обучающей выборке, что исключает утечку данных.
  • Параметр sampling_strategy=0.5 задаёт желаемое соотношение между классами.
  • Оценка модели производится по стандартным метрикам (precision, recall, f1-score, accuracy).

7.3 Пример 3: Использование Borderline-SMOTE

from imblearn.over_sampling import BorderlineSMOTE

# Применяем Borderline-SMOTE для генерации синтетических примеров
bsmote = BorderlineSMOTE(random_state=42, k_neighbors=5, kind='borderline-1')
X_res_b, y_res_b = bsmote.fit_resample(X, y)

print("Распределение классов после Borderline-SMOTE:", np.bincount(y_res_b))

Пояснения:

  • Borderline-SMOTE уделяет внимание точкам, находящимся у границы между классами, что может повысить чувствительность модели к «сложным» примерам.
  • Параметр kind позволяет выбрать между различными реализациями метода.

7.4 Пример 4: Комбинирование SMOTE с кросс-валидацией и GridSearchCV

При оптимизации гиперпараметров модели важно включать этап балансировки данных в кросс-валидацию. Пример ниже показывает, как это можно сделать:

from sklearn.model_selection import GridSearchCV, StratifiedKFold
from sklearn.svm import SVC
from imblearn.pipeline import Pipeline

# Пайплайн с SMOTE и SVM-классификатором
pipeline = Pipeline([
    ('smote', SMOTE(random_state=42)),
    ('svc', SVC())
])

# Определяем параметры для поиска
param_grid = {
    'smote__k_neighbors': [3, 5, 7],
    'svc__C': [0.1, 1, 10],
    'svc__kernel': ['rbf', 'linear']
}

# Настраиваем кросс-валидацию (Stratified для сохранения пропорций классов)
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

grid_search = GridSearchCV(pipeline, param_grid, cv=cv, scoring='f1', n_jobs=-1)
grid_search.fit(X_train, y_train)

print("Лучшие параметры:", grid_search.best_params_)
print("Лучший f1-score:", grid_search.best_score_)

Пояснения:

  • GridSearchCV в сочетании с пайплайном позволяет одновременно настраивать параметры SMOTE и классификатора.
  • StratifiedKFold гарантирует, что в каждой подвыборке сохранены пропорции классов.
  • Использование метрики F1-score особенно актуально для несбалансированных данных.

8. Риски, ограничения и рекомендации по использованию SMOTE

8.1 Риски и недостатки

  • Генерация шумовых данных: Если в исходном наборе присутствуют выбросы или объекты с нечеткими границами, SMOTE может создать нехарактерные синтетические точки, что снизит качество модели.
  • Повышенная вычислительная сложность: При больших объемах данных поиск ближайших соседей может оказаться ресурсоёмким.
  • Неидеальное отражение сложных зависимостей: Линейная интерполяция может не захватывать нелинейные взаимосвязи между признаками, что особенно важно в сложных реальных задачах.

8.2 Рекомендации

  • Предварительная обработка: Обязательно выполняйте масштабирование и анализ выбросов. Рассмотрите возможность удаления аномальных точек перед применением SMOTE.
  • Интеграция в пайплайн: Используйте инструменты пайплайнов (например, imblearn.pipeline.Pipeline), чтобы избежать утечки данных и обеспечить корректную работу кросс-валидации.
  • Эксперименты с модификациями: В зависимости от задачи тестируйте не только стандартный SMOTE, но и его модификации (Borderline-SMOTE, SVM-SMOTE, ADASYN) для выбора оптимального метода.
  • Оценка результатов: Обязательно анализируйте метрики не только по accuracy, но и по precision, recall и f1-score, чтобы понять влияние балансировки на каждую из классовых групп.

SMOTE — это мощный и гибкий инструмент для борьбы с проблемой дисбалансированных данных, позволяющий создавать синтетические образцы посредством линейной интерполяции между ближайшими соседями меньшинственного класса. Благодаря таким особенностям, как возможность адаптации (через параметры и модификации) и интеграция в стандартные пайплайны машинного обучения, SMOTE нашёл широкое применение в самых разных областях. Однако, как и любой метод, он требует тщательной настройки и предварительного анализа данных, чтобы избежать генерации шумовых примеров и ухудшения качества модели.

Дополнительные ресурсы

Nerd IT 🌀 ML, DS, ANN, GPT
Привет! Меня зовут Семён, я работаю в сфере ML и аналитики данных и пишу в блог nerdit.ru статьи о своем опыте и том, что может пригодиться начинающим в начале их пути изучения больших данных.

Подписаться на новости Nerd IT

Не пропустите последние выпуски. Зарегистрируйтесь сейчас, чтобы получить полный доступ к статьям.
jamie@example.com
Подписаться