← Назад к вопросам

Что такое SMOTE?

1.8 Middle🔥 201 комментариев
#Pandas и обработка данных#Машинное обучение

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

SMOTE (Synthetic Minority Over-sampling Technique)

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

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

В реальных задачах данные часто несбалансированы:

# Пример несбалансированного датасета
import numpy as np
from collections import Counter

y = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1])  # 90% класс 0, 10% класс 1
print(Counter(y))  # Counter({0: 9, 1: 1})

# Проблема: модель просто предсказывает 0 и получает 90% accuracy

Просто дублировать примеры (over-sampling) плохо, так как модель переобучается на одних и тех же примерах.

Как работает SMOTE

SMOTE создаёт синтетические примеры путём интерполяции между существующими примерами меньшинства:

Алгоритм:

  1. Для каждого примера меньшинства класса:

    • Находим K ближайших соседей (обычно K=5) среди примеров того же класса
    • Случайно выбираем одного соседа
    • Генерируем новый пример как линейную комбинацию текущего и выбранного соседа
  2. Формула синтетического примера:

    x_synthetic = x_current + random(0, 1) * (x_neighbor - x_current)
    

Математическое описание

import numpy as np

def smote_create_sample(x_current, x_neighbor):
    """
    Создаёт один синтетический пример
    x_current: текущий пример меньшинства (shape: n_features)
    x_neighbor: случайно выбранный сосед (shape: n_features)
    """
    # Генерируем случайный вес
    alpha = np.random.uniform(0, 1)
    
    # Интерполируем
    x_synthetic = x_current + alpha * (x_neighbor - x_current)
    return x_synthetic

# Пример
x1 = np.array([1.0, 2.0])
x2 = np.array([3.0, 4.0])
x_new = smote_create_sample(x1, x2)
print(x_new)  # Точка на линии между x1 и x2

Практический пример с scikit-learn

import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from imblearn.over_sampling import SMOTE
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, roc_auc_score

# 1. Создаём несбалансированный датасет
X, y = make_classification(
    n_samples=1000,
    n_features=20,
    n_informative=10,
    n_redundant=5,
    weights=[0.9, 0.1],  # 90% класс 0, 10% класс 1
    random_state=42
)

print(f"Исходный датасет: {np.bincount(y)}")
# Output: Исходный датасет: [900 100]

# 2. Разделяем на train и test
# ВАЖНО: SMOTE применяем только к train данным!
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)

# 3. Применяем SMOTE к train данным
smote = SMOTE(random_state=42, k_neighbors=5)
X_train_smote, y_train_smote = smote.fit_resample(X_train, y_train)

print(f"После SMOTE: {np.bincount(y_train_smote)}")
# Output: После SMOTE: [630 630]  (классы сбалансированы)

# 4. Обучаем модель
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train_smote, y_train_smote)

# 5. Оцениваем
y_pred = model.predict(X_test)
y_pred_proba = model.predict_proba(X_test)[:, 1]

print(classification_report(y_test, y_pred))
print(f"ROC-AUC: {roc_auc_score(y_test, y_pred_proba):.4f}")

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

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

# Создаём 2D датасет для визуализации
X, y = make_classification(
    n_samples=200,
    n_features=2,
    n_informative=2,
    n_redundant=0,
    weights=[0.85, 0.15],
    random_state=42
)

# Применяем SMOTE
smote = SMOTE(random_state=42)
X_smote, y_smote = smote.fit_resample(X, y)

# Визуализация
plt.figure(figsize=(12, 5))

# До SMOTE
plt.subplot(1, 2, 1)
plt.scatter(X[y==0, 0], X[y==0, 1], label='Класс 0', alpha=0.7)
plt.scatter(X[y==1, 0], X[y==1, 1], label='Класс 1 (меньшинство)', alpha=0.7, c='red')
plt.title('До SMOTE')
plt.legend()

# После SMOTE
plt.subplot(1, 2, 2)
plt.scatter(X_smote[y_smote==0, 0], X_smote[y_smote==0, 1], label='Класс 0', alpha=0.7)
plt.scatter(X_smote[y_smote==1, 0], X_smote[y_smote==1, 1], label='Класс 1 (синтетические)', alpha=0.7, c='red')
plt.title('После SMOTE')
plt.legend()

plt.tight_layout()
plt.show()

Варианты SMOTE

1. Стандартный SMOTE

Интерполирует между случайным соседом:

from imblearn.over_sampling import SMOTE

smote = SMOTE(k_neighbors=5, random_state=42)
X_resampled, y_resampled = smote.fit_resample(X, y)

2. Borderline-SMOTE

Сосредоточивается на примерах вблизи границы между классами:

from imblearn.over_sampling import BorderlineSMOTE

bl_smote = BorderlineSMOTE(kind=1, k_neighbors=5, random_state=42)
X_resampled, y_resampled = bl_smote.fit_resample(X, y)

3. SVM-SMOTE

Использует опорные вектора для выбора примеров для SMOTE:

from imblearn.over_sampling import SVMSMOTE

svm_smote = SVMSMOTE(k_neighbors=5, random_state=42)
X_resampled, y_resampled = svm_smote.fit_resample(X, y)

4. ADASYN (Adaptive Synthetic Sampling)

Генерирует больше синтетических примеров в сложных регионах:

from imblearn.over_sampling import ADASYN

adasyn = ADASYN(n_neighbors=5, random_state=42)
X_resampled, y_resampled = adasyn.fit_resample(X, y)

Комбинирование SMOTE с Under-sampling

Часто SMOTE комбинируют с под-выборкой (under-sampling) для оптимального баланса:

from imblearn.pipeline import Pipeline
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler

# Создаём pipeline: SMOTE + Under-sampling
sm = SMOTE(random_state=42)
rUS = RandomUnderSampler(random_state=42)

pipeline = Pipeline([
    ('over', sm),
    ('under', rUS)
])

X_resampled, y_resampled = pipeline.fit_resample(X_train, y_train)
print(f"После SMOTE + Under-sampling: {np.bincount(y_resampled)}")

Лучшие практики использования SMOTE

1. Применяй только к train данным

# ✅ Правильно
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
smote = SMOTE()
X_train_balanced, y_train_balanced = smote.fit_resample(X_train, y_train)
model.fit(X_train_balanced, y_train_balanced)
model.predict(X_test)  # Тестируем на оригинальных данных

# ❌ Неправильно
X_balanced, y_balanced = smote.fit_resample(X, y)
X_train, X_test, y_train, y_test = train_test_split(X_balanced, y_balanced)

2. Выбор правильного соотношения

# SMOTE с параметром sampling_strategy
smote = SMOTE(
    sampling_strategy=0.5,  # Минимальный класс будет 50% от большинства
    k_neighbors=5,
    random_state=42
)

3. Используй кросс-валидацию с SMOTE

from imblearn.pipeline import Pipeline
from sklearn.model_selection import cross_validate

pipe = Pipeline([
    ('smote', SMOTE()),
    ('model', RandomForestClassifier())
])

scores = cross_validate(pipe, X_train, y_train, cv=5, scoring='roc_auc')
print(f"ROC-AUC: {scores['test_score'].mean():.4f}")

Когда использовать SMOTE

Используй SMOTE если:

  • Дисбаланс классов существенный (1:10 или больше)
  • Меньшинство класса хорошо представлено в признаках
  • У тебя достаточно памяти для синтетических данных
  • Переобучение менее критично чем недообучение

НЕ используй SMOTE если:

  • Дисбаланс очень сильный (1:1000)
  • Меньшинство класса очень мало примеров (< 50)
  • Признаки высоко коррелированы или содержат шум
  • Вычислительные ресурсы ограничены

Альтернативы SMOTE

  • Class weights: Увеличивают штраф за ошибки на меньшинстве
  • Threshold adjustment: Меняют порог классификации
  • Cost-sensitive learning: Разные затраты на ошибки классов
  • Ensemble методы: Balanced Random Forest, Easy Ensemble

Заключение

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

Что такое SMOTE? | PrepBro