Обработка несбалансированных наборов данных с помощью SMOTE в Python

Оглавление

Введение: сбалансированные и несбалансированные наборы данных

Закройте глаза. Теперь представьте себе идеальный мир данных. Что вы видите? Что вы хотели бы увидеть? Точно, я тоже. Безупречно сбалансированный набор данных. Набор данных, метки которых образуют великолепное соотношение 1:1: 50% этого, 50% того; ни капли слева, ни капли справа. Все идеально сбалансировано, как и должно быть. А теперь откройте глаза и вернитесь в реальный мир.

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

Тем не менее, существуют экстремальные случаи, когда соотношение классов просто неверно, например, набор данных, в котором 95% меток принадлежат классу A, а остальные 5% относятся к классу B - соотношение не такое уж редкое в таких случаях, как обнаружение мошенничества. В таких крайних случаях идеальным вариантом будет сбор большего количества данных.

Однако, как правило, это неосуществимо; фактически, это дорого, требует времени и в большинстве случаев невозможно. К счастью для нас, существует альтернатива, известная как передискретизация. Перебор подразумевает использование имеющихся у нас данных для создания большего их количества.

Что такое передискретизация данных?

Переборка данных - это техника, применяемая для генерации данных таким образом, чтобы они напоминали базовое распределение реальных данных. В этой статье я объясняю, как мы можем использовать технику передискретизации под названием Synthetic Minority Over-Sampling Technique или SMOTE, чтобы сбалансировать наш набор данных.

Что такое SMOTE?

SMOTE - это алгоритм переборки, который опирается на концепцию ближайших соседей для создания своих синтетических данных. Предложенный еще в 2002 году Chawla et. al, SMOTE стал одним из самых популярных алгоритмов переборки.

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

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

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

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

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

Чтобы показать, как работает SMOTE, предположим, что у нас есть несбалансированный двумерный набор данных, такой как на следующем изображении, и мы хотим использовать SMOTE для создания новых точек данных.

example of an imbalanced dataset

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

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

В нашем примере (показанном на следующем изображении) синяя обведенная точка - это текущее наблюдение, синяя не обведенная точка - его ближайший сосед, а зеленая точка - синтетическое наблюдение.

new synthetic data point generated by SMOTE

Новая точка синтетических данных

Теперь давайте сделаем это на языке Python.

Учебник по использованию дисбалансного обучения

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

Сначала я создаю идеально сбалансированный набор данных и обучаю на нем модель машинного обучения, которую я буду называть "базовая модель". Затем я разбалансирую набор данных и обучу вторую систему, которую я назову " несбалансированной моделью".

Наконец, я буду использовать SMOTE для балансировки набора данных, а затем подгоню к нему третью модель, которую назову "SMOTE'd". Обучая новую модель на каждом этапе, мы сможем лучше понять, как несбалансированный набор данных может повлиять на систему машинного обучения.

Базовая модель

Для начальной задачи я подгоню модель support-vector machine (SVM), используя созданный, идеально сбалансированный набор данных. Я выбрал эту модель из-за того, что ее легко визуализировать и понять границу принятия решения, а именно гиперплоскость, отделяющую один класс от другого.

Для создания сбалансированного набора данных я буду использовать функцию scikit-learn make_classification, которая создает n кластеров нормально распределенных точек, подходящих для решения задачи классификации.

Мой фальшивый набор данных состоит из 700 точек выборки, двух характеристик и двух классов. Чтобы убедиться, что каждый класс является одним блоком данных, я установлю параметр n_clusters_per_class в 1.

Для упрощения я удалю лишние признаки и установлю число информативных признаков равным 2. Наконец, я useflip_y=0.06 уменьшу количество шума.

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

import matplotlib.pyplot as plt
import pandas as pd

from sklearn.datasets import make_classification
from imblearn.datasets import make_imbalance

# for reproducibility purposes
seed = 100

# create balanced dataset
X1, Y1 = make_classification(n_samples=700, n_features=2, n_redundant=0,
                            n_informative=2, n_clusters_per_class=1,
                            class_sep=1.0, flip_y=0.06, random_state=seed)

plt.title('Balanced dataset')
plt.xlabel('x')
plt.ylabel('y')
plt.scatter(X1[:, 0], X1[:, 1], marker='o', c=Y1,
           s=25, edgecolor='k', cmap=plt.cm.coolwarm)
plt.show()
# concatenate the features and labels into one dataframe
df = pd.concat([pd.DataFrame(X1), pd.DataFrame(Y1)], axis=1)
df.columns = ['feature_1', 'feature_2', 'label']
# save the dataset because we'll use it later
df.to_csv('df_base.csv', index=False, encoding='utf-8')

a perfectly balanced dataset with 1:1 ratio

Сбалансированный набор данных

Как видно из предыдущего изображения, наш сбалансированный набор данных выглядит аккуратным и хорошо определенным. Итак, если мы применим SVM-модель к этим данным (код ниже), как будет выглядеть граница принятия решения?

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

Вторая функция, plot_svm_boundary, строит график границы принятия решения модели SVM. Ее параметры также включают набор данных и подпись к графику.

Вот эти функции:

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.svm import SVC


def train_SVM(df):
   # select the feature columns
   X = df.loc[:, df.columns != 'label']
   # select the label column
   y = df.label

   # train an SVM with linear kernel
   clf = SVC(kernel='linear')
   clf.fit(X, y)

   return clf


def plot_svm_boundary(clf, df, title):
   fig, ax = plt.subplots()
   X0, X1 = df.iloc[:, 0], df.iloc[:, 1]

   x_min, x_max = X0.min() - 1, X0.max() + 1
   y_min, y_max = X1.min() - 1, X1.max() + 1
   xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02))

   Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
   Z = Z.reshape(xx.shape)
   out = ax.contourf(xx, yy, Z, cmap=plt.cm.coolwarm, alpha=0.8)

   ax.scatter(X0, X1, c=df.label, cmap=plt.cm.coolwarm, s=20, edgecolors='k')
   ax.set_ylabel('y')
   ax.set_xlabel('x')
   ax.set_title(title)
   plt.show()

Чтобы подогнать и построить модель, сделайте следующее:

df = pd.read_csv('df_base.csv', encoding='utf-8', engine='python')
clf = train_SVM(df)
plot_svm_boundary(clf, df, 'Decision Boundary of SVM trained with a balanced dataset')

decision boundary of an SVM model trained with a balanced dataset

Синие точки на синей стороне и красные точки на красной стороне означают, что модель смогла найти функцию, которая разделяет классы

На изображении выше представлена гиперплоскость базовой модели. На ней мы можем наблюдать, насколько четко разделяются наши классы. Однако что произойдет, если мы разбалансируем наш набор данных? Как будет выглядеть граница принятия решения? Перед этим давайте сбалансируем набор данных, вызвав функцию make_imbalance из пакета imbalanced-learn.

Несбалансированная модель

Вызывая make_imbalance, я вручную устанавливаю стратегию выборки, чтобы иметь более тонкий контроль над тем, как я хочу распределить данные; в данном случае я хочу, чтобы 340 точек принадлежали классу 0 (красный), и 10 точек классу 1 (синий):

X_res, y_res = make_imbalance(X1, Y1, sampling_strategy={0: 340, 1: 10}, random_state=seed)
plt.title('Imbalanced dataset')
plt.xlabel('x')
plt.ylabel('y')
plt.scatter(X_res[:, 0], X_res[:, 1], marker='o', c=y_res,
           s=25, edgecolor='k', cmap=plt.cm.coolwarm)
plt.show()


df = pd.concat([pd.DataFrame(X_res), pd.DataFrame(y_res)], axis=1)
df.columns = ['feature_1', 'feature_2', 'label']
df.to_csv('df_imbalanced.csv', index=False, encoding='utf-8')

Вот как выглядит несбалансированный набор данных:

an imbalanced dataset made of 340 observations of one class and 10 of the other

Сильно несбалансированный набор данных; 10 точек данных может быть недостаточно для модели

Совсем другое дело, верно? Теперь, когда у нас есть очень, очень, несбалансированный набор данных, давайте обучим вторую SVM и сравним границы принятия решений.

df = pd.read_csv('df_imbalanced.csv', encoding='utf-8', engine='python')
clf = train_SVM(df)
plot_svm_boundary(clf, df, 'Decision Boundary of SVM trained with an imbalanced dataset')

"decision boundary of an SVM model trained with an imbalanced dataset

Просто синий.

Полное отсутствие границ принятия решения. Создав сверхсбалансированный набор данных, мы смогли подогнать SVM, который не показывает границы принятия решений. Другими словами, алгоритм не смог обучиться на данных меньшинства, потому что его функция принятия решения встала на сторону класса, имеющего большее количество образцов.

SMOTE модель

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

Создание набора данных SMOTE'd с помощью imbalanced-learn представляет собой несложный процесс. Во-первых, как и в make_imbalance, нам нужно указать стратегию выборки, которую в данном случае я оставил на auto, чтобы алгоритм повторно выбрал весь обучающий набор данных, за исключением класса меньшинства. Затем мы определяем k соседей, которое в данном случае равно 1. Следующий фрагмент кода показывает, как улучшить предыдущий несбалансированный набор данных с помощью SMOTE.

import pandas as pd
import matplotlib.pyplot as plt

from imblearn.over_sampling import SMOTE

# for reproducibility purposes
seed = 100
# SMOTE number of neighbors
k = 1

df = pd.read_csv('df_imbalanced.csv', encoding='utf-8', engine='python')
# make a new df made of all the columns, except the target class
X = df.loc[:, df.columns != 'label']
y = df.label
sm = SMOTE(sampling_strategy='auto', k_neighbors=k, random_state=seed)
X_res, y_res = sm.fit_resample(X, y)

plt.title('Dataset balanced with synthetic or SMOTE'd data ({} neighbors)'.format(k))
plt.xlabel('x')
plt.ylabel('y')
plt.scatter(X_res[:, 0], X_res[:, 1], marker='o', c=y_res,
           s=25, edgecolor='k', cmap=plt.cm.coolwarm)
plt.show()

df = pd.concat([pd.DataFrame(X_res), pd.DataFrame(y_res)], axis=1)
# rename the columns
df.columns = ['feature_1', 'feature_2', 'label']
df.to_csv('df_smoted.csv', index=False, encoding='utf-8')

На следующем изображении показан полученный набор данных.

synthetic dataset created with SMOTE

Вижу ли я счастливое лицо среди красных точек?

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

При дальнейшем рассмотрении оказывается, что эта линия соединяет точки несбалансированных данных. Объяснение этого явления заключается в том, что мы используем k=1. Установка числа соседей равным 1 подразумевает, что во время каждой итерации SMOTE алгоритм создает искусственные данные между точкой, которую он рассматривает в данный момент, и той, к которой он ближе (как мы видели в первом примере).

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

Если мы увеличим k до 2, мы увидим, как расширяется связность между точками.

synthetic dataset created with SMOTE

По-прежнему нет красных данных

При k=8 мы можем наблюдать более яркий, сферический и классический вид набора данных.

synthetic dataset created with SMOTE

От 10 красных точек до 340. Использование SMOTE с восемью ближайшими соседями приводит к набору данных, который может сойти за настоящий, не синтетический набор данных

Если мы сравним этот набор данных с исходным, то увидим, что основное различие заключается в том, насколько плотно самодостаточны новые данные.

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

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

 

df = pd.read_csv('df_smoted.csv', encoding='utf-8', engine='python')
clf = train_SVM(df)
plot_svm_boundary(clf, df, 'Decision Boundary of SVM trained with a synthetic dataset')

Decision boundaries of models trained with real data and synthetic data

Сбалансированная модель и гиперплоскости модели SMOTE'd.

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

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

Подведение итогов и заключение

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

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

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

Вернуться на верх