Sistema de Recomendación con Python

En este post vas a apredenr a programar un sistema de recomendación con Python.

Para ello, primero veremos qué tipos de sistema de recomendación existen, junto con sus ventajas y desventajas. Después, programaremos cada uno de ellos de forma individual desde cero. Por último, veremos herramientas que puedes utilizar para crear sistemas de recomendación con Python.

¿Te suena interesante? ¡Vamos con ello!

Tipos de sistema de recomendación

Un sistema de recomendación se basa en ofrecer contenido a los usuarios. En este sentido existen dos grandes tipos de sistemas de recomendación:

  1. Los sistemas de recomendación no personalizados: son aquellos sistemas que recomiendan a todo el mundo el mismo contenido. Por ejemplo, cuando una página te recomienda los posts más populares, esa recomendación es para todo el mundo la misma.
  2. Los sistemas de recoemndación personalizados: en este caso a cada usuario se le recomienda contenido diferente, buscando siempre que el contenido ofrecido se pueda ajustar a los gustos del usuario. En estos casos es dificil que dos personas reciban la misma recomendación.

Como este es un blog sobre Machine Learning nos centraremos únicamente en los sistemas de recomendación personalizados. Pero, ¿en qué se basa un sistema de recomendación para personalizar el contenido? Atento.

Cómo funciona un sistema de recomendación personalizado

Dentro de los sistemas de recomendación personalizados existen tres grandes enfoques que se pueden aplicar: basarse en contenido, basarse en usuarios, y un sistema híbrido. Veamos en qué consiste cada uno de ellos.

Sistema de recomendación basado en contenido

Como su nombre indica un sistema de recomendación basado en contenido utiliza las características del contenido para hacer las recomendaciones. En otras palabras, un sistema de recomendación basado en contenido recomendará contenidos similares a los contenidos que le han gustado al usuario.

Supongamos que me gustan mucho las películas de acción y últimamente me he pegado un atracón a la saga Jonhn Wick. Así pues, un sistema de recomendación me recomendaría películas que fueran de acción, protagonizadas por Keanu Reeves, dirigidas por Chad Stahelski (director de John Wick), que además fueran del mismo año, etc.

En resumen, la clave de los sistemas de recomendación basados en contenido es definir las características del contenido que se usarán para encontrar productos parecidos. Para el caso que he comentado: actor principal, director, categoría de la pelicula, año, etc.

Pros y contras del sistema de recomendación basado en contenido

Pros del sistema de recomendación basado en contenido

  • El sistema permite realizar predicciones desde el momento en el que un usuario consume un producto. Es decir, el sistema no sufre del problema conocido como cold-start.
  • Se trata de un sistema adaptativo: si el usuario cambia sus gustos, el sistema se dará cuenta de esos cambios de forma rápida.
  • Se pueden recomendar productos poco populares.
  • Se trata de un sistema que es fácil de escalar.

Contras del sistema de recomendación basado en contenido

  • El contenido recomendado siempre es parecido. Es decir, si una persona consume peliculas de acción es dificil que se le recomienden productos de otra categoría.

Sistema de recomendación basado en usuarios

En el sistema de recomendación basado en usuario, la idea es encontrar usuarios con gustos similares a los tuyos y recomendarte el contenido que a esos usuarios les han gustado, pero que tú no has consumido.

Por ejemlo, supongamos que yo tengo un gusto similar a mi amigo Mike en lo que ha peliculas se refiere y a los dos nos gusta John Wick, pero Mike ha visto The Equalizer e Inside Out, ambas peliculaes le han gustado, y yo no las he visto. Estas peliculas serían candidatas a ser recomendadas.

En resumen, la clave de un sistema de recomendación basado en usuarios es poder perfilar bien los gustos de los usuarios para así poder realizar las recomendaciones lo más acertadas posible.

Para conseguir este objetivo, existen dos enfoques diferentes:

  1. Sistemas basados en memoria: consiste en construit una matriz Usuario-Contenido y usar buscar usuarios similares. Aunque interesante intuitivo, este enfoque es poco escalable, puesto que cuando tienes millones de usuarios y miles de contenidos, la matriz resultante es demasiado grande.
  2. Sistemas de recomendación basado en modelos: estos sistemas buscan superar las limitaciones de los sistemas basados en memoria, mediante la utilización de modelos de ML como redes neuronales, redes bayesianas, modelos de clusterización, etc.

Pros y contras del sistema de recomendación basado en usuarios

Pros del sistema de recomendación basado en usuarios

  • Puede ofrecer contenido diferente al que generalmente se consume. Volviendo a los ejemplos, el sistema de recomendación basado en usuarios es capaz de recomendar Upside Down (pelicula de animación), a pesar de que sea muy diferente a lo que usualmente suela ver (películas de acción).

Contras del sistema de recomendación basado en usuarios

  • Dificultar para recomendar contenido nuevo. Como pocas (o ninguna) persona habrá consumido dicho contenido, es dificil que un sistema de recomendación basado en usuarios recomiende un contenido nuevo.
  • Sufren del problema de cold start. Requieres de un consumo elevado de contenido para poder perfilar de forma correcta a un usuario y así realizar recomendaciones

Sistema de recomendación híbrido

Un sistema de recomendación híbrido se basa en combinar las predicciones realizadas por un sistema de recomendación basado en contenido y un sistema de recomendación basado en usuarios.

De esta forma, el sistema de recomendación híbrido cuenta con los puntos fuertes tanto del sistema de recomendación basado en contenido como el sistema de recomendación basado en usuarios.

Ahora que ya sabemos qué es y cómo funciona cada uno de los diferentes tipos de sistemas de recomendación, vamos a programar cada uno de ellos desde cero en Python. ¡Vamos con ello!

Cómo programar un sistema de recomendación basado en contenido en Python

1. Descarga de datos

Lo primero de todo, para programar un sistema de recomendación hace falta un dataset. Para ello, vamos a usar el dataset IMDB, el cual es un dataset con información sobre más de 1.000 peliculas y series valoradas en IMDB. Puedes descargarte el dataset desde aquí.

Así pues lo primero de todo vamos a cargar el dataset:

import pandas as pd

imdb = pd.read_csv('imdb_top_1000.csv')
imdb.head()

Como podemos ver, contamos con varias variables (Genre, Director, Stars, etc.) que nos permiten caracterizar la pelicula.

Ahora que conocemos cómo son los datos, vamos a prepararlos.

2. Preparación de datos

De cara a entrenar el modelo considero que hay dos enfoques principales:

  1. Realizar el sistema en base a la descripcción de la película, de tal forma que se recomienden películas con descripcciones similares.
  2. Realizar el sistema de recomendación en base a las características de la película (director, actores, género, etc.).

Sin duda alguna, en un caso real utilizaría una combinación de ambas. Sin embargo, para facilitar el proceso simplemente voy a coger las características de la película (excluyendo la descripcción), puesto que la descripcción puede incluir ciertos sesogs según el año, compañía, etc.

Asi pues como una película puede pertenecer a varios géneros, voy a dumificar los géneros. Para ello utilizaré la función OneHotEncoder de la librería Sklearn.

Si quieres aprender más sobre la librería Sklearn, te recomiendo que leas este post donde te explico paso a paso cómo usarla.

from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.pipeline import Pipeline


# Filter data
keep_cols = ['Genre', 'Director', 'Star1', 'Star2']
imdb_filtered = imdb.loc[:,keep_cols]


# Create pipeline for numerical variables
numeric_pipe = Pipeline([
    ('scaler', StandardScaler())
    ])

# Create pipeline for categorical variable
categorical_pipe = Pipeline([
    ('encoder', OneHotEncoder(drop = 'first'))
    ])

# Create ColumnTransform
col_transf = ColumnTransformer([
    ('numeric', numeric_pipe, imdb_filtered._get_numeric_data().columns.tolist()),
    ('categoric', categorical_pipe, imdb_filtered.select_dtypes('object').columns.tolist()) 
    ])

col_transf_fit = col_transf.fit(imdb_filtered)
imdb_filtered_transf = col_transf_fit.transform(imdb_filtered)
imdb_filtered_transf
<1000x2247 sparse matrix of type '<class 'numpy.float64'>'
    with 3986 stored elements in Compressed Sparse Row format>

Con esto ya tenemos los datos preparados, ahora veamos cómo crear nuestro sistema de recomendación basado en contenido con Python.

3. Programar el sistema de recomendación basado en contenido con Python

Generar un sistema de recomendación basado en contenido es relativamente sencillo. El sistema se basa en dos pasos:

  1. Encontrar la similitud de una película que le ha gustado al usuario respecto al resto de películas.
  2. Seleccionar las N peliculas que más se parezcan a la película que le ha gustado al usuario.

Si te fijas, todo este proceso se parece mucho al algoritmo kNN. Sin embargo, hay que tener en cuenta que contamos con una matriz sparse, por lo que no podemos aplicar cualquier tipo de distancia, sino que tendremos que aplicar una distancia como el distancia del coseno.

Si quieres aprender más cómo funciona el algoritmo kNN, los tipos de distancia que existen y cuándo usar cada una de ellas, te recomiendo que leas este post. Está programado en R en vez de en Python, pero a nivel teórico seguro que te es util.

Asi pues, para montar el sistema de recomendación basado en contenido vamos a calcular la matriz de similitudes entre las diferentes películas. Para ello vamos a usar la función NearestNeighbor fijando como el timpo de distancia cosine:

from sklearn.neighbors import NearestNeighbors

n_neighbors=5

nneighbors = NearestNeighbors(n_neighbors = n_neighbors, metric = 'cosine').fit(imdb_filtered_transf)

Ya tendríamos nuestro recomendador entrenado. Ahora solo falta pasar una nueva película para hacer recomendaciones. Veamos pues qué recomienda el sistema a una persona que ve la película The Godfather:

dif, ind = nneighbors.kneighbors(imdb_filtered_transf[1])

print("Liked Film")
print("="*80)
imdb.loc[ind[0][0], :]
print("Recommended Films")
print("="*80)
imdb.loc[ind[0][1:], :]
Liked Film
================================================================================
Recommended Films
================================================================================

Como podemos ver nuestro sistema de recomendación basado en contenido ha funcionado correctamente, recomendando películas parecidas a las que le he han gustado al usuario.

Ahora que ya sabemos cómo programar un sistema de recomendación basado en contenido en Python, veamos cómo programar un sistema de recomendación basado en usuarios.

Sistema de recomendación basado en usuarios en Python

1. Carga de Datos

Para poder entrenar un sistema de recomendación basado en usuarios necesitamos, dos cosas:

  1. Información sobre el contenido que existe.
  2. Si ha cada usuario le ha gustado o no los contenidos que ha visto. Esto se suele medir mediante la valoración del usuario, «Me Gustas», etc.

Para ello, voy a descargarme los datos de esta página la cual incluye información sobre libros, usuarios y los ratings que han dado los usuarios a dichos libros:

import pandas as pd
import requests
#from zipfile import ZipFile
import shutil

url = "http://www2.informatik.uni-freiburg.de/~cziegler/BX/BX-CSV-Dump.zip"
resp = requests.get(url)

data_file = 'BX-CSV-Dump'

# Make request
resp = requests.get(url)

# Get filename
filename = url.split('/')[-1]

# Download zipfile
with open(filename, 'wb') as f:
  f.write(resp.content)

# Extract Zip
# with ZipFile(filename, 'r') as zip:
#   zip.extractall('')


shutil.unpack_archive(filename, '/content')

ratings = pd.read_csv('BX-Book-Ratings.csv', sep = ";", encoding='latin-1', on_bad_lines='skip')

ratings.head()

Ahora que tenemos los datos, y antes de pasar a ponernos a transformalos, vamos a intentar entenderlos. Para ello, vamos a realizar un histograma con las valoraciones:

ratings['Book-Rating'].hist()

Como podeos ver, parece que contamos con muchas valoraciones con el valor de 0. Aunque esto pueda parecer un error, si nos vamos al origen de los datos (enlace) nos indican que las valoraciones de 0 son valoraciones implícitas, es decir, valoraciones que no ha hecho el usuario, sino que han extraido a partir del comportamiento del mismo.

Así pues, ahora que tenemos los datos y entendemos cómo funcionan, vamos a preparar los datos para poder entrenar nuestro sistema de recomendación.

2. Preparación de los datos

Como hemos visto anteriormente, existen dos tipos de sistemas de recomendación basado en usuarios: sistemas basados en memoria y los sistemas basados en ML.

Así pues, de cara a decidir qué tipo de sistema vamos a utilizar, lo primero es crear una matriz usuario-contenido.

Para ello, lo primero de todo vamos a quitar las valoraciones implicitas que vienen en los datos, puesto que no conocemos muy bien cómo se ha llegado a ellas y nos pueden añadir ruido al modelo.

Además, vamos a crear una matriz sparse, puesto que si pivotáramos, lo más probable es que tuviéramos problemas de memoria, ya que se trata de un dataset muy grande:

from scipy import sparse

res = (
  ratings
 .query('`Book-Rating` != 0')
)

Ahora que tenemos los datos preparados vamos a pasar a crear nuestro sistema de recomendación basado en usuario con Python.

3. Programar el sistema de recomendación basado en usuarios con Python

Existen diferentes formas con las que podríamos crear nuestro sistema de recomendación. En nuestro caso vamos a optar por el análisis semántico latente o LSA. La idea es sencilla: descomponer la matriz varias submatrices cuyo producto de como resultado la matriz original.

Además, guardaremos algunos datos reales para comprobar qué tal está funcionando nuestro sistema de recomendación, puesto que, si funciona correctamente, los valores propuestos para nuestros datos deberían coincidir con los valores reales del modelo.

Aunque dicho así suene muy complejo, aplicarlo es muy sencillo, puesto que la librería Surprise ya cuenta con funciones específicas para este propósito.

Nota: la librería Surprise se centra en recomendaciones explícitas, es decir, recomendaciones dadas explícitamente por el usuario (como el rating, por ejemplo).

Lo primero de todo será instalar surprise, lo cual puedes hacer con el siguiente comando:

pip install scikit-surprise

Una vez instalado, vamos a convertir nuestro dataframe en un Dataframe de Surprise. Para ello, vamos a usar la función load_from_df, así como un reader que indique la escala de las valoraciones y el orden de las columnas.

Importante: los dataset de Surprise solo pueden tener 3 columnas, en el orden de usuario, item, valoración.

from surprise import Dataset, Reader

# Convert dataframes to surprise datasets
reader = Reader(line_format='user item rating', rating_scale=(1, 5))
data = Dataset.load_from_df(res, reader)

Ahora que hemos cargado los datos, vamos a generar nuestro set de entrenamiento y vamos a entrenar nuestro sistema de recomendación.

El objetivo de este post no es entrenar el mejor sistema de recomendación posible, sino explicar, a grandes rasgos, cómo crear un sistema de recomendación colaborativo en Python.

Para ello, la librería Surprise cuenta con varios tipos de modelos, entre los que se encuentra SVD, que es lo mismo que el modelo LSA que hemos comentado previamente.

Así pues, entrenamos nuestro modelo:

from surprise import SVD

# Build full trainset
data_train_surp = data.build_full_trainset()

# Define the model
svd = SVD()

# Train the model
svd.fit(data_train_surp)
<surprise.prediction_algorithms.matrix_factorization.SVD at 0x7effdb9f2690>

Ahora que tenemos el modelo entrenado, vamos a ver qué tal funciona sobre datos nuevos. Para ello, voy a crer el set de test y hacer las predicciones sobre dicho set.

Por último, vamos a usar las funciones ofrecidas por surprise para evaluar qué tal funciona nuestro modelo:

from surprise import accuracy

data_test_surp = data_train_surp.build_testset()

#data_test_surp = data_test_surp.construct_testset()
predictions = svd.test(data_test_surp)

accuracy.mae(predictions)
2.7562021230889315

Como podemos ver, contamos con un MAE de 2.75, que, considerando que hablamos de 1 a 5 estrellas es bastante elevado.

Para poder mejorarlo se podría (y debería) aplicar diferentes técnicas, como la optimziacion de hiperparámetros mediante Grid Search.

De hecho, surprise cuenta con la función GridSearch que cumple con dicho propósito, aunque no es el objetivo de este post.

En cualquier caso, veamos cómo obtener predicciones para un usuario concreto y una película concreta. Para ello, le debemos pasar una tuple con los valores id_usuario y id_contenido a nuestro sistema de recomendación:

user_id = 276726
content_id = '0155061224'

svd.predict(user_id, content_id)
Prediction(uid=276726, iid='0155061224', r_ui=None, est=5, details={'was_impossible': False})

Conclusión

En este psot he explorado los diferentes tipos de sistema de recomendación que existen y hemos visto cómo crear los sistemas de recomendación básicos con Python.

Sin duda alguna, este post solo pretende ser una pequeña introducción a un mundo mucho más amplio y complejo, puesto que en la vida real los sistemas de recomendación deben resolver problemas como las recomendaciones implícitas, la puesta en producción o la realización de A/B testing para medir la capacidad predictiva de los algoritmos.

En cualquier caso, espero que este post te haya servido para saber un poco más sobre el mundo de los sistemas de recomendación, los tipos que hay, los pros y contras de cada uno y cómo se desarrollan.

Si este psot te ha gustado, te recomiendo que te suscribas para ser notificado cada vez que publique un nuevo post. ¡Hasta la próxima!