Дисбаланс данных и алгоритм Smote

алгоритм
Дисбаланс данных и алгоритм Smote

Эта статья подготовлена ​​технической командой OPPO Internet, укажите автора для перепечатки. В то же время приглашаем обратить внимание на нашу общедоступную учетную запись: OPPO_tech, чтобы поделиться с вами передовыми интернет-технологиями и деятельностью OPPO.

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

Чтобы решить проблему дисбаланса данных, Чавла предложил алгоритм SMOTE в 2002 году, который был единодушно признан академическими кругами и промышленностью. В этой статье будет проанализирован алгоритм Smote, несколько вариантов алгоритма Smote и исходный код основной реализации алгоритма smote с открытым исходным кодом.

1. Обзор проблемы дисбаланса данных

1.1 Распространенные сценарии дисбаланса данных

Медицинская визуализация: идентификация раковых клеток, соотношение здоровых клеток к раковым клеткам 20:1

Страхование: набор данных о возмещении расходов по автострахованию, соотношение возмещения и невозмещения 26: 1

Астрономия: другие записи и записи о солнечном ветре, соотношение 26: 1

CTR: отношение числа ненажатых и нажатых записей 57:1.

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

1.2 Оценка точности в несбалансированных сценариях

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

В наборе данных по обнаружению мошенничества в коммунальных службах есть следующие данные:

Всего наблюдений = 1000

Наблюдения за мошенничеством (положительные образцы) = 20

Наблюдения без мошенничества (отрицательные образцы) = 980

Доля редких событий = 2%

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

Таким образом, в этом случае показатель точности серьезно завышает производительность модели.

1.3 Оценка AUC в несбалансированных сценариях

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

Частота ложноположительных результатов: FPR = FP/(TN+FP), вероятность ошибочного принятия отрицательного образца за положительный.

Истинная частота случаев: TPR=TP/(TP+FN), вероятность объединения положительного образца.

Конечно, чем ниже FPR, тем лучше, а чем выше TPR, тем лучше.

Значение AUC – это значение вероятности. Когда вы случайным образом выбираете положительный образец и отрицательный образец, вероятность того, что текущий алгоритм классификации ранжирует положительный образец перед отрицательным образцом в соответствии с вычисленным значением Score, является значением AUC. Чем больше AUC значение, текущая классификация. Чем более вероятно, что алгоритм ранжирует положительные выборки выше отрицательных, тем лучше он способен классифицировать.

Общий стандарт оценки AUC:

  • 0,1–0,5: модель работает хуже, чем случайное угадывание

  • 0,5–0,7: Менее эффективен, но достаточно хорош для прогнозирования запасов.

  • 0,7–0,85: довольно эффективно

  • 0,85–0,95: хорошо работает

  • 0,95–1: Очень хороший эффект, но в целом маловероятен.

Следующие небольшие анимации помогут вам лучше понять AUC:

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

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

Рисунок кривой ROC такой же, как и кривой PR, то есть она пересекает все пороги модели, вычисляет FTR и PTR при текущем пороге, а затем соединяет все (FPR, TPR) под порогом в кривая. Видно, что значение AUC модели не изменится при изменении порога.

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

1.4 Небольшой эксперимент: влияние несбалансированных данных на производительность модели

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

использоватьsklearn.datasets make_classificationЧтобы сгенерировать бинарные данные для теста, доля положительных образцов/популяция составляет [0,01, 0,05, 0,1, 0,2, 0,5], данные являются двумерными, а количество образцов равно 500. Подберите модель дерева решений со значениями по умолчанию, вычислите ее значение AUC и нарисуйте границу разделения.

from sklearn.datasets import *
from sklearn.model_selection import *
from sklearn import tree
from sklearn.metrics import *
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
#决策边界绘制函数
def plot_decision_boundary(X_in,y_in,pred_func):
    x_min, x_max = X_in[:, 0].min() - .5, X_in[:, 0].max() + .5
    y_min, y_max = X_in[:, 1].min() - .5, X_in[:, 1].max() + .5
    h = 0.01
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    Z = pred_func(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    plt.contourf(xx, yy, Z,cmap=plt.get_cmap('GnBu'))
    plt.scatter(X_in[:, 0], X_in[:, 1], c=y_in,cmap=plt.get_cmap('GnBu'))
#在不同的正负样本比例下训练决策树,并画出决策边界,计算AUC
for weight_minority in [0.01,0.05,0.1,0.2,0.5]:
X,y=make_classification(n_samples=500,n_features=2,n_redundant=0,random_state=2,n_clusters_per_class=1,weights=(weight_minority,1-weight_minority))
    plt.scatter(X[:,0],X[:,1],c=y)
    X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y,random_state=6)
    clf = tree.DecisionTreeClassifier()
    clf = clf.fit(X_train, y_train)
    plot_decision_boundary(X,y,lambda x: clf.predict(x))
    plt.ion()
    plt.title("Decision Tree with imbalance rate: "+str(weight_minority))
    plt.show()
    print("current auc:"+str(roc_auc_score(y_test, clf.predict(X_test))))
    print("----------------------------------")

Соотношение положительных и отрицательных образцов 1: 100, AUC: 0,5

Соотношение положительных и отрицательных образцов 1:20, AUC: 0,8039

Соотношение положительных и отрицательных образцов 1:10, AUC: 0,9462

Соотношение положительных и отрицательных образцов 1:5, AUC: 0,9615

Соотношение положительных и отрицательных образцов 1:1, AUC: 0,9598

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

2. Общие методы решения проблемы дисбаланса данных

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

2.1 Передискретизация

Случайная избыточная выборка: случайным образом нарисуйте несколько копий отрицательных образцов.

Smote: Метод интерполяции используется для генерации новых выборок Конкретный принцип будет подробно описан в Разделе III.

Adasyn: принцип аналогичен Smote, за исключением того, что при выборе исходных образцов будет использоваться классификатор KNN для выбора тех образцов, которые с большей вероятностью будут неправильно классифицированы.

2.2 Недостаточная выборка

случайная недостаточная выборка

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

EasyEnsemble

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

BalanceCascade

Применяется контролируемый метод в сочетании с бустингом. В n-м раунде обучения подмножество, отобранное из образцов большинства классов, объединяется с образцами класса меньшинства для обучения базового ученика H, а образцы, которые могут быть правильно классифицированы H в классе большинства, будут исключены после обучения.

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

NearMiss

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

NearMiss-1: выберите выборку класса большинства с ближайшим средним расстоянием до ближайших K выборок класса меньшинства.

NearMiss-2: выберите выборку класса большинства с ближайшим средним расстоянием до самых дальних K выборок класса меньшинства.

NearMiss-3: Для каждой выборки класса меньшинства выберите K ближайших выборок класса большинства, чтобы убедиться, что каждая выборка класса меньшинства окружена образцами класса большинства.

Tomek Link

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

Edited Nearest Neighbours (ENN)

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

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

2.3 Передискретизация + Недостаточная выборка

Многие эксперименты показывают, что сочетание избыточной и недостаточной выборки дает лучшие результаты, чем использование этих двух методов по отдельности.Обычные комбинации избыточной и недостаточной выборки включают SMOTE + ENN, SMOTE + Tomek.

2.4 Методы обнаружения аномалий

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

Тестирование статистического метода

Статистический метод также относительно прост и обычно делится на два этапа:

  • Сначала предположим, что все данные подчиняются определенному распределению, такому как обычное нормальное распределение, распределение Пуассона и т. д.;

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

OneClassSVM

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

изолированный лес

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

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

3. Принцип алгоритма Смота и его деформация

3.1 Что такое смот

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

Зачем использовать SMOTE, нельзя ли просто сделать передискретизацию напрямую? Следующий пример взят из оригинальной статьи SMOTE.

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

В частности, для выборки класса меньшинстваX_iИспользуйте метод K-ближайших соседей (значение k необходимо указать заранее), чтобы найти расстояниеX_ik ближайших выборок класса меньшинства, где расстояние определяется как евклидово расстояние в n-мерном пространстве признаков между выборками. Затем случайным образом выберите одного из k ближайших соседей, чтобы сгенерировать новую выборку, используя следующую формулу:

X_{(new)}=X_i+(\hat X-X_I)\times \delta

в\hat X_iДля выбранных k-ближайших соседей δ∈[0,1] является случайным числом. На следующем рисунке показан пример выборки, сгенерированной SMOTE. Используется ближайший сосед 3. Видно, что выборка, сгенерированная SMOTE, обычно находится вX_iи\hat X_iНа соединенных линиях:

Ниже приведен псевдокод оригинальной статьи, заинтересованные студенты могут его внимательно изучить, на самом деле основной код — это строка 22: логика генерации интерполяции.

3.2 Варианты алгоритма Смота

Border-line SMOTE

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

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

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

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

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

  • «шум»: все выборки k-ближайших соседей принадлежат мажоритарному классу.

  • «опасность»: более половины выборок k-ближайших соседей принадлежат классу большинства

  • «безопасный»: более половины образцов k-ближайших соседей принадлежат классу меньшинства

Алгоритм Border-line SMOTE будет только случайным образом выбирать образцы в состоянии «опасности», а затем использовать алгоритм SMOTE для создания новых образцов. Выборки в состоянии «опасности» представляют выборки класса меньшинства вблизи «границы», а выборки вблизи границы часто с большей вероятностью будут ошибочно классифицированы. Таким образом, Border-line SMOTE только искусственно синтезирует выборки класса меньшинства, близкие к «границе», в то время как SMOTE обрабатывает все выборки класса меньшинства одинаково.

SVM SMOTE

Используйте классификатор SVM, чтобы найти опорные векторы, а затем синтезируйте новые выборки на основе опорных векторов. Подобно Broderline Smote, SVM smote также определяет тип выборок (безопасные, опасные, шумовые) в соответствии с атрибутами K-ближайших соседей, а затем использует опасные выборки для обучения SVM.

Kmeans SMOTE

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

SMOTE-NC

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

На следующем рисунке представлена ​​визуализация результатов передискретизации нескольких вариантов Smote:

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

4. Реализация smote с открытым исходным кодом в Imbalanced_learn

Основной реализацией Smote на рынке является проект contrib, несбалансированный_learn от sklearn. Smote, использующий Balanced_learn, соответствует спецификации API sklearn. Ниже приведен пример кода, использующего smote:

>>> from collections import Counter
>>> from sklearn.datasets import make_classification
>>> from imblearn.over_sampling import SMOTE
>>> X, y = make_classification(n_classes=2, class_sep=2,
... weights=[0.1, 0.9], n_informative=3, n_redundant=1, flip_y=0,
... n_features=20, n_clusters_per_class=1, n_samples=1000, random_state=10)

>>> print('Original dataset shape %s' % Counter(y))
Original dataset shape Counter({1: 900, 0: 100})

>>> sm = SMOTE(random_state=42)
>>> X_res, y_res = sm.fit_resample(X, y)
>>> print('Resampled dataset shape %s' % Counter(y_res))

Resampled dataset shape Counter({0: 900, 1: 900})

Отношение наследования классов в исходном коде выглядит следующим образом:

Для анализа исходного кода возьмем boardline_smote и проанализируем коды клавиш нескольких основных классов в порядке снизу вверх (SMOTE->BorderlineSMOTE->BaseSMOTE).

SMOTE

В процессе использования самым базовым классом является SMOTE.Класс SMOTE может использовать оригинальный алгоритм SMOTE, либо использовать BoardlineSmote и SVMSmote путем передачи параметров.

Метод синтеза образцов SMOTEfit_resamplefit_resampleМетод делает две вещи:

Прежде всего, необходимо выполнить работу по проверке модели, в том числе определить, используется ли экземпляр модели KNN последовательно, и определить тип текущего экземпляра Smote.Если текущий алгоритм — BoardLineSMOTE или SVMSmote, укажите соответствующий алгоритм._sampleМетод привязан к текущему объекту;

Второе — позвонить_sampleметод, в котором базовый примитив SMOTE_sampleМетод обучает классификатор KNN, получает K ближайших соседей всех миноритарных классов через KNN, а затем вызывает_make_samplesметод, логика интерполяции, о которой мы упоминали ранее, находится в_make_samplesреализуется в,_make_samplesОн реализован BaseSMOTE, родительским классом для SVMSMOTE и BorderlineSMOTE, о которых мы поговорим позже.

Если используется алгоритм BoardLineSMOTE или SVMSMOTE, он вызовет родительский класс SMOTE, реализованный в SVMSMOTE и BorderlineSMOTE._sampleметод.

class SMOTE(SVMSMOTE, BorderlineSMOTE):
    
    def __init__(self,
                 sampling_strategy='auto',
                 random_state=None,
                 k_neighbors=5,
                 m_neighbors='deprecated',
                 out_step='deprecated',
                 kind='deprecated',
                 svm_estimator='deprecated',
                 n_jobs=1,
                 ratio=None):
        # FIXME: in 0.6 call super()
        BaseSMOTE.__init__(self, sampling_strategy=sampling_strategy,
                           random_state=random_state, k_neighbors=k_neighbors,
                           n_jobs=n_jobs, ratio=ratio)
        self.kind = kind
        self.m_neighbors = m_neighbors
        self.out_step = out_step
        self.svm_estimator = svm_estimator
        self.n_jobs = n_jobs

   #主要用于检测KNN模型实例是否被串用,还有检测当前的Smote实例的类型
    def _validate_estimator(self):
        # FIXME: in 0.6 call super()
        BaseSMOTE._validate_estimator(self)
        # FIXME: remove in 0.6 after deprecation cycle

        #判断模型类型,把具体类型模型的sample方法绑定到当前对象上
        if self.kind != 'deprecated' and not (self.kind == 'borderline-1' or
                                              self.kind == 'borderline-2'):
            if self.kind not in SMOTE_KIND:
                raise ValueError('Unknown kind for SMOTE algorithm.'
                                 ' Choices are {}. Got {} instead.'.format(
                                     SMOTE_KIND, self.kind))
            else:
                warnings.warn('"kind" is deprecated in 0.4 and will be '
                              'removed in 0.6. Use SMOTE, BorderlineSMOTE or '
                              'SVMSMOTE instead.', DeprecationWarning)
            #以BorderLine为例:如果类型是borderline的话,就是用BorderlineSMOTE类的sample方法
            if self.kind == 'borderline1' or self.kind == 'borderline2':
                self._sample = types.MethodType(BorderlineSMOTE._sample, self)
                self.kind = ('borderline-1' if self.kind == 'borderline1'
                             else 'borderline-2')

            elif self.kind == 'svm':
                self._sample = types.MethodType(SVMSMOTE._sample, self)

                if self.out_step == 'deprecated':
                    self.out_step = 0.5
                else:
                    warnings.warn('"out_step" is deprecated in 0.4 and will '
                                  'be removed in 0.6. Use SVMSMOTE class '
                                  'instead.', DeprecationWarning)

                if self.svm_estimator == 'deprecated':
                    warnings.warn('"svm_estimator" is deprecated in 0.4 and '
                                  'will be removed in 0.6. Use SVMSMOTE class '
                                  'instead.', DeprecationWarning)
                if (self.svm_estimator is None or
                        self.svm_estimator == 'deprecated'):
                    self.svm_estimator_ = SVC(gamma='scale',
                                              random_state=self.random_state)
                elif isinstance(self.svm_estimator, SVC):
                    self.svm_estimator_ = clone(self.svm_estimator)
                else:
                    raise_isinstance_error('svm_estimator', [SVC],
                                           self.svm_estimator)

            if self.kind != 'regular':
                if self.m_neighbors == 'deprecated':
                    self.m_neighbors = 10
                else:
                    warnings.warn('"m_neighbors" is deprecated in 0.4 and '
                                  'will be removed in 0.6. Use SVMSMOTE class '
                                  'or BorderlineSMOTE instead.',
                                  DeprecationWarning)

                self.nn_m_ = check_neighbors_object(
                    'm_neighbors', self.m_neighbors, additional_neighbor=1)
                self.nn_m_.set_params(**{'n_jobs': self.n_jobs})

    # FIXME: to be removed in 0.6
    def _fit_resample(self, X, y):
        self._validate_estimator()
        return self._sample(X, y)

    #采样关键函数
    def _sample(self, X, y):
        # FIXME: uncomment in version 0.6
        # self._validate_estimator()
        X_resampled = X.copy()
        y_resampled = y.copy()

        for class_sample, n_samples in self.sampling_strategy_.items():
            if n_samples == 0:
                continue
            target_class_indices = np.flatnonzero(y == class_sample)
            X_class = safe_indexing(X, target_class_indices)
      # 训练一个KNN分类器得到少数类的K近邻
            self.nn_k_.fit(X_class)
            nns = self.nn_k_.kneighbors(X_class, return_distance=False)[:, 1:]
      # 真正进行过采样的方法_make_samples,由BaseSMOTE实现
            X_new, y_new = self._make_samples(X_class, y.dtype, class_sample,
                                              X_class, nns, n_samples, 1.0)
      # 支持对稠密数据和稀疏数据的过采样 
            if sparse.issparse(X_new):
                X_resampled = sparse.vstack([X_resampled, X_new])
                sparse_func = 'tocsc' if X.format == 'csc' else 'tocsr'
                X_resampled = getattr(X_resampled, sparse_func)()
            else:
                X_resampled = np.vstack((X_resampled, X_new))
            y_resampled = np.hstack((y_resampled, y_new))

        return X_resampled, y_resampled

BorderlineSMOTE, этот класс можно вызвать, когда параметр Boardline передается при использовании SMOTE, или его можно вызвать напрямую.Основная функция в этом классе_sampleметод,_sampleсделал две вещи:

Первое, что похоже на SMOTE, проверьте, используется ли классификатор KNN в сочетании, а также проверьте, использует ли пользователь алгоритм borderline-1 или borderline-2.

Во-вторых, это логика генерации новых выборок, где логика интерполяции также вызывает BaseSMOTE._make_samplesметод, отличие Borderline от оригинального SMOTE в том, что сэмплы нужно разделить на безопасные и опасные (этот метод реализован и в BaseSMOTE) для отбора начальных семплов, а затем генерировать новые семплы по разным стратегиям в borderline-1/2 .

См. комментарии в коде для подробной логики.

class BorderlineSMOTE(BaseSMOTE):
       def __init__(self,
                 sampling_strategy='auto',
                 random_state=None,
                 k_neighbors=5,
                 n_jobs=1,
                 m_neighbors=10,
                 kind='borderline-1'):
        super().__init__(
            sampling_strategy=sampling_strategy, random_state=random_state,
            k_neighbors=k_neighbors, n_jobs=n_jobs, ratio=None)
        self.m_neighbors = m_neighbors
        self.kind = kind

    def _validate_estimator(self):
        super()._validate_estimator()
        self.nn_m_ = check_neighbors_object(
            'm_neighbors', self.m_neighbors, additional_neighbor=1)
        self.nn_m_.set_params(**{'n_jobs': self.n_jobs})
        if self.kind not in ('borderline-1', 'borderline-2'):
            raise ValueError('The possible "kind" of algorithm are '
                             '"borderline-1" and "borderline-2".'
                             'Got {} instead.'.format(self.kind))

    # FIXME: rename _sample -> _fit_resample in 0.6
    def _fit_resample(self, X, y):
        return self._sample(X, y)

    def _sample(self, X, y):
        self._validate_estimator()
         #拿到copy
        X_resampled = X.copy()
        y_resampled = y.copy()

        for class_sample, n_samples in self.sampling_strategy_.items():
            if n_samples == 0:
                continue

            #得到少数类的索引
            target_class_indices = np.flatnonzero(y == class_sample)

            #得到少数类样本列表
            X_class = safe_indexing(X, target_class_indices)

            #使用全量样本训练KNN模型(这个模型是用来计算危险样本的)
            self.nn_m_.fit(X)

            #得到危险样本的索引列表
            danger_index = self._in_danger_noise(
                self.nn_m_, X_class, class_sample, y, kind='danger')

            #如果没有危险样本就跳过
            if not any(danger_index):
                continue

            #使用少数类训练一个KNN模型
            self.nn_k_.fit(X_class)
 
            #得到危险样本的近邻
            nns = self.nn_k_.kneighbors(safe_indexing(X_class, danger_index),
                                        return_distance=False)[:, 1:]

            # divergence between borderline-1 and borderline-2
            #borderline-1 采样做插值的近邻只属于少数类
            if self.kind == 'borderline-1':
                # Create synthetic samples for borderline points.
                X_new, y_new = self._make_samples(
                    safe_indexing(X_class, danger_index), y.dtype,
                    class_sample, X_class, nns, n_samples)
                if sparse.issparse(X_new):
                    X_resampled = sparse.vstack([X_resampled, X_new])
                else:
                    X_resampled = np.vstack((X_resampled, X_new))
                y_resampled = np.hstack((y_resampled, y_new))

            #borderline-2 采样做插值的近邻可能属于任何一个类
            elif self.kind == 'borderline-2':
                random_state = check_random_state(self.random_state)
                fractions = random_state.beta(10, 10)

                # only minority
                X_new_1, y_new_1 = self._make_samples(
                    safe_indexing(X_class, danger_index),
                    y.dtype,
                    class_sample,
                    X_class,
                    nns,
                    int(fractions * (n_samples + 1)),
                    step_size=1.)

                # we use a one-vs-rest policy to handle the multiclass in which
                # new samples will be created considering not only the majority
                # class but all over classes.
                X_new_2, y_new_2 = self._make_samples(
                    safe_indexing(X_class, danger_index),
                    y.dtype,
                    class_sample,
                    safe_indexing(X, np.flatnonzero(y != class_sample)),
                    nns,
                    int((1 - fractions) * n_samples),
                    step_size=0.5)

                if sparse.issparse(X_resampled):
                    X_resampled = sparse.vstack(
                        [X_resampled, X_new_1, X_new_2])
                else:
                    X_resampled = np.vstack((X_resampled, X_new_1, X_new_2))
                y_resampled = np.hstack((y_resampled, y_new_1, y_new_2))
        return X_resampled, y_resampled

BaseSMOTE

Это родительский класс для SVMSMOTE и BoardlineSMOTE, он в основном реализует методы, которые должны использовать несколько подклассов, о которых только что упоминалось._make_samples,_in_danger_noiseи_generate_sample.

  • _make_samplesОн в основном реализует логику обхода выборки.

  • _in_danger_noiseОн в основном реализует логику оценки того, является ли образец опасным/безопасным/шумовым.

  • _generate_sampleЭто логика интерполяции, упомянутая ранее.

См. комментарии для подробного анализа кода.

SMOTE_KIND = ('regular', 'borderline1', 'borderline2', 'svm')
class BaseSMOTE(BaseOverSampler):
    """Base class for the different SMOTE algorithms."""
    def __init__(self,
                 sampling_strategy='auto',
                 random_state=None,
                 k_neighbors=5,
                 n_jobs=1,
                 ratio=None):
        super().__init__(
            sampling_strategy=sampling_strategy, ratio=ratio)
        self.random_state = random_state
        self.k_neighbors = k_neighbors
        self.n_jobs = n_jobs

    def _validate_estimator(self):
        """Check the NN estimators shared across the different SMOTE
        algorithms.
        """
        self.nn_k_ = check_neighbors_object(
            'k_neighbors', self.k_neighbors, additional_neighbor=1)
        self.nn_k_.set_params(**{'n_jobs': self.n_jobs})

    #制造合成样本的函数
    def _make_samples(self,
                      X,
                      y_dtype,
                      y_type,
                      nn_data,
                      nn_num,
                      n_samples,
                      step_size=1.):
        """A support function that returns artificial samples constructed along
        the line connecting nearest neighbours.

        Parameters
        ----------
        X : {array-like, sparse matrix}, shape (n_samples, n_features)
            Points from which the points will be created.

        y_dtype : dtype
            The data type of the targets.

        y_type : str or int
            The minority target value, just so the function can return the
            target values for the synthetic variables with correct length in
            a clear format.

        nn_data : ndarray, shape (n_samples_all, n_features)
            Data set carrying all the neighbours to be used

        nn_num : ndarray, shape (n_samples_all, k_nearest_neighbours)
            The nearest neighbours of each sample in `nn_data`.

        n_samples : int
            The number of samples to generate.

        step_size : float, optional (default=1.)
            The step size to create samples.

        Returns
        -------
        X_new : {ndarray, sparse matrix}, shape (n_samples_new, n_features)
            Synthetically generated samples.

        y_new : ndarray, shape (n_samples_new,)
            Target values for synthetic samples.

        """
         #得到当前的Random_state实例
        random_state = check_random_state(self.random_state)
        #得到一个数组,长度为生成样本数,每个值是生成样本使用的哪个近邻(使用一个数字存储行列坐标,要用的时候在拆出来)
        samples_indices = random_state.randint(
            low=0, high=len(nn_num.flatten()), size=n_samples)
        #步长,默认为1,如果超过一,那生成的样本就是在原始样本和近邻的延长线上
        steps = step_size * random_state.uniform(size=n_samples)
        #除近邻个数得到行数
        rows = np.floor_divide(samples_indices, nn_num.shape[1])
        #求近邻个数求余得到列数
        cols = np.mod(samples_indices, nn_num.shape[1])
        #创建生成样本的标签列
        y_new = np.array([y_type] * len(samples_indices), dtype=y_dtype)
 
        #如果输入的X是稀疏矩阵
        if sparse.issparse(X):
            #记录行列
            row_indices, col_indices, samples = [], [], []

            for i, (row, col, step) in enumerate(zip(rows, cols, steps)):
                #如果当前样本非空
                if X[row].nnz:
                    #生成合成样本
                    sample = self._generate_sample(X, nn_data, nn_num,
                                                   row, col, step)
                    #记录行列indece
                    row_indices += [i] * len(sample.indices)
                    col_indices += sample.indices.tolist()
                    #记录样本
                    samples += sample.data.tolist()
             #返回生成样本构成的稀疏矩阵csr_matrix
            return (sparse.csr_matrix((samples, (row_indices, col_indices)),
                                      [len(samples_indices), X.shape[1]],
                                      dtype=X.dtype),
                    y_new)

 #如果不是稀疏矩阵
        else:
 #构造返回结果集ndarray
            X_new = np.zeros((n_samples, X.shape[1]), dtype=X.dtype)
            for i, (row, col, step) in enumerate(zip(rows, cols, steps)):
 #对当前的行列生成一个样本,并放到第i个位置
                X_new[i] = self._generate_sample(X, nn_data, nn_num,
                                                 row, col, step)
            return X_new, y_new‘

 #生成样本的函数
    def _generate_sample(self, X, nn_data, nn_num, row, col, step):
        r"""Generate a synthetic sample.

        The rule for the generation is:

        .. math::
           \mathbf{s_{s}} = \mathbf{s_{i}} + \mathcal{u}(0, 1) \times
           (\mathbf{s_{i}} - \mathbf{s_{nn}}) \,

        where \mathbf{s_{s}} is the new synthetic samples, \mathbf{s_{i}} is
        the current sample, \mathbf{s_{nn}} is a randomly selected neighbors of
        \mathbf{s_{i}} and \mathcal{u}(0, 1) is a random number between [0, 1).

        Parameters
        ----------
        X : {array-like, sparse matrix}, shape (n_samples, n_features)
            Points from which the points will be created.

        nn_data : ndarray, shape (n_samples_all, n_features)
            Data set carrying all the neighbours to be used.

        nn_num : ndarray, shape (n_samples_all, k_nearest_neighbours)
            The nearest neighbours of each sample in `nn_data`.

        row : int
            Index pointing at feature vector in X which will be used
            as a base for creating new sample.

        col : int
            Index pointing at which nearest neighbor of base feature vector
            will be used when creating new sample.

        step : float
            Step size for new sample.

        Returns
        -------
        X_new : {ndarray, sparse matrix}, shape (n_features,)
            Single synthetically generated sample.

        """
        #X[row]为原始样本
        #step是随机比值
        #nn_num[row, col]是当前所用的近邻在全量样本中的索引值
        #nn_data[nn_num[row, col]]为近邻样本
        return X[row] - step * (X[row] - nn_data[nn_num[row, col]])

    #检查样本是否处于危险之中或者是噪声
    def _in_danger_noise(self, nn_estimator, samples, target_class, y,
                         kind='danger'):
        """Estimate if a set of sample are in danger or noise.

        Used by BorderlineSMOTE and SVMSMOTE.

        Parameters
        ----------
        nn_estimator : estimator
            An estimator that inherits from
            :class:`sklearn.neighbors.base.KNeighborsMixin` use to determine if
            a sample is in danger/noise.

        samples : {array-like, sparse matrix}, shape (n_samples, n_features)
            The samples to check if either they are in danger or not.

        target_class : int or str
            The target corresponding class being over-sampled.

        y : array-like, shape (n_samples,)
            The true label in order to check the neighbour labels.

        kind : str, optional (default='danger')
            The type of classification to use. Can be either:

            - If 'danger', check if samples are in danger,
            - If 'noise', check if samples are noise.

        Returns
        -------
        output : ndarray, shape (n_samples,)
            A boolean array where True refer to samples in danger or noise.

        """
        #拿到目标样本的K近邻放进一个矩阵
        x = nn_estimator.kneighbors(samples, return_distance=False)[:, 1:]
 
        #多数类的标签列表,多数类为1,少数类为0
        nn_label = (y[x] != target_class).astype(int)

        #多数类的个数
        n_maj = np.sum(nn_label, axis=1)

        #如果是求危险值
        if kind == 'danger':
            # Samples are in danger for m/2 <= m' < m
            #如果多数类的数量大于大于一半切小于K,那就是危险样本、
            #这里使用bitwise对条件值进行按位与,效率比循环高
            return np.bitwise_and(n_maj >= (nn_estimator.n_neighbors - 1) / 2,
                                  n_maj < nn_estimator.n_neighbors - 1)
        elif kind == 'noise':
            # Samples are noise for m = m'
            #所有的近邻都是多数类,那就是噪声
            return n_maj == nn_estimator.n_neighbors - 1
        else:
            raise NotImplementedError

Ссылка на источник:GitHub.com/SCI комплект-Обучение…

5. Случай практического применения Smote

5.1 Параметр Smote в Imbalanced_learn


SMOTE(ratio='auto', random_state-None, k_neighbors=5, m_neighbors=10,
  out_step=0.5, kind="regular", svm_estimator=None, n_jobs=1)

соотношение:Он используется для указания пропорции повторной выборки.Если вы укажете значение символа, это может быть «меньшинство», что означает выборку выборок категории меньшинства, «большинство», что означает выборку выборок категории большинства , а «не меньшинство» означает использование меньшего метода выборки, «все» означает метод передискретизации, значение по умолчанию — «авто», что эквивалентно «всем» и «не меньшинству»; если указано типичное значение слова , ключ — это метка каждой категории, а значение — размер выборки в категории;

случайное_состояние:Начальное число, используемое для указания генератора случайных чисел, по умолчанию — None, что означает использование генератора случайных чисел по умолчанию;

k_соседи:Укажите количество соседей, по умолчанию 5;

м_соседи:Укажите количество выборок, случайно выбранных из ближайших соседних выборок, по умолчанию 10;

своего рода:Он используется для указания параметров, используемых алгоритмом SMOTE при создании новых образцов.По умолчанию используется значение «обычный», что означает, что образцы нескольких категорий выбираются случайным образом, а также могут быть «граница1», «граница2» и « свм';

svm_estimator:Используется для указания классификатора SVM, значение по умолчанию — sklearn.svm.SVC. Назначение этого параметра — использовать машинный классификатор опорных векторов для генерации опорных векторов, а затем генерировать новые выборки классов меньшинства;

n_jobs:Он используется для указания количества ЦП, требуемого алгоритму SMOTE при передискретизации.Значение по умолчанию равно 1, что означает, что для запуска алгоритма используется только 1 ЦП, то есть функция параллельной работы не используется.

5.2 Приложение SMOTE набора данных CHURN от Deutsche Telekom об оттоке пользователей

Набор данных получен из исторических данных о транзакциях клиентов телекоммуникационной отрасли в Германии.Набор данных содержит в общей сложности записи 5000 и функции 17. Метка оттока представляет собой двоичную переменную, да означает отток клиентов, а нет означает отсутствие оттока клиентов;

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

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn import model_selection
from sklearn import tree
from imblearn.over_sampling import SMOTE
from sklearn.metrics import *
from sklearn.linear_model import LogisticRegression
#清洗数据
churn=pd.read_csv(r'C:\Users\Administrator\Desktop\Work\data\churn_all.txt',sep='\t')
churn=churn.drop(['Instance_ID'],axis=1)
col=['State', 'Account Length', 'Area Code', 'Phone', 'Intl Plan', 'VMail Plan', 'VMail Message', 'Day Mins', 'Day Calls', 'Day Charge', 'Eve Mins', 'Eve Calls', 'Eve Charge', 'Night Mins', 'Night Calls', 'Night Charge', 'Intl Mins', 'Intl Calls', 'Intl Charge', 'CustServ Calls', 'churn'] 
churn.columns=col
churn['churn'].value_counts()
#正负样本比例5:1
plt.rcParams['font.sans-serif']=['Microsoft Yahei']
plt.axes(aspect='equal')
counts=churn.churn.value_counts()
plt.pie(x=counts,labels=pd.Series(counts.index))
plt.show()
#数据清洗
churn.drop(labels=['State','Area Code','Phone'],axis=1,inplace=True)
churn['Intl Plan']=churn['Intl Plan'].map({' no':0,' yes':1})
churn['VMail Plan']=churn['VMail Plan'].map({' no':0,' yes':1})
churn['churn']=churn['churn'].map({' False.':0,' True.':1})
#构建训练集和测试集
predictors=churn.columns[:-1]
X_train,X_test,y_train,y_test=model_selection.train_test_split(churn[predictors],churn.churn,random_state=12)
#使用不平衡数据训练lr模型,查看AUC
lr=LogisticRegression()
lr.fit(X_train,y_train)
pred=lr.predict(X_test)
roc_auc_score(y_test, pred)
#打印ROC曲线,计算AUC
fpr,tpr,threshold=metrics.roc_curve(y_test,pred)
roc_auc=metrics.auc(fpr,tpr)
plt.stackplot(fpr,tpr,color='steelblue',alpha=0.5,edgecolor='black')
plt.plot(fpr,tpr,color='black',lw=1)
plt.plot([0,1],[0,1],color='red',linestyle='--')
plt.text(0.5,0.3,'ROC cur (area=%0.3f)' % roc_auc)
plt.xlabel('1-Specificity')
plt.ylabel('Sensitivity')
plt.show()
#使用SMOTE进行重采样
over_samples=SMOTE(random_state=1234)
over_samples_X,over_samples_y=over_samples.fit_sample(X_train,y_train)
#采样前后样本数对比
print(y_train.value_counts())
print(pd.Series(over_samples_y).value_counts())
#使用均衡数据训练LR模型
lr2=LogisticRegression()
lr2.fit(over_samples_X,over_samples_y)
fpr,tpr,threshold=metrics.roc_curve(y_test,pred2)
roc_auc=metrics.auc(fpr,tpr)
plt.stackplot(fpr,tpr,color='steelblue',alpha=0.5,edgecolor='black')
plt.plot(fpr,tpr,color='black',lw=1)
plt.plot([0,1],[0,1],color='red',linestyle='--')
plt.text(0.5,0.3,'ORC cur (area=%0.3f)' % roc_auc)
plt.xlabel('1-Specificity')
plt.ylabel('Sensitivity')
plt.show()

Соотношение положительных и отрицательных образцов составляет 5:1.

AUC до недостаточной выборки: 0,55

AUC после недостаточной выборки: 0,735

AUC модели, обученной на данных с передискретизацией, увеличилась на 20%!

использованная литература

1 How to handle Imbalanced Classification Problems in machine learning?

Woohoo.аналитика vi.com/blog/2017/0…

2 SMOTE: Synthetic Minority Over-sampling Technique

3. Проблема дисбаланса данных — оценка алгоритма SMOTE

blog.CSDN.net/QQ_33472765…

4 Imbalanced learn User Guide

несбалансированный-learn.org/en/stable/U….

5 A scikit-learn-contrib to tackle learning from imbalanced data

Леметр.GitHub.IO/talks/2018_…

Наконец, кстати, отправьте несколько объявлений о вакансиях.

OPPO набирает несколько вакансий в области интернет-технологий:

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

Доставка резюме: chenquan#oppo.com

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

Возобновление доставки: liushun#oppo.com

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

Возобновление доставки: ping.wang#oppo.com