-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0313a9f
commit 49b26e3
Showing
1 changed file
with
206 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
--- | ||
title: "Recomendaciones (Netflix)" | ||
format: html | ||
--- | ||
|
||
```{r, message=FALSE, warning=FALSE} | ||
library(tidyverse) | ||
library(sparklyr) | ||
# configuración para spark | ||
spark_install(version = "3.1.2") | ||
config <- spark_config() | ||
config$`spark.env.SPARK_LOCAL_IP.local` <- "0.0.0.0" | ||
config$`sparklyr.shell.driver-memory` <- "8G" | ||
config$spark.executor.memory <- "2G" | ||
``` | ||
|
||
```{r} | ||
# conectar con "cluster" local | ||
sc <- spark_connect(master = "local", config = config) | ||
``` | ||
|
||
|
||
```{r} | ||
spark_set_checkpoint_dir(sc, './checkpoint') | ||
``` | ||
|
||
|
||
```{r leertabla} | ||
dat_tbl <- spark_read_csv(sc, name="netflix", "../datos/netflix/dat_muestra_nflix.csv") | ||
dat_tbl <- dat_tbl |> select(-fecha) | ||
peliculas_num <- dat_tbl |> group_by(peli_id) |> | ||
count() |> collect() | ||
#dat_tbl <- dat_tbl |> left_join(peliculas_num) |> | ||
# filter(n > 2000) |> select(-n) | ||
``` | ||
|
||
|
||
|
||
```{r} | ||
usuario_val <- dat_tbl |> select(usuario_id) |> | ||
sdf_distinct() |> | ||
sdf_sample(fraction = 0.1) |> | ||
compute("usuario_val") | ||
pelicula_val <- dat_tbl |> select(peli_id) |> | ||
sdf_distinct() |> | ||
sdf_sample(fraction = 0.1) |> | ||
compute("pelicula_val") | ||
valida_tbl <- dat_tbl |> | ||
inner_join(usuario_val) |> | ||
inner_join(pelicula_val) |> | ||
compute("valida_tbl") | ||
``` | ||
|
||
```{r} | ||
entrena_tbl <- dat_tbl |> anti_join(valida_tbl) |> | ||
compute("entrena_tbl") | ||
entrena_tbl |> tally() | ||
valida_tbl |> tally() | ||
``` | ||
|
||
Vamos a hacer primero una descomposición en pocos factores, | ||
con regularización relativamente alta: | ||
|
||
```{r als-spark} | ||
modelo <- ml_als(entrena_tbl, | ||
rating_col = 'calif', | ||
user_col = 'usuario_id', | ||
item_col = 'peli_id', | ||
rank = 20, reg_param = 0.05, | ||
checkpoint_interval = 10, | ||
max_iter = 100) | ||
# Nota: checkpoint evita que la gráfica de cálculo | ||
# sea demasiado grande. Cada 10 iteraciones hace una | ||
# nueva gráfica con los resultados de la última iteración. | ||
``` | ||
|
||
```{r} | ||
modelo | ||
``` | ||
|
||
Hacemos predicciones para el conjunto de validación: | ||
|
||
```{r, warning = FALSE, message = FALSE, fig.width=5, fig.asp=0.7} | ||
preds <- ml_predict(modelo, valida_tbl) |> | ||
mutate(prediction = ifelse(isnan(prediction), 3.5, prediction)) | ||
ml_regression_evaluator(preds, label_col = "calif", prediction_col = "prediction", | ||
metric_name = "rmse") | ||
``` | ||
|
||
Y podemos traer a R los datos de validación (que son chicos) para examinar: | ||
|
||
```{r} | ||
preds_df <- preds |> collect() #traemos a R con collect | ||
ggplot(preds_df, aes(x = prediction)) + geom_histogram() | ||
``` | ||
|
||
|
||
Examinamos ahora las dimensiones asociadas con películas: | ||
|
||
|
||
```{r} | ||
modelo$item_factors | ||
``` | ||
|
||
```{r} | ||
V_df <- modelo$item_factors |> | ||
select(id, features) |> collect() |> | ||
unnest_wider(features, names_sep = "_") | ||
``` | ||
|
||
Nota: La columna *features* contiene la misma información de *feature_1,feature_2,...*, pero en forma de lista. | ||
|
||
Examinemos la interpretación de los factores latentes de las | ||
películas. | ||
|
||
```{r} | ||
pelis_nombres <- read_csv('../datos/netflix/movies_title_fix.csv', col_names = FALSE, na = c("", "NA", "NULL")) | ||
names(pelis_nombres) <- c('peli_id','año','nombre') | ||
medias_peliculas <- entrena_tbl |> group_by(peli_id) |> | ||
summarise(num_calif_peli = n(), media_peli = mean(calif)) |> | ||
collect() | ||
latentes_pelis <- V_df |> | ||
rename(peli_id = id) |> | ||
left_join(pelis_nombres |> left_join(medias_peliculas)) | ||
latentes_pelis <- latentes_pelis |> | ||
mutate(num_grupo = ntile(num_calif_peli, 10)) | ||
``` | ||
|
||
Podemos examinar las dimensiones latentes: | ||
|
||
```{r} | ||
top_tail <- function(latentes_pelis, feature){ | ||
top_df <- arrange(latentes_pelis, {{ feature }} ) |> | ||
select(nombre, {{ feature }}, media_peli, num_calif_peli) |> | ||
filter(num_calif_peli > 2000) |> | ||
head(100) | ||
tail_df <- arrange(latentes_pelis, desc({{ feature }}) ) |> | ||
select(nombre, {{ feature }}, media_peli, num_calif_peli) |> | ||
filter(num_calif_peli > 2000) |> | ||
head(100) | ||
print(top_df) | ||
print(tail_df) | ||
bind_rows(top_df, tail_df) | ||
} | ||
res <- top_tail(latentes_pelis, features_1) | ||
``` | ||
|
||
Otra dimensión latente: | ||
|
||
```{r} | ||
res <- top_tail(latentes_pelis, features_3) | ||
``` | ||
|
||
|
||
```{r} | ||
res <- top_tail(latentes_pelis, features_10) | ||
``` | ||
|
||
**Nota**: Podemos usar **ml_recommend** para producir recomendaciones de películas para | ||
usuarios, o para cada película los usuarios más afines. | ||
|
||
```{r} | ||
pelis_nombres_sdf <- pelis_nombres |> select(peli_id, nombre) |> | ||
sdf_import(sc = sc, overwrite = TRUE) | ||
top_recom <- ml_recommend(modelo, type = "items", n = 40) |> | ||
left_join(pelis_nombres_sdf) | ||
top_recom | ||
``` | ||
|
||
A partir de los candidatos producidos, podemos usar otras reglas | ||
para decidir qué recomendar. En este caso filtramos películas con pocas | ||
vistas, pero otros criterios son posibles. | ||
|
||
```{r} | ||
#num_usuario <- 22 | ||
num_usuario <- 771 | ||
peliculas_tbl <- peliculas_num |> collect() |> select(peli_id, n) | ||
# recomendaciones | ||
recoms <- top_recom |> filter(usuario_id==num_usuario) |> | ||
collect() |> | ||
left_join(peliculas_tbl) |> filter(n > 150) |> | ||
select(nombre) | ||
recoms | ||
# qué le gustó? | ||
vistas <- entrena_tbl |> filter(usuario_id==num_usuario) |> collect() |> | ||
left_join(pelis_nombres |> collect() |> select(peli_id, nombre)) |> | ||
arrange(desc(calif)) |> filter(calif > 4) |> | ||
select(nombre, calif) | ||
vistas | ||
``` | ||
|
||
|
||
```{r} | ||
recoms |> anti_join(vistas) | ||
``` | ||
|
||
|
||
## Similitud de películas | ||
|
||
|
||
|
||
|
||
|
||
```{r} | ||
sparklyr::spark_disconnect_all() | ||
``` |