Выполнение обнаружения выбросов в реальном времени для выявления мошеннических покупок по кредитным картам с использованием DBSCAN и Deephaven
Дж.Дж. Броснан
Мошенничество с кредитными картами ежегодно наносит ущерб в миллиарды долларов. Самые печально известные случаи затронули от десятков до сотен миллионов потребителей в результате единичных атак из-за незаконного раскрытия личной информации (PII), связанной с кредитными картами. Отдельные случаи также распространены и могут быть вызваны различными методами, включая скимминг, социальную инженерию и мошенничество с приложениями.
Чтобы защитить своих клиентов, компании, выпускающие кредитные карты, полагаются на программное обеспечение для обнаружения и предотвращения мошенничества для анализа покупок по кредитным картам. Эти программы ищут необычные или неожиданные шаблоны, чтобы классифицировать их как возможно мошеннические. В этом блоге мы предлагаем собственное решение для обнаружения мошенничества с кредитными картами в реальном времени с использованием Python и Deephaven.
Код в этом блоге использует SciKit-Learn, который не поставляется с базовыми изображениями Deephaven. Чтобы запустить этот код, убедитесь, что у вас установлен модуль. Вот как вы можете Установить пакеты Python и Использовать пакеты Python в запросах. У нас также есть руководство Как использовать SciKit-Learn в Дипхейвене.
Набор данных
Мы используем набор данных о мошенничестве с кредитными картами, который находится в открытом доступе на Kaggle и в репозитории примеров Deepphaven.
Набор данных состоит из 284 807 покупок по кредитным картам в течение 48 часов европейскими держателями карт. Только 492 из этих покупок являются мошенническими, что делает этот набор данных сильно несбалансированным по действительным покупкам. Из-за конфиденциального характера данных показатели покупки, состоящие из 28 переменных (с именами от V1
до V28
), были преобразованы и анонимизированы, чтобы в них не содержалась идентифицируемая информация.
Примем следующие основные положения:
- Мошеннические покупки представляют собой пространственные выбросы среди их действительных аналогов.
- Не все показатели покупки помогут нам получить хорошее решение.
- Неправильная классификация действительной покупки как мошеннической лучше, чем неправильная классификация мошеннической покупки как действительной.
Первое предположение несколько наивно, но может дать адекватные результаты при надлежащей подготовке и изучении данных.
Исследование данных
Сначала нам нужно импортировать данные в память.
# Required imports from deephaven.TableTools import readCsv from deephaven import Plot # Read the CSV file into a Deephaven table creditcard = readCsv("/data/examples/CreditCardFraud/csv/creditcard.csv")
Прежде чем мы попытаемся избавить мир от мошенничества с кредитными картами, давайте изучим данные. Мы решили подойти к проблеме с идеей, что мошеннические покупки можно считать пространственными выбросами в данных. Мы можем использовать возможности построения графиков Deephaven, чтобы проверить наше предположение. График гистограммы может показать нам, насколько похожи или различны действительные и мошеннические покупки для любого заданного показателя покупок.
В приведенном ниже коде определяется функция, которая создаст график гистограммы, показывающий, как распределение баллов отличается между действительными и мошенническими покупками для любой из метрик анонимных покупок в таблице.
# Required imports from deephaven import Plot def plot_valid_vs_fraud(col_name): # Set the creditcard table as global to make sure we can access it for the plot global creditcard # Make sure the input corresponds to a column allowed_col_names = [item for item in range(1, 29)] + ["V" + str(item) for item in range(1, 29)] if col_name not in allowed_col_names: raise ValueError("The column name you specified is not valid.") if isinstance(col_name, int): col_name = "V" + str(col_name) # Some convenience variables for plotting num_valid_bins = 50 num_fraud_bins = 50 valid_label = col_name + "_Valid" fraud_label = col_name + "_Fraud" valid_string = "Class = 0" fraud_string = "Class = 1" # Create a fancy histogram plot valid_vs_fraud = \ Plot\ .histPlot(valid_label, creditcard.where(valid_string), col_name, num_valid_bins)\ .twinX()\ .histPlot(fraud_label, creditcard.where(fraud_string), col_name, num_valid_bins)\ .show() return valid_vs_fraud
Есть 28 столбцов данных для изучения, поэтому мы не будем показывать их все здесь. Гистограммы столбцов V4
, V12
и V14
показывают значительные различия в том, как действительные и мошеннические покупки распределяются в этих областях. Таким образом, мы будем использовать эти три столбца для нашего анализа.
valid_vs_fraud_V4 = plot_valid_vs_fraud("V4") valid_vs_fraud_V12 = plot_valid_vs_fraud("V12") valid_vs_fraud_V14 = plot_valid_vs_fraud("V14")
В дальнейшем наши запросы будут анализировать только следующие столбцы:
Time
V4
V12
V14
Class
Решение
Алгоритм кластеризации
Итак, мы хотим классифицировать пространственные выбросы. Как это можно сделать с имеющимися данными?
Мы собираемся использовать DBSCAN — спектральную кластеризацию приложений с шумом на основе плотности. Зачем использовать DBSCAN?
- DBSCAN может находить кластеры произвольной формы — мы мало знаем о том, как будут выглядеть наши кластеры.
- DBSCAN может найти произвольное количество кластеров — нам нужен один-единственный кластер.
- DBSCAN устойчив к выбросам — это именно то, что нам нужно.
- Реализация SciKit-Learn проста в использовании в Deephaven.
Реализация SciKit-Learn, sklearn.cluster.DBSCAN, имеет два обязательных входа:
- Расстояние по соседству.
- Количество соседей, которые считаются частью кластера.
Существует ряд дополнительных необязательных входных параметров, которые можно указать, хотя мы не будем их здесь рассматривать, поскольку не будем их использовать.
Мы собираемся использовать данные о покупках за четыре часа, чтобы соответствовать нашей модели DBSCAN, а затем четыре часа данных в реальном времени, на основе которых мы будем делать прогнозы. Эти два набора будут разделены 24 часами. После того, как мы разделим данные, нам нужно установить входные параметры нашей модели.
Наш обучающий набор будет состоять из покупок, которые происходят между 12 и 16 часами. Затем наш тестовый набор будет состоять из четырехчасового окна, которое происходит через 24 часа — покупки, которые происходят между 36 и 40 часами. Мы разделяем наши обучающие и тестовые наборы. на 24 часа, потому что мы ожидаем, что покупки будут иметь сходство в течение дня.
Выбор расстояния окрестности
Первый входной параметр, который мы должны выбрать, — это расстояние до окрестности. В DBSCAN расстояние соседства соответствует радиусу вокруг заданной точки. Таким образом, «окрестность» вокруг точки — это сфера с точкой в центре. Принятие обоснованного решения о соседнем расстоянии повысит точность модели DBSCAN.
Расстояние соседства DBSCAN обычно выбирается на основе расстояния каждой точки до ближайшего соседа. Если эти расстояния ближайших соседей расположены в порядке возрастания, создается кривая. Наиболее распространенный выбор расстояния соседства — колено или локоть кривой». Давайте создадим эту кривую, построим ее и выберем наше соседнее расстояние.
from deephaven.TableTools import readCsv from deephaven import Plot from deephaven import tableToDataFrame from deephaven import dataFrameToTable from sklearn.neighbors import KDTree as kdtree import pandas as pd import numpy as np # Read external data, remove unwanted parts, and split into train/test creditcard = readCsv("/data/examples/CreditCardFraud/csv/creditcard.csv") creditcard = creditcard.select("Time", "V4", "V12", "V14", "Amount", "Class") train_data = creditcard.where("Time >= 43200 && Time < 57600") test_data = tableToDataFrame(creditcard.where("Time >= 129600 && Time < 144000")) # Turn the training data into a Pandas DataFrame data = tableToDataFrame(train_data.select("V4", "V12", "V14")).values # Get nearest neighbor distances using a K-d tree tree = kdtree(data) dists, inds = tree.query(data, k = 2) # Sort the nearest neighbor distances in ascending order neighbor_dists = np.sort(dists[:, 1]) x = np.array(range(len(neighbor_dists))) # Turn our x and y (sorted neighbor distances) into a Deephaven table nn_dists = pd.DataFrame({"X": x, "Y": neighbor_dists}) nn_dists = dataFrameToTable(nn_dists) # Plot the last few hundred points so we can see the "elbow" neighbor_dists = Plot.plot("Nearest neighbor distance", nn_dists.where("X > 30000"), "X", "Y").show()
В нашем окне обучения 31 181 покупка. Изгиб кривой находится прямо возле конца нашего графика, поэтому мы вырезаем первые 30 000 точек из нашего графика. Изгиб этой кривой возникает там, где расстояние соседства почти точно равно единице. Таким образом, мы будем использовать 1 в качестве нашего первого входа в DBSCAN.
Количество соседей
Второй входной параметр, который мы должны выбрать, — это количество соседей. Этот ввод соответствует количеству других точек, которые находятся в пределах расстояния окрестности данной точки, чтобы она считалась частью кластера. Таким образом, DBSCAN проверяет каждую точку в наборе, чтобы увидеть, сколько других точек находится в ее окрестности. Если точка имеет большее или равное указанному количеству соседей, она является частью кластера. Если меньше, точка считается шумом. Существует некоторая специальная обработка, позволяющая отличить кластеры друг от друга, но это не относится к этой проблеме. Нам нужен единый кластер действительных покупок. Любая точка, не принадлежащая этому единственному кластеру, считается мошеннической покупкой.
Существует больше гибкости при выборе этого параметра. Общее эмпирическое правило заключается в том, что это значение должно быть больше, чем количество измерений в данных. У нас есть 3 измерения. Мы будем использовать 10 для количества соседей на основе проб и ошибок.
Чтобы увидеть влияние каждого ввода на обучающие данные, рассмотрите возможность их изменения, выполнив подгонку кода модели ниже и увидев, как они влияют на точность модели.
Подгонка модели и использование подогнанной модели
Мы подгоним модель к данным за четыре часа, а затем используем подобранную модель для прогнозирования мошенничества в четырехчасовом окне, которое произойдет через 24 часа. Мы делаем это, потому что ожидаем определенного сходства между количеством покупок и уровнем мошенничества в одни и те же периоды времени в разные дни.
Если бы DBSCAN использовался в реальной системе мошенничества, было бы разумно обучать модели для разных фаз дня, а затем развертывать эти различные модели в то время дня, когда они наиболее эффективны.
Код
Хорошо, много разговоров, но пока мало кода. Давайте перейдем к фактическому решению, как оно работает в Дипхейвене.
Подгонка модели
Сначала нам нужно подогнать модель под наши данные. Наши функции обучения состоят из столбцов V4
, V12
и V14
. Наши тренировочные цели указаны в столбце Class
. Для тренировки нам нужны часы 12, 13, 14 и 15.
Давайте подгоним модель, используя эти обучающие данные и входные данные, которые мы указали ранее.
В приведенном ниже коде происходит довольно много. Разобьем на этапы:
- Импортируйте все, что нам нужно для статического анализа и анализа в реальном времени.
- Считайте данные CSV в память и создайте из них обучающие и тестовые таблицы.
- Добавьте метки времени в таблицу тестирования для последующего использования.
- Создайте функцию, чтобы соответствовать модели DBSCAN с выбранными нами входными данными для обучающего набора.
- Создавайте функции для распределения и сбора данных в таблицы Deephaven и из них.
- Примените DBSCAN к тренировочным данным с помощью функции
learn
. - Проверьте, как работает модель.
# Deephaven imports replayer = jpy.get_type("io.deephaven.db.v2.replay.Replayer") from deephaven import DBTimeUtils as dbtu from deephaven.TableTools import readCsv from deephaven import dataFrameToTable from deephaven import tableToDataFrame from deephaven import learn # Python imports from sklearn.neighbors import KDTree as kdtree from sklearn.cluster import DBSCAN as dbscan import pandas as pd import numpy as np import scipy as sp # Read external data, remove unwanted parts, and split into train/test creditcard = readCsv("/data/examples/CreditCardFraud/csv/creditcard.csv") creditcard = creditcard.select("Time", "V4", "V12", "V14", "Amount", "Class") train_data = creditcard.where("Time >= 43200 && Time < 57600") test_data = creditcard.where("Time >= 129600 && Time < 144000") # This base time will be used to generate time stamps base_time = dbtu.convertDateTime("2021-11-16T00:00:00 NY") # This function will create a timestamp column from the time offset column def timestamp_from_offset(t): global base_time db_period = "T{}S".format(t) return dbtu.plus(base_time, dbtu.DBPeriod(db_period)) # Add a timestamp column to the test data for later replay test_data = test_data.update("TimeStamp = (DBDateTime)timestamp_from_offset(Time)") # This placeholder will be replaced by our trained DBSCAN model db = 0 # A function to apply DBSCAN with eps = 1 and min_samples = 10 def perform_dbscan(data): global db db = dbscan(eps = 1, min_samples = 10).fit(data) return db.labels_ # Our gather function for DBSCAN def dbscan_gather(idx, cols): gathered = np.empty([idx.getSize(), len(cols)], dtype = float) iter = idx.iterator() i = 0 while(iter.hasNext()): it = iter.next() j = 0 for col in cols: gathered[i, j] = col.get(it) j += 1 i += 1 return np.squeeze(gathered) # Our scatter function for DBSCAN def dbscan_scatter(data, idx): if data[idx] == -1: data[idx] = 1 return data[idx] # Perform DBSCAN on our train_data table clustered = learn.learn( table = train_data, model_func = perform_dbscan, inputs = [learn.Input(["V4", "V12", "V14"], dbscan_gather)], outputs = [learn.Output("PredictedClass", dbscan_scatter, "short")], batch_size = train_data.size() ) # Split DBSCAN guesses (correct and incorrect) into separate tables dbscan_correct_valid = clustered.where("Class == 0 && PredictedClass == 0") dbscan_correct_fraud = clustered.where("Class == 1 && PredictedClass == 1") dbscan_false_positives = clustered.where("Class == 0 && PredictedClass == 1") dbscan_false_negatives = clustered.where("Class == 1 && PredictedClass == 0") # Report the accuracy of the model print("DBSCAN guesses valid - correct! " + str(dbscan_correct_valid.size())) print("DBSCAN guesses fraud - correct! " + str(dbscan_correct_fraud.size())) print("DBSCAN guesses valid - wrong! " + str(dbscan_false_positives.size())) print("DBSCAN guesses fraud - wrong! " + str(dbscan_false_negatives.size()))
Сладкий! Разберем производительность модели:
- 31063 из 31075 действительных покупок идентифицированы правильно (>99%).
- 33 из 45 мошеннических покупок идентифицируются правильно (73%).
- 12 мошеннических покупок ошибочно идентифицированы как действительные — это ложноотрицательные результаты (27%).
- 71 действительная покупка ошибочно идентифицирована как мошенническая — это ложные срабатывания (‹1%).
Эти результаты довольно хороши. Давайте двигаться вперед!
Обнаружение мошенничества в режиме реального времени
У нас уже есть таблица test_data
в памяти. Мы хотим воспроизвести его в реальном времени на основе меток времени, которые мы создали в столбце DateTime
. Мы можем сделать это, используя Replayer
.
DBSCAN — это не модель, созданная для обработки в реальном времени. Мы должны использовать то, что мы знаем о модели DBSCAN, которую мы создали на наших обучающих данных, чтобы создать метод в реальном времени, который использует нашу модель:
- Подогнанная модель имеет один кластер.
- Точка является частью этого единственного кластера, если она имеет 10 соседей в радиусе 1.
- Точка не является частью кластера, если любой из ее ближайших 10 соседей находится дальше, чем на расстоянии 1 от нее.
Обладая этими знаниями, мы можем сравнить тестовые данные, поступающие в режиме реального времени, с нашими обучающими данными и посмотреть, являются ли они частью единого кластера.
Код ниже можно разбить на шаги:
- Воспроизведите таблицу test_data в режиме реального времени.
- Создайте функцию для прогнозирования действительности входящих покупок, используя обучающие данные и нашу модель.
- Создайте функцию для распределения и сбора данных в таблицу Deephaven и из нее.
- Используйте
learn
, чтобы применить нашу модель к таблице обновления в реальном времени. - Создавайте производные таблицы, которые показывают, как наши модели работают в режиме реального времени.
start_time = dbtu.convertDateTime("2021-11-17T12:00:00 NY") end_time = dbtu.convertDateTime("2021-11-17T16:01:00 NY") # Replay the test_data table test_data_replayer = replayer(start_time, end_time) creditcard_live = test_data_replayer.replay(test_data, "TimeStamp") test_data_replayer.start() creditcard_live = creditcard_live.view("Time", "V4", "V12", "V14", "Amount", "Class") # A function to place new observations into our existing clusters def dbscan_predict(X_new): n_rows = X_new.shape[0] data_with_new = np.vstack([dbscanned, X_new]) tree = kdtree(data_with_new) dists, points = tree.query(data_with_new, 10) dists = dists[-n_rows:] detected_fraud = [0] * n_rows for idx in range(len(dists)): if any(dists[idx] > 1): detected_fraud[idx] = 1 return np.array(detected_fraud) # A function to gather data from a Deephaven table into a NumPy array def gather(idx, cols): rst = np.empty([idx.getSize(), len(cols)], dtype = np.single) iter = idx.iterator() i = 0 while (iter.hasNext()): it = iter.next() j = 0 for col in cols: rst[i, j] = col.get(it) j += 1 i += 1 return np.squeeze(rst) # A function to scatter data back into an output table def scatter(data, idx): return data[idx] predicted_fraud_live = learn.learn( table = creditcard_live, model_func = dbscan_predict, inputs = [learn.Input(["V4", "V12", "V14"], gather)], outputs = [learn.Output("PredictedClass", scatter, "short")], batch_size = 1000 ) dbscan_correct_valid = predicted_fraud_live.where("PredictedClass == 0 && Class == 0") dbscan_correct_fraud = predicted_fraud_live.where("PredictedClass == 1 && Class == 1") dbscan_false_positive = predicted_fraud_live.where("PredictedClass == 1 && Class == 0") dbscan_false_negative = predicted_fraud_live.where("PredictedClass == 0 && Class == 1")
Заключение
Теперь у нас есть подогнанная модель DBSCAN, классифицирующая мошенничество в режиме реального времени! Мы видим, что по мере добавления новых строк наша модель точно определяет мошенничество по мере его поступления! Однако наша модель не выявляет каждую мошенническую покупку. Возможно, модель DBSCAN с другими параметрами будет работать лучше? Могут быть разные модели, которые лучше подходят для решения этой задачи в целом.
Тем не менее, мы можем построить относительно простую модель, которая довольно хорошо работает для решения сложной задачи. Расширение модели для использования таблиц Deephaven является интуитивно понятным и простым, равно как и расширение статического решения для работы с оперативными данными в реальном времени.
Какие проблемы науки о данных вы можете решить с помощью живых данных, используя Deephaven? Существует бесчисленное множество данных, которые необходимо обработать, и Deephaven — отличный инструмент для этого.