Introducción

Este es el segundo documento de una serie dedicada al desarrollar modelos predictivos en el servicio de justicia. En esta serie abordamos la predicción de las sentencias que se dictarán a futuro, sin perjuicio de lo cual entendemos que con los ajustes del caso el pipeline de trabajo que describimos puede aplicarse a otros tipos de predicciones.

El objetivo de este artículo de la serie es crear modelos de aprendizaje automático para datos temporales. Emplearemos la librería modeltime que se utiliza para integrar modelos según la lógica de programación de tidymodels.

Con esta implementación continuamos el workflow que vimos en la Parte 1 para el desarrollo de modelos predictivos, deteniéndonos en las especificaciones generales de cada modelo y la receta de preprocesamiento correspondiente. Recordemos que cada modelo exige cierta estructura en los datos de entrada, para lo cual la especificación que realizamos a través de la función recipe es fundamental. Además veremos las nociones de tabla de modelos y tabla de calibración que nos serán utiles para evaluar y pronosticar todos los modelos al mismo tiempo para las 8 series temporales que estamos tratando de modelar en este ejemplo (sentencias dictadas en cada materia). Finalmente, realizaremos algunos pronósticos en datos de testeo para tener una idea del ajuste de los modelos.

Código y ambiente de trabajo en R

Los scripts completos de esta implementación y los documentos RMarkdown que sirven a esta presentación puede accederse en mi repositorio de github castillosebastian. En el README de ese repositorio se puede encontrar un detalle de la configuración del ambiente de trabajo que he utilizado en el proyecto y recomendaciones para su instalación.

Como veremos más adelante, algunas partes de las operaciones de desarrollo de modelos (vinculadas a un pipeline de MLOps) requieren cierto hardware particular (sobretodo RAM y CPU). En nuestro caso hemos trabajado en un Centos 7, con 12 vCPU y 32G de RAM, y en ocasiones esos recursos fueron insuficientes, por lo que algo más de disponibilidad será de ayuda.

Respecto del pipeline de trabajo nos hemos apoyado exclusivamente en R, y particularmente en tidymodels, gracias al aprecio que tenemos por tidyverse. Este framework nos brinda toda una batería de herramientas que facilitan el trabajo y la experimentación, aspecto fundamental en el desarrollo de modelos de machine learning. Para culquiera intersado en él dirigirse aquí: https://www.tidymodels.org/.

El repositorio está armado de tal forma que los scripts puedan correrse de dos formas:

Librerías

A continuación presentamos las librerías que emplearemos en este documento. Cada nueva publicación de la serie incluirá un detalle de sus recursos.

pacman::p_load(tidyverse,
               timetk,
               tsibble,
               tsibbledata,
               fastDummies,
               skimr, 
               ranger, 
               lightgbm,
               tidymodels, 
               modeltime, # workflow 
               treesnip, # lightgbm
               kableExtra)

options(scipen = 999)

Generando modelos de Machine Learning

Los pasos que daremos para la generación de los modelos serán los siguientes:

  1. Crearemos 4 workflows, uno por cada modelo que seleccionamos para resolver nuestro problema de predicción, fijando sus correspondientes especificaciones, algoritmo de implementación, receta de preprocesamiento y ajuste a los datos de entrenamiento según las particiones generadas en la primera parte,
  2. Agregaremos los 4 workflows a una table de modelos y una tabla de calibración,
  3. Evaluaremos los modelos a partir de ciertas métricas extendidas en ML, y
  4. Realizaremos algunas predicciones con los datos de test,

Abrimos los productos generados en la Parte 1

Abrimos el dataset aumentado que generamos antes (feature_engeniering.R).

artifacts <- read_rds(paste0(HOME_DIR, "/exp/001/feature_engineering_artifacts_list.rds"))
splits            <- artifacts$splits
recipe_spec       <- artifacts$recipes$recipe_spec
materia        <- artifacts$data$materia
#rm(artifacts, recipe_spec, materia)

Estableciendo los workflows

Los workflows son objetos que guardan/agrupan distintas partes relativas a la generación de modelos. Son contenedores que guardan mucha información que de otro modo sería difícil de administrar, y saturaría nuestros ambientes de trabajo.

Para permitir que cualquier persona consultante pueda ejecutar este documento RMarkdown hemos condicionado el ajuste de modelos para evitar saturar los recursos del ordenador. Si desea ejecutar este RMarkdown y ajustar todos los modelos que contiene fije la variable eval_models = TRUE que se encuentra en el primer chunk.

Eligiendo modelos: arboles y más arboles

Dentro de los modelos que elegimos para la tarea hay un protagonismo evidente de los algoritmos basados en árboles de decisión. Estos algoritmos largamente documentados, con abundantes implementaciones y aplicaciones, resultan particularmente robustos para modelar relaciones no lineales y datos tabulares. Abonando esta conclusión Léo Grinsztajn et all dicen: Esta superioridad se explica por características específicas de los datos tabulares: patrones irregulares en la función objetivo,[la presencia de ] atributos no informativos y datos no rotacionalmente invariantes donde las combinaciones lineales pierden capacidad de representar apropiadamete la información. (la traducción es nuestra).

Random Forest

Conocidos como bosques aleatorios este tipo de modelo se basa en la implementación de un conjunto (bagging) de arboles de decisión. Cada random forest -compuesto por múltiples modelos- se entrena en un subconjunto de datos (observaciones de nuestro dataset) aleatereamente seleccionados, y luego se combina los resultados de los modelos para obtener un resultado final. Mas sobre Random Forest puede verse aquí. En nuestro caso utilizaremos la librería ranger.

wflw_fit_rf <- workflow() %>%
  add_model(
    spec = rand_forest(
      mode = "regression"
    ) %>% 
      set_engine("ranger")
  ) %>%
  add_recipe(recipe_spec %>% 
               step_rm(mes)) %>%
               fit(training(splits))

XGBoost

Este modelo, también se basa en árboles de decisión, pero implementa un algoritmo (Gradient Boosted Decision Trees) de entrenamiento secuencial que minimiza el error y mejora las predicciones. Mientras que los bosques aleatorios construyen un conjunto de árboles independientes profundos (de múltiples niveles), los algoritmos basados en Gradient Boosting construyen un conjunto de árboles poco profundos (pocas particiones), donde cada nuevo modelo busca minimizar los errores de predicción (residuos) del anterior. Mas sobre xgboos puede verse aquí.

wflw_fit_xgboost <- workflow() %>%
  add_model(
    spec = boost_tree(
      mode = "regression"
    ) %>%
      set_engine("xgboost")
  ) %>%
  add_recipe(recipe_spec %>% 
               step_rm(mes)) %>%
               fit(training(splits))

Prophet

Prophet es un algoritmo desarrollado por científicos de Datos de META (puede verse la documentación aquí) con una implementación en R. Según mencionan sus creadores este algoritmo está basado en un modelo aditivo en el que las tendencias no lineales se ajustan a la estacionalidad anual, semanal y diaria. Funciona mejor con series temporales que tienen fuertes efectos estacionales y varias temporadas de datos históricos. El paper del modelo puede verse aquí.

wflw_fit_prophet <- workflow() %>%
  add_model(
    spec = prophet_reg(
      seasonality_daily  = FALSE, 
      seasonality_weekly = FALSE, 
      seasonality_yearly = TRUE
    ) %>% 
      set_engine("prophet")
  ) %>%
  add_recipe(recipe_spec) %>%
  fit(training(splits))

Prophet Boost

Este modelo combina Prophet con XGBoost para obtener -dice su creador- lo mejor de ambos mundos (es decir, Prophet Automation + Machine Learning). El algoritmo funciona así:
- Primero modela la serie univariante usando Prophet, y luego - Usando los demás predictores genera una regresión de los residuos de Prophet con el modelo XGBoost.

wflw_fit_prophet_boost <- workflow() %>%
  add_model(
    spec = prophet_boost(
      seasonality_daily  = FALSE, 
      seasonality_weekly = FALSE, 
      seasonality_yearly = FALSE
    ) %>% 
      set_engine("prophet_xgboost")
  ) %>%
  add_recipe(recipe_spec) %>%
  fit(training(splits))

Lightgbm

Este algoritmo fue creado por Microsoft, y al igual que XGboots se base en arboles de decisión y en el método de Gradient Boosting para el ajuste del modelo, pero a diferencia del anterior la generación de arboles no procede a través de niveles sino por hoja o nodos finales (mediante la técnica de Gradient-based One-Side Sampling). Eso le confiere al lightgbm rapidez y gran performance en dataset muy grandes.

wflw_fit_ligthgbm <- workflow() %>%
  add_model(
    spec =  parsnip::boost_tree(
      mtry = 5, 
      trees = 100
    ) %>% 
      set_mode("regression") %>%
      set_engine("lightgbm", 
                 quiet  = TRUE,
                 objective = "root_mean_squared_error")) %>%
  add_recipe(recipe_spec) %>%
  fit(training(splits))

Tabla de Modelos

En esta etapa creamos una tabla donde se agregan los modelos que actúa como contenedora de todos los workflows que hemos generado.

submodels_tbl <- modeltime_table(
  wflw_fit_rf,
  wflw_fit_xgboost,
  wflw_fit_prophet,
  wflw_fit_prophet_boost,
  wflw_fit_ligthgbm
)
submodels_tbl
## # Modeltime Table
## # A tibble: 5 × 3
##   .model_id .model     .model_desc              
##       <int> <list>     <chr>                    
## 1         1 <workflow> RANGER                   
## 2         2 <workflow> XGBOOST                  
## 3         3 <workflow> PROPHET W/ REGRESSORS    
## 4         4 <workflow> PROPHET W/ XGBOOST ERRORS
## 5         5 <workflow> LIGHTGBM

Tabla de Calibración

En la etapa de calibración de los modelos se calculan predicciones y residuos a partir de datos de test, no incluidos en el entrenamiento. Una salida de esta etapa es una lista de dataset con las observaciones más las predicciones y residuos.

#Model Evaluation
calibrated_wflws_tbl <- submodels_tbl %>%
  modeltime_calibrate(new_data = testing(splits))

Evaluación

Finalmente, la hora de la verdad: la evaluación. Los resultados de la calibración se utilizan para los obtener distintas métricas de la performance de nuestros modelos en datos de testeo.

calibrated_wflws_tbl %>%
  modeltime_accuracy(testing(splits)) %>%
  arrange(rmse)
## # A tibble: 5 × 9
##   .model_id .model_desc           .type    mae   mape  mase smape   rmse     rsq
##       <int> <chr>                 <chr>  <dbl>  <dbl> <dbl> <dbl>  <dbl>   <dbl>
## 1         2 XGBOOST               Test   0.538   81.2  1.08  115.  0.627 0.364  
## 2         4 PROPHET W/ XGBOOST E… Test   0.626  130.   1.26  133.  0.705 0.514  
## 3         1 RANGER                Test   0.630   86.3  1.27  140.  0.751 0.228  
## 4         5 LIGHTGBM              Test   0.682  135.   1.37  133.  0.828 0.0375 
## 5         3 PROPHET W/ REGRESSORS Test  12.8   2385.  25.7   199. 14.6   0.00683

Vemos que el rsq (variabilidad explicada por parte de nuestros modelos) es baja, señalando un bajo poder predictivo. Sin embargo, hay que tener en cuenta que aquí solamente hemos ‘activado’ nuestros modelos con las configuraciones por defecto, sin ningún ajuste de parámetros que es fundamental para mejorar resultados.

Guardamos el trabajo

Finalmente metemos los workflow y tabla de calibración en una lista y los guardaremos como archivo RDS.

archivo_salida  <-  paste0(HOME_DIR,"/exp/001/workflows_artifacts_list.rds")
# Save your work
workflow_artifacts <- list(

  workflows = list(

    wflw_random_forest = wflw_fit_rf,
    wflw_xgboost = wflw_fit_xgboost,
    wflw_prophet = wflw_fit_prophet,
    wflw_prophet_boost = wflw_fit_prophet_boost,
    wflw_lightgbm = wflw_fit_ligthgbm

  ),

  calibration = list(calibration_tbl = calibrated_wflws_tbl)

)
#Para guardar el trabajo generado descomente este chunk
# workflow_artifacts %>%
#   write_rds(archivo_salida)

Conclusion

A lo largo del documento presentamos los modelos predictivos que emplearemos para predecir las sentencias dictadas por materia, configurando los workflowsde entrenamiento de cada uno y sus especificaciones generales.

Luego hemos reunido toda la información de los modelos en tablas y evaluado cada uno.

En los próximos documentos avanzaremos la búsqueda de los mejores hiperparámetros de nuestros modelos: alphabet, xgboost, random forest y ligthgbm.