Motores de Recomendación con Python (2/3)
- Antonio Romero Camacho
- Feb 14, 2021
- 9 min read
En el post anterior usamos los elementos que le gustaban a un usuario para sugerirle otros elementos similares. Esta propuesta funciona bien cuando tenemos mucha información sobre los elementos a recomendar, pero sin embargo tenemos pocos datos sobre los sentimientos de los usuarios hacia dichos items.

En este nuevo post, mostraremos la forma de encontrar los usuarios con las preferencias más similares al sujeto al que le estamos haciendo recomendaciones y, en base a las preferencias de ese grupo, lanzaremos nuestras recomendaciones.
1. Filtrado Colaborativo
El filtrado colaborativo es la técnica que predice o filtra elementos que podrían interesar a un usuario basándose en las preferencias de usuarios similares. Esta técnica sienta sus bases en las siguientes premisas:
Una persona A tiene gustos similares a otras personas B y C
Si a las personas B y C les gusta otro elemento
Entonces ese nuevo elemento también le gustará a la persona A
A continuación lo representamos gráficamente por simplicidad. A los usuarios A, B y C les gusta la serie "Defending Jacob". Por otro lado, B y C también han puntuado positivamente la serie "Servant". por lo tanto, cabría esperar que al usuario A (que coincidía en gustos con B y C) también le guste la serie "Servant".

La pregunta es, ¿cómo conseguimos encontrar usuarios con gustos similares?. Lo haremos a través de las valoraciones que hacen los usuarios de distintos productos. Si los datos los tenemos ordenados tal y como se muestra en la tabla izquierda de la siguiente figura, será difícil hacer comparaciones entre usuarios. Por ello, será necesario generar una matriz que directamente relacione los usuarios con los items que han valorado. Vemos un ejemplo de ellos en la tabla derecha de la siguiente figura.

Tras realizar la transformación a la nueva matriz, podemos hacer comparaciones entre usuarios. Vemos claramente que el Usuario 1 y el Usuario 3 tienen gustos más similares que el Usuario 1 y el Usuario 2.
Para mostrar cómo generar esta tabla en Python, vamos a trabajar con un dataset de películas. El dataset incluye información de los usuarios, la película evaluada y su valoración numérica.

Antes de comenzar, eliminamos los duplicados para evitar tener problemas a la hora de generar la matriz. Si tuviéramos valores duplicados para los pares usuario-ítem, Python lanzaría un error pues no podría asignar valores diferentes a una misma casilla.

Al tener los datos organizados en un DataFrame, se puede utilizar el método pivot para reorganizar los datos de una forma diferente. Vamos a comenzar usando los ID de usuario como índices, los items por columnas y en los lugares marcados por los pares anteriores incluiremos las puntuaciones dadas por un usuario para un producto concreto.

Lo primero que observamos tras la transformación es que hay un gran número de pares sin valoración, lo cual queda reflejado por los NaN. No es de extrañar este comportamiento, pues es raro que un usuario puntúe cada uno de los elementos presentes en el dataset. Del mismo modo es muy raro que un artículo haya sido valorado por todos los usuarios. Esto es un problema, pues las mayorías de las métricas que miden el grado de similitud no funcionan bien cuando hay tantos NaN.
¿Cómo solucionamos este problema?. La respuesta no es eliminar todas las filas y columnas con NaN, pues estas matrices suelen ser tan dispersas que en algunos casos implicaría acabar prácticamente con toda la matriz (véase lo que ocurre si eliminamos los NaN en nuestro caso de uso).

¿Cuál es la alternativa en estos casos?. Una alternativa podría ser la de rellenar esas posiciones de la matriz con 0s. Éste método podría ser válido con algunos modelos de Machine Learning pero con motores de recomendación puede llegar a ser problemático. Al poner un cero como valoración estaríamos asignando una puntuación negativa a una película por parte de un usuario de forma totalmente incorrecta.
Otra alternativa es la de centrar las puntuaciones de cada usuario en torno al 0 restándole la media de las puntuaciones del usuario y rellenando las posiciones vacías con 0s finalmente. En este caso, al asignar los ceros después de substraer la media lo que hacemos es asignar valores neutros a esas posiciones sin valoración.

A continuación mostramos como realizar esta operación con Python. En primer lugar calculamos la media por filas. Después, le restamos a cada fila el valor de la media de esa fila. Este es el resultado de estas dos operaciones:

Una vez llegados aquí, rellenamos los NaN con 0s. No es la solución ideal ya que esos valores pierden algo de interpretabilidad, pero es suficiente para hacer comparaciones entre usuarios.

2. Resolviendo el problema de encontrar similitud
A lo largo de este post hemos centrado nuestra atención en encontrar usuarios similares. A esta subcategoría se la conoce como filtrado colaborativo basado en el usuario (user-based collaborative filtering). Sin embargo, existe la posibilidad de comparar elementos en lugar de usuarios; a esta subcategoría se la conoce como filtrado colaborativo basado en elementos (item-based colaborativo filtering).
Filtrado colaborativo basado en elementos
Esta subcategoría de filtrado asume que si los elementos 1 y 2 reciben valoraciones de los usuarios similares (independiente de que sean buenas o malas), entonces la opinión de un sujeto sobre el elemento 1 será similar a la que tenga sobre el elemento 2.
A continuación lo explicamos con un ejemplo. Imaginemos que el sujeto B ha disfrutado mucho de las dos series y que el sujeto C ha respondido que no volvería a verlas porque le han resultado aburridas. Entonces, si un nuevo sujeto ha valorado la serie "Defending Jacob" de forma positiva, cabría esperar que sienta lo mismo por la serie "Servant".

Si tuviéramos los datos organizados tal y como hemos mostrado a lo largo de esta publicación, ¿cómo podríamos transformarlos para recomendar en base a elementos?. Sólo hay que trasponer la matriz para que los elementos pasen a ser filas y los usuarios columnas. Mostramos la explicación gráficamente a continuación.

Con Python podemos conseguirlo a partir de la matriz punt_usu_pivot_cent_f usando la biblioteca pandas. La matriz traspuesta se obtiene aplicando el método punto T a la matriz a trasponer. A continuación podemos ver la matriz basada en usuarios y debajo la traspuesta que es la matriz basada en elementos. Más adelante analizaremos qué opción es más recomendable pero, a alto nivel, la respuesta es que depende de los datos. De ahora en adelante en el post nos vamos a centrar en los datos basados en elementos.


Si nos fijamos en las dimensiones de la matriz original (610 x 9719) y de la traspuestas (9719 x 610), vemos que se han cambiado filas por columnas y que por tanto la operación se ha realizado correctamente.
Similitud coseno
Con matrices para recomendar en base a elementos que contienen una fila por película, podemos calcular la similitud y la distancia entre los distintos elementos del dataset, al igual que hicimos en el post anterior. Seguiremos usando la similitud coseno para calcular el grado de similitud. Si embargo, es importante que recordemos que los datos ahora están centrados en torno a cero, y por lo tanto los valores del coseno oscilaran entre -1 y 1, siendo 1 el grado de similitud más alto y -1 el de menor similitud.
A pesar de que los rangos de las variables de similitud serán diferentes, esto no implicará ningún cambio durante la implementación.
A continuación haremos una comparación entre dos películas de Disney. Para ello, tendremos que acceder a las filas con la información de las dos películas a comparar. Usando "Aladdin" y "La Bella y La Bestia" la similitud coseno indica que ambas son similares (recordemos que sus valores oscilan entre -1 y 1).
Sin embargo si comparamos dos películas como "The Twilight Samurai" y "El Señor de los Anillos" la similitud coseno no muestra tanto grado de parecido, proporcionando valores de similitud negativos.

El objetivo de la similitud coseno no es comparar películas por pares, sino realizar recomendaciones. Para calcular la similitud entre todas las películas de una sola vez, necesitamos aplicar la similitud coseno al conjunto entero como vemos a continuación.El resultado lo almacenamos en un DataFrame que toma como índices y como columnas los nombres de las películas.

Una vez tenemos la matriz calculada, podemos lanzar recomendaciones en base a los elementos que han sido puntuados de forma más parecida al item elegido por un usuario en concreto. En base a eso, y ordenando las puntuaciones en orden descendente, seremos capaces de recomendar, En el siguiente ejemplo hemos determinado las películas más parecidas a "Harry Potter y La Piedra Filosofar" en base a las puntuaciones de las diferentes películas. Vemos que las que han obtenido mayor similitud coseno son el resto de las película de la saga.


Con lo que hemos visto hasta ahora, somos capaces de recomendar elementos en base a las puntuaciones que han dado los usuarios. ¿Nos valdrían los métodos vistos hasta ahora para predecir la puntuación que daría un usuario a un elemento en caso de que ese elemento no se parezca a otros que ya ha visto?. La respuesta es no.
A continuación veremos como usando modelos de K-Nearest Neighbors podemos identificar los usuarios con gustos más parecidos al usuario concreto y analizar cómo puntúan ellos al item sobre el que queremos predecir la puntuación.
3. K-Nearest Neighbors
K-Nearest Neighbours viene a ayudarnos en situaciones como la que mostramos en la siguiente figura. Tenemos un usuario que no ha visto ninguna película similar a la película Greyhound (sabemos que este usuario ama la comedia y odia los dibujos animados pero desconocemos su opinión en géneros de acción y drama) y queremos lanzar una predicción sobre la puntuación que le daría.

En otros posts del blog DataHintES hemos hecho predicciones para resolver problemas de clasificación y de regresión. Como recordatorio, el modelo de KNN nos permitirá identificar a los usuarios que están más cercanos a un usuario en concreto de acuerdo a una métrica determinada. En el siguiente ejemplo, cuando k toma valores de 3 el modelo encuentra los tres usuarios más cercanos en rojo y obtiene sus puntuaciones. En el caso de k igual a 5, encuentra a los cinco usuarios más cercanos y de nuevo toma sus puntuaciones. La obtención de esta información permitirá predecir el sentimiento de un usuario hacia un producto concreto, incluso si no lo han visto antes. La biblioteca Scikit-Learn tiene este modelo listo para ser usado. Antes de emplearlo, vamos a aprender cómo funcionan estos modelos paso a paso.

Puesto que ahora vamos a identificar usuarios similares partiremos de la matriz User-based y calcularemos la similitud coseno de la misma. Como siempre, almacenaremos los valores en un DataFrame que tenga como índices y columnas los ID de usuario.

KNN paso a paso
El ejercicio que vamos a resolver ahora es el siguiente: ¿Qué puntuación le daría el Usuario 1 a la película El Rey León?
Supongamos que quiero encontrar los 10 usuarios más parecidos al usuario con ID de usuario igual a 1. Para ello seleccionamos las similitudes coseno del usuario 1 y las ordenamos de mayor a menor. Por último seleccionaremos los diez primeros usuarios sin tener en cuenta el de índice 0 porque será él mismo.

A continuación, nos vamos a la matriz original donde teníamos las puntuaciones sin centrar en cero (porque queremos predecir la puntuación) y extraemos las puntuaciones dadas por esos 10 usuarios. De todas las puntuaciones que han dado nos quedaremos con sus valoraciones a El Rey León. Haciendo la media de esos valores obtendríamos la predicción e puntuación del usuario 1 a la película El Rey León.

KNN con Scikit Learn
Para ello vamos a necesitar dos datasets:
punt_usuario_pivot: dataset user-based, con una fila por usuario, una columna por película y valores de puntuación no centrados en 0.
punt_usu_pivot_cent_f: dataset user-based, con una fila por usuario, una columna por película y valores de puntuación centrados en 0.
Nuestro target sigue siendo el mismo, la película El Rey León. Por tanto eliminaremos la columna El Rey León.

Después extraemos las puntuaciones del Usuario 1del resultado anterior. Usamos los corchetes para que el resultado siga siendo un DataFrame. Esta variable la dejamos apartada de momento porque será la entrada que usaremos para realizar la predicción.

A continuación, extraemos de la matriz original las puntuaciones que le han dado los distintos usuarios a la película El Rey León.

¿Cómo calcular nuestra matriz de características de entrenamiento?. Estas estarán formadas por todas las puntuaciones centradas de los usuarios que hayan puntuado la película El Rey León. Por ello escogeremos de la matriz punt_usu_pivot_cent_f aquellos usuarios que no tengan un valor NaN en la columna El Rey León.

Del DataFrame other_users_y eliminamos los valores NaN. Este DataFrame se convertirá en nuestras Ys de entrenamiento.

Ahora podemos importar e inicializar el modelo KNeighborsRegressor desde la biblioteca Scikit-Learn, especificando como métrica la similitud coseno. Especificamos que el número de vecinos k es igual a 10. Ajustamos con el método .fit y predecimos el valor de Y para los valores de X del usuario 1.

Vemos que el resultado es similar al que obtuvimos haciendo KNN paso a paso (4.05 con Scikit-Learn y 4.0 con el método paso a paso).
4. Conclusión y presentación del próximo post
Con la información que hemos recogido en este post deberíamos ser capaces de generar recomendaciones para cualquier usuario de nuestro dataset así como de predecir la puntuación que un usuario le daría a distintos elementos que desconoce a través de los modelos KNN.
Todas estas técnicas funcionan muy bien con datasets densos en los que todos los elementos han sido puntuados por muchas personas. En ese escenario, la aplicación de los modelos de KNN proporciona predicciones parecidas a los gustos del usuario.
¿Qué ocurre cuando la matriz de datos no está tan llena de información?. Bienvenidos al mundo real, pues este es el problema con el que vamos a tener que lidiar cuando queramos hacer motores de recomendación para cualquier aplicación. Por regla general, el número de elementos que tendrá el dataset será elevado y el número de reviews muy bajo. Trabajar con matrices de este tipo puede ser problemático al aplicar modelos de KNN. Por ello, en el próximo post hablaremos de matrices dispersas y de cómo lidiar con este problema en la vida real mediante la técnica de Factorización Matricial.
Espero que esta cadena de posts esté siendo de vuestro interés. Para cualquier duda no dudéis en poneros en contacto a través del directorio.
Keywords: Filtrado Colaborativo, matriz item-based, matriz user-based, similitud coseno, predicción, K-Nearest Neighbours, motores de recomendación.
Comentários