7 Selección y evaluación de modelos
7.1 Entrenamiento, Validación y Prueba
El enfoque que vimos antes, en donde dividimos la muestra en dos partes al azar, es la manera más fácil de seleccionar modelos. En general, el proceso es el siguiente:
- Una parte con los que ajustamos todos los modelos que nos interesa. Esta es la muestra de entrenamiento
- Una parte como muestra de prueba, con el que evaluamos el desempeño de cada modelo ajustado en la parte anterior. En este contexto, a esta muestra se le llama muestra de validación}.
- Posiblemente una muestra adicional independiente, que llamamos muestra de prueba, con la que hacemos una evaluación final del modelo seleccionado arriba. Es una buena idea apartar esta muestra si el proceso de validación incluye muchos métodos con varios parámetros afinados (como la \(\lambda\) de regresión ridge).
knitr::include_graphics("./figuras/div_muestra.png")
Cuando tenemos datos abundantes, este enfoque es el usual. Por ejemplo, podemos dividir la muestra en 50-25-25 por ciento. Ajustamos modelos con el primer 50%, evaluamos y seleccionamos con el segundo 25% y finalmente, si es necesario, evaluamos el modelo final seleccionado con la muestra final de 25%. Igual que mencionamos anteriormente, lo importante es que las muestras de validación sean suficientemente grandes en tamaño absoluto (número de casos) para discriminar a los mejores modelos y evaluar su error de manera apropiada
La razón de este proceso es que así podemos ir y venir entre entrenamiento y validación, buscando mejores enfoques y modelos, y no ponemos en riesgo la estimación final del error. (Pregunta: ¿por qué probar agresivamente buscando mejorar el error de validación podría ponder en riesgo la estimación final del error del modelo seleccionado? )
7.2 Validación cruzada
En muchos casos, no queremos apartar una muestra de validación para seleccionar modelos, pues no tenemos muchos datos (al dividir la muestra obtendríamos un modelo relativamente malo en relación al que resulta de todos los datos).
Un criterio para seleccionar la regularización adecuada es el de validación cruzada, que es un método computacional para producir una estimación interna (usando sólo muestra de entrenamiento) del error de predicción.
Validación cruzada también tiene nos da diagnósticos adicionales para entender la variación del desempeño según el conjunto de datos de entrenamiento que usemos, algo que es más difícil ver si solo tenemos una muestra de validación.
En validación cruzada (con \(k\) vueltas), construimos al azar una partición, con tamaños similares, de la muestra de entrenamiento \({\mathcal L}=\{ (x_i,y_i)\}_{i=1}^n\):
\[ {\mathcal L}={\mathcal L}_1\cup {\mathcal L}_2\cup\cdots\cup {\mathcal L}_k.\]
knitr::include_graphics("./figuras/div_muestra_cv.png")
Construimos \(k\) modelos distintos, digamos \(\hat{f}_j\), usando solamente la muestra \({\mathcal L}-{\mathcal L}_j\), para \(j=1,2,\ldots, k\). Cada uno de estos modelos lo evaluamos usando la parte que no usamos para entrenarlo, \({\mathcal L}_j\), para obtener una estimación honesta del error del modelo \(\hat{f}_k\), a la que denotamos por \(\hat{e}_j\).
Notemos entonces que tenemos \(k\) estimaciones del error \(\hat{e}_1,\ldots, \hat{e}_k\), una para cada uno de los modelos que construimos. La idea ahora es que
- Cada uno de los modelos \(\hat{f}_j\) es similar al modelo ajustado con toda la muestra \(\hat{f}\), de forma que podemos pensar que cada una de las estimaciones \(\hat{e}_j\) es un estimador del error de \(\hat{f}\).
- Dado el punto anterior, podemos construir una mejor estimación promediando las \(k\) estimaciones anteriores, para obtener: \[\widehat{cv} = \frac{1}{k} \sum_{j=1}^k \hat{e}_j.\]
- ¿Cómo escoger \(k\)? Usualmente se usan \(k=5,10,20\), y \(k=10\) es el más popular. La razón es que cuando \(k\) es muy chico, tendemos a evaluar modelos construidos con pocos datos (comparado al modelo con todos los datos de entrenamiento). Por otra parte, cuando \(k\) es grande el método puede ser muy costoso (por ejemplo, si \(k=N\), hay que entrenar un modelo para cada dato de entrada).
Ejemplo
Consideremos nuestro problema de predicción de grasa corporal. Definimos el flujo de procesamiento, e indicamos qué parametros queremos afinar:
library(tidymodels)
dat_grasa <- read_csv(file = './datos/bodyfat.csv')
set.seed(183)
grasa_particion <- initial_split(dat_grasa, 0.75)
grasa_ent <- training(grasa_particion)
grasa_pr <- testing(grasa_particion)
# nota: con glmnet no es neceario normalizar, pero aquí lo hacemos
# para ver los coeficientes en términos de las variables estandarizadas:
grasa_receta <- recipe(grasacorp ~ ., grasa_ent) |>
step_filter(estatura > 40) |>
step_normalize(all_predictors()) |>
prep()
modelo_regularizado <- linear_reg(mixture = 0, penalty = 0.5) |>
set_engine("glmnet")
flujo_reg <- workflow() |>
add_model(modelo_regularizado) |>
add_recipe(grasa_receta)Y ahora construimos los cortes de validación cruzada. Haremos validación cruzada 10
set.seed(88)
validacion_particion <- vfold_cv(grasa_ent, v = 10)
# tiene información de índices en cada "fold" o "doblez" o "vuelta"
validacion_particion## # 10-fold cross-validation
## # A tibble: 10 × 2
## splits id
## <list> <chr>
## 1 <split [170/19]> Fold01
## 2 <split [170/19]> Fold02
## 3 <split [170/19]> Fold03
## 4 <split [170/19]> Fold04
## 5 <split [170/19]> Fold05
## 6 <split [170/19]> Fold06
## 7 <split [170/19]> Fold07
## 8 <split [170/19]> Fold08
## 9 <split [170/19]> Fold09
## 10 <split [171/18]> Fold10
Estimamos el error por validación cruzada
metricas_vc <- fit_resamples(flujo_reg,
resamples = validacion_particion,
metrics = metric_set(rmse, mae, rsq))
metricas_vc |> unnest(.metrics)## # A tibble: 30 × 7
## splits id .metric .estimator .estimate .config .notes
## <list> <chr> <chr> <chr> <dbl> <chr> <list>
## 1 <split [170/19]> Fold01 rmse standard 6.75 Preprocessor… <tibble […
## 2 <split [170/19]> Fold01 mae standard 5.05 Preprocessor… <tibble […
## 3 <split [170/19]> Fold01 rsq standard 0.735 Preprocessor… <tibble […
## 4 <split [170/19]> Fold02 rmse standard 4.94 Preprocessor… <tibble […
## 5 <split [170/19]> Fold02 mae standard 4.02 Preprocessor… <tibble […
## 6 <split [170/19]> Fold02 rsq standard 0.689 Preprocessor… <tibble […
## 7 <split [170/19]> Fold03 rmse standard 3.66 Preprocessor… <tibble […
## 8 <split [170/19]> Fold03 mae standard 3.05 Preprocessor… <tibble […
## 9 <split [170/19]> Fold03 rsq standard 0.678 Preprocessor… <tibble […
## 10 <split [170/19]> Fold04 rmse standard 3.93 Preprocessor… <tibble […
## # … with 20 more rows
Vemos que esta función da un valor del error para cada vuelta de validación cruzada:
## # A tibble: 3 × 2
## # Groups: .metric [3]
## .metric n
## <chr> <int>
## 1 mae 10
## 2 rmse 10
## 3 rsq 10
Para resumir, como explicamos arriba, podemos resumir con media y error estándar:
metricas_resumen <- metricas_vc |>
collect_metrics()
metricas_resumen## # A tibble: 3 × 6
## .metric .estimator mean n std_err .config
## <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 mae standard 3.80 10 0.176 Preprocessor1_Model1
## 2 rmse standard 4.75 10 0.263 Preprocessor1_Model1
## 3 rsq standard 0.727 10 0.0159 Preprocessor1_Model1
Nótese que la estimación del error de predicción por validación cruzada incluye un error de estimación (intervalos).
7.3 ¿Qué incluir en la validación cruzada?
Un principio importante que debemos seguir cuando hacemos validación cruzada es el siguiente:
- En cada vuelta de validación cruzada, se deben repetir todos los pasos de preprocesamiento para cada subdivisión de los datos.
- Un error común que invalida la estimación de validación cruzada es preprocesar primero los datos, y luego hacer validación cruzada sobre los datos preprocesados.
- Esto es especialmente crítico cuando el preprocesamiento utiliza la variable respuesta para construir o filtrar variables de entrada (por ejemplo, decidir cortes, filtrar por correlación, etc.)
Un caso dramático de este problema puede verse en el siguiente ejemplo:
Supongamos que tenemos una respuesta \(y\) independiente de las entradas \(x\), de forma que la mejor predicción que podemos hacer es la media de la \(y\), cuyo error cuadrático es la varianza de \(y\). Tenemos una gran cantidad de entradas y relativamente pocos casos:
set.seed(112)
x <- rnorm(50 * 10000, 0, 1) |> matrix(nrow = 50, ncol = 10000)
# y es independiente de las x's:
y <- rnorm(50, 0 , 10)
sd(y)## [1] 10.2887
Supongamos que queremos construir un modelo pero consideramos que tenemos “demasiadas” variables de entrada. Decidimos entonces seleccionar solamente las 10 variables que más relacionadas con \(y\).
correlaciones <- cor(x, y) |> as.numeric()
orden <- order(correlaciones, decreasing = TRUE)
seleccionadas <- orden[1:10]
correlaciones[seleccionadas] |> round(2)## [1] 0.57 0.49 0.49 0.48 0.46 0.44 0.44 0.44 0.43 0.43
Una vez que seleccionamos las variables hacemos validación cruzada con un modelo lineal (nota: esto es un error!)
## Warning: The `x` argument of `as_tibble.matrix()` must have unique column names if `.name_repair` is omitted as of tibble 2.0.0.
## Using compatibility `.name_repair`.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.
vc_particion <- vfold_cv(datos, v = 10)
modelo_lineal <- linear_reg()
flujo <- workflow() |> add_model(modelo_lineal) |> add_formula(y ~ .)
resultados <- fit_resamples(flujo, resamples = vc_particion, metrics = metric_set(rmse)) |>
collect_metrics()
resultados## # A tibble: 1 × 6
## .metric .estimator mean n std_err .config
## <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 rmse standard 5.29 10 0.702 Preprocessor1_Model1
La estimación del error es demasiado baja.
#devtools::install_github("stevenpawley/recipeselectors")
library(recipeselectors)
# esta función es una modificación simple de step_select_roc
source("R/step_select_corr.R")Si incluimos la selección de variables en la receta, entonces en cada corte de validación cruzada seleccionamos las variables que tienen correlación más alta:
datos_completos <- as_tibble(x) |> mutate(y = y)
vc_particion_comp <- vfold_cv(datos_completos, v = 10)
receta_mala <- recipe(y ~ ., data = datos_completos) |>
step_select_corr(all_predictors(), outcome = "y", top_p = 10)
flujo <- workflow() |>
add_recipe(receta_mala) |>
add_model(modelo_lineal)
resultados <- fit_resamples(flujo, resamples = vc_particion_comp, metrics = metric_set(rmse)) |>
collect_metrics()
resultados## # A tibble: 1 × 6
## .metric .estimator mean n std_err .config
## <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 rmse standard 10.3 10 1.02 Preprocessor1_Model1
Y vemos que esta estimación es consistente con que la desviación estándar de \(y\) es igual a 10.
7.4 Afinación de hiperparámetros (intro)
Cuando tenemos una división entrena-validación-prueba, podemos usar validación para afinar hiperparámetros de los modelos que ajustamos (por ejemplo, cuánta regularización). Si tenemos una división entrena-prueba, podemos usar validación cruzada para hacer esa afinación.
Consideremos el ejemplo de grasa corporal:
library(tidymodels)
dat_grasa <- read_csv(file = './datos/bodyfat.csv')
set.seed(183)
grasa_particion <- initial_split(dat_grasa, 0.7)
grasa_ent <- training(grasa_particion)
grasa_pr <- testing(grasa_particion)
grasa_receta <- recipe(grasacorp ~ ., grasa_ent) |>
step_filter(estatura > 40) |>
step_center(all_numeric_predictors())Usamos la función tune() para indicar que queremos probar varios valores
# con tune() indicamos que ese parámetro será afinado
modelo_regularizado <- linear_reg(mixture = tune(), penalty = tune()) |>
set_engine("glmnet") |>
set_args(lambda.min.ratio = 1e-20)
flujo_reg <- workflow() |>
add_model(modelo_regularizado) |>
add_recipe(grasa_receta)Preparamos nuestro conjunto de particiones de validación cruzada:
set.seed(88)
validacion_particion <- vfold_cv(grasa_ent, v = 10)
# tiene información de índices en cada "fold" o "doblez" o "vuelta"Y especificamos que parámetros queremos probar, haciendo todas las combinaciones posibles:
hiper_param <- crossing(mixture = c(0.0, 0.25, 0.5, 0.75, 1.0),
penalty = 10^seq(-3, 2, 0.5))
hiper_param## # A tibble: 55 × 2
## mixture penalty
## <dbl> <dbl>
## 1 0 0.001
## 2 0 0.00316
## 3 0 0.01
## 4 0 0.0316
## 5 0 0.1
## 6 0 0.316
## 7 0 1
## 8 0 3.16
## 9 0 10
## 10 0 31.6
## # … with 45 more rows
Y ajustamos con cada combinación, estimando el error por validación cruzada
metricas_vc <- tune_grid(flujo_reg,
resamples = validacion_particion,
grid = hiper_param,
metrics = metric_set(rmse, mae))
metricas_vc |> unnest(.metrics)## # A tibble: 1,100 × 9
## splits id penalty mixture .metric .estimator .estimate .config .notes
## <list> <chr> <dbl> <dbl> <chr> <chr> <dbl> <chr> <list>
## 1 <split … Fold01 1 e-3 0 rmse standard 3.15 Preproc… <tibbl…
## 2 <split … Fold01 3.16e-3 0 rmse standard 3.15 Preproc… <tibbl…
## 3 <split … Fold01 1 e-2 0 rmse standard 3.15 Preproc… <tibbl…
## 4 <split … Fold01 3.16e-2 0 rmse standard 3.18 Preproc… <tibbl…
## 5 <split … Fold01 1 e-1 0 rmse standard 3.26 Preproc… <tibbl…
## 6 <split … Fold01 3.16e-1 0 rmse standard 3.46 Preproc… <tibbl…
## 7 <split … Fold01 1 e+0 0 rmse standard 3.86 Preproc… <tibbl…
## 8 <split … Fold01 3.16e+0 0 rmse standard 4.59 Preproc… <tibbl…
## 9 <split … Fold01 1 e+1 0 rmse standard 5.64 Preproc… <tibbl…
## 10 <split … Fold01 3.16e+1 0 rmse standard 6.68 Preproc… <tibbl…
## # … with 1,090 more rows
collect_metrics(metricas_vc) |>
filter(.metric == "rmse") |>
ggplot(aes(x = penalty, y = mean, ymin = mean - std_err, ymax = mean + std_err,
group = mixture, colour = factor(mixture))) +
geom_line() + geom_linerange() + geom_point() + scale_x_log10()
Podemos mostrar los mejores modelos como sigue:
show_best(metricas_vc, n = 3, metric = "rmse")## # A tibble: 3 × 8
## penalty mixture .metric .estimator mean n std_err .config
## <dbl> <dbl> <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 0.1 1 rmse standard 4.43 10 0.298 Preprocessor1_Model49
## 2 0.1 0.75 rmse standard 4.44 10 0.292 Preprocessor1_Model38
## 3 0.1 0.5 rmse standard 4.45 10 0.285 Preprocessor1_Model27
En este caso, vemos que valores mixture no es muy importante cuando escogemos la penalización adecuada. Usaremos el modelo con mejor resultados, y ahora podemos finalizar nuestro ajuste:
flujo_ajustado <-
finalize_workflow(flujo_reg,
parameters = select_best(metricas_vc, metric = "rmse")) |>
fit(grasa_ent)Nuestro modelo final es:
library(gt)
flujo_ajustado |> extract_fit_parsnip() |> tidy() |>
filter(estimate != 0) |>
mutate(estimate = round(estimate, 4)) |>
select(-penalty) |> gt()| term | estimate |
|---|---|
| (Intercept) | 19.1417 |
| edad | 0.0442 |
| peso | -0.0232 |
| cuello | -0.4016 |
| abdomen | 0.8036 |
| cadera | -0.0001 |
| tobillo | -0.2553 |
| biceps | 0.0610 |
| antebrazo | 0.3883 |
| muñeca | -1.6709 |
Podemos también seleccionar un modelo ligeramente más regularizado, consistente con los resultados del mejor:
flujo_ajustado <-
finalize_workflow(flujo_reg,
parameters =
select_by_one_std_err(metricas_vc, metric = "rmse", desc(penalty))) |>
fit(grasa_ent)Nuestro modelo final es:
flujo_ajustado |> extract_fit_parsnip() |> tidy() |>
filter(estimate != 0) |>
mutate(estimate = round(estimate, 4)) |>
select(-penalty) |> gt()| term | estimate |
|---|---|
| (Intercept) | 19.1417 |
| edad | 0.0899 |
| peso | 0.0069 |
| estatura | -0.1526 |
| cuello | -0.3950 |
| pecho | 0.1715 |
| abdomen | 0.4288 |
| cadera | 0.0689 |
| muslo | 0.1666 |
| rodilla | 0.1903 |
| tobillo | -0.4010 |
| biceps | 0.0811 |
| antebrazo | 0.3187 |
| muñeca | -1.5680 |
Y finalmente, veamos su desempeño en prueba:
predict(flujo_ajustado, grasa_pr) |>
bind_cols(grasa_pr |> select(grasacorp)) |>
ggplot(aes(x = .pred, y = grasacorp)) +
geom_point() + geom_abline() +
coord_obs_pred()
7.5 ¿Cómo se desempeña validación cruzada como estimación del error?
Podemos comparar el desempeño estimado con validación cruzada con el de muestra de prueba: Consideremos nuestro ejemplo simulado de regresión logística. Repetiremos varias veces el ajuste y compararemos el error de prueba con el estimado por validación cruzada:
set.seed(28015)
a_vec <- rnorm(50, 0, 0.2)
a <- tibble(term = paste0('V', 1:length(a_vec)), valor = a_vec)
modelo_1 <- linear_reg(penalty = 0.01) |>
set_engine("glmnet", lambda.min.ratio = 1e-20)
flujo_1 <- workflow() |>
add_model(modelo_1) |>
add_formula(y ~ .)
sim_datos <- function(n, beta){
p <- nrow(beta)
mat_x <- matrix(rnorm(n * p, 0, 0.5), n, p) + rnorm(n)
colnames(mat_x) <- beta |> pull(term)
beta_vec <- beta |> pull(valor)
f_x <- (mat_x %*% beta_vec)
y <- as.numeric(f_x) + rnorm(n, 0, 1)
datos <- as_tibble(mat_x) |>
mutate(y = y)
datos
}
simular_evals <- function(rep, flujo, beta){
datos <- sim_datos(n = 4000, beta = beta)
particion <- initial_split(datos, 0.05)
datos_ent <- training(particion)
datos_pr <- testing(particion)
# evaluar con muestra de prueba
metricas <- metric_set(rmse)
flujo_ajustado <- flujo_1 |> fit(datos_ent)
eval_prueba <- predict(flujo_ajustado, datos_pr) |>
bind_cols(datos_pr |> select(y)) |>
metricas(y, .pred)
eval_entrena <- predict(flujo_ajustado, datos_ent) |>
bind_cols(datos_ent |> select(y)) |>
metricas(y, .pred)
# particionar para validación cruzada
particiones_val_cruzada <- vfold_cv(datos_ent, v = 5)
eval_vc <- flujo_1 |>
fit_resamples(resamples = particiones_val_cruzada, metrics = metricas) |>
collect_metrics()
res_tbl <-
eval_prueba |> mutate(tipo = "prueba") |>
bind_rows(eval_entrena |> mutate(tipo = "entrenamiento")) |>
bind_rows(eval_vc |>
select(.metric, .estimator, .estimate = mean) |>
mutate(tipo = "val_cruzada"))
}
set.seed(82853)
evals_tbl <- tibble(rep = 1:20) |>
mutate(data = map(rep, ~ simular_evals(.x, flujo_1, beta = a))) |>
unnest(data)
ggplot(evals_tbl |>
filter(.metric == "rmse") |>
pivot_wider(names_from = tipo, values_from = .estimate) |>
pivot_longer(cols = c(entrenamiento, val_cruzada), names_to = "tipo"),
aes(x = prueba, y = value)) +
geom_point() + facet_wrap(~ tipo) +
geom_abline(colour = "red") +
xlab("Error de predicción (prueba)") +
ylab("Error") +
coord_obs_pred()
En primer lugar, vemos que el error de entrenamiento subestima considerablemente al error de predicción. En la segunda gráfica notamos que el error de prueba y la estimación de validación cruzada están centradas en lugares similares. De estas dos observaciones concluimos en primer lugar que usar la estimación de validación cruzada para estimar el error de predicción es mejor que simplemente tomar el error de entrenamiento.
La segunda observación es que el error por validación cruzada no está muy correlacionado con el error de prueba, aún cuando están centrados en lugares similares, de modo que no parece evaluar apropiadamente el modelo particular que ajustamos en cada caso.
Sin embargo, cuando usamos validación cruzada para seleccionar modelos tenemos lo siguiente:
set.seed(859)
datos <- sim_datos(n = 4000, beta = a[1:40, ])
modelo <- linear_reg(mixture = 0, penalty = tune()) |>
set_engine("glmnet", lambda.min.ratio = 1e-20)
flujo <- workflow() |>
add_model(modelo) |>
add_formula(y ~ .)
# crear partición de análisis y evaluación
particion_val <- validation_split(datos, 0.05)
candidatos <- tibble(penalty = exp(seq(-5, 5, 1)))
# evaluar
val_resultado <- tune_grid(flujo, resamples = particion_val, grid = candidatos,
metrics = metric_set(rmse)) |>
collect_metrics() |>
select(penalty, .metric, mean) |>
mutate(tipo ="datos de validación")
# extraer datos de entrenamiento
set.seed(834)
datos_ent <- analysis(particion_val$splits[[1]])
particion_vc <- vfold_cv(datos_ent, v = 10)
val_c_resultado <- tune_grid(flujo, resamples = particion_vc, grid = candidatos,
metrics = metric_set(rmse)) |>
collect_metrics() |>
select(penalty, .metric, mean) |>
mutate(tipo = "validación cruzada")
comparacion_val <- bind_rows(val_resultado, val_c_resultado) |>
filter(.metric == "rmse")
ggplot(comparacion_val, aes(x = penalty, y = mean, colour = tipo)) +
geom_line() + geom_point() +
facet_wrap(~.metric) +
scale_x_log10()
Vemos que la estimación en algunos casos no es tan buena, aún cuando todos los datos fueron usados. Pero el mínimo se encuentra en lugares similares. La razón es:
El resultado es que:
- Usamos validación cruzada para escoger la complejidad adecuada de la familia de modelos que consideramos.
- Como estimación del error de predicción del modelo que ajustamos, validación cruzada es más seguro que usar el error de entrenamiento, que muchas veces puede estar fuertemente sesgado hacia abajo. Sin embargo, lo mejor en este caso es utilizar una muestra de prueba.
- Existen variaciones (validación cruzada anidada, puedes ver el paper, y está implementado en tidymodels con la función nested_cv) que aún cuando es más exigente computacionalmente, produce mejores resultados cuando queremos utilizarla como estimación del error de prueba.
- Estratificación: especialmente en casos donde queremos predecir una variable categórica con algunas clases muy minoritarias, o cuando la respuesta tiene colas largas, puede ser buena idea estratificar la selecciones de muestra de prueba y las muestras de validación cruzada, de manera que cada corte es similar en composición de la variable respuesta. Esto es para evitar variación debida a la composición de muestras de validación, especialmente cuando la muestra de entrenamiento es relativamente chica.
7.6 Validación cruzada repetida
Con el objeto de reducir la varianza de las estimaciones por validación cruzada, podemos repetir varias veces usando distintas particiones seleccionadas al azar.
Por ejemplo, podemos repetir 5 veces validación cruzada con 10 vueltas, y ajustamos un total de 50 modelos. Esto no es lo mismo que validación cruzada con 50 vueltas. Hay razones para no subdividir tanto la muestra de entrenamiento:
- Aunque esquemas de validación cruzada-\(k\) con \(k\) grande pueden ser factibles, estos no se favorecen por la cantidad de cómputo necesaria y porque presentan sesgo hacia modelos más complejos (Shao 1993).
- En el extremo, podemos hacer validación leave-one-out (LOOCV)
- En estudios de simulación se desempeñan mejor métodos con \(k=5, 10, 20\), y cuando es posible, es mejor usar repeticiones