Detección de fraudes con Resolución de Entidades y Redes Neuronales Gráficas

Fraud detection with Entity Resolution and Graph Neural Networks

Una guía práctica sobre cómo la resolución de entidades mejora el aprendizaje automático para detectar fraudes

Representación de una Red Neuronal de Grafos (Imagen generada por el Autor utilizando Bing Image Creator)

El fraude en línea es un problema cada vez mayor para las finanzas, el comercio electrónico y otras industrias relacionadas. En respuesta a esta amenaza, las organizaciones utilizan mecanismos de detección de fraudes basados en el aprendizaje automático y análisis de comportamiento. Estas tecnologías permiten detectar patrones inusuales, comportamientos anormales y actividades fraudulentas en tiempo real.

Desafortunadamente, a menudo solo se tiene en cuenta la transacción actual, por ejemplo, un pedido, o el proceso se basa únicamente en datos históricos del perfil del cliente, que se identifica mediante un id de cliente. Sin embargo, los estafadores profesionales pueden crear perfiles de clientes utilizando transacciones de bajo valor para construir una imagen positiva de su perfil. Además, pueden crear múltiples perfiles similares al mismo tiempo. Solo después de que se haya producido el fraude, la empresa atacada se da cuenta de que estos perfiles de clientes estaban relacionados entre sí.

Usando la resolución de entidades es posible combinar fácilmente diferentes perfiles de clientes en una vista completa del cliente de 360°, lo que permite ver el panorama completo de todas las transacciones históricas. Si se utiliza estos datos en el aprendizaje automático, por ejemplo, utilizando una red neuronal o incluso una simple regresión lineal, ya se proporcionaría valor adicional para el modelo resultante, el verdadero valor surge al también considerar cómo las transacciones individuales están conectadas entre sí. Aquí es donde entran en juego las redes neuronales de grafos (GNN). Además de examinar las características extraídas de los registros transaccionales, también ofrecen la posibilidad de examinar características generadas a partir de los bordes del grafo (cómo se vinculan las transacciones entre sí) o incluso solo la disposición general del grafo de entidades.

Datos de ejemplo

Antes de adentrarnos en los detalles, quiero hacer una advertencia aquí: soy un desarrollador y experto en resolución de entidades, no un científico de datos o experto en aprendizaje automático. Si bien creo que el enfoque general es correcto, es posible que no esté siguiendo las mejores prácticas, ni puedo explicar ciertos aspectos como el número de nodos ocultos. Utilice este artículo como inspiración y aproveche su propia experiencia cuando se trate del diseño o configuración de GNN.

Con fines de este artículo, quiero enfocarme en los conocimientos obtenidos a partir de la disposición del grafo de entidades. Para este propósito, creé un pequeño script en Golang que genera entidades. Cada entidad está etiquetada como fraudulenta o no fraudulenta y consta de registros (pedidos) y bordes (cómo se vinculan esos pedidos). A continuación, vea el siguiente ejemplo de una sola entidad:

{  "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    }  ]}

Cada registro tiene dos características (potenciales), el valor total y el número de elementos comprados. Sin embargo, el script de generación aleatorizó completamente estos valores, por lo que no deberían proporcionar valor cuando se trata de adivinar la etiqueta de fraude. Cada borde también viene con dos características R1 y R2. Estas podrían representar, por ejemplo, si los dos registros A y B están vinculados a través de un nombre y dirección similares (R1) o a través de una dirección de correo electrónico similar (R2). Además, intencionalmente dejé fuera todos los atributos que no son relevantes para este ejemplo (nombre, dirección, correo electrónico, número de teléfono, etc.), pero que suelen ser relevantes para el proceso de resolución de entidades previo. Como R1 y R2 también están aleatorizados, tampoco proporcionan valor para el GNN. Sin embargo, según la etiqueta de fraude, los bordes se disponen de dos formas posibles: un diseño en forma de estrella (fraud=0) o un diseño aleatorio (fraud=1).

La idea es que un cliente no fraudulento es más propenso a proporcionar datos coincidentes relevantes y precisos, generalmente la misma dirección y el mismo nombre, con solo algunos errores ortográficos aquí y allá. Por lo tanto, es más probable que se reconozcan nuevas transacciones como duplicadas.

Entidad Duplicada (Imagen del Autor)

Un cliente fraudulento podría querer ocultar el hecho de que sigue siendo la misma persona detrás de la computadora, utilizando varios nombres y direcciones. Sin embargo, las herramientas de resolución de entidades aún pueden reconocer la similitud (por ejemplo, similitud geográfica y temporal, patrones recurrentes en la dirección de correo electrónico, IDs de dispositivos, etc.), pero el grafo de entidades puede verse más complejo.

Entidad Compleja, Posiblemente Fraudulenta (Imagen del Autor)

Para hacerlo un poco menos trivial, el script de generación también tiene una tasa de error del 5%, lo que significa que las entidades se etiquetan como fraudulentas cuando tienen una disposición en forma de estrella y se etiquetan como no fraudulentas para la disposición aleatoria. También hay algunos casos en los que los datos son insuficientes para determinar la disposición real (por ejemplo, solo uno o dos registros).

{  "fraude":1,  "registros":[    {      "id":0,      "valorTotal":85,      "ítems":5    }  ],  "aristas":[      ]}

En realidad, lo más probable es que obtenga información valiosa de los tres tipos de características (atributos de registros, atributos de aristas y disposición de aristas). Los siguientes ejemplos de código considerarán esto, pero los datos generados no lo hacen.

Creando el Conjunto de Datos

El ejemplo utiliza python (excepto la generación de datos) y DGL con una biblioteca pytorch. Puede encontrar el cuaderno jupyter completo, los datos y el script de generación en GitHub.

Comencemos importando el conjunto de datos:

import osos.environ["DGLBACKEND"] = "pytorch"import pandas as pdimport torchimport dglfrom dgl.data import DGLDatasetclass ConjuntoEntidades(DGLDataset):    def __init__(self, archivoEntidades):        self.archivoEntidades = archivoEntidades        super().__init__(name="entidades")    def process(self):        entidades = pd.read_json(self.archivoEntidades, lines=1)        self.grafos = []        self.etiquetas = []        for _, entidad in entidades.iterrows():            a = []            b = []            atributos_r1 = []            atributos_r2 = []            for arista in entidad["aristas"]:                a.append(arista["a"])                b.append(arista["b"])                atributos_r1.append(arista["R1"])                atributos_r2.append(arista["R2"])            a = torch.LongTensor(a)            b = torch.LongTensor(b)            atributos_aristas = torch.LongTensor([atributos_r1, atributos_r2]).t()            atributos_nodo = [[nodo["valorTotal"], nodo["ítems"]] for nodo in entidad["registros"]]            atributos_nodos = torch.tensor(atributos_nodo)            g = dgl.graph((a, b), num_nodes=len(entidad["registros"]))            g.edata["feat"] = atributos_aristas            g.ndata["feat"] = atributos_nodos            g = dgl.add_self_loop(g)            self.grafos.append(g)            self.etiquetas.append(entidad["fraude"])        self.etiquetas = torch.LongTensor(self.etiquetas)    def __getitem__(self, i):        return self.grafos[i], self.etiquetas[i]    def __len__(self):        return len(self.grafos)conjunto_entidades = ConjuntoEntidades("./entidades.jsonl")print(conjunto_entidades)print(conjunto_entidades[0])

Esto procesa el archivo de entidades, que es un archivo de líneas JSON, donde cada fila representa una sola entidad. Mientras itera sobre cada entidad, genera los atributos de las aristas (tensor largo con forma [e, 2], e = número de aristas) y los atributos de los nodos (tensor largo con forma [n, 2], n = número de nodos). Luego procede a construir el grafo basado en a y b (tensores largos cada uno con forma [e, 1]) y asigna los atributos de las aristas y del grafo a ese grafo. Todos los grafos resultantes se agregan al conjunto de datos.

Arquitectura del Modelo

Ahora que tenemos los datos listos, debemos pensar en la arquitectura de nuestra GNN. Esto es lo que se me ocurrió, pero probablemente se pueda ajustar mucho más a las necesidades reales:

import torch.nn as nnimport torch.nn.functional as Ffrom dgl.nn import NNConv, SAGEConvclass ModuloGrafoEntidad(nn.Module):    def __init__(self, atributos_nodo_entrada, atributos_arista_entrada, h_feats, num_classes):        super(ModuloGrafoEntidad, self).__init__()        lin = nn.Linear(atributos_arista_entrada, atributos_nodo_entrada * h_feats)        funcion_arista = lambda atributos_arista: lin(atributos_arista)        self.conv1 = NNConv(atributos_nodo_entrada, h_feats, funcion_arista)        self.conv2 = SAGEConv(h_feats, num_classes, "pool")    def forward(self, g, atributos_nodo, atributos_arista):        h = self.conv1(g, atributos_nodo, atributos_arista)        h = F.relu(h)        h = self.conv2(g, h)        g.ndata["h"] = h        return dgl.mean_nodes(g, "h")

El constructor toma el número de características de los nodos, el número de características de las aristas, el número de nodos ocultos y el número de etiquetas (clases). Luego crea dos capas: una capa NNConv que calcula los nodos ocultos basados en las características de las aristas y los nodos, y luego una capa GraphSAGE que calcula la etiqueta resultante basada en los nodos ocultos.

Entrenamiento y Prueba

Casi terminamos. A continuación, preparamos los datos para el entrenamiento y la prueba.

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)

Dividimos con una proporción de 80/20 utilizando un muestreo aleatorio y creamos un cargador de datos para cada una de las muestras.

El último paso es inicializar el modelo con nuestros datos, ejecutar el entrenamiento y luego probar el resultado.

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("Precisión de prueba:", acc)

Inicializamos el modelo proporcionando los tamaños de características para los nodos y las aristas (ambos 2 en nuestro caso), los nodos ocultos (64) y la cantidad de etiquetas (2 porque es fraude o no). Luego se inicializa el optimizador con una tasa de aprendizaje de 0.01. Después ejecutamos un total de 50 iteraciones de entrenamiento. Una vez que el entrenamiento haya terminado, probamos los resultados utilizando el cargador de datos de prueba e imprimimos la precisión resultante.

Para varias ejecuciones, obtuve una precisión típica en el rango del 70% al 85%. Sin embargo, con algunas excepciones, la precisión llegó a ser del 55%.

Conclusión

Dado que la única información utilizable de nuestro conjunto de datos de ejemplo es la explicación de cómo están conectados los nodos, los resultados iniciales parecen muy prometedores y sugieren que sería posible obtener tasas de precisión más altas con datos del mundo real y más entrenamiento.

Obviamente, al trabajar con datos reales, el diseño no es tan consistente y no proporciona una correlación evidente entre el diseño y el comportamiento fraudulento. Por lo tanto, también debes tener en cuenta las características de las aristas y los nodos. La idea principal de este artículo debería ser que la resolución de entidades proporciona los datos ideales para la detección de fraudes utilizando redes neuronales gráficas y debería considerarse como parte del arsenal de herramientas de un ingeniero de detección de fraudes.

Publicado originalmente en https://tilores.io.