Борьба с несбалансированными данными

6fab36835da6b4de56071d0b66b8035b.jpg

Привет, Хабр!

Почему несбалансированные данные — это такая большая проблема? Все начинается с того, что в реальном мире классы могут быть не равномерно представлены в наших данных. Например, в задаче обнаружения мошенничества с кредитными картами, обычные транзакции будут составлять большую часть данных, в то время как мошеннические операции будут редкими. Если модель обучается на таких данных, она склонна к смещению в сторону более представленного класса, и это может привести к плохим результатам в реальном мире.

Борьба с несбалансированными данными — это не просто задача улучшения производительности моделей, это вопрос надежности и безопасности.

Что же делает борьбу с несбалансированными данными такой сложной задачей?

  1. Недостаточное количество примеров менее представленного класса: По своей природе, менее представленные классы имеют меньше данных для обучения, что затрудняет создание надежных моделей.

  2. Искажение метрик: Классические метрики, такие как точность, могут быть вводящими в заблуждение при оценке моделей на несбалансированных данных. Модель может достичь высокой точности, предсказывая преобладающий класс, но при этом быть бесполезной.

  3. Выбор подходящих методов балансировки: Существует множество методов борьбы с несбалансированными данными, и выбор правильного подхода зависит от конкретной задачи. Этот выбор требует глубокого понимания данных и проблемы.

  4. Обработка признаков: Несбалансированные данные также могут требовать особого внимания к предобработке признаков, чтобы добиться лучших результатов.

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

Несбалансированные данные — это одна из наиболее распространенных проблем, с которой сталкиваются в машинном обучении. Они возникают, когда классы, которые мы пытаемся предсказать, представлены неравномерно. Другими словами, один класс имеет гораздо меньше примеров, чем другой, что создает дисбаланс в распределении данных.

Существует несколько причин, почему данные могут оказаться несбалансированными:

  1. Реальное распределение классов: В некоторых задачах, как в примере с мошенническими транзакциями, дисбаланс классов отражает реальное распределение в природе. Мошеннических транзакций действительно меньше, чем нормальных.

  2. Сложности сбора данных: В некоторых областях сбор данных о классе-меньшинстве может быть затруднен, например, при обнаружении редких заболеваний.

  3. Неправильная выборка: Иногда дисбаланс данных может быть следствием неправильной выборки, когда выборка сделана несбалансированно.

Последствия несбалансированных данных:

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

  2. Неверная интерпретация метрик: Классические метрики, такие как точность (accuracy), могут быть обманчивыми при несбалансированных данных. Модель с высокой точностью может быть практически бесполезной, если она не обнаруживает менее распространенные классы.

  3. Увеличение числа ложных срабатываний: Модели могут совершать больше ложных срабатываний, предсказывая класс-меньшинство там, где его на самом деле нет.

  4. Потеря информации: При игнорировании класса-меньшинства мы теряем ценные данные, которые могли бы быть полезными.

Методы сбалансирования данных

Взвешивание классов (Class Weighting)

Одним из первых методов, которые стоит рассмотреть, является взвешивание классов, или Class Weighting. Этот метод позволяет учесть дисбаланс между классами в процессе обучения модели. Он основан на идее того, что модель будет штрафовать более сильно за ошибки в классе-меньшинстве, поощряя более точное предсказание.

Принцип работы взвешивания классов заключается в присвоении разных весов каждому классу в зависимости от его доли в данных. Это позволяет модели лучше справляться с классом-меньшинством.

Пример 1: Взвешивание классов в библиотеке Scikit-learn (Python)

from sklearn.ensemble import RandomForestClassifier
from sklearn.utils.class_weight import compute_class_weight

# Получение весов классов
class_weights = compute_class_weight('balanced', classes=np.unique(y), y=y)

# Создание модели с взвешиванием классов
model = RandomForestClassifier(class_weight=dict(enumerate(class_weights)))

В приведенном коде мы используем библиотеку Scikit-learn для вычисления весов классов с помощью compute_class_weight. Параметр 'balanced' автоматически вычисляет веса классов на основе их доли в данных. Затем мы передаем эти веса в нашу модель случайного леса, что позволяет учесть дисбаланс классов.

Пример 2: Взвешивание классов в TensorFlow (Python)

import tensorflow as tf

# Создание модели
model = tf.keras.Sequential([
    # добавьте слои вашей модели
])

# Определение весов классов
class_weights = {
    0: 1.0,  # Вес для класса 0
    1: 10.0,  # Вес для класса 1 (пример взвешивания, где класс-меньшинство получает более высокий вес)
}

# Компиляция модели с учетом весов классов
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'],
              class_weight=class_weights)

В данном примере мы создаем нейронную сеть с использованием TensorFlow и задаем веса классов вручную с помощью словаря class_weights. Здесь класс-меньшинство (класс 1) получает более высокий вес (10.0), что отражает нашу попытку сбалансировать дисбаланс данных.

Увеличение (Oversampling) и уменьшение (Undersampling) выборки

Одним из основных способов борьбы с несбалансированными данными является увеличение (oversampling) и уменьшение (undersampling) выборки. Эти методы направлены на достижение баланса между классами путем изменения количества примеров в каждом классе.

Увеличение выборки (Oversampling)

Увеличение выборки заключается в добавлении дополнительных примеров класса-меньшинства, чтобы сделать его более представительным. Это можно сделать различными способами, например, путем дублирования существующих примеров, генерации синтетических данных или комбинирования этих методов.

Пример 1: Увеличение выборки с использованием библиотеки imbalanced-learn (Python)

from imblearn.over_sampling import RandomOverSampler

# Создание экземпляра RandomOverSampler
ros = RandomOverSampler()

# Применение увеличения выборки к данным
X_resampled, y_resampled = ros.fit_resample(X, y)

В этом примере мы используем библиотеку imbalanced-learn для увеличения выборки с помощью RandomOverSampler. Этот метод случайным образом выбирает примеры из класса-меньшинства и дублирует их, пока не достигнется баланс.

Уменьшение выборки (Undersampling)

Уменьшение выборки заключается в уменьшении количества примеров в классе-большинстве, чтобы сделать его менее доминирующим. Это может быть полезным, если дублирование примеров класса-меньшинства нежелательно.

Пример 2: Уменьшение выборки с использованием библиотеки imbalanced-learn (Python)

from imblearn.under_sampling import RandomUnderSampler

# Создание экземпляра RandomUnderSampler
rus = RandomUnderSampler()

# Применение уменьшения выборки к данным
X_resampled, y_resampled = rus.fit_resample(X, y)

Здесь мы используем библиотеку imbalanced-learn для уменьшения выборки с помощью RandomUnderSampler. Этот метод случайным образом удаляет примеры из класса-большинства до достижения баланса.

Генерация синтетических данных (SMOTE, ADASYN и др.)

SMOTE (Synthetic Minority Over-sampling Technique)

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

Пример 1: Применение SMOTE с использованием библиотеки imbalanced-learn (Python)

from imblearn.over_sampling import SMOTE

# Создание экземпляра SMOTE
smote = SMOTE()

# Применение SMOTE к данным
X_resampled, y_resampled = smote.fit_resample(X, y)

В этом примере мы используем библиотеку imbalanced-learn для применения SMOTE к данным. SMOTE анализирует ближайших соседей класса-меньшинства и создает синтетические примеры, что позволяет балансировать классы.

ADASYN (Adaptive Synthetic Sampling)

ADASYN работает аналогично SMOTE, но с добавлением адаптивности. Он уделяет больше внимания классам, которые более затруднительны для классификации, генерируя больше синтетических примеров для таких классов.

Пример 2: Применение ADASYN с использованием библиотеки imbalanced-learn (Python)

from imblearn.over_sampling import ADASYN

# Создание экземпляра ADASYN
adasyn = ADASYN()

# Применение ADASYN к данным
X_resampled, y_resampled = adasyn.fit_resample(X, y)

ADASYN подходит для ситуаций, где дисбаланс классов меняется от примера к примеру, и помогает справиться с более сложными сценариями несбалансированных данных.

Ансамблирование моделей (Ensemble Techniques)

Принцип ансамблирования моделей

Идея ансамблирования заключается в том, чтобы объединить несколько моделей, каждая из которых может обнаруживать разные аспекты данных, и сделать предсказание на основе голосования (в случае классификации) или среднего (в случае регрессии) результатов моделей-членов. Это позволяет увеличить стабильность и точность предсказаний.

Пример 1: Использование ансамбля RandomForest (Python)

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

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

# Создание ансамбля из 100 случайных лесов
ensemble = [RandomForestClassifier(n_estimators=100) for _ in range(10)]

# Обучение каждой модели-члена на подмножестве данных
for model in ensemble:
    sample_indices = np.random.choice(len(X_train), size=len(X_train), replace=True)
    X_subset, y_subset = X_train[sample_indices], y_train[sample_indices]
    model.fit(X_subset, y_subset)

# Совместное предсказание
predictions = np.array([model.predict(X_test) for model in ensemble])
ensemble_predictions = mode(predictions)[0][0]

# Оценка точности ансамбля
accuracy = accuracy_score(y_test, ensemble_predictions)
print("Точность ансамбля: {:.2f}".format(accuracy))

В этом примере мы создаем ансамбль из 10 моделей случайного леса и обучаем каждую из них на случайных подмножествах данных. Затем мы объединяем их предсказания с помощью голосования и оцениваем точность ансамбля.

Пример 2: Использование библиотеки XGBoost (Python)

import xgboost as xgb
from sklearn.metrics import f1_score

# Создание модели XGBoost
model = xgb.XGBClassifier()

# Обучение модели на сбалансированных данных
model.fit(X_resampled, y_resampled)

# Предсказание на тестовом наборе
y_pred = model.predict(X_test)

# Вычисление F1-меры вместо точности
f1 = f1_score(y_test, y_pred)
print("F1-мера модели XGBoost: {:.2f}".format(f1))

Здесь мы используем библиотеку XGBoost, которая представляет собой мощный алгоритм градиентного бустинга. Обратите внимание, что мы обучаем модель на сбалансированных данных (X_resampled и y_resampled), что делает ее более способной к работе с несбалансированными данными.

Адаптивный подход и выбор оптимального метода

Анализ данных и выбор метода

Прежде чем принимать решение о методе сбалансирования, важно провести анализ данных. Рассмотрите следующие аспекты:

  1. Распределение классов: Оцените, насколько сильно несбалансированы классы. Это позволит определить, действительно ли необходима борьба с дисбалансом.

  2. Природа данных: Изучите данные, чтобы понять, почему дисбаланс существует. Может быть, это естественное свойство данных, и вам не стоит его исправлять.

  3. Цель задачи: Определите, какие метрики оценки важны для вашей задачи. Например, в некоторых случаях может быть важнее точность, в других — полнота или F1-мера.

Пример 1: Адаптивный выбор метода сбалансирования (Python)

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from sklearn.metrics import f1_score

# Создание синтетических данных
X, y = make_classification(n_classes=2, weights=[0.95, 0.05], random_state=42)

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

# Анализ данных и выбор метода
if sum(y_train) < len(y_train) / 2:
    # Если класс-меньшинство составляет менее половины выборки, используем oversampling
    oversampler = SMOTE()
    X_resampled, y_resampled = oversampler.fit_resample(X_train, y_train)
else:
    # В противном случае используем undersampling
    undersampler = RandomUnderSampler()
    X_resampled, y_resampled = undersampler.fit_resample(X_train, y_train)

# Обучение модели и оценка F1-меры
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_resampled, y_resampled)
y_pred = model.predict(X_test)
f1 = f1_score(y_test, y_pred)
print("F1-мера модели: {:.2f}".format(f1))

В этом примере, мы адаптивно выбираем метод сбалансирования данных в зависимости от распределения классов в обучающем наборе. Если класс-меньшинство составляет менее половины выборки, мы используем SMOTE для oversampling, иначе — RandomUnderSampler для undersampling.

Пример 2: Использование кросс-валидации (Python)

from imblearn.under_sampling import NearMiss
from imblearn.over_sampling import ADASYN
from sklearn.model_selection import cross_val_score

# Создание экземпляров методов сбалансирования
undersampler = NearMiss(version=1)
oversampler = ADASYN()

# Модель (замените на свою)
model = RandomForestClassifier(n_estimators=100, random_state=42)

# Применение кросс-валидации
cv_f1_scores = cross_val_score(model, X, y, cv=5, scoring='f1')

# Печать средней F1-меры для кросс-валидации
print("Средняя F1-мера для модели: {:.2f}".format(cv_f1_scores.mean()))

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

Оценка эффективности методов

F1-мера (F1-score)

F1-мера — это гармоническое среднее между точностью (precision) и полнотой (recall). Она позволяет учесть как долю правильно классифицированных положительных примеров, так и способность модели обнаруживать все положительные примеры.

from sklearn.metrics import f1_score

y_true = [0, 1, 1, 0, 1, 1, 0, 0]
y_pred = [0, 1, 0, 1, 0, 1, 0, 1]

f1 = f1_score(y_true, y_pred)
print("F1-мера: {:.2f}".format(f1))

ROC-AUC (Receiver Operating Characteristic — Area Under the Curve)

ROC-AUC — это площадь под кривой ROC, которая измеряет способность модели разделять классы. Значение ROC-AUC близкое к 1 указывает на хорошую способность модели разделять классы.

from sklearn.metrics import roc_auc_score

y_true = [0, 1, 1, 0, 1, 1, 0, 0]
y_scores = [0.1, 0.9, 0.8, 0.3, 0.7, 0.6, 0.2, 0.4]

roc_auc = roc_auc_score(y_true, y_scores)
print("ROC-AUC: {:.2f}".format(roc_auc))

Gini Coefficient

Gini Coefficient — это мера неравенства, которая также может использоваться для оценки эффективности моделей в задачах с несбалансированными данными. В случае классификации, Gini Coefficient рассчитывается как удвоенное значение площади между кривой Лоренца и линией равенства.

gini_coefficient = 2 * roc_auc - 1
print("Gini Coefficient: {:.2f}".format(gini_coefficient))

Оценка важности классов

Для оценки важности классов, можно воспользоваться метрикой, такой как classification_report из библиотеки scikit-learn. Эта метрика предоставляет информацию о точности, полноте, F1-мере и поддержке для каждого класса.

from sklearn.metrics import classification_report

y_true = [0, 1, 1, 0, 1, 1, 0, 0]
y_pred = [0, 1, 0, 1, 0, 1, 0, 1]

report = classification_report(y_true, y_pred)
print(report)

Кросс-валидация и стратификация

Для надежной оценки модели на несбалансированных данных рекомендуется использовать кросс-валидацию. Кроме того, важно правильно настроить стратификацию, чтобы сохранить баланс классов при разделении данных на фолды.

from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import make_scorer

# Создание стратифицированных фолдов для кросс-валидации
cv = StratifiedKFold(n_splits=5)

# Определение метрики для использования в кросс-валидации
scorer = make_scorer(f1_score)

# Применение кросс-валидации
cv_scores = cross_val_score(model, X, y, cv=cv, scoring=scorer)

Оценка эффективности методов и моделей на несбалансированных данных в целом самое важный этап для оценки результатов работы по сбалансировки.

Другие методы борьбы с несбалансированными данными

Когда стандартные методы балансировки данных не приносят желаемых результатов, возникает необходимость в более продвинутых подходах. Один из таких подходов — использование контекстных методов и адаптации алгоритмов. Эти методы нацелены на более тонкую настройку моделей, чтобы они лучше работали с дисбалансом классов. Давайте рассмотрим их подробнее и предоставим примеры.

Контекстные методы и адаптация алгоритмов

Контекстные методы ориентированы на адаптацию стандартных алгоритмов машинного обучения к конкретной проблеме с несбалансированными данными. Они учитывают особенности данных и решают проблему дисбаланса на уровне алгоритма. Вот несколько примеров контекстных методов:

  1. Cost-sensitive learning: Этот метод назначает разные веса различным классам в зависимости от их важности. Например, классу-меньшинству можно присвоить больший вес, чтобы модель более активно учила его представления.

  2. Cascade-классификация: Этот метод включает несколько классификаторов, каждый из которых обучается на своем уровне сложности данных. Если объект не может быть классифицирован на более простом уровне, он передается на следующий уровень.

Пример 1: Cost-sensitive learning (Python)

from sklearn.ensemble import RandomForestClassifier

# Определение весов классов (в данном случае, классу 0 присваивается вес 1, а классу 1 - 10)
class_weights = {0: 1, 1: 10}

# Создание модели Random Forest с учетом весов классов
model = RandomForestClassifier(class_weight=class_weights)

# Обучение модели на данных
model.fit(X_train, y_train)

В этом примере мы используем class_weight в модели Random Forest, чтобы указать веса классов в соответствии с их важностью.

Пример 2: Cascade-классификация (Python)

from sklearn.base import BaseEstimator, ClassifierMixin
from sklearn.ensemble import RandomForestClassifier

class CascadeClassifier(BaseEstimator, ClassifierMixin):
    def __init__(self, classifiers):
        self.classifiers = classifiers

    def fit(self, X, y):
        for i, clf in enumerate(self.classifiers):
            if i == 0:
                clf.fit(X, y)
            else:
                y_pred = clf.predict(X)
                X = X[y_pred == 1]
                y = y[y_pred == 1]
                clf.fit(X, y)

    def predict(self, X):
        for clf in self.classifiers:
            y_pred = clf.predict(X)
            if 1 not in y_pred:
                return y_pred
        return y_pred

# Создание CascadeClassifier с несколькими Random Forest классификаторами
classifiers = [RandomForestClassifier(n_estimators=100), RandomForestClassifier(n_estimators=100)]
cascade_classifier = CascadeClassifier(classifiers)

# Обучение и предсказание
cascade_classifier.fit(X_train, y_train)
y_pred = cascade_classifier.predict(X_test)

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

Заключение

Борьба с несбалансированными данными — это сложная, но важная задача, и для успешного решения необходимо применять разнообразные методы и стратегии. Разработчики должны учитывать дисбаланс классов при разработке и обучении моделей, чтобы достичь наилучших результатов.

А больше практической информации про аналитику данных вы можете узнать в рамках онлайн-курсов от OTUS. Ознакомиться с полной линейкой курсов можно в каталоге по ссылке.

© Habrahabr.ru