Обнаружение мошенничества с использованием сопоставления сущностей и графовых нейронных сетей

Mошенничество - сопоставление сущностей и графовые нейронные сети

Практическое руководство по тому, как разрешение сущностей улучшает машинное обучение для обнаружения мошенничества

Отображение графовой нейронной сети (изображение, созданное автором с использованием Bing Image Creator)

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

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

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

Пример данных

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

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

{  "fraud":1,  "records":[    {      "id":0,      "totalValue":85,      "items":2    },    {      "id":1,      "totalValue":31,      "items":4    },    {      "id":2,      "totalValue":20,      "items":9    }  ],  "edges":[    {      "a":1,      "b":0,      "R1":1,      "R2":1    },    {      "a":2,      "b":1,      "R1":0,      "R2":1    }  ]}

У каждой записи есть два (потенциальных) признака: общая стоимость и количество приобретенных товаров. Однако скрипт генерации полностью случайным образом определил эти значения, поэтому они не должны быть полезными при определении метки мошенничества. Каждое ребро также имеет два признака R1 и R2. Они могут, например, указывать, связаны ли две записи A и B через похожее имя и адрес (R1) или через похожий адрес электронной почты (R2). Кроме того, я специально опустил все атрибуты, которые не являются значимыми для этого примера (имя, адрес, электронная почта, номер телефона и т. д.), но обычно они значимы для процесса разрешения сущностей заранее. Так как R1 и R2 также являются случайными, они также не предоставляют ценности для GNN. Однако на основе метки мошенничества ребра располагаются двумя возможными способами: в виде звездной структуры (fraud=0) или случайной структуры (fraud=1).

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

Дедуплицированная сущность (Изображение автора)

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

Сложная, возможно мошенническая сущность (Изображение автора)

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

{  "fraud":1,  "records":[    {      "id":0,      "totalValue":85,      "items":5    }  ],  "edges":[      ]}

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

Создание набора данных

Для примера используется Python (за исключением генерации данных) и DGL с поддержкой PyTorch. Вы можете найти полный jupyter-ноутбук, данные и скрипт генерации на GitHub.

Давайте начнем с импорта набора данных:

import osos.environ["DGLBACKEND"] = "pytorch"import pandas as pdimport torchimport dglfrom dgl.data import DGLDatasetclass EntitiesDataset(DGLDataset):    def __init__(self, entitiesFile):        self.entitiesFile = entitiesFile        super().__init__(name="entities")    def process(self):        entities = pd.read_json(self.entitiesFile, lines=1)        self.graphs = []        self.labels = []        for _, entity in entities.iterrows():            a = []            b = []            r1_feat = []            r2_feat = []            for edge in entity["edges"]:                a.append(edge["a"])                b.append(edge["b"])                r1_feat.append(edge["R1"])                r2_feat.append(edge["R2"])            a = torch.LongTensor(a)            b = torch.LongTensor(b)            edge_features = torch.LongTensor([r1_feat, r2_feat]).t()            node_feat = [[node["totalValue"], node["items"]] for node in entity["records"]]            node_features = torch.tensor(node_feat)            g = dgl.graph((a, b), num_nodes=len(entity["records"]))            g.edata["feat"] = edge_features            g.ndata["feat"] = node_features            g = dgl.add_self_loop(g)            self.graphs.append(g)            self.labels.append(entity["fraud"])        self.labels = torch.LongTensor(self.labels)    def __getitem__(self, i):        return self.graphs[i], self.labels[i]    def __len__(self):        return len(self.graphs)dataset = EntitiesDataset("./entities.jsonl")print(dataset)print(dataset[0])

Это обрабатывает файл сущностей, который является файлом JSON-строки, где каждая строка представляет собой одну сущность. При переборе каждой сущности он генерирует атрибуты ребер (длинный тензор с формой [e, 2], e = количество ребер) и атрибуты узлов (длинный тензор с формой [n, 2], n = количество узлов). Затем он приступает к построению графа на основе a и b (длинные тензоры с формой [e, 1]) и присваивает атрибуты ребра и графа этому графу. Все полученные графы затем добавляются в набор данных.

Архитектура модели

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

import torch.nn as nnimport torch.nn.functional as Ffrom dgl.nn import NNConv, SAGEConvclass EntityGraphModule(nn.Module):    def __init__(self, node_in_feats, edge_in_feats, h_feats, num_classes):        super(EntityGraphModule, self).__init__()        lin = nn.Linear(edge_in_feats, node_in_feats * h_feats)        edge_func = lambda e_feat: lin(e_feat)        self.conv1 = NNConv(node_in_feats, h_feats, edge_func)        self.conv2 = SAGEConv(h_feats, num_classes, "pool")    def forward(self, g, node_features, edge_features):        h = self.conv1(g, node_features, edge_features)        h = F.relu(h)        h = self.conv2(g, h)        g.ndata["h"] = h        return dgl.mean_nodes(g, "h")

Конструктор принимает количество признаков узлов, количество признаков ребер, количество скрытых узлов и количество меток (классов). Затем создаются два слоя: слой NNConv, который вычисляет скрытые узлы на основе признаков ребер и узлов, а затем слой GraphSAGE, который вычисляет результирующую метку на основе скрытых узлов.

Обучение и тестирование

Почти готово. Затем мы подготавливаем данные для обучения и тестирования.

from torch.utils.data.sampler import SubsetRandomSamplerfrom dgl.dataloading import GraphDataLoadernum_examples = len(dataset)num_train = int(num_examples * 0.8)train_sampler = SubsetRandomSampler(torch.arange(num_train))test_sampler = SubsetRandomSampler(torch.arange(num_train, num_examples))train_dataloader = GraphDataLoader(    dataset, sampler=train_sampler, batch_size=5, drop_last=False)test_dataloader = GraphDataLoader(    dataset, sampler=test_sampler, batch_size=5, drop_last=False)

Мы делим данные в соотношении 80/20 с помощью случайной выборки и создаем загрузчик данных для каждой выборки.

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

h_feats = 64learn_iterations = 50learn_rate = 0.01model = EntityGraphModule(    dataset.graphs[0].ndata["feat"].shape[1],    dataset.graphs[0].edata["feat"].shape[1],    h_feats,    dataset.labels.max().item() + 1)optimizer = torch.optim.Adam(model.parameters(), lr=learn_rate)for _ in range(learn_iterations):    for batched_graph, labels in train_dataloader:        pred = model(batched_graph, batched_graph.ndata["feat"].float(), batched_graph.edata["feat"].float())        loss = F.cross_entropy(pred, labels)        optimizer.zero_grad()        loss.backward()        optimizer.step()num_correct = 0num_tests = 0for batched_graph, labels in test_dataloader:    pred = model(batched_graph, batched_graph.ndata["feat"].float(), batched_graph.edata["feat"].float())    num_correct += (pred.argmax(1) == labels).sum().item()    num_tests += len(labels)acc = num_correct / num_testsprint("Точность тестирования:", acc)

Мы инициализируем модель, указав размеры признаков для узлов и ребер (в нашем случае оба равны 2), скрытые узлы (64) и количество меток (2, потому что это либо мошенничество, либо нет). Затем оптимизатор инициализируется с коэффициентом обучения 0.01. Затем мы выполняем общее количество итераций обучения, равное 50. После завершения обучения мы тестируем результаты, используя загрузчик тестовых данных, и выводим полученную точность.

Для различных запусков я получал типичную точность в диапазоне от 70 до 85%. Однако в некоторых случаях точность упала до примерно 55%.

Заключение

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

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

Оригинал опубликован на https://tilores.io.