Unidad 4: Aprendizaje Supervisado

Introducción

En el Machine Learning Supervisado, trabajamos con datos etiquetados. Esto significa que cada ejemplo de nuestro conjunto de datos tiene una salida o etiqueta asociada. Nuestro objetivo es construir un modelo que aprenda de estos ejemplos etiquetados para resolver problemas de clasificación o regresión con datos nuevos.

Estructura General del Workflow en ML Supervisado

Basándonos en la metodología CRISP-DM para ciencia de datos, (ver libro Fundamentos de ciencia de datos en R) en ML supervisado podríamos definir las siguientes etapas:

Fuente: CRISP-DM en IBM

IBM (2024)

Fernández-Avilés and Montero (2024)

  • Definición del Problema: Comprender y definir claramente qué problema se está tratando de resolver.

  • Recolección de Datos: Obtener un conjunto de datos relevante y suficientemente grande para entrenar el modelo.

  • Preprocesamiento de Datos: Preparar los datos para el entrenamiento, lo que incluye limpieza, normalización, codificación, y más.

  • División de Datos: Separar los datos en conjuntos de entrenamiento, validación y prueba.

  • Selección y Entrenamiento del Modelo: Elegir un algoritmo de ML adecuado y usar el conjunto de entrenamiento para ajustar el modelo.

  • Evaluación del Modelo: Usar el conjunto de validación (y eventualmente el de prueba) para evaluar el rendimiento del modelo.

  • Ajuste y Optimización: Refinar el modelo para mejorar su rendimiento, ajustando parámetros o incluso volviendo a la etapa de preprocesamiento.

  • Despliegue: Implementar el modelo en un entorno real para hacer predicciones sobre nuevos datos.

Para el primer punto, debemos tener cierto conocimiento del negocio y los datos. Es fundamental para poder llevar a cabo la recolección de datos y la fase de preprocesamiento, que con frecuencia, ocupan la mayor parte del tiempo de un proyecto de datos.

Recolección y preprocesamiento

Esta etapa suele llevar el mayor % de carga de trabajo.

  • Conocer y documentar la descripción y las fuentes de datos

  • Integración con diferentes fuentes (unión y combinaciones)

  • Exploración de los datos (EDA)

  • Selección de variables y subconjuntos de registros

  • Limpieza: imputación de valores perdidos, auditorías de calidad de datos, búsqueda de errores o incoherencias, conversiones de tipos, etc.

  • Construir nuevas variables (feature engineering)

  • Formateo: codificación de variables, escalado, transformaciones

  • Automatización o planificación de procesos de recolección <–> despliegue

Modelado

  • Selección de técnicas o algoritmos de ML supervisado

  • Generar un diseño de comprobación: fijar unos criterios para evaluar diferentes modelos con las mismas reglas

  • Generación de modelos: se entrenan varios modelos

  • Validación del modelo: En base a los modelos generados y el diseño de comprobación

Evaluación

Se comprueba el modelo desde el punto de vista del negocio. ¿Cumple los requisitos específicos? coste de computación, rendimiento, métricas apropiadas, … En esta fase es posible que con frecuencia, se deba volver a fases anteriores.

Implementación

  • Planificar el despliegue del modelo en producción

  • Planificar control y mantenimiento (monitorizaciones, control de versiones, alertas)

  • Comunicación: Elaboración de un informe o medio para presentar los resultados

  • Revisión final

Técnicas de preprocesamiento

Dataset ejemplo: Idealista

Code
# devtools::install_github("paezha/idealista18")
library(idealista18)
library(tidyverse)
library(caret)
library(rsample)
library(randomForest)
Code
# Lectura
Madrid_Sale <- as.data.frame(idealista18::Madrid_Sale)

Integración de datos

Limpieza de datos

Errores estructurales (Conjunto o variable):

  • Formatos incorrectos (tipos, fechas, enteros/caracteres)

  • Errores tipográficos. Ej. variables categóricas: mayúsculas, espacios,

  • Incoherencias en criterios de nombres y o categorías, etc.

Datos duplicados o irrelevantes

  • Datos irrelevantes: Fuera del área de interés
  • Datos duplicados: Errores en exportaciones, consultas o entrada de origen

Outliers no deseados:

  • Posibles entradas incorrectas (visualización)
  • Datos fuera de rango. Podrían no ser outliers según boxplot, pero hay que tenerlos en cuenta.

Gestión de valores faltantes: Nulos o missing.

  • Informativos: provienen de formularios incompletos o fallos en la recopilación
  • Aleatorios: Son independientes del proceso de recopilación de datos, por ejemplo fallos de alimentación o fallos en los servicios de ETL

Imputación de falores nulos

  • Eliminación: si hay muchos y/o aportan poco al modelo
  • Imputación por la mediana o media
  • Modelos de ML para imputación por regresión o clasificación (KNN)

Validación y control de calidad

  • No modificar la distribución original de variables
  • Minimizar los campos a corregir. Es preferible hablar con los responsbles del origen de los datos.

Selección de variables

  • Evitar variables redundantes

  • Evitar variables predictoras irrelevantes

  • Moderar la cantidad de variables

Selección de variables

Objetivos:

  • Acelerar el algoritmo reduciendo el grado de complejidad del algoritmo

  • Evitar el overfitting

  • Incrementar la precisión

  • Filtro: Técnicas estadísticas para elegir un subconjunto, el mejor puntuado

  • Envoltura (wrapper): Mejores prestaciones en las métricas finales

  • Intrínsecos al algoritmo (ej. Random Forest): El propio algoritmo las selecciona

Preselección/descarte:

  • Descartar variables con varianza nula
  • Descartar variaables con elevada correlación
  • Evitar combinaciones lineales

Varianza nula: Ejemplo

Variables que aportan poco valor, dan problemas al dividir el conjunto.

Code
Madrid_Sale_num <- Madrid_Sale %>%
  select_if(is.numeric)
Code
varianza <- nearZeroVar(Madrid_Sale_num, saveMetrics = T)
# Con el argumento saveMetrics, se guardan los valores que se han utilizado para los cálculos.
# Se muestran los resultados

varianza |>
  filter(nzv == TRUE) 
                  freqRatio percentUnique zeroVar  nzv
PARKINGSPACEPRICE 400.96970   0.153984074   FALSE TRUE
ISDUPLEX           36.89568   0.002109371   FALSE TRUE
ISSTUDIO           35.29977   0.002109371   FALSE TRUE
ISINTOPFLOOR       42.17623   0.002109371   FALSE TRUE
BUILTTYPEID_1      31.89903   0.002109371   FALSE TRUE
Code
variables_descartadas <- varianza |>
  filter(nzv == TRUE) |> rownames()
Code
variables_descartadas
[1] "PARKINGSPACEPRICE" "ISDUPLEX"          "ISSTUDIO"         
[4] "ISINTOPFLOOR"      "BUILTTYPEID_1"    
Code
Madrid_Sale_num <- Madrid_Sale_num |>
  select(-(variables_descartadas))
Warning: Using an external vector in selections was deprecated in tidyselect 1.1.0.
ℹ Please use `all_of()` or `any_of()` instead.
  # Was:
  data %>% select(variables_descartadas)

  # Now:
  data %>% select(all_of(variables_descartadas))

See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.

Correlación excesiva: Ejemplo

La existencia de correlaciones elevadas tiene consecuencias negativas sobre la fiabilidad de las predicciones (o de la clasificación realizada). En el caso extremo el modelo tendrá problemas de colinealidad o multicolinealidad.

Code
madrid_cor <- cor(Madrid_Sale_num[, 1:20]) # se cogen las 20 primeras a modo de ejemplo, porque no tienen NAs
alta_corr <- findCorrelation(madrid_cor, cutoff = .9)
colnames(madrid_cor[,(madrid_cor[alta_corr,] > 0.9)])
[1] "HASPARKINGSPACE"               "ISPARKINGSPACEINCLUDEDINPRICE"
Code
cor(Madrid_Sale_num$HASPARKINGSPACE, Madrid_Sale_num$ISPARKINGSPACEINCLUDEDINPRICE)
[1] 1

Combinaciones lineales

Code
Madrid_Sale_num_na <- drop_na(Madrid_Sale_num) # Es necesario eliminar los NA.
combos <- findLinearCombos(Madrid_Sale_num_na)
combos
$linearCombos
$linearCombos[[1]]
[1] 12 11


$remove
[1] 12
Code
#Madrid_Sale_num_na[, -combos$remove]
Code
Madrid_Sale_num <- Madrid_Sale_num[, -alta_corr]
Code
Madrid_Sale_num_na <- drop_na(Madrid_Sale_num) # Es necesario eliminar los NA.
combos <- findLinearCombos(Madrid_Sale_num_na)
combos
$linearCombos
list()

$remove
NULL

Selección de variables de tipo intrínseco: Ejemplo

Code
# Se toma una muestra con el paquete rsample
set.seed(7)
Madrid_Sale_num_sample <- sample(1:nrow(Madrid_Sale_num), size = 5000, replace = FALSE)
Madrid_Sale_num_sample <- Madrid_Sale_num[Madrid_Sale_num_sample, ]
# Se realiza binning con cuatro bins
Madrid_Sale_num_sample_bin <- Madrid_Sale_num_sample |>
  mutate(price_bin = cut(PRICE, breaks = c(0, 250000, 500000, 750000, 10000000), labels = c("primerQ", "segundoQ", "tercerQ", "c"), include.lowest = TRUE)) |>
  select(price_bin, CONSTRUCTEDAREA, ROOMNUMBER, BATHNUMBER, HASTERRACE, HASLIFT)
# Se eliminan los registros con valores missing
Madrid_Sale_sample_na <- drop_na(Madrid_Sale_num_sample_bin)

# Usar random forest para la selección de variables
rf_modelo <- randomForest(price_bin ~ ., data = Madrid_Sale_num_sample_bin)

# Listar las variables más importantes
varImp(rf_modelo)
                  Overall
CONSTRUCTEDAREA 830.69055
ROOMNUMBER      162.13449
BATHNUMBER      306.86141
HASTERRACE       41.42717
HASLIFT         192.62940

Transformación de variables

Cambios de origen y escalas

Depende del tipo de modelo (si lo requiere) y la distribución de los datos:

  • z-score
  • min-max

Ingeniería de variables (feature engineering)

Consiste en crear nuevas variables con la información disponible para que los modelos obtengan mejores resultados:

  • codificación: one-hot
  • agrupamiento: binning
  • imputación de variables cíclicas

Reducción de dimensionalidad

  • Técnicas que reducen el número de variables sin eliminarlas (PCA)

División conjunto entrenamiento/test

Muestreo aleatorio simple

En la unidad anterior hemos entrenado y probado los algoritmos con el mismo dataset. Esto provoca unos resultados con buenas métricas pero que no se ajustarían correctamente a datos que no se hayan incluído en el entrenamiento. Buscamos un algoritmo que no solo ajuste bien los datos pasados sino, lo que es más importante, que proporcione predicciones (futuras) acertadas (y precisas). Para ello, inicialmente, se dividen los datos en dos subconjuntos:

  • Entrenamiento (training): para desarrollar conjuntos de funciones, entrenar algoritmos, ajustar hiperparámetros, comparar modelos y realizar todas las demás actividades necesarias para seleccionar un modelo final.

  • Prueba (test): para validar la precisión del modelo seleccionado en la fase de entrenamiento. A la hora de dividir el conjunto de datos en los dos subconjuntos anteriores, hay que tomar dos decisiones:

  • ¿Qué porcentaje de los datos (casos, observaciones) se incluye en cada subconjunto?

  • ¿Cómo se seleccionan los casos u observaciones que van a cada subconjunto?

Cuanto más grande sea el subconjunto de entrenamiento, mejor será el predictor (o clasificador), aunque las mejoras serán cada vez más pequeñas. Por el contrario, cuanto más grande sea el subconjunto de prueba o test, más precisa será la estimación del error de predicción. Lo ideal sería tener un conjunto de datos muy grande y que ambos subconjuntos fueran grandes. De esta manera, los errores de predicción serían pequeños y tendrían poca variabilidad.

Este no es el caso en la práctica, y el dilema es elegir un buen predictor (o clasificador) o una buena estimación del error de predicción. En la práctica, lo más frecuente es incluir el 70% de los datos en el subconjunto de entrenamiento y el 30% en el de test, aunque los repartos 80% - 20% y 60% - 40% también son muy populares.

La selección de los datos se realiza mediante métodos de muestreo, siendo los más utilizados el muestreo aleatorio simple (m.a.s.) y el muestreo aleatorio estratificado. Si el conjunto de datos es pequeño, el m.a.s. podría desequilibrar los predictores y la variable objetivo.

Code
set.seed(123)
index <- createDataPartition(Madrid_Sale_num$PRICE, p = 0.7, list = FALSE)
train <- Madrid_Sale_num[index, ]
test <- Madrid_Sale_num[-index, ]
Code
dim(Madrid_Sale_num) # 94815
[1] 94815    34
Code
dim(train) # 66373
[1] 66373    34
Code
dim(test) # 28442
[1] 28442    34

Muestreo estratificado

Code
set.seed(123) # para permitir reproducirlo
table(Madrid_Sale_num_sample_bin$price_bin) |> prop.table()

 primerQ segundoQ  tercerQ        c 
  0.4716   0.2992   0.1072   0.1220 
Code
#         0.4776    0.3062    0.1024    0.1116
split_estrat <- initial_split(Madrid_Sale_num_sample_bin, prop = 0.7, strata = "price_bin")
train_estrat <- training(split_estrat)
test_estrat <- testing(split_estrat)
Code
table(train_estrat$price_bin) |> prop.table()

  primerQ  segundoQ   tercerQ         c 
0.4715633 0.2992284 0.1071735 0.1220349 
Code
#   0.4777015 0.2913093 0.1132075 0.1177816
table(test_estrat$price_bin) |> prop.table()

  primerQ  segundoQ   tercerQ         c 
0.4716855 0.2991339 0.1072618 0.1219187 
Code
#   0.4799886 0.3061750 0.1023442 0.1114923

Datos desbalanceados

A menudo, los datos utilizados en determinadas áreas tienen menos del 1% de eventos raros, pero precisamente su rareza es lo que los hace “interesantes”: por ejemplo, estafas en operaciones bancarias o usuarios que hacen clic en anuncios. En otros términos, una de las clases de la variable objetivo es dominante, pero la clase minoritaria es la que presenta interés. Sin embargo, la mayoría de los algoritmos no funcionan bien con variables cuyas clases están desequilibradas. Hay varias técnicas para manejar este problema:

  • Downsampling: equilibra el conjunto de datos reduciendo el tamaño de las clases abundantes para que coincida con el de la clase menos prevalente. Este método es de utilidad cuando el tamaño del conjunto de datos es suficientemente grande para ser aplicado.

  • Upsampling: equilibra el conjunto de datos aumentando el tamaño de las clases más raras. En lugar de deshacerse de datos de las clases abundantes, se generan nuevos datos para las clases raras mediante repetición o bootstrapping. Este procedimiento es de utilidad cuando no hay suficientes datos en la clase (o clases) rara.

  • Creación de datos sintéticos: esta técnica consiste en equilibrar el conjunto de entrenamiento generando nuevos registros sintéticos, esto es, inventados, de la clase minoritaria. Existen diversos algoritmos que realizan esta tarea, siendo uno de los más conocidos la técnica de SMOTE

  • Otras técnicas: como que el algoritmo implemente mecanismos para dar mayor peso a los casos de la clase minoritaria, etc.

Code
# Se especifica que el modelo se entrene con downsampling
ctrl <- trainControl(
  method = "repeatedcv", repeats = 5,
  classProbs = TRUE,
  sampling = "down"
)
Madrid_Sale_num_sample_bin_downsample <- train(price_bin ~ .,
  data = Madrid_Sale_num_sample_bin,
  method = "gbm",
  preProcess = c("range"),
  verbose = FALSE,
  trControl = ctrl
)

Entrenamiento, test y evaluación de modelos

Code
bank <- read.csv("https://raw.githubusercontent.com/jesusturpin/curintel2324/main/data/bank.csv")
Code
glimpse(bank)
Rows: 11,162
Columns: 17
$ age       <int> 59, 56, 41, 55, 54, 42, 56, 60, 37, 28, 38, 30, 29, 46, 31, …
$ job       <chr> "admin.", "admin.", "technician", "services", "admin.", "man…
$ marital   <chr> "married", "married", "married", "married", "married", "sing…
$ education <chr> "secondary", "secondary", "secondary", "secondary", "tertiar…
$ default   <chr> "no", "no", "no", "no", "no", "no", "no", "no", "no", "no", …
$ balance   <int> 2343, 45, 1270, 2476, 184, 0, 830, 545, 1, 5090, 100, 309, 1…
$ housing   <chr> "yes", "no", "yes", "yes", "no", "yes", "yes", "yes", "yes",…
$ loan      <chr> "no", "no", "no", "no", "no", "yes", "yes", "no", "no", "no"…
$ contact   <chr> "unknown", "unknown", "unknown", "unknown", "unknown", "unkn…
$ day       <int> 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, …
$ month     <chr> "may", "may", "may", "may", "may", "may", "may", "may", "may…
$ duration  <int> 1042, 1467, 1389, 579, 673, 562, 1201, 1030, 608, 1297, 786,…
$ campaign  <int> 1, 1, 1, 1, 2, 2, 1, 1, 1, 3, 1, 2, 4, 2, 2, 1, 3, 1, 2, 1, …
$ pdays     <int> -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, …
$ previous  <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ poutcome  <chr> "unknown", "unknown", "unknown", "unknown", "unknown", "unkn…
$ deposit   <chr> "yes", "yes", "yes", "yes", "yes", "yes", "yes", "yes", "yes…

Preprocesamiento

Code
# Convertimos a factor las variables categóricas
bank.1 <- bank %>%
  mutate_if(is.character, as.factor)
Code
bank.1 %>%
  select_if(is.factor) %>%
  summary()
          job           marital         education    default     housing   
 management :2566   divorced:1293   primary  :1500   no :10994   no :5881  
 blue-collar:1944   married :6351   secondary:5476   yes:  168   yes:5281  
 technician :1823   single  :3518   tertiary :3689                         
 admin.     :1334                   unknown  : 497                         
 services   : 923                                                          
 retired    : 778                                                          
 (Other)    :1794                                                          
  loan           contact         month         poutcome    deposit   
 no :9702   cellular :8042   may    :2824   failure:1228   no :5873  
 yes:1460   telephone: 774   aug    :1519   other  : 537   yes:5289  
            unknown  :2346   jul    :1514   success:1071             
                             jun    :1222   unknown:8326             
                             nov    : 943                            
                             apr    : 923                            
                             (Other):2217                            

Dividimos el conjunto en bank.training y bank.testing con la función createDataPartition de caret (Validación simple)

Code
# Proporción de clases en la variable objetivo
prop.table(table(bank.1$deposit))

       no       yes 
0.5261602 0.4738398 
Code
set.seed(123)

# Generamos los índices que irán a entrenamiento (muestreo estratificado)
trainIndex <- createDataPartition(bank.1$deposit, p = .8, list = FALSE, times = 1)

# Creamos los dos subconjuntos entrenamiento y test
bank.training <- bank.1[trainIndex, ]
bank.testing <- bank.1[-trainIndex, ]

Entrenamiento del modelo

A partir de aquí, entrenaremos el modelo con el conjunto bank.training

Code
library(ranger)

Attaching package: 'ranger'
The following object is masked from 'package:randomForest':

    importance
Code
# Entrenar el modelo RandomForest con ranger
ranger_model <- ranger(
  formula = deposit ~ .,  # La fórmula indica que 'deposit' es la variable a predecir y el '.' incluye todas las otras variables como predictores
  data = bank.training,  # El conjunto de datos de entrenamiento
  num.trees = 500,  # Número de árboles a crecer en el bosque, ajustar este número según sea necesario
  importance = 'impurity',  # Tipo de importancia de la variable a calcular, puede ser 'none', 'impurity', o 'permutation'
  mtry = floor(sqrt(ncol(bank.training) - 1)),  # Número de variables a considerar en cada división, el predeterminado es la raíz cuadrada del número de variables para clasificación
  min.node.size = 5,  # Tamaño mínimo de nodos, ajusta según sea necesario para controlar el sobreajuste
  probability = TRUE # Las predicciones son probabilidad y no la clase directamente
)

# Mostrar un resumen del modelo
print(ranger_model)
Ranger result

Call:
 ranger(formula = deposit ~ ., data = bank.training, num.trees = 500,      importance = "impurity", mtry = floor(sqrt(ncol(bank.training) -          1)), min.node.size = 5, probability = TRUE) 

Type:                             Probability estimation 
Number of trees:                  500 
Sample size:                      8931 
Number of independent variables:  16 
Mtry:                             4 
Target node size:                 5 
Variable importance mode:         impurity 
Splitrule:                        gini 
OOB prediction error (Brier s.):  0.1105526 

Evaluación

Ver capítulo

Se evalúa con el conjunto de test

Code
predicciones_obj <- predict(ranger_model, data = bank.testing) # objeto predicciones testing
bank.testing$probs <- predicciones_obj$predictions # vectores probabilidades (yes, no)
bank.testing$probs <- bank.testing$probs[, 2] # quitamos columna "no"
bank.testing$fitted_class <- round(bank.testing$probs) # cutoff = 0.5

Evaluación con AUC

Code
library(pROC)
Type 'citation("pROC")' for a citation.

Attaching package: 'pROC'
The following objects are masked from 'package:stats':

    cov, smooth, var
Code
ROC <- roc(bank.testing$deposit, bank.testing$probs)
Setting levels: control = no, case = yes
Setting direction: controls < cases
Code
plot(ROC, col = "blue")    
text(0.5, 0.2, paste("AUC =", round(auc(ROC), 3)))

Evaluación con matrix de confusión

Code
library(yardstick)

Attaching package: 'yardstick'
The following objects are masked from 'package:caret':

    precision, recall, sensitivity, specificity
The following object is masked from 'package:readr':

    spec
Code
conf_matrix <- table(1*(bank.testing$deposit == "yes"), bank.testing$fitted_class)
conf_matrix
   
      0   1
  0 947 227
  1 128 929

Métricas derivadas de la matriz de confusión

Code
confusion <- conf_mat(conf_matrix)
summary(confusion, event_level = "second") %>%
  filter(.metric %in% c("accuracy", "sens", "spec"))
# A tibble: 3 × 3
  .metric  .estimator .estimate
  <chr>    <chr>          <dbl>
1 accuracy binary         0.841
2 sens     binary         0.804
3 spec     binary         0.881

Validación Cruzada

Rodrigo (2020)

Validación simple

Es el ejemplo previo que hemos visto. La validación simple divide las observaciones en dos grupos aleatorios para entrenar y evaluar el modelo, siendo un método fácil pero con limitaciones: alta variabilidad en la estimación del error (problema de varianza) y reducción de la capacidad del modelo por no usar todas las observaciones en el entrenamiento, llevando a una sobrestimación del error (problema de bias). La división suele ser 80-20 u otras combinaciones 70-30, 60-40.

LOOCV (Leave One Out Cross-Validation)

El método LOOCV es iterativo, usa todas las observaciones menos una para el entrenamiento y la restante para validación, repitiendo el proceso hasta que cada observación ha sido usada para validación una vez, reduciendo la variabilidad al emplear todos los datos para entrenamiento y validación, garantizando reproducibilidad.

Aunque reduce la variabilidad, su desventaja radica en el alto coste computacional, requiriendo reajustes múltiples excepto en ciertos casos como la regresión por mínimos cuadrados. A pesar de su aplicabilidad general y su popularidad, existe el riesgo de overfitting al usar todas las observaciones para entrenamiento, por lo que se sugiere considerar alternativas como K-Fold Cross-Validation.

K-Fold Cross-Validation

El método K-Fold Cross-Validation, un proceso iterativo que divide aleatoriamente los datos en k grupos de tamaño similar, utilizando k-1 para entrenamiento y uno para validación, repitiendo este proceso k veces para generar un promedio de k estimaciones de error como resultado final.

Ofrece ventajas sobre LOOCV en términos computacionales y en el equilibrio entre bias y varianza. Al elegir un k entre 5 y 10 (dependerá de las dimensiones del conjunto), reduce la necesidad de iteraciones en comparación con LOOCV, que iguala k al número total de observaciones.

Se minimiza la varianza al evitar el alto solapamiento en los datos de entrenamiento presentes en LOOCV, y manteniendo un bias controlado al utilizar un número significativo de observaciones para el entrenamiento. Esto conduce a una estimación de error más precisa y reduce el riesgo de overfitting, haciendo que K-Fold Cross-Validation con k en el rango de 5 a 10 sea una elección equilibrada para la validación de modelos.

Ejemplo: Problema de regresión:

Code
insurance <- read.csv("https://raw.githubusercontent.com/stedy/Machine-Learning-with-R-datasets/master/insurance.csv")
Code
glimpse(insurance)
Rows: 1,338
Columns: 7
$ age      <int> 19, 18, 28, 33, 32, 31, 46, 37, 37, 60, 25, 62, 23, 56, 27, 1…
$ sex      <chr> "female", "male", "male", "male", "male", "female", "female",…
$ bmi      <dbl> 27.900, 33.770, 33.000, 22.705, 28.880, 25.740, 33.440, 27.74…
$ children <int> 0, 1, 3, 0, 0, 0, 1, 3, 2, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0…
$ smoker   <chr> "yes", "no", "no", "no", "no", "no", "no", "no", "no", "no", …
$ region   <chr> "southwest", "southeast", "southeast", "northwest", "northwes…
$ charges  <dbl> 16884.924, 1725.552, 4449.462, 21984.471, 3866.855, 3756.622,…
Code
folds <- createFolds(insurance$charges, k = 3) # crea una lista de 3 elementos
# vectores (índices)
Code
ins.train1 <- insurance[-folds$Fold3,]
ins.test1 <- insurance[folds$Fold3,]

ins.train2 <- insurance[-folds$Fold2,]
ins.test2 <- insurance[folds$Fold2,]

ins.train3 <- insurance[-folds$Fold1,]
ins.test3 <- insurance[folds$Fold1, ]
Code
model1 <- lm(formula = charges ~., data = ins.train1)
model2 <- lm(formula = charges ~., data = ins.train2)
model3 <- lm(formula = charges ~., data = ins.train3)
Code
predictions1 <- predict(model1, newdata = ins.test1)
residuos1 <- ins.test1$charges - predictions1
#RMSE
rmse1 <- sqrt(mean(residuos1^2))

rmse1
[1] 6091.668
Code
predictions2 <- predict(model2, newdata = ins.test2)
residuos2 <- ins.test2$charges - predictions2
#RMSE
rmse2 <- sqrt(mean(residuos2^2))

rmse2
[1] 5996.544
Code
predictions3 <- predict(model3, newdata = ins.test3)

residuos3 <- ins.test3$charges - predictions3

#RMSE
rmse3 <- sqrt(mean(residuos3^2))

# Muestra el RMSE
rmse3
[1] 6186.614
Code
rmse_cv <- mean(c(rmse1, rmse2, rmse3))
rmse_cv
[1] 6091.609

Repeated k-Fold-Cross-Validation

Es exactamente igual al método k-Fold-Cross-Validation pero repitiendo el proceso completo n veces. Por ejemplo, 10-Fold-Cross-Validation con 5 repeticiones implica a un total de 50 iteraciones ajuste-validación, pero no equivale a un 50-Fold-Cross-Validation.

Bootstrapping

Una muestra bootstrap es una muestra obtenida a partir de la muestra original por muestreo aleatorio con reposición, y del mismo tamaño que la muestra original. Muestreo aleatorio con reposición (resampling with replacement) significa que, después de que una observación sea extraída, se vuelve a poner a disposición para las siguientes extracciones. Como resultado de este tipo de muestreo, algunas observaciones aparecerán múltiples veces en la muestra bootstrap y otras ninguna. Las observaciones no seleccionadas reciben el nombre de out-of-bag (OOB). Por cada iteración de bootstrapping se genera una nueva muestra bootstrap, se ajusta el modelo con ella y se evalúa con las observaciones out-of-bag.

Obtener una nueva muestra del mismo tamaño que la muestra original mediante muestro aleatorio con reposición.

Ajustar el modelo empleando la nueva muestra generada en el paso 1.

Calcular el error del modelo empleando aquellas observaciones de la muestra original que no se han incluido en la nueva muestra. A este error se le conoce como error de validación.

Repetir el proceso n veces y calcular la media de los n errores de validación.

Finalmente, y tras las n repeticiones, se ajusta el modelo final empleando todas las observaciones de entrenamiento originales.

References

Fernández-Avilés, Gema, and José-María Montero. 2024. Fundamentos de Ciencia de Datos Con r. 1st ed. Publicado: 1 de Enero de 2024. https://cdr-book.github.io/.
IBM. 2024. “Introduction to CRISP-DM.” https://www.ibm.com/docs/es/spss-modeler/saas?topic=guide-introduction-crisp-dm.
Rodrigo, Joaquín Amat. 2020. “Validación de Modelos Predictivos: Cross-Validation, OneLeaveOut, Bootstraping.” https://cienciadedatos.net/documentos/30_cross-validation_oneleaveout_bootstrap#Introducci%C3%B3n.