Parte 2. {recommenderlab}

Paula López Casado - 21-12-2022

En esta sessión veremos…

  • Cómo instalar {recommenderlab}
  • Exploración de matrices de valoraciones
  • Visualización de matrices de similaridad
  • Ejemplos de con diferentes algoritmos de recomendación

{recommenderlab} – la librería de R

  • Funciones de clase S4

  • Se basa en el filtro colaborativo

  • La librería consta de 3 funciones principales, entre otras.

    • Recommender
    • predict
    • evaluationScheme

Instalación

  • Versión de CRAN desde R:
install.packages("recommenderlab")
  • Version en desarrollo:
install.packages("recommenderlab", repos = "https://mhahsler.r-universe.dev")

Podemos abrir la ayuda del paquete para obtener más informacioo4ón sobre las funciones disponibles.

help(package = "recommenderlab")

Y los datos disponibles:

data(package = "recommenderlab")

Ejemplo 1. MovieLense.

Datos

En primer lugar, cargamos los datos y los preparamos para utilizar el paquete {recommenderlab}. Utilizaremos el dataset MovieLense incluido en la propia librería.

Los datos de MovieLense contienen las valoraciones de películas de 1 a 5. Para este primer ejemplo tomaremos tan solo 100 usuarios y 50 películas.

library(recommenderlab)
library(tidyverse)
ggplot2::theme_set(ggplot2::theme_minimal())

data("MovieLense")
MovieLense
943 x 1664 rating matrix of class 'realRatingMatrix' with 99392 ratings.
MovieLense[1:100, 1:50] -> MovieLenseSmall
MovieLenseSmall
100 x 50 rating matrix of class 'realRatingMatrix' with 834 ratings.

Visualizar la matriz de ratings

image(MovieLense, main = "Heatmap de la matriz de ratings")

getRatings

  • Con la función getRatings vemos los valores de calificación no ausentes de la matriz

  • Con la función getRatingMatrix vemos todos valores de calificación no ausentes de la matriz

getRatingMatrix(MovieLenseSmall[1:10, 1:4])
10 x 4 sparse Matrix of class "dgCMatrix"
   Toy Story (1995) GoldenEye (1995) Four Rooms (1995) Get Shorty (1995)
1                 5                3                 4                 3
2                 4                .                 .                 .
3                 .                .                 .                 .
4                 .                .                 .                 .
5                 4                3                 .                 .
6                 4                .                 .                 .
7                 .                .                 .                 5
8                 .                .                 .                 .
9                 .                .                 .                 .
10                4                .                 .                 4

¿Cuál es la valoración más común?

A continuación, visualizamos la distribución de valoraciones con la librería ggplot2.

Para extraer las valoraciones de la matriz de ratings, utilizamos de nuevo getRatings.

data.frame(valoraciones = getRatings(MovieLenseSmall)) -> df_val

ggplot(df_val, aes(valoraciones)) + 
  geom_bar(width = 0.75, fill = "dodgerblue2") +
  labs(title = 'Distribución de las valoraciones de MovielenseSmall')

¿Cuál es la valoración más común?

¿Cuántas películas valora cada usuario?

Para conocer cuántas películas valora cada usuario realizamos un diagrama de densidad del número de reviews que realiza cada usuario.

data.frame(reviews_por_persona = 
             rowCounts(MovieLenseSmall)) -> df_reviews_usuario

ggplot(df_reviews_usuario) + 
  geom_density(aes(x = reviews_por_persona), 
               fill = "dodgerblue2", color = "dodgerblue2", alpha = 0.5) +
  labs(title = 'Distribución del número de reviews por persona', 
       x ="Número de reviews")

¿Cuántas películas valora cada usuario?

¿Cuántas valoraciones tiene cada película?

Para conocer cuántas valoraciones tiene cdaa película, aplicamos la función colCounts sobre la matriz de valoraciones.

data.frame(reviews_por_pelicula = colCounts(MovieLenseSmall)) ->
  df_reviews_pelicula

ggplot(df_reviews_pelicula) + 
  geom_col(aes(x = reorder(row.names(df_reviews_pelicula), -reviews_por_pelicula), 
               y = reviews_por_pelicula),
          fill = "dodgerblue2") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  labs(title = 'Número de reviews por persona', 
       x = "Usuario", y = "Número de reviews")

¿Cuántas valoraciones tiene cada película?

Ejercicios

Ejercicio 1. Calcula el porcentaje de usuarios que valoran, como máximo, 20 películas.

Ejercicio 2. Encuentra la película más valorada y la mejor valorada (de media).

Puedes calcular la media por fila con rowMeans y la media por columna con colMeans.

Visualizar la similaridad entre usuarios

reshape2::melt(as.matrix(similarity(MovieLenseSmall)), na.rm = TRUE) |>
  ggplot(aes(x=Var1, y=Var2, fill=value)) + 
    geom_tile() +
    scale_fill_distiller(palette = "PuRd", direction = 1) +
    labs(title = "Matriz de Similiaridades enter usuarios")

Visualizar la similaridad entre usuarios

Visualizar la similaridad entre ítems

as.matrix(
  similarity(MovieLense[,1:5], 
             method = "cosine", # cosine, pearson, o jaccard
             which = "items")
  ) -> m_items

m_items |>
  as.data.frame() |>
  rownames_to_column("peliculas") |>
  pivot_longer(-c(peliculas)) |>
  ggplot(aes(x=name, y=peliculas, fill=value)) + 
    geom_raster() +
    scale_fill_distiller(palette = "PuRd", direction = 1) +
    labs(title = "Matriz de Similiaridades enter 5 películas")

Visualizar la similaridad entre ítems

Ejemplo básico

Como primer ejemplo podemos dividir manualmente el conjunto de datos en entrenamiento y test, aplicar un modelo de recomendación, con los parámetros por defecto del modelo, y predecir una lista de películas.

1) Dividir en dataset en entrenamiento y test

Dado que contamos con 100 usuarios, tomaremos los 75 primeros para entrenar, y los 25 restantes para testear.

train <- MovieLenseSmall[1:75, ]
test <- MovieLenseSmall[76:100, ]

train
75 x 50 rating matrix of class 'realRatingMatrix' with 606 ratings.
test
25 x 50 rating matrix of class 'realRatingMatrix' with 228 ratings.

2) Aplicar un algoritmo de recomendación

  • Los algoritmos de recomendación se almacenan en un objeto de registro llamado recommenderRegistry
recommenderRegistry$get_entries(dataType = "realRatingMatrix")
  • Aplicaremos el recomendador basado en el filtrado colaborativo de usuarios, UBCF con los parámetros por defecto del modelo.
rec_ubcf <- Recommender(train, method = "UBCF")
rec_ubcf
Recommender of type 'UBCF' for 'realRatingMatrix' 
learned using 75 users.

3) Generar nuevas recomendaciones

  • Vamos a crear recomendaciones para el usuario 76 (no visto por el algoritmo anteriormente)

  • Para crear nuevas recomendaciones utilizamos la función predict a la que pasaremos el modelo previamente creado de clase Recommender y el usuario al cual queremos recomendar

pred <- predict(rec_ubcf, MovieLenseSmall[76], type = "ratings")
pred
1 x 50 rating matrix of class 'realRatingMatrix' with 44 ratings.

library(knitr)
as(pred, "data.frame") |>
  arrange(-rating) |>
  kable()
user item rating
76 Hoop Dreams (1994) 4.401564
76 Eat Drink Man Woman (1994) 4.327858
76 Star Wars (1977) 4.284340
76 French Twist (Gazon maudit) (1995) 4.267342
76 Dead Man Walking (1995) 4.107345
76 Angels and Insects (1995) 4.086667
76 Disclosure (1994) 4.086667
76 Exotica (1994) 4.086667
76 Braveheart (1995) 4.072939
76 Mr. Holland’s Opus (1995) 4.060142
76 Apollo 13 (1995) 4.060021
76 Postino, Il (1994) 3.977975
76 Antonia’s Line (1995) 3.973700
76 Dolores Claiborne (1994) 3.970217
76 Mighty Aphrodite (1995) 3.963076
76 Toy Story (1995) 3.943202
76 Desperado (1995) 3.910056
76 Belle de jour (1967) 3.902709
76 I.Q. (1994) 3.819883
76 Ed Wood (1994) 3.738320
76 Four Rooms (1995) 3.701961
76 Get Shorty (1995) 3.631081
76 Crimson Tide (1995) 3.594167
76 Crumb (1994) 3.556890
76 Babe (1995) 3.554113
76 Strange Days (1995) 3.481789
76 Brothers McMullen, The (1995) 3.364756
76 White Balloon, The (1995) 3.302032
76 GoldenEye (1995) 3.273429
76 Richard III (1995) 3.188702
76 Birdcage, The (1996) 3.115318
76 To Wong Foo, Thanks for Everything! Julie Newmar (1995) 3.038237
76 Bad Boys (1995) 3.007599
76 Seven (Se7en) (1995) 2.927373
76 From Dusk Till Dawn (1996) 2.821857
76 Net, The (1995) 2.817318
76 Batman Forever (1995) 2.626085
76 Copycat (1995) 2.515713
76 Muppet Treasure Island (1996) 2.184374
76 Doom Generation, The (1995) 2.086667
76 Mad Love (1995) 2.086667
76 Billy Madison (1995) 2.086667
76 Nadja (1994) 1.737000
76 Free Willy 2: The Adventure Home (1995) 1.086667

  • Se pueden generar recomendaciones para varios usuarios al mismo tiempo, pero tengamos en cuenta que la función predict tiene como límite 10 usuarios.

  • También se puede obtener directamente un top-N de reocmendaciones en forma de lista, obviando las valoraciones estimadas.

pred_top_n <- predict(rec_ubcf, MovieLenseSmall[76], n = 5)
pred_top_n
Recommendations as 'topNList' with n = 5 for 1 users. 

as(pred_top_n, "list") |>
  kable(col.names = c("Top 5 películas"))
Top 5 películas
Hoop Dreams (1994)
Eat Drink Man Woman (1994)
Star Wars (1977)
French Twist (Gazon maudit) (1995)
Dead Man Walking (1995)

Podríamos aplicar otro algoritmo de recomendación y compararlo.

rec_popular <- Recommender(train, method = "POPULAR")
rec_popular
Recommender of type 'POPULAR' for 'realRatingMatrix' 
learned using 75 users.
pred_top_n <- predict(rec_popular, MovieLenseSmall[76], n = 5)
pred_top_n
Recommendations as 'topNList' with n = 5 for 1 users. 

as(pred_top_n, "list") |>
  kable(col.names = c("Top 5 películas"))
Top 5 películas
Star Wars (1977)
Dead Man Walking (1995)
Braveheart (1995)
Mr. Holland’s Opus (1995)
Postino, Il (1994)

Ejemplo avanzado.

El ejemplo anterior nos ha servido como caso inicial como primera toma de contacto con la librería. Sin embargo, existen funciones más avanzadas que nos permitirán llegar a realizar un ejemplo más completo y evaluar el modelo de recomendación o comparar algoritmos de recomendación entre sí.

1) Generar un esquema de evaluación

set.seed(1)
evaluationScheme(MovieLense, # Conjunto de datos como `ratingMatrix`
                 method = 'split', # Método de splitting
                 train = 0.75, # Fracción de entrenamiento
                 given = 10,# Número único de elementos dados para la evaluación
                 goodRating = 4 # Umbral para dar calificaciones como buenas
                 ) -> MovieLense_esquema
MovieLense_esquema
Evaluation scheme with 10 items given
Method: 'split' with 1 run(s).
Training set proportion: 0.750
Good ratings: >=4.000000
Data set: 943 x 1664 rating matrix of class 'realRatingMatrix' with 99392 ratings.

2) Aplicar un algoritmo de recomendación

  • Definir los parámetros del modelo
list(method = "cosine", # Métrica de similaridad
     nn = 25 # Tamaño del conjunto de usuarios más cercanos
     ) -> model_params
  • Aplicar el modelo
Recommender(
  getData(MovieLense_esquema, "train"), # Matriz de entrenamiento
            method = "UBCF", # Método de recomendación
            parameter = model_params # Parámetros del algoritmo de recomendación
  ) -> rec_UBCF_avanzado
rec_UBCF_avanzado
Recommender of type 'UBCF' for 'realRatingMatrix' 
learned using 707 users.

3) Generar nuevas recomendaciones

  • La función predict permite generar nuevas recomendaciones utilizando un modelo de recomendación y datos sobre nuevos usuarios

  • De entrada, se utiliza la parte de los datos de test conocida (oculta para el algoritmo)

predict(rec_UBCF_avanzado, # Modelo de recomendación de clase Recommender
        getData(MovieLense_esquema, "known"), # Datos de usuarios test
        type = "ratings" # Tipo de recomendación: topNList, ratings, ratingsMatrix
        ) -> pred_UBCF_avanzado
pred_UBCF_avanzado
236 x 1664 rating matrix of class 'realRatingMatrix' with 153759 ratings.

4) Evaluar la precisión de la predicción

  • La función calcPredictionAccuracy permite evaluar el error de predicción

  • Se puede dar por usuario o en global

  • Para el cálculo de las métricas se utilizan las datos de test desconocidos

calcPredictionAccuracy(pred_UBCF_avanzado, 
                       getData(MovieLense_esquema, "unknown"), 
                       byUser = TRUE) -> error_UBCF_avanzado
head(error_UBCF_avanzado, 3) # Error de los 3 primeros usuarios
       RMSE      MSE       MAE
2  1.167064 1.362037 0.8677961
10 1.019151 1.038669 0.8414931
18 1.111185 1.234732 0.8571896

5) Evaluar el agoritmo para el top-N

  • La función evaluate nos permite un algoritmo para diferentes top-N elementos

  • Generamos listas top-N de diferente número de recomendaciones, por ejemplo, 1, 3, 7, 10, 15, 20.

evaluate(MovieLense_esquema, method = "UBCF", type = "topNList", 
          n = c(1, 3, 7, 10, 15, 20)) -> eval_UBCF
UBCF run fold/sample [model time/prediction time]
     1  [0.003sec/0.706sec] 
getConfusionMatrix(eval_UBCF)
[[1]]
             TP         FP       FN       TN    N  precision      recall
[1,] 0.03389831  0.9661017 49.54661 1603.453 1654 0.03389831 0.000289396
[2,] 0.12288136  2.8771186 49.45763 1601.542 1654 0.04096045 0.002079180
[3,] 0.36016949  6.6398305 49.22034 1597.780 1654 0.05145278 0.005583783
[4,] 0.53813559  9.4618644 49.04237 1594.958 1654 0.05381356 0.010432299
[5,] 0.79237288 14.2076271 48.78814 1590.212 1654 0.05282486 0.015400873
[6,] 1.06355932 18.9364407 48.51695 1585.483 1654 0.05317797 0.021077997
             TPR          FPR  n
[1,] 0.000289396 0.0006017372  1
[2,] 0.002079180 0.0017927193  3
[3,] 0.005583783 0.0041346359  7
[4,] 0.010432299 0.0058920890 10
[5,] 0.015400873 0.0088487832 15
[6,] 0.021077997 0.0117942723 20
plot(eval_UBCF, annotate=TRUE) # Curva ROC

plot(eval_UBCF, "prec/rec", annotate=TRUE)

6) Comparación de algoritmos

  • En los sistemas de recomendación es necesario a veces probar con diferentes modelos y elegir el de mejor resultado

  • La función evaluate también nos permite evaluar varios algoritmos al mismo tiempo

algoritmos <- list(
    `random items` = list(name = "RANDOM", param = NULL), 
    `popular items` = list(name = "POPULAR", param = NULL), 
    `user-based CF` = list(name = "UBCF", param = list(nn = 3)), 
    `item-based CF` = list(name = "IBCF", param = list(k = 100)))

evaluate(MovieLense_esquema, algoritmos, type = "topNList", 
         n = c(1, 3, 7, 10, 15, 20)) -> eval_varios
RANDOM run fold/sample [model time/prediction time]
     1  [0.001sec/0.082sec] 
POPULAR run fold/sample [model time/prediction time]
     1  [0.004sec/0.35sec] 
UBCF run fold/sample [model time/prediction time]
     1  [0.003sec/0.616sec] 
IBCF run fold/sample [model time/prediction time]
     1  [1.72sec/0.035sec] 
plot(eval_varios, annotate = 2, legend = "topleft")

Conclusiones

  • El peor modelo es el aleatorio, seguido por el filtro colaborativo basado en ítems.
  • El mejor modelo es el popular, distando en mayor medida del siguiente mejor modelo, UBCF
  • Para listas top-1, el IBCF es ligeramente mejor que UBCF
  • Para lista top-n, con \(n>1\), UBCF funciona mejor que IBCF

Ejercicios

Ejercicio 3. Aplica sobre el conjunto de datos MovieLense el algoritmo IBCF y compara el resultado con el UBCF. Compara ambos modelos a través de las métricas de evaluación vistas. Por ejemplo, utiliza la curva ROC para comparalos.

Bibliografía