(Re)introducción
Recientemente, mi compañera Suni escribió unas primeras pinceladas sobre quiénes y de qué manera usan sistemas de recomendación. En su post menciona diferentes métodos para calcular estas predicciones, de los cuales nosotros vamos a emplear el filtrado colaborativo.
Un enfoque mixto, como el que tiene Spotify con sus playlist diarias, posiblemente sería el más acertado, detectando, por ejemplo, cuál es el tema más interesante para ese usuario y haciendo las recomendaciones sobre ese tema (filtrado basado en contenido + filtrado colaborativo). Posteriormente podrían añadirse reglas para ponderar estas recomendaciones basadas en aspectos demográficos (filtrado basado en reglas), pero, en principio, el propósito de este ejercicio es demostrar la implementación de una arquitectura funcional que cumpla el propósito de la recomendación de la manera más comprensible.
Hace algún tiempo que nuestro equipo encontró a su vez esta solución recomendada por Google para realizar este tipo de predicción sobre su arquitectura: Google Cloud Platform (GCP). En parte a raíz de ese artículo, en parte por la necesidad de ponernos en forma a la hora de desplegar servicios en la Cloud, y en parte por pura curiosidad del equipo, empujamos este proyecto hasta llevarlo a puerto.
Recolección
¿Así que estás decidido a hacer recomendaciones?, ¿sobre qué?
Una buena forma de optimizar las vistas a nuestro blog, y a su vez presentar contenido más relevante a nuestros lectores, podría ser recomendar artículos ya publicados. Además, podemos saber si a un usuario le ha enganchado un artículo si termina de leerlo, así que cada vez que se lee un artículo recogemos la siguiente información:
- Lector (id): identificador que nos permita separar las lecturas de un usuario, de las de los demás.
- % de lectura (rating): en función de si le gusta o no, el usuario abandonará el artículo o lo terminará.
- Artículo: (URL): identificador de este artículo.
Almacenaremos esta información en una base de datos para luego calcular la recomendación.
Nota: en este caso hemos optado por desarrollar una recolección personalizada, pero el mismo dato podría extraerse de una implementación de analítica, aunque efectos similares se podrían haber conseguido utilizando el tiempo en página como rating, una valoración directa del artículo, el número de comentarios, etc.

a de la arquitectura del recomendador empleando módulos de GCP.
En nuestro caso, como estamos realizando este ejercicio empleando GCP, recogemos los datos valiéndonos de Cloud Functions y los insertamos en una base de datos Cloud SQL. Para no distraer de la narrativa, pondré a disposición del lector la función de recolección e inserción de la base de datos en lugar de insertarlo aquí.

Figura 2: Diagrama entidad-relación de la base de datos para almacenar la información relacionada con las predicciones basadas en vistas de artículos.
De acuerdo con la Figura 2, que explica el diagrama entidad-relación de nuestra base de datos, tendremos dos tablas principales, una para los datos proporcionados por la capa de recolección y otra para los calculados por nuestro algoritmo de recomendación. Además, tendremos dos tablas para traducir los identificadores de usuario y de post en algo manejable por el algoritmo (la librería que emplearemos nos obligará a utilizar índices naturales para representar a cada usuario y cada producto a recomendar).
Predicción
A la hora de ejecutar el algoritmo predictivo, necesitaremos transformar los datos que hemos extraído en algo que pueda manejar el algoritmo de predicción. Como comentábamos antes, tenemos dos tablas de traducción de identificadores de usuario y producto en índices.
Partiendo del dato recuperado que podemos ver en la tabla 1, tendremos un dato traducido como en la tabla 2.
visitorId |
url |
rating |
015e7e6…3d08100408 |
http://www.analiticawe…-para-ser-mejor-analista/ |
25 |
015e7f1…3d06a0086e |
http://www.analiticawe…-para-ser-mejor-analista/ |
0 |
015e80d…3d06a0086e |
http://www.analiticawe…-para-ser-mejor-analista/ |
100 |
015e810…3d06a00ac2 |
http://www.analiticawe…-para-ser-mejor-analista/ |
50 |
015e814…3d06b008a0 |
http://www.analiticawe…-para-ser-mejor-analista/ |
25 |
015fac9…190700086e |
http://www.divisadero….po-de-datos-estas-usando/ |
75 |
015f528…1c06b0086e |
http://www.divisadero….po-de-datos-estas-usando/ |
75 |
0160571…4506a0086e |
http://www.divisadero….po-de-datos-estas-usando/ |
50 |
0160894…4506a0086e |
http://www.divisadero….po-de-datos-estas-usando/ |
50 |
015f2a0…1906b0086e |
http://www.divisadero….iva-sistema-recomendacion |
50 |
015ffec…1900d0086e |
http://www.divisadero….iva-sistema-recomendacion |
50 |
0160040…4208a0093c |
http://www.divisadero….iva-sistema-recomendacion |
0 |
016007d…5700d007e8 |
http://www.divisadero….iva-sistema-recomendacion |
25 |
… |
… |
… |
Tabla 1: Tabla con los IDs de usuario e identificadores del producto (en nuestro caso la URL) sin indexar.
userId |
postId |
rating |
323452 |
234 |
25 |
532342 |
234 |
0 |
421254 |
234 |
100 |
655343 |
234 |
50 |
367561 |
234 |
25 |
345323 |
121 |
75 |
432122 |
121 |
75 |
316052 |
121 |
50 |
787238 |
121 |
50 |
732637 |
104 |
50 |
283712 |
104 |
50 |
983716 |
104 |
0 |
367167 |
104 |
25 |
… |
… |
… |
Tabla 2: Tabla con los IDs de usuario e identificadores del producto indexados.
El algoritmo de recomendación que estamos utilizando es el de filtrado colaborativo. Para ejecutarlo podemos seguir la recomendación de buenas prácticas para ejecutar tareas periódicas en GCP. En nuestro caso hemos escrito este script en python para ejecutar en pyspark sobre Dataproc,tomando como modelo el que provee Google.
La ejecución de este script sobrescribe la tabla de predicciones (como podemos ver en la Figura 1) con la información más fresca, y nos deja un resultado como el de la tabla 3.
userId |
postId |
rating |
18872 |
2289 |
98 |
42044 |
1644 |
23 |
42044 |
4723 |
22 |
42044 |
13860 |
21 |
42044 |
698 |
21 |
42044 |
148 |
20 |
24688 |
1655 |
53 |
24688 |
58 |
50 |
24688 |
1654 |
45 |
24688 |
4482 |
43 |
… |
.. |
… |
Tabla 3: Resultado del algoritmo de predicción
Egresión
Gracias a las Cloud Functions y demás arquitectura Serverless que ofrecen los grandes proveedores de servicios en la nube (AWS con las Lambda Functions, Google con las Cloud Functions y Microsoft con Azure Functions) podemos implementar este tipo de APIs muy fácilmente.
Hace unos meses, mi compañero Nico revisaba la ventaja estratégica que supone este tipo de servicios en su artículo. Puede servirnos de introducción si queremos un poco de contexto antes de ponernos manos a la obra.
Símplemente pediremos las recomendaciones (si las hubiera) para el usuario actual desde el frontusando una función similar a esta (sirva de plantilla). Es interesante señalar que estamos ingresando datos de orígenes disjuntos como son nuestros dos blogs (Analitica Web y el blog de Actualidad de Divisadero) por lo que, para que las recomendaciones tengan sentido dentro de cada uno, deberemos filtrar las que se refieren a este. Es decir, dentro del blog de Analitica serviremos solamente recomendaciones sobre este mismo blog.
Al pedir los datos a la api para cierto ID, debería devolvernos una respuesta de este tipo:
{
“tuuid”: “015e949d37bb001a1b2316be0a3e04072003606a00918”,
“post_array”: [
“https://www.analiticaweb.es/torturando-los-datos-capitulo-i/”,
“https://www.analiticaweb.es/cross-device-tracking-google-analytics/”,
“https://www.analiticaweb.es/un-paso-mas-en-data-studio/”,
“https://www.analiticaweb.es/multi-channel-funnels-o-embudos-multicanal-de-ga/”,
“https://www.analiticaweb.es/trucos-lowcost-para-adobe-analytics/”
]
}
Después es tarea personal mostrar esta información de manera relevante sobre la página.
Next Steps
Ahora mismo las recomendaciones se calculan periódicamente, el dato no es absolutamente fresco. Es decir, si una visitante habitual del blog lee un artículo, esta información no se incluirá en las recomendaciones hasta la próxima vez que se recalculen. Se dará la paradoja de que después de leer los artículos recomendados, seguiremos recibiendo las mismas recomendaciones. Aunque las predicciones puedan recalcularse con mucha frecuencia, seguirán siendo procesos en batch, cuando lo que necesitamos es un procesamiento en streaming con el dato siempre fresco.
Para este propósito disponemos en GCP de una herramienta llamada Dataflow, fácilmente operable en python, que junto con esta librería (FluRS) podría realizar esta predicción casi en tiempo real. Funciona implementando el algoritmo de Descenso Estocástico del Gradiente (SGD) para calcular un modelo fiable, pero con la particularidad de hacerlo de manera incremental, de tal forma que se pueden añadir datos al vuelo.
Da gusto trabajar con la plataforma Fully Managed de GCP, con toda la potencia al alcance de la mano. Espero que el lector pueda extraer información práctica del artículo. Como siempre, las preguntas son bien recibidas.