En una notebook anterior seguí una entrada de blog de R Studio, pero analicé un set de datos distinto que obtuve con rtweet y se experimentaron ligeros cambios con respecto al ejercicio del blog. Lo más importante para mí fue entender el ejercicio y llevarlo a una notebook.
En esa notebook se analizaba un set de datos obtenidos de Twitter con el fin de desarrollar un modelo que predijera la popularidad (una variable proxy construída con la suma de favs y retweets) de los tweets. Y el ejercicio se hacía con kerasformula, que es una librería que, en resumen, recibe la variable dependiente y las independientes como se hace con una regresión lineal y los pasa a Keras, que realmente corre con TensorFlow como background en un ambiente de Python… y en vez de una regresión lineal lo que entrena es una red multicapa que además podemos definir fácilmente.
Si bien esto permite iteraciones muy rápidas, computacionalmente me pareció muy costoso y propuse que quizá haya maneras más sencillas y eficientes de hacer lo mismo. En este caso con h2o.
Como en la notebook anterior, lo primero es obtener los datos. Como ya los tengo y quiero poder comparar la precisión sobre el mismo set de datos, comenté las líneas para obtener los tweets (con el fin de que no se ejecuten).
library(rtweet)
# No correr las líneas comentadas (#) si ya se obtuvieron los datos con rtweet
#frwc <- search_tweets("#rusia2018", n = 18000, include_rts = FALSE) # obtenemos los tweets
#saveRDS(frwc, "./tweets/fifa_russia_wc_1.rds") # guardamos los tweets
frwc <- readRDS("./tweets/fifa_russia_wc_1.rds")
dim(frwc)
## [1] 11776 87
Verificamos nuevamente que en segundos se obtienen 11776 tweets, cada uno con sus 87 variables
head(frwc)
No voy a reproducir el escaso análisis exploratorio de datos, pueden verlo en la notebook anterior.
Al igual que en la notebook anterior, vamos a necesitar una función auxiliar que nos ayude a contar el número de elementos de las listas, y con dplyr vamos a hacer el preprocesamiento que hicimos directamente:
# Aquí creamos la función que devuelve el número de elementos que tiene una lista.
n <- function(x) {
unlist(lapply(x, function(y){length(y) - is.na(y[1])}))
}
breaks <- c(-1, 0, 1, 10, 100, 1000, 10000)
library(dplyr)
df <- frwc %>%
transmute(pop = cut(retweet_count + favorite_count, breaks),
screen_name = screen_name,
source = source,
n_hashtags = n(hashtags),
n_mentions = n(mentions_screen_name),
n_urls = n(urls_url),
n_chars = nchar(text),
is_photo = grepl('photo', media_type),
hour = format(created_at, '%H'))
df$screen_name <- as.factor(df$screen_name)
df$source <- as.factor(df$source)
df$hour <- as.integer(df$hour)
head(df)
glimpse(df)
## Observations: 11,776
## Variables: 9
## $ pop <fct> (-1,0], (1,10], (0,1], (-1,0], (0,1], (-1,0], (-1,...
## $ screen_name <fct> JeanPaulLeroux, JeanPaulLeroux, Bathily_90, incidi...
## $ source <fct> Twitter for iPhone, Twitter for iPhone, Twitter fo...
## $ n_hashtags <int> 3, 2, 4, 1, 2, 2, 6, 2, 2, 1, 1, 1, 3, 5, 3, 1, 1,...
## $ n_mentions <int> 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0,...
## $ n_urls <int> 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,...
## $ n_chars <int> 49, 263, 282, 138, 76, 278, 147, 246, 187, 81, 106...
## $ is_photo <lgl> FALSE, FALSE, TRUE, TRUE, FALSE, FALSE, FALSE, FAL...
## $ hour <int> 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20...
La belleza que encuentro en dplyr, y en general en tidyverse es la claridad que sobre el proceso obtenemos, lo cual hace el proceso reproducible y transmisible: aquí es mucho más palpable lo que está pasando.
Ahora que tenemos el set de datos definido, vamos a entrenar el modelo con h2o:
library(h2o)
##
## ----------------------------------------------------------------------
##
## Your next step is to start H2O:
## > h2o.init()
##
## For H2O package documentation, ask for help:
## > ??h2o
##
## After starting H2O, you can use the Web UI at http://localhost:54321
## For more information visit http://docs.h2o.ai
##
## ----------------------------------------------------------------------
##
## Attaching package: 'h2o'
## The following objects are masked from 'package:stats':
##
## cor, sd, var
## The following objects are masked from 'package:base':
##
## ||, &&, %*%, apply, as.factor, as.numeric, colnames,
## colnames<-, ifelse, %in%, is.character, is.factor, is.numeric,
## log, log10, log1p, log2, round, signif, trunc
h2o.init(nthreads = 3, max_mem_size = "4G") # usamos 3 núcleos y 4 Gb de RAM
##
## H2O is not running yet, starting it now...
##
## Note: In case of errors look at the following log files:
## /tmp/RtmpF1uCkm/h2o_oscar_started_from_r.out
## /tmp/RtmpF1uCkm/h2o_oscar_started_from_r.err
##
##
## Starting H2O JVM and connecting: . Connection successful!
##
## R is connected to the H2O cluster:
## H2O cluster uptime: 1 seconds 736 milliseconds
## H2O cluster timezone: America/Bogota
## H2O data parsing timezone: UTC
## H2O cluster version: 3.18.0.11
## H2O cluster version age: 2 months
## H2O cluster name: H2O_started_from_R_oscar_dcm349
## H2O cluster total nodes: 1
## H2O cluster total memory: 3.56 GB
## H2O cluster total cores: 4
## H2O cluster allowed cores: 3
## H2O cluster healthy: TRUE
## H2O Connection ip: localhost
## H2O Connection port: 54321
## H2O Connection proxy: NA
## H2O Internal Security: FALSE
## H2O API Extensions: XGBoost, Algos, AutoML, Core V3, Core V4
## R Version: R version 3.4.4 (2018-03-15)
#h2o.removeAll() # Por precaución, en caso de que hay un clúster inicializado
h2o.no_progress() # Turn off progress bars for notebook readability
Nos da el estado del clúster y la manera de acceder a su interfaz gráfica (en el localhost, en caso de que decidan explorar).
df_h2o <- as.h2o(df) # ingerimos datos
# Particionamos datos
splits <- h2o.splitFrame(
df_h2o,
## dividimos el set de datos importado creando particiones aleatorias de las proporciones indicadas
c(0.6,0.2),
## en resumen 0.6 / 0.2 / 1 - (0.6+0.2) = 0.6/0.2/0.2
seed=1981) ## fijando la reprodicutibilidad del proceso en H2o (no en R)
train <- h2o.assign(splits[[1]], "train.hex") ## R train, H2O train.hex
valid <- h2o.assign(splits[[2]], "valid.hex") ## R valid, H2O valid.hex
test <- h2o.assign(splits[[3]], "test.hex") ## R test, H2O test.hex
dl_model <- h2o.deeplearning(x = c(2:9), # variables independientes
y = 1, # variable dependiente
model_id = "deeplearning",# nombre del modelo
training_frame = train, # set de datos para entrenamiento
validation_frame = valid, # set de datos para validación
#nfolds = 10, # particiones para cross validation (validación cruzada)
#keep_cross_validation_fold_assignment = TRUE,
#fold_assignment = "Stratified",
activation = "RectifierWithDropout",
#score_each_iteration = TRUE,
hidden = c(256, 128), # definimos las capas ocultas y sus unidades
epochs = 15, # rondas de entrenamiento
variable_importances = TRUE, # Para que retenga la importancia de las variables
export_weights_and_biases = TRUE,
seed = 42,
balance_classes = TRUE # balanceamos las clases
)
Realmente este modelo logra correr en mi laptop (Ubuntu 16.04 LTS Intel® Core™ i7-6500U CPU @ 2.50GHz × 4 con 8 GiB de RAM), al igual que el otro. Tendría que hacer un benchmark para comparar tiempos y cómputo requerido.
Las métricas generales las veríamos así:
h2o.performance(dl_model)
## H2OMultinomialMetrics: deeplearning
## ** Reported on training data. **
## ** Metrics reported on temporary training frame with 10014 samples **
##
## Training Set Metrics:
## =====================
##
## MSE: (Extract with `h2o.mse`) 0.2947549
## RMSE: (Extract with `h2o.rmse`) 0.5429133
## Logloss: (Extract with `h2o.logloss`) 0.8166706
## Mean Per-Class Error: 0.3787324
## Confusion Matrix: Extract with `h2o.confusionMatrix(<model>,train = TRUE)`)
## =========================================================================
## Confusion Matrix: Row labels: Actual class; Column labels: Predicted class
## (-1,0] (0,1] (1,10] (10,100] (100,1e+03] (1e+03,1e+04]
## (-1,0] 1628 0 56 2 0 0
## (0,1] 1621 0 58 3 0 0
## (1,10] 1464 0 190 16 2 0
## (10,100] 327 0 108 1210 5 0
## (100,1e+03] 0 0 0 139 1497 0
## (1e+03,1e+04] 0 0 0 0 0 1688
## Totals 5040 0 412 1370 1504 1688
## Error Rate
## (-1,0] 0.0344 = 58 / 1,686
## (0,1] 1.0000 = 1,682 / 1,682
## (1,10] 0.8864 = 1,482 / 1,672
## (10,100] 0.2667 = 440 / 1,650
## (100,1e+03] 0.0850 = 139 / 1,636
## (1e+03,1e+04] 0.0000 = 0 / 1,688
## Totals 0.3796 = 3,801 / 10,014
##
## Hit Ratio Table: Extract with `h2o.hit_ratio_table(<model>,train = TRUE)`
## =======================================================================
## Top-6 Hit Ratios:
## k hit_ratio
## 1 1 0.620431
## 2 2 0.850110
## 3 3 0.993110
## 4 4 1.000000
## 5 5 1.000000
## 6 6 1.000000
Podríamos ver muchísimas más métricas con ’summary(dl_model), pero no es el caso.
Lo que nos muestra la métrica similar a las arrojadas por el modelo de kerasformula es el error en las clasificaciones sobre el set de validación, lo cual nos permite comparar:
h2o.scoreHistory(dl_model)[, c("validation_classification_error")]
## [1] NaN 0.3705542 0.3639371 0.3817204 0.3639371
Para ponerlo en términos similares a los de la notebook anterior, digamos que el error fue de 1 - error: Muy similar.
Por último, lo que me gusta de h2o es la cantidad de métricas de los modelos, que pueden ver con summary(dl_model). Entre éstas, la importancia de variables:
h2o.varimp_plot(dl_model)
Así sabemos qué factores son más determinantes en la popularidad de los tweets: las cuentas que los emiten.
Espero que les haya gustado.
Siempre atento a sugerencias, comentarios y oportunidades de mejora.