Цель этого проекта — создать модель машинного обучения, которая использует алгоритмы и методы классификации для точной идентификации мошеннических и законных транзакций.
В этой статье я буду использовать набор данных Обнаружение мошеннических транзакций от Kaggle для создания модели машинного обучения.
Введение данных
- ID: представляет номер строки транзакции в данных.
- TRANSACTION_ID: представляет собой уникальный идентификатор для каждой транзакции.
- TX_DATETIME: представляет дату и время транзакции.
- CUSTOMER_ID: уникальный идентификатор клиента, совершившего транзакцию.
- TERMINAL_ID: уникальный идентификатор терминала, на котором была совершена транзакция.
- TX_AMOUNT: представляет сумму транзакции.
- TX_TIME_SECONDS: представляет продолжительность транзакции в секундах.
- TX_TIME_DAYS: представляет продолжительность транзакции в днях.
- TX_FRAUD: указывает, является ли транзакция мошеннической или нет, где 0 представляет законную транзакцию, а 1 представляет мошенническая транзакция.
- TX_FRAUD_SCENARIO: представляет тип обнаруженного мошеннического сценария, если таковой имеется. Значение 0 указывает на отсутствие мошенничества, а другие значения указывают на различные типы сценариев мошенничества.
Шаг 1 — проверка и предварительная обработка данных
Во-первых, мы импортируем библиотеки, которые будем использовать в этом проекте. Затем мы читаем CSV-файл, содержащий набор данных, который мы будем использовать для анализа.
import pandas as pd import numpy as np import matplotlib.pyplot as plt import plotly.express as px import seaborn as sns df = pd.read_csv('/Final Transactions.csv')
Далее мы рассмотрим информацию о наборе данных как размерность и типы данных фрейма данных, используя приведенные ниже коды.
df.shape (1754155, 10) df.head(10)
Мы видим, что набор данных имеет 1 754 155 строк и 10 столбцов. Целевой функцией является столбец TX_FRAUD
.
Теперь мы отобразим все типы данных столбцов, чтобы помочь нам понять, с какими данными мы имеем дело, с точки зрения числового и категориального распределения. Это также прольет свет на некоторые исследовательские графики и графики, которые можно использовать.
df.dtypes Unnamed: 0 int64 TRANSACTION_ID int64 TX_DATETIME object CUSTOMER_ID int64 TERMINAL_ID int64 TX_AMOUNT float64 TX_TIME_SECONDS int64 TX_TIME_DAYS int64 TX_FRAUD int64 TX_FRAUD_SCENARIO int64 dtype: object
Мы видим несколько категориальных (объектных) признаков. Мы также видим, что целью является целое число, представленное как 1 для мошеннических транзакций и 0 для немошеннических транзакций. Это делает target
бинарной функцией.
df.head(10)
Далее мы рассмотрим статистические данные, такие как процентиль и среднее значение. а также проверить, есть ли какие-либо пропущенные значения в наборе данных.
df.describe().round(2)
df.isna().sum() Unnamed: 0 0 TRANSACTION_ID 0 TX_DATETIME 0 CUSTOMER_ID 0 TERMINAL_ID 0 TX_AMOUNT 0 TX_TIME_SECONDS 0 TX_TIME_DAYS 0 TX_FRAUD 0 TX_FRAUD_SCENARIO 0 dtype: int64
Судя по выходным данным, во фрейме данных нет пропущенных значений, поэтому нам не нужно удалять или заменять какие-либо выборки из фрейма данных.
Шаг 2 — Исследовательский анализ данных
EDA, сокращенно от исследовательского анализа данных, представляет собой начальную фазу процесса обработки данных, цель которого — получить как можно больше информации о данных, сводя к минимуму время, затрачиваемое на это. С помощью EDA мы можем получить интуитивно понятный и практичный обзор данных, который включает в себя структуру набора данных, возможные подходы к очистке, целевую переменную и методы моделирования.
Мы изучим распределение целевой функции, чтобы изучить баланс целевой функции. Мы будем использовать диаграмму, чтобы построить распределение и получить интуитивное представление о распределении.
count1 = 0 count0 = 0 for i in df['TX_FRAUD'].values: if i == 1: count1 += 1 else: count0 += 1 count1 = (count1/len(df['TX_FRAUD']))*100 count0 = (count0/len(df['TX_FRAUD']))*100 x = ['Fraudulent Transaction(TARGET=1)','Legitimate Transaction(TARGET=0)'] y = [count1, count0] explode = (0.1, 0) # only "explode" the 1st slice fig1, ax1 = plt.subplots() ax1.pie(y, explode=explode, labels=x, autopct='%1.1f%%', shadow=True, startangle=110) ax1.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle. plt.title('Data Distribution',fontsize=25) plt.show()
Прежде всего, давайте выясним корреляцию между каждыми функциями в наборе данных.
df_corr = df.drop(['TRANSACTION_ID', 'Unnamed: 0','CUSTOMER_ID','TERMINAL_ID'], axis=1) corr = df_corr.corr() plt.figure(figsize=(6,6)) sns.heatmap(corr, cmap='RdBu_r', annot=True, vmax=1, vmin=-1) plt.show()
Далее мы будем отображать количество мошеннических и немошеннических транзакций за каждый месяц, чтобы выяснить, есть ли какие-либо потенциальные тенденции или выбросы.
df["month"] = pd.DatetimeIndex(df["TX_DATETIME"]).month grouped = df.groupby(["month", "TX_FRAUD"]).size().reset_index(name="count") fig = px.bar(grouped, x="month", y="count", color="TX_FRAUD", barmode="group", labels={"month": "Month", "count": "Number of Transactions", "TX_FRAUD": "Transaction Type"}) fig.show()
Кроме того, мы также можем изучить частоту каждого сценария мошенничества для мошеннических транзакций.
df = df[(df['TX_FRAUD_SCENARIO'] != 0) & (df['TX_AMOUNT'] != 0)] grouped = df.groupby("TX_FRAUD_SCENARIO").size().reset_index(name="count") fig = px.bar(grouped, x="TX_FRAUD_SCENARIO", y="count", color="TX_FRAUD_SCENARIO", labels={"TX_FRAUD_SCENARIO": "Fraud Scenario", "count": "Number of Transactions"}) fig.show()
Кроме того, мы можем глубже изучить мошеннические транзакции, извлекая среднюю транзакцию для каждого сценария мошенничества.
df = df[(df['TX_FRAUD_SCENARIO'] != 0) & (df['TX_AMOUNT'] != 0)] grouped = df.groupby('TX_FRAUD_SCENARIO')['TX_AMOUNT'].mean().reset_index(name='mean_amount') fig = px.bar(grouped, x='TX_FRAUD_SCENARIO', y='mean_amount', labels={'TX_FRAUD_SCENARIO':'Fraud Scenario','mean_amount':'Average Transaction Amount'}) fig.show()
Кроме того, мы будем изучать частоту мошеннических транзакций в каждый отдельный день. График обеспечит ценную визуализацию временных тенденций мошеннической деятельности в течение заданного периода времени.
df['TX_DATETIME'] = pd.to_datetime(df['TX_DATETIME']) df['date'] = df['TX_DATETIME'].dt.date grouped = df[df['TX_FRAUD'] == 1].groupby('date').size().reset_index(name='count') fig = px.line(grouped, x='date', y='count', labels={'date':'Date','count':'Number of Fraud Transactions'}) fig.show()
Шаг 3. Выбор показателей
Основным приоритетом в обнаружении мошенничества с кредитными картами является минимизация количества ложноотрицательных результатов, поскольку они могут привести к значительным финансовым потерям. Крайне важно принять необходимые меры для предотвращения мошеннических транзакций, и любой случай классификации мошеннических транзакций как законных транзакций будет крайне пагубным.
Таким образом, отзыв является более важным показателем оценки, чем точность, когда речь идет о большинстве случаев обнаружения с высоким риском.
Шаг 4 — Манипуляции с данными
Выбор функций
df_features = df.drop(['TRANSACTION_ID', 'TX_DATETIME','Unnamed: 0','CUSTOMER_ID','TERMINAL_ID','TX_FRAUD_SCENARIO'], axis=1) print(df_features.head())
Стратифицированное разделение набора данных для обучения и тестирования
При использовании стратифицированного разбиения для разделения набора данных на наборы для обучения и проверки цель состоит в том, чтобы гарантировать, что пропорция каждого класса в разбиениях остается неизменной.
from sklearn.model_selection import train_test_split X = df_features.drop(["TX_FRAUD"], axis=1) y = df_features["TX_FRAUD"] X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, test_size=0.3, random_state=42)
Масштабирование функций
from sklearn.preprocessing import StandardScaler def Standard_Scaler (df, col_names): features = df_features[col_names] scaler = StandardScaler().fit(features.values) features = scaler.transform(features.values) df_features[col_names] = features return df col_names = ["TX_FRAUD"] X_train = Standard_Scaler (X_train, col_names) X_test = Standard_Scaler (X_test, col_names)
Шаг 4. Моделирование
4.1 Классификация случайного леса
4.1.1 Без передискретизации
Стратифицированная K-кратная перекрестная проверка
Самая оптимальная модель машинного обучения — это не та, которая просто дает точные прогнозы на обучающих данных, а та, которая хорошо работает с новыми данными, избегая при этом таких проблем, как переобучение и недообучение.
Перекрестная проверка — это метод, используемый для оценки моделей машинного обучения на небольшой выборке данных. Этот метод включает разбиение выборки данных на K групп, где K — единственный параметр, определяющий количество групп. Этот процесс известен как K-кратная перекрестная проверка.
from sklearn.model_selection import StratifiedKFold from sklearn.ensemble import RandomForestClassifier #We are going to ensure that we have the same splits of the data every time. #We can ensure this by creating a K-Fold object, kf, and passing cv=kf instead of the more common cv=5. kf = StratifiedKFold(n_splits=5, shuffle=False) rf = RandomForestClassifier(n_estimators=100, random_state=13)
Напомним: Способность модели находить все соответствующие наблюдения в наборе данных.
Количество истинных положительных результатов, деленное на количество истинных положительных результатов плюс количество ложноотрицательных результатов.
from sklearn.model_selection import cross_val_score score = cross_val_score(rf, X_train, y_train, cv=kf, scoring='recall') print("Cross Validation Recall scores are: {}".format(score)) print("Average Cross Validation Recall score: {}".format(score.mean()))
Настройка гиперпараметров с помощью GridSearchCV
GridSearchCV — это метод, используемый для определения идеальных значений гиперпараметров из указанной сетки параметров, и, по сути, это метод перекрестной проверки.
Хотя параметры, определенные GridSearchCV, считаются «наилучшими» возможными значениями, они оптимальны только в пределах параметров, включенных в сетку параметров.
from sklearn.model_selection import GridSearchCV params = { 'n_estimators': [1, 10, 20], 'max_depth': [2, 4, 6, 8], 'random_state': [13] } grid_rf = GridSearchCV(rf, param_grid=params, cv=kf, scoring='recall').fit(X_train, y_train) print('Best parameters:', grid_rf.best_params_) print('Best score:', grid_rf.best_score_)
y_pred = grid_rf.predict(X_test) from sklearn.metrics import accuracy_score, confusion_matrix from sklearn.metrics import precision_score, recall_score, f1_score cm = confusion_matrix(y_test, y_pred) rf_Recall = recall_score(y_test, y_pred) rf_Precision = precision_score(y_test, y_pred) rf_f1 = f1_score(y_test, y_pred) rf_accuracy = accuracy_score(y_test, y_pred) print(cm) [[455456 0] [ 2471 68320]]
Давайте посмотрим на сводку баллов по тестовому набору.
ndf = [(rf_Recall, rf_Precision, rf_f1, rf_accuracy)] rf_score = pd.DataFrame(data = ndf, columns=['Recall','Precision','F1 Score', 'Accuracy']) rf_score.insert(0, 'Random Forest with', 'No Under/Oversampling') rf_score
4.1.2 Случайная передискретизация
Случайная избыточная выборка включает в себя создание новой преобразованной версии обучающего набора данных путем дублирования примеров в классе меньшинства.
from imblearn.over_sampling import RandomOverSampler from collections import Counter # Randomly over sample the minority class ros = RandomOverSampler(random_state=42) X_train_ros, y_train_ros= ros.fit_resample(X_train, y_train) # Check the number of records after over sampling print(sorted(Counter(y_train_ros).items())) [(0, 1062730), (1, 1062730)]
Мы будем визуализировать распределение данных после передискретизации, используя пир-диаграмму.
count1 = 0 count0 = 0 for i in y_train_ros.values: if i == 1: count1 += 1 else: count0 += 1 count1 = (count1/len(y_train_ros))*100 count0 = (count0/len(y_train_ros))*100 x = ['Fraudulent Transaction(TARGET=1)','Legitimate Transaction(TARGET=0)'] y = [count1, count0] explode = (0.1, 0) # only "explode" the 1st slice fig1, ax1 = plt.subplots() ax1.pie(y, explode=explode, labels=x, autopct='%1.1f%%', shadow=True, startangle=110) ax1.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle. plt.title('Data Distribution After Balancing',fontsize=25) plt.show()
Конвейер
Цель конвейера состоит в том, чтобы собрать несколько шагов, которые могут быть проверены вместе при установке различных параметров.
В процессе перекрестной проверки набор данных разбивается на сегменты обучения и проверки. Во время каждого сегмента класс меньшинства подвергается избыточной выборке, классификатор обучается на обучающем сегменте и проверяется на оставшемся сегменте.
Конвейерный подход является эффективным методом для выполнения этих задач.
from imblearn.pipeline import Pipeline, make_pipeline random_overs_pipeline = make_pipeline(RandomOverSampler(random_state=42), RandomForestClassifier(n_estimators=20, random_state=13)) #cross_val_score(random_overs_pipeline, X_train, y_train, scoring='recall', cv=kf) score2 = cross_val_score(random_overs_pipeline, X_train, y_train, scoring='recall', cv=kf) print("Cross Validation Recall Scores are: {}".format(score2)) print("Average Cross Validation Recall score: {}".format(score2.mean()))
new_params = {'randomforestclassifier__' + key: params[key] for key in params} grid_over_rf = GridSearchCV(random_overs_pipeline, param_grid=new_params, cv=kf, scoring='recall', return_train_score=True) grid_over_rf.fit(X_train, y_train) print('Best parameters:', grid_over_rf.best_params_) print('Best score:', grid_over_rf.best_score_)
y_pred = grid_over_rf.best_estimator_.named_steps['randomforestclassifier'].predict(X_test) cm = confusion_matrix(y_test, y_pred) over_rf_Recall = recall_score(y_test, y_pred) over_rf_Precision = precision_score(y_test, y_pred) over_rf_f1 = f1_score(y_test, y_pred) over_rf_accuracy = accuracy_score(y_test, y_pred) print(cm) [[455456 0] [ 2471 68320]] ndf = [(over_rf_Recall, over_rf_Precision, over_rf_f1, over_rf_accuracy)] over_rf_score = pd.DataFrame(data = ndf, columns=['Recall','Precision','F1 Score', 'Accuracy']) over_rf_score.insert(0, 'Random Forest with', 'Random Oversampling') over_rf_score
4.2 Классификация повышения градиента
Мы повторим те же шаги, что и в предыдущей модели.
4.2.1 Без избыточной выборки
from sklearn.ensemble import GradientBoostingClassifier gb = GradientBoostingClassifier() score3 = cross_val_score(gb, X_train, y_train, cv=kf, scoring='recall') print("Cross Validation Recall scores are: {}".format(score3)) print("Average Cross Validation Recall Score: {}".format(score3.mean()))
params = { 'n_estimators': [1, 10, 20], 'max_depth': [2, 4, 6, 8], 'random_state': [13] } grid_gb = GridSearchCV(gb, param_grid=params, cv=kf, scoring='recall').fit(X_train, y_train) print('Best parameters:', grid_gb.best_params_) print('Best score:', grid_gb.best_score_)
y_pred_GB = grid_gb.predict(X_test) cm = confusion_matrix(y_test, y_pred_GB) gb_Recall = recall_score(y_test, y_pred_GB) gb_Precision = precision_score(y_test, y_pred_GB) gb_f1 = f1_score(y_test, y_pred_GB) gb_accuracy = accuracy_score(y_test, y_pred_GB) print(cm) [[455456 0] [ 2471 68320]] ndf = [(gb_Recall, gb_Precision, gb_f1, gb_accuracy)] gb_score = pd.DataFrame(data = ndf, columns=['Recall','Precision','F1 Score', 'Accuracy']) gb_score.insert(0, 'Gradient Boosting with', 'No Under/Oversampling') gb_score
4.2.2 Случайная передискретизация
#Pipeline gb_overs_pipeline = make_pipeline(RandomOverSampler(random_state=42), GradientBoostingClassifier(n_estimators=20, random_state=13)) score4 = cross_val_score(gb_overs_pipeline, X_train, y_train, scoring='recall', cv=kf) print("Cross Validation Recall Scores are: {}".format(score4)) print("Average Cross Validation Recall score: {}".format(score4.mean()))
new_params = {'gradientboostingclassifier__' + key: params[key] for key in params} grid_over_gb = GridSearchCV(gb_overs_pipeline, param_grid=new_params, cv=kf, scoring='recall', return_train_score=True) grid_over_gb.fit(X_train, y_train) print('Best parameters:', grid_over_gb.best_params_) print('Best score:', grid_over_gb.best_score_)
y_pred_overGB = grid_over_gb.best_estimator_.named_steps['gradientboostingclassifier'].predict(X_test) cm = confusion_matrix(y_test, y_pred_overGB) over_gb_Recall = recall_score(y_test, y_pred_overGB) over_gb_Precision = precision_score(y_test, y_pred_overGB) over_gb_f1 = f1_score(y_test, y_pred_overGB) over_gb_accuracy = accuracy_score(y_test, y_pred_overGB) print(cm) [[455449 7] [ 2471 68320]] ndf = [(over_gb_Recall, over_gb_Precision, over_gb_f1, over_gb_accuracy)] over_gb_score = pd.DataFrame(data = ndf, columns=['Recall','Precision','F1 Score', 'Accuracy']) over_gb_score.insert(0, 'Gradient Boosting with', 'Oversampling') over_gb_score
Заключение
Наконец, мы сравним производительность всех моделей в таблице.
predictions = pd.concat([rf_score, over_rf_score, gb_score, over_gb_score], ignore_index=True, sort=False) predictions.sort_values(by=['Recall'], ascending=False)
Подводя итог, можно сделать следующие наблюдения на основе таблицы:
- Все модели имеют одинаковый показатель отзыва, который составляет 96,5%.
- Модель Gradient Boosting Oversampling имеет более низкую точность и показатель точности по сравнению с другими моделями.
Существуют также другие методы повторной выборки и метрики, которые мы можем рассмотреть, прежде чем выбрать лучшую модель для использования в качестве модели обнаружения мошеннических транзакций.
Методы повторной выборки:
- Неполная выборка
- УДАР
- Класс Вес
Метрики:
- Кривая AUC-ROC