Введение

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

Смущенная, с сумками, полными закусок и напитков, она снова попробовала карту, но, к сожалению, с теми же результатами. Чуть позже на ее мобильный приходит сообщение: «Нажмите 1, если вы действительно хотите потратить 500 долларов на продукты».

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

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

Следовательно, исследователи из Общества вычислительной разведки IEEE (IEEE-CIS) хотят улучшить этот показатель мошеннических транзакций, а также улучшить качество обслуживания клиентов. Благодаря более высокой точности обнаружения мошенничества клиенты могут совершать покупки без каких-либо хлопот.

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

Оглавление:

  1. Сбор данных
  2. понимание бизнеса
  3. Преобразование бизнес-задачи в задачу машинного обучения
  4. Анализ данных и разработка функций
  5. Построение модели, настройка гиперпараметров и важность функций
  6. Комбинированные результаты
  7. Результаты теста
  8. Развертывание
  9. Конечные примечания

1.Сбор данных

Я собрал этот набор данных с Kaggle. Ниже приведена ссылка.



Набор данных состоит из 2 файлов .csv.

transaction.csv — содержит 394 функции.

indentity.csv — содержит 41 функцию.

Эти два файла объединены столбцом TransactionID. Описание каждой колонки можно найти здесь — ссылка.

2. Понимание бизнеса

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

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

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

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

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

3. Цели и ограничения

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

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

Интерпретируемость— модель должна давать вероятностный вывод (вероятность транзакции относится к классу мошеннических)

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

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

"Показатель AUC — это степень/мера отделимости, и мы хотим, чтобы наша модель хорошо отделяла законные транзакции от мошеннических транзакций"

Мы будем дополнительно использовать Матрицу путаницы, чтобы сделать модели более интерпретируемыми.

4. Анализ данных и функцииРазработка

Во-первых, я загрузил файлы данных transaction.csv и identity.csv и объединил их в столбце TransactionID.

train_df = pd.merge( train_transaction, train_identity, how ='left')
test_df = pd.merge(test_transaction, test_identity, how='left')

Набор данных поезда состоит из 590540 точек данных, а тест состоит из 506691. В совокупности у нас есть 433 столбца, включая целевую переменную «is_fraud», которая имеет двоичные значения: 0 указывает на законную транзакцию, а 1 указывает на мошенническую транзакцию.

Как видите, 96,50% транзакций являются законными, и только 3,50% транзакций являются мошенническими. Это показывает, что это очень несбалансированный набор данных.

Теперь давайте проверим наличие повторяющихся строк и нулевых значений. Многие функции идентификации имеют более 99% пропущенных значений. Лучше удалить эти столбцы.

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

Поскольку здесь невозможно объяснить каждую функцию. Я объясню некоторые интересные особенности и наблюдения.

ТранзакцияDT

Это представляет дельту времени от заданной эталонной даты и времени. Первое минимальное значение TransactionDT равно 86400, что соответствует количеству секунд в сутках (60 минут * 60 секунд * 24 часа = 86400). Следовательно, мы можем рассматривать этот столбец TransactionDT как время в секундах. Здесь мы видим, что тестовые данные опережают данные поезда, поэтому мы не будем делать случайное разделение. Кроме того, одно из наблюдений заключается в том, что в начальный момент транзакции поезда и в последний раз тестовых данных количество транзакций превышает 12500.

Давайте теперь посмотрим на мошенническую транзакцию на неделе и в разные дни.

Здесь мы можем наблюдать, что в первый день недели самые высокие транзакции, а в третий день недели самые низкие транзакции. Из часового графика можно сделать вывод, что с 5-го по 10-й час мошеннических транзакций больше, а с 13-го по 18-й час легальных транзакций сравнительно много.

Сумма_транзакции

Это сумма, связанная с каждой транзакцией в долларах США.

plt.figure(figsize = (20,6))

plt.subplot(1,2,1)
sns.distplot(train_df[train_df['isFraud']==0]['TransactionAmt'])
sns.distplot(train_df[train_df['isFraud']==1]['TransactionAmt'])
plt.ylabel('Probability Density')
plt.legend(['legit','fraud'])
plt.title('Train')
plt.suptitle('TransactionAmt Distribution' , fontsize = 14)
plt.subplot(1,2,2)
sns.distplot(test_df['TransactionAmt'])
plt.ylabel('Probability Density')
plt.title('Test')

plt.show()

Мы можем заметить, что существует несколько выбросов, значения которых > 30000. Лучше удалить эти значения, так как это повлияет на модели, основанные на расстоянии. Подобные выбросы могут привести к проблемам переобучения. Распределение сумм транзакций имеет правосторонние данные (длинный хвост). Следовательно, мы можем преобразовать журнал для этой функции и создать новую функцию с именем LogTransactionAmt, которая имеет нормальное распределение.

Компакт-диск с продуктом

Это код продукта, связанный с каждой транзакцией.

Из приведенного выше графика видно, что если транзакция является мошеннической, вероятность того, что это продукт C, составляет около 40%. Учитывая, что только 10% являются законными транзакциями в C, вероятность того, что транзакция является мошеннической, высока, если она принадлежит продукту C. Большинство транзакций было совершено для ProductCD «W».

Карта

Можно сделать вывод, что большая часть транзакций происходила через Visa и Mastercard. Функция карты6 указывает тип карты — дебетовая или кредитная, и наблюдается, что держатели кредитных карт имеют более высокий процент мошенничества, чем держатели дебетовых карт.

Электронная почта — P_emaildomain и R_emaildomain

Это домены электронной почты покупателя и получателя соответственно. В доменах электронной почты покупателей чаще всего письма приходят с gmail.com. Среди тех больше нет. мошеннических транзакций происходит с домена protonmail.com. R_emaildomain содержит более 70% пропущенных значений.

C_функции

Такие подсчеты, как количество найденных адресов, связанных с платежной картой, и т. д. Фактическое значение маскируется. Это, например, количество телефонных номеров, адресов электронной почты, имя, связанное с пользователем, устройство, платежный адрес как для покупателя, так и для получателя, что удваивает их.

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

M_features

Большинство функций M имеют значения T, F. M4 имеет другие значения, как M2, M0.

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

Тип устройства и информация

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

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

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

import gc
# Maping each device to their corresponding brand.
def device_to_brand(dataframe):
    dataframe['device_name'] = dataframe['DeviceInfo'].str.split('/', expand=True)[0]
    dataframe['device_version'] = dataframe['DeviceInfo'].str.split('/', expand=True)[1]

    dataframe.loc[dataframe['device_name'].str.contains('SM', na=False), 'device_name'] = 'Samsung'
    dataframe.loc[dataframe['device_name'].str.contains('SAMSUNG', na=False), 'device_name'] = 'Samsung'
    dataframe.loc[dataframe['device_name'].str.contains('GT-', na=False), 'device_name'] = 'Samsung'
    dataframe.loc[dataframe['device_name'].str.contains('Moto G', na=False), 'device_name'] = 'Motorola'
    dataframe.loc[dataframe['device_name'].str.contains('Moto', na=False), 'device_name'] = 'Motorola'
    dataframe.loc[dataframe['device_name'].str.contains('moto', na=False), 'device_name'] = 'Motorola'
    dataframe.loc[dataframe['device_name'].str.contains('LG-', na=False), 'device_name'] = 'LG'
    dataframe.loc[dataframe['device_name'].str.contains('rv:', na=False), 'device_name'] = 'RV'
    dataframe.loc[dataframe['device_name'].str.contains('HUAWEI', na=False), 'device_name'] = 'Huawei'
    dataframe.loc[dataframe['device_name'].str.contains('ALE-', na=False), 'device_name'] = 'Huawei'
    dataframe.loc[dataframe['device_name'].str.contains('-L', na=False), 'device_name'] = 'Huawei'
    dataframe.loc[dataframe['device_name'].str.contains('Blade', na=False), 'device_name'] = 'ZTE'
    dataframe.loc[dataframe['device_name'].str.contains('BLADE', na=False), 'device_name'] = 'ZTE'
    dataframe.loc[dataframe['device_name'].str.contains('Linux', na=False), 'device_name'] = 'Linux'
    dataframe.loc[dataframe['device_name'].str.contains('XT', na=False), 'device_name'] = 'Sony'
    dataframe.loc[dataframe['device_name'].str.contains('HTC', na=False), 'device_name'] = 'HTC'
    dataframe.loc[dataframe['device_name'].str.contains('ASUS', na=False), 'device_name'] = 'Asus'

    dataframe.loc[dataframe.device_name.isin(dataframe.device_name.value_counts()[dataframe.device_name.value_counts() < 200].index), 'device_name'] = "Others"
    
    gc.collect()
    
    return dataframe

id_30, 31, 33

Id_30 выглядит как интересная функция и состоит из операционной системы и ее версии, которую люди использовали для совершения транзакций. Id_31 показывает, какую версию браузера используют люди. Id_33 представляет собой высоту и ширину экрана, которые впоследствии можно разделить на отдельные функции.

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

5. Модель построения и настройка гиперпараметров

Теперь давайте сначала удалим сильно коррелированные функции. Существует корреляция между признаками C и D.

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

Мы видим, что функции «C2», «C6», «C10», «C11», «C12», «C14» сильно коррелируют с другими функциями. Точно так же несколько признаков D «D2», «D6», «D7», «D12» сильно коррелированы. Мы удалим эти функции перед обучением модели.

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

# impute numerical value by median for each feature
for i in train_df.columns:
    if train_df[i].dtypes == 'int64' or train_df[i].dtypes == 'int32' or
       train_df[i].dtypes == 'int16' or train_df[i].dtypes == 'int8' or 
       train_df[i].dtypes == 'float64' or train_df[i].dtypes == 'float32' or
       train_df[i].dtypes == 'float16' :
        train_df[i].fillna(train_df[i].median(), inplace = True )
# imputation of categorical features by mode 
for i in train_df.columns:
    if train_df[i].dtypes == 'object':
        train_df[i].fillna(train_df[i].mode()[0], inplace = True)

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

Все функции теперь готовы к передаче в модель. Затем я разделил данные в соотношении 70:30 для обучения и проверки. Но разделение здесь основано не на случайном, а на временном разделении, поскольку мы знаем, что это данные временного ряда, и все тестовые данные идут после данных поезда. После разделения я стандартизирую свои данные, используя функцию StandardScaler(), чтобы стандартизировать значения набора данных в стандартном формате. Этот шаг поможет нам выполнить центрирование среднего и масштабирование дисперсии, где среднее значение будет равно 0, а стандартное значение будет равно 1.

Теперь давайте начнем с обучения модели. Учитывая бизнес-ограничения, я использовал 4 модели: 1) логистическая регрессия, 2) дерево решений, 3) случайный лес и 4) LGBM.

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

parameters = {"alpha":[10**-4, 10**-3, 10**-2, 10**-1, 10**0, 10**1, 10**2, 10**3, 10**4]}
clf = SGDClassifier(loss = "log", class_weight = 'balanced', penalty = "l2")
rand_clf = RandomizedSearchCV(clf, parameters, scoring = "f1", n_iter = 500, n_jobs = -1, cv = 5, random_state = 1, return_train_score = True)
rand_clf.fit(sc_x_train, y_train)
print(rand_clf.best_params_)
print(rand_clf.best_estimator_)

Итак, здесь для настройки гиперпараметров я передал альфа-значения, используя регуляризацию L2, а также присвоил class_weight как сбалансированный, поскольку наши данные сильно несбалансированы, и наша модель не должна быть смещена в сторону большинства классов. Я нашел альфа = 0,01 лучшим, и с этим значением альфа я получил CV AUC как 85,79 и Train AUC как 85,86.

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

Вторая модель, которую я пробовал, это Decision Tree. Эта модель является интерпретируемой, и деревья можно визуализировать. Используемые гиперпараметры, такие как max_depth и критерий.

parameter = {          
             "max_depth":[3, 5, 7, 9, 11, 13, 15],
             "criterion": ["gini", "entropy","log_loss"],                       
            }
clf = DecisionTreeClassifier(class_weight = 'balanced')
rand_clf = RandomizedSearchCV(clf, parameter, scoring = 'f1', n_jobs = -1)
rand_clf.fit(x_train, y_train)
print(rand_clf.best_params_)
print(rand_clf.best_estimator_)

После обучения я получил лучший показатель AUC с max_depth равным 15 и критерием Джини. CV-AUC получил здесь 83,30, а поезд AUC = 94,65.

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

Следующая модель, которую я пробую, — это модель Random Forest. Здесь я использовал другие параметры, как показано ниже.

random_parameter = {
 'bootstrap': [True, False],
 'max_depth': [3, 5, 7, 9, 11, 13, None],
 'max_features': ['auto', 'sqrt'],
 'n_estimators': [25, 50, 100, 200, 300, 500, 700, 900, 1000]
}

Лучший гиперпараметр, который я получил здесь, — это n_estimator со значением 25. Окончательный полученный CV AUC равен 89,71, а показатель Train AUC — 99,99, эта модель работает сравнительно лучше, чем другие модели на основе дерева. Поскольку модель случайного леса менее подвержена влиянию шума и устойчива к выбросам.

Теперь, наконец, давайте попробуем самую мощную модель LGBM.

parameter_lgbm = {
             'max_depth':[1, 3, 4, 5],
             'learning_rate':[0.001, 0.01, 0.1], 
             'n_estimators': [100, 300, 500, 600, 800, 1100],
             'min_child_samples': sp_randint(100, 400), 
             'min_child_weight': [1e-5, 1e-3, 1e-2, 1e-1, 1, 1e1],
             'subsample': sp_uniform(loc = 0.2, scale = 0.8), 
             'reg_alpha': [0, 1e-1, 1, 2, 5, 7],
             'reg_lambda': [0, 1e-1, 1, 5, 10, 12]
             }
# training model with hypertuned lgbm
clf = LGBMClassifier(class_weight = 'balanced')
rand_clf = RandomizedSearchCV(clf, parameter_lgbm, return_train_score = True)
rand_clf.fit(x_train, y_train)
print(rand_clf.best_params_)
print(rand_clf.best_estimator_)

Я настроил эту модель с помощью гиперпараметров с различными параметрами и получил наилучшие результаты с n_estimators равным 1100. CV AUC, полученный с использованием этой модели, составляет 94,77, а Train AUC - 98,91, что является довольно хорошим показателем.

6. Комбинированные результаты

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

7. Тестовый прогноз

Итак, наконец, ребята, после работы над всеми вещами, наше путешествие подошло к концу, теперь пришло время подвести итоги. Так как модель LGBM дает нам наилучшие результаты. Я использовал это как свою окончательную модель для отправки и развертывания.

8. Развертывание

Я развернул свою окончательную модель с помощью веб-фреймворка Flask. Полный код можно найти в моей учетной записи GitHub.

Будущая работа и примечания

Построение модели в реальном мире — это итеративный процесс, в котором завершается и развертывается модель после выполнения всех необходимых шагов для подачи данных в модель. Здесь я получил приличную оценку AUC с использованием модели LGBM, и эту оценку можно увеличить. Наука о данных — это эксперимент. Оценка может быть увеличена путем дальнейшего дополнительного анализа, создания новых функций из существующих функций, преобразования функций, различных подходов к исключению функций и прохождения дискуссионного форума на Kaggle и применения различных моделей повышения. Также для дальнейшего улучшения оценки можно использовать модель глубокого обучения для экспериментов. Это поможет усовершенствовать модель для последовательного получения лучших результатов.

Найти полную работу в моем репозитории GitHub здесь

Спасибо, что читаете мой блог. Надеюсь, вам понравится.

Ссылки