Un recomendador de canciones por metodología de filtrado colaborativo usuario-usuario e ítem-ítem.
Proyecto simple de machine learning con Python
Ing. Leonardo López
Introducción:
En este proyecto para diseñar el recomendador busco calcular las similitudes entre las personas que son usuarios de nuestro sistema, por lo que calculo las distancias vectoriales que las separan, y para ello me baso primero trazar la Matriz de Utilidad.
In [4]:
#Importamos las librerías principales import math from scipy.spatial.distance import euclidean from pandas import read_csv import seaborn as sns import pandas as pd
In [5]:
#Importamos los datos levantados del servidor de GitHub from pandas import read_csv data_url = 'https://gist.githubusercontent.com/jackbandy/5cd988ab5c3d95b79219364dce7ee5ae/raw/731ecdbecc7b33030f23cd919e6067dfbaf42feb/song-ratings.csv' ratings = read_csv(data_url,index_col=0)
In [6]:
from IPython.display import display, HTML display(HTML(ratings.to_html()))
One Dance (Drake) | Lean On (Major Lazer) | Sunflower (Post Malone and Swae Lee) | Somebody That I Used To Know (Gotye) | Rolling in the Deep (Adele) | Can’t Hold Us (Macklemore) | 7 Rings (Ariana Grande) | Wake Me Up (Avicii) | Love The Way You Lie (Eminem and Rihanna) | bad guy (Billie Eilish) | Rather Be (Clean Bandit and Jess Glynne) | Call Me Maybe (Carly Rae Jepsen) | We Are Young (fun.) | Shape of You (Ed Sheeran) | Closer (The Chainsmokers) | Cheerleader (OMI) | Radioactive (Imagine Dragons) | Señorita (Shawn Mendes and Camila Cabello) | Airplanes (B.o.B and Hayley Williams) | Want (Birdtalker) | Without You (David Guetta and Usher) | Half Love (Red Hearse) | Old Town Road – Remix (Lil Nas X and Billy Ray Cyrus) | Never Really Over (Katy Perry) | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
First Name (or Alias) | ||||||||||||||||||||||||
Jack | 3.0 | 5.0 | 5.0 | 2 | 4 | 4 | NaN | 5.0 | 2.0 | 4.0 | 5.0 | 5 | 4.0 | 2 | 5.0 | 2.0 | 5 | 2.0 | 2.0 | 5.0 | 5.0 | 4.0 | 5 | 5 |
Nick | 2.0 | 5.0 | 5.0 | 4 | 5 | 3 | 5.0 | 2.0 | 3.0 | 5.0 | 4.0 | 5 | 5.0 | 1 | 1.0 | 2.0 | 2 | 3.0 | 3.0 | 4.0 | 3.0 | 4.0 | 2 | 5 |
Jubilee | 5.0 | 4.0 | 2.0 | 3 | 3 | 5 | 4.0 | 3.0 | 2.0 | 5.0 | 5.0 | 2 | 4.0 | 1 | 3.0 | 2.0 | 5 | 1.0 | 5.0 | 1.0 | 1.0 | 1.0 | 4 | 5 |
Jules | 5.0 | 5.0 | 3.0 | 3 | 5 | 4 | 3.0 | 4.0 | 3.0 | 5.0 | 3.0 | 3 | 3.0 | 3 | 4.0 | 2.0 | 3 | 2.0 | 3.0 | NaN | 4.0 | NaN | 1 | 5 |
Trevor | 4.0 | 3.0 | 2.0 | 4 | 3 | 1 | 3.0 | 4.0 | 3.0 | 2.0 | NaN | 1 | 3.0 | 4 | 4.0 | 1.0 | 5 | 4.0 | 3.0 | NaN | NaN | NaN | 1 | 3 |
Megan | 5.0 | 4.0 | 4.0 | 3 | 3 | 3 | 3.0 | 5.0 | 4.0 | 5.0 | 5.0 | 4 | 3.0 | 4 | 3.0 | 2.0 | 5 | 3.0 | 3.0 | 3.0 | 4.0 | NaN | 2 | 5 |
Joe | 3.0 | 4.0 | 5.0 | 4 | 5 | 4 | 3.0 | 4.0 | 3.0 | 4.0 | 3.0 | 4 | 4.0 | 4 | 2.0 | 4.0 | 3 | 3.0 | 4.0 | 4.0 | 3.0 | 4.0 | 5 | 3 |
Hallie | NaN | NaN | NaN | 3 | 5 | 3 | NaN | NaN | NaN | NaN | NaN | 3 | NaN | 5 | NaN | NaN | 4 | NaN | NaN | NaN | NaN | NaN | 2 | 4 |
Tratamiento de nulos.
Como ocurre en la vida real, no todos los usuarios han puntuado los ítems, por lo que tengo que reemplazar los valores nulos, ya que el método de distancia euclídea no soporta valores nulos, entonces elijo los reemplazarlos por valores 3, como un “promedio”.
In [7]:
#Ajustamos y ploteamos un mapa de calor para observar los resultados hasta aquí import matplotlib.pyplot as plt plt.figure(figsize=(12, 9)) ratings=ratings.fillna(3) sns.heatmap(ratings, annot=True)
Out[7]:
<AxesSubplot:ylabel='First Name (or Alias)'>

Funciones para calcular distancias
Existen diferentes metodos para calcular distancias entre elementos.
Resulta que el confiable teorema de Pitágoras se generaliza bastante bien. (Aunque aclaro que la mayoría de los recomendadores usan otras métricas de distancia como la distancia del coseno). Si hay tres dimensiones, podemos llamarlas a, b y c, y luego simplemente sumarlas debajo de la raíz cuadrada: distancia = sqrt (a ^ 2 + b ^ 2 + c ^ 2). Y de hecho podemos hacer esto con cualquier número de dimensiones: distancia = sqrt (a ^ 2 + b ^ 2 + c ^ 2 + d ^ 2 + … + n ^ 2).
In [46]:
#Utilizo esta función euclidiana del sistema para calcular la similitud entre los usuarios def eDistance(person1,person2): distance = euclidean(person1,person2) return distance
Solución en 4 fases
Fase 1: Definir función para encontrar similitud entre personas.
Sistema de Recomendacion de Filtrado Colaborativo User-User
Definir una función o varias que crean necesarios para encontrar la persona mas similar a otra recibida por parametro. Para ello realizo con la función una comparación iterativa entre todos los usuarios, y retorna el usuario mas similar.
In [47]:
#Definir función para calcular similitud entre personas def most_similar_to(name): person = ratings.loc[name] closest_distance=float('inf') closest_person='' for other_person in ratings.itertuples(): if other_person.Index==name: # don't compare a person to themself continue distance_to_other_person = eDistance(person,ratings.loc[other_person.Index]) #print(distance_to_other_person) if distance_to_other_person < closest_distance: # new high score! save it closest_distance = distance_to_other_person closest_person = other_person.Index return closest_person, closest_distance
In [48]:
#Introduzca entre las comillas, el nombre de la persona a la que que quiere encontrar el mas similar #Las opciones son Hallie, Jack, Joe, Megan, Trevor, Jules, Jubilee y Nick name='Nick' closest_person, closest_distance = most_similar_to(name) print(name,"es más similar a",closest_person,"con una distancia de",closest_distance)
Nick es más similar a Joe con una distancia de 6.6332495807108
Fase 2: Definir función para obtener los ítems preferidos de una persona.
Ahora la nueva función recibe un nombre de usuario por parametro, y devuelve las canciones preferidas ( con puntuacion = 5)
In [49]:
#Devuelvo la función con los ítems o canciones más preferidas por el usuario def preferred_items(name): person = ratings.loc[name] preferred_items = person[person==5].index.values return preferred_items
In [50]:
#Introduzca entre las comillas, el nombre de la persona a la que que quiere encontrar sus canciones preferidas #Los usuarios son Hallie, Jack, Joe, Megan, Trevor, Jules, Jubilee y Nick name='Joe' recommended_items = preferred_items(closest_person) print("Las canciones preferidas de",name,"son:\n",recommended_items)
Las canciones preferidas de Joe son: ['Sunflower (Post Malone and Swae Lee)' 'Rolling in the Deep (Adele)' 'Old Town Road - Remix (Lil Nas X and Billy Ray Cyrus)']
Fase 3: Integración de las funciones definidas anteriormente.
Integro las funciones anteriormente definidas, para generar una solucion que reciba por parametro una persona por ejemplo “Hallie”, luego que encuentre la persona mas similiar a ella, y le recomiende sus items preferidos.
In [55]:
#Introduzca entre las comillas, el nombre de la persona a la que que quiere encontrar su usuario más similar #y sus canciones preferidas #Los usuarios posibles son Hallie, Jack, Joe, Megan, Trevor, Jules, Jubilee y Nick name='Jules' closest_person, closest_distance = most_similar_to(name) recommended_items = preferred_items(closest_person) print(name,"es más similar a",closest_person,"con una distancia de",closest_distance) print("Las canciones preferidas de",name,"son:\n",recommended_items)
Jules es más similar a Megan con una distancia de 4.69041575982343 Las canciones preferidas de Jules son: ['One Dance (Drake)' 'Wake Me Up (Avicii)' 'bad guy (Billie Eilish)' 'Rather Be (Clean Bandit and Jess Glynne)' 'Radioactive (Imagine Dragons)' 'Never Really Over (Katy Perry)']
Fase 4: Definir función para encontrar ítems similares entre sí.
Sistema de Recomendacion de Filtrado Colaborativo Item-Item
Defino una función que recibe por parametro un item y retorna el item mas parecido.
In [58]:
#Metodo para encontrar el item mas similiar a otro def most_similar_item_to(item): item = ratings.loc[:,item_name] closest_distance=float('inf') closest_item='' for column in ratings: if column==item_name: continue distance_to_other_item = eDistance(item,ratings.loc[:,column]) if distance_to_other_item < closest_distance: closest_distance = distance_to_other_item closest_item = column return closest_item, closest_distance
In [59]:
#Tema preferido de Hallie item_name = "Lean On (Major Lazer)" closest_item = most_similar_item_to(item_name) print(item_name,"es muy similar a",closest_item)
Lean On (Major Lazer) es muy similar a ('bad guy (Billie Eilish)', 2.0)
In [60]:
#Tema preferido de Hallie item_name = "Rolling in the Deep (Adele)" closest_item = most_similar_item_to(item_name) print(item_name,"es muy similar a",closest_item)
Rolling in the Deep (Adele) es muy similar a ('Lean On (Major Lazer)', 2.8284271247461903)
In [62]:
tema1 = ratings.loc[:,"Rolling in the Deep (Adele)"] tema2 = ratings.loc[:,"Never Really Over (Katy Perry)"] plt.figure(figsize=(12, 9)) comparativo = pd.concat([tema1, tema2], axis=1) sns.heatmap(comparativo, annot=True)
Out[62]:
<AxesSubplot:ylabel='First Name (or Alias)'>

