El enigma de la casa Lancaster: un ejercicio práctico con Mercury-Reels
Mercury-reels, desarrollado en BBVA AI Factory, es un componente de nuestra librería de Python escrito en código C++, que permite analizar secuencias de eventos. Para poder trabajar con los eventos, entendidos como cualquier acción que ocurre en un entorno dado, éstos tienen que identificarse con un código específico y una marca de tiempo. Los eventos accionados por un mismo usuario o cliente, por ejemplo en la aplicación móvil de BBVA, forman una secuencia individual de eventos.
En otras palabras, podemos entender una secuencia de eventos como un clip de vídeo, donde los eventos son cada uno de los fotogramas. Los clips de vídeo se almacenan en un carrete –reels en inglés– donde también se han grabado clips de otros usuarios.
Lo importante aquí es que, mediante estos clips o secuencias de eventos, podemos generar un modelo analítico para predecir eventos futuros y el momento en el que ocurrirán. A estos eventos los llamamos eventos objetivo (target events). Para predecirlos, aplicamos un algoritmo sencillo, que consiste en alinear cada secuencia o clip en orden inverso. Partiendo del último evento que tuvo lugar, recreamos el árbol completo, esto es, el camino de decisiones que condujo a un usuario al evento objetivo. También almacenamos las frecuencias y los tiempos transcurridos hasta que ocurre este evento.
A continuación, mediante una eficiente búsqueda dentro del árbol, el algoritmo identifica todas las coincidencias parciales o completas entre las diferentes secuencias y consolida sus resultados utilizando opciones de agregación predefinidas. De esta forma, podemos predecir qué pasos previos (eventos) tienen que ocurrir para que tenga lugar el evento objetivo.
Mercury-reels ofrece otras ventajas, entre las que destacan su alta eficiencia e interpretabilidad, así como la facilidad a la hora de probar las opciones disponibles y ajustar los hiperparámetros a cada caso de uso. Hoy pondremos a prueba sus funcionalidades a través de una historia ficcionada.
El enigma de la mansión de Cedric Lancaster
Aunque parezca una mansión como cualquier otra, la antigua casa de Cedric Lancaster encierra un misterio. Durante los últimos años se han sucedido una serie de asesinatos con el mismo modus operandi: la víctima y el verdugo solos en la sala del billar, una pistola como arma y un único disparo.
Sabemos quiénes fueron los culpables de los últimos asesinatos. Ahora, nos hemos propuesto descubrir cuál es el recorrido que llevaron a cabo los culpables antes del crimen, para así poder prevenirlo en futuras ocasiones. Nuestro evento objetivo, es decir, el suceso que queremos predecir, es el disparo que tiene lugar en la sala del billar cuando se encuentran dos personas en ella.
A partir de este evento objetivo vamos a reconstruir la secuencia de eventos previa. Analizamos los movimientos por las habitaciones de la mansión de algunos invitados inocentes, como lo son la princesa Anastasia Romanov, el dueño de la casa Cedric Lancaster y el marqués Lorenzo di Sant-Angelo, así como los movimientos de los culpables, entre ellos, la condesa Adelaide Beaumont.
Para registrar los movimientos, hemos creado un dataset que recoge las acciones de cada invitado (eventos individuales). Un evento está compuesto por una hora determinada, una persona entrando en una habitación concreta, y el número de personas que hay en la misma.
Los eventos por sí solos no dicen mucho, pero es posible identificar patrones cuando se analiza el historial de actividad de los invitados como una secuencia de eventos individuales. El reto consiste en analizar eficazmente los millones de secuencias de eventos que se han almacenado para así detectar los patrones de comportamiento de los culpables.
Manos a la obra: resolvemos el misterio con mercury-reels
Reels es un framework sobre el cual existen dos tutoriales: uno general y uno en torno a la optimización de eventos, los cuales pueden ejecutarse en Google Colab sin instalar nada previamente. Este ejercicio práctico ofrece un ejemplo básico que demuestra cómo funcionan juntos todos los componentes.
Recordamos los dos conjuntos de datos de los que disponemos para llevar a cabo nuestro ejercicio. En primer lugar, un dataset que simula el movimiento de los invitados por las habitaciones de la mansión, solos o en grupo, cada uno con un identificador y con una marca temporal. El otro dataset contempla a los invitados que efectivamente dispararon; es decir, que alcanzaron el evento objetivo.
Nuestra misión es predecir el momento en que es probable que el asesino potencial dispare. En el conjunto de datos de disparos, eso ocurre exactamente un segundo después de que el culpable tenga una pistola y esté en la sala de billar con otro invitado.
Antes de continuar, abre una ventana de consola. En nuestro ejemplo, utilizamos un sistema Linux. La sintaxis exacta puede variar dependiendo de la plataforma. Asegúrate de tener instalado `Python 3.8` o superior y `pip`. Actualiza `pip` a la última versión e instala `mercury-reels`. También puedes saltarte estos pasos y descubrir más abajo las conclusiones del caso.
pip install --upgrade pip
pip install mercury-reels
Si has decidido continuar, ahora asegúrate de tener tu intérprete de Python preferido, ya sea Jupyter, un IDE, o el intérprete de Python directamente, abierto y listo para usar, para así ejecutar los ejemplos de código. Descargamos los dos conjuntos de datos directamente en pandas datasets.
import reels
import pandas as pd
room_access = pd.read_csv('https://raw.githubusercontent.com/BBVA/mercury-reels/master/notebooks/data/reels_mansion.csv', sep = '\t')
target_data = pd.read_csv('https://raw.githubusercontent.com/BBVA/mercury-reels/master/notebooks/data/reels_mansion_targets.csv', sep = '\t')
Seguidamente, tenemos que definir los eventos. Para ello, creamos un objeto `Intake`, una herramienta para iterar sobre el conjunto de datos y utilizarlo para rellenar un objeto `Events`. Un evento puede verse como una arista en un grafo; por tanto, el objeto espera un ID de nodo de origen, un ID de destino y un peso. En este caso, no tenemos dos nodos; simplemente damos el mismo ID como origen y destino.
events = reels.Events()
access_in = reels.Intake(room_access)
access_in.insert_rows(events, columns = ['room', 'room', 'num_occ'])
#To examine the object's content, we could print `list(events.describe_events())`.
Una vez definidos los eventos, debemos calcular los clips —qué evento le ha ocurrido a cada uno y en qué momento— de todos los invitados. Rellenamos el objeto `Clips` con el mismo `Intake`, pasando los nombres de las columnas según corresponda y un objeto `Clients` vacío para seleccionar todos los participantes.
clips = reels.Clips(reels.Clients(), events)
access_in.scan_events(clips, columns = ['room', 'room', 'num_occ', 'guest', 'time'])
#If we wanted to examine a clip, we could just `clips.describe_clip('Sir Cedric Lancaster')`.
Este objeto `Clips` y los tiempos objetivo son todo lo que necesitamos para ajustar el modelo. Lo hacemos utilizando un nuevo `Intake` para los objetivos (targets).
targets = reels.Targets(clips)
targets_in = reels.Intake(target_data)
targets_in.insert_targets(targets, columns = ['guest', 'time'])
targets.fit(x_form = 'linear', agg = 'longest', p = 0, depth = 100)
Conclusiones del caso: la secuencia de eventos desde el origen
A partir del cálculo de los eventos, de los clips y de los targets ahora sabemos cuál es la secuencia de eventos que siguió el asesino.
El culpable necesitaba encontrarse solo en el salón para hacerse con la llave del armario. Una vez tenía la llave, esperó a encontrarse solo en la sala de estudio para abrir un armario de armas. A continuación, el asesino se dirigió hacia la sala de billar, donde escondió el arma hasta que entró otro invitado, al cual le disparó. Así, conocemos cuál es el patrón de eventos críticos que llevan al crimen.
Dado que el algoritmo ordena inversamente los eventos en forma de árbol a partir del evento objetivo, podemos saber finalmente qué caminos tomó el culpable, en este caso Adelaide Beaumont, para poder disparar.
En este caso sencillo, hemos ilustrado el funcionamiento de mercury-reels con secuencias completas que identifican a cada persona. Sin embargo, en un caso real con millones de secuencias, trabajaremos con coincidencias parciales.
Ahora, para conocer los tiempos en los que actúan los culpables, podemos hacerlo de la siguiente manera:
list(targets.predict_clients(['Countess Adelaide Beaumont']))
Confirmamos que está muy cerca de un segundo, que es el resultado esperado, por lo que podemos decir que Adelaide Beaumont cumple el mismo patrón secuencial y temporal que los demás asesinos.
Por otro lado, podemos también predecir los tiempos de los inocentes utilizando otros nombres dentro del dataset, y veremos que estos suelen predecirse como en un análisis de supervivencia. Como no se observa ningún objetivo, la división por cero se realiza mediante una constante (100 años en segundos).
list(targets.predict_clients(['Princess Anastasia Romanov','Sir Cedric Lancaster']))
Si has llegado hasta aquí, quizá quieras probarlo con tus propios datos. Antes de ello, te recomendamos que primero completes los tutoriales. Para ver todo lo que reels puede hacer, consulta nuestro repositorio en Github.
Mercury-reels en BBVA: caso de uso
Esta historia es otra forma de utilizar mercury-reels, pero adquiere un valor mayor cuando lo extrapolamos a casos de uso reales. De hecho, mercury-reels surgió para comprender mejor cómo navegan los clientes de BBVA por la aplicación móvil del banco.
Ante esta necesidad, pusimos en marcha una iniciativa de exploración con datos en forma de Proyecto X, que nos permitiera investigar alternativas a los embeddings de grafos y al análisis de series temporales. De esta forma, hemos conseguido obtener información procesable sobre el comportamiento de navegación de nuestros clientes y usuarios.
Notas
- Sólo estamos almacenando 50 secuencias lo suficientemente largas para ser únicas. Ergo, estamos sobreajustando. Esto, por supuesto, no es la forma prevista de utilizar `reels`. Reels proporciona todas las herramientas para controlar la longitud de las secuencias y buscar patrones significativos, como se explica en los tutoriales.
- Las secuencias para los asesinos terminan intencionadamente después de que los asesinos disparen. Es importante recordar que cuando hay un objetivo, todo lo que hay después de él en el clip no se utiliza. El clip se alinea con el último evento antes del objetivo.
- En este caso concreto, no elegimos ni hacer una agregación ni una transformación y simplemente lo emparejamos con el fit más largo. Consulta la documentación sobre los argumentos de `fit()`.