Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Curso: Machine Learning · Profesor: Lihki Rubio Ortega Grupo: Yuryen Rollo, Betzaida Ruiz

En el capítulo anterior ajustamos un SVR lineal que pronostica la velocidad del viento a 24 h con RMSE = 0.733 m/s en 2025 y vimos que sus residuos conservan estructura no lineal. En este capítulo comparamos siete modelos de regresión (KNN, Ridge, Lasso, árbol de decisión, Random Forest, XGBoost y SVR) y cuatro estrategias de búsqueda de hiperparámetros (rejilla, búsqueda aleatoria, optimización bayesiana con Optuna y algoritmo genético con DEAP), con validación cruzada anidada respetando el orden temporal. Además medimos el costo computacional de cada modelo, revisamos los residuos, interpretamos el mejor modelo con SHAP y LIME y comparamos los pronósticos con pruebas estadísticas para pronósticos (MCS, Giacomini-White, Clark-West, Diebold-Mariano y SPA).

Reproducibilidad. Usamos el mismo conjunto de modelado causal de la sección 3 (26 predictoras de la sección 2.0, objetivo y = vel(t + 24 h)). Los experimentos están en la carpeta e3/ (módulos con docstrings: datos.py, modelos.py, optimizadores.py, evaluacion.py) y en scripts/, y este notebook solo lee sus resultados. Desde la carpeta del repositorio:

python scripts/e3_preparar_datos.py    # conjunto de modelado (≈ 1 min)
python scripts/e3_experimentos.py      # 142 búsquedas con validación anidada (≈ 5 h en 6 procesos y una GPU)
python scripts/e3_computacional.py     # tiempos, memoria, variantes, paralelismo y perfilamiento (≈ 30 min)
python scripts/e3_semillas.py          # sensibilidad a la semilla (≈ 40 min)

Cada búsqueda guarda un JSON con su historial y sus predicciones, de modo que una corrida interrumpida se reanuda sin repetir lo terminado. La semilla es 42 en todo el proyecto y las versiones están en requirements.txt y al final de este capítulo. Usamos un procesador de 16 hilos, 32 GB de RAM y una GPU NVIDIA RTX 4060 Ti para XGBoost.

Entrenamiento: 402,200 filas (57 estaciones) | prueba 2025: 213,972 filas (53 estaciones) | 26 predictoras | semilla 42

5.1 Diseño experimental

Validación anidada temporal. El bucle externo son los cuatro trimestres de 2024: para cada uno entrenamos con todo lo anterior (ventana creciente, con las filas cuyo objetivo cae antes del inicio del trimestre) y medimos en el trimestre. Dentro de cada pliegue externo, la búsqueda de hiperparámetros usa como bucle interno los tres trimestres anteriores, de modo que nunca ve el trimestre con el que luego se la evalúa. La configuración elegida se reentrena con todo el entrenamiento del pliegue y se mide en el trimestre reservado: esos cuatro valores son los que reportamos como media ± desviación estándar. Para la evaluación final repetimos la búsqueda con los cuatro trimestres de 2024 como bucle interno, reentrenamos con 2020–2024 y predecimos 2025 una sola vez.

Presupuesto igual para todos. Cada optimizador dispone de 40 evaluaciones de la función objetivo, que es el RMSE medio en los pliegues internos. Para acotar el costo, cada pliegue interno usa submuestras fijas de 40 000 filas de ajuste y 10 000 de validación, las mismas para todas las configuraciones; así las comparaciones entre configuraciones son pareadas. En total son 7 modelos × 4 optimizadores × 5 búsquedas (4 pliegues externos y la final) = 140 búsquedas, más dos de eliminación sucesiva.

Optimizadores.

OptimizadorDetalle
RejillaLos niveles se reparten entre hiperparámetros según su importancia esperada hasta agotar las 40 evaluaciones; los de escala logarítmica se espacian en log
AleatoriaMuestreo uniforme (log-uniforme en los rangos logarítmicos)
Bayesiana (Optuna)Sustituto TPE multivariado, que modela por separado las densidades l(x) de las configuraciones buenas y g(x) de las malas; la adquisición es la mejora esperada, que equivale a maximizar l(x)/g(x); 10 evaluaciones iniciales aleatorias
Genética (DEAP)Esquema (μ + λ) con μ = λ = 8 en el espacio normalizado [0, 1]^d; selección por torneo de tamaño 3, cruce SBX (η = 15, p = 0.7), mutación polinomial (η = 20, p = 0.3, probabilidad 1/d por gen) y elitismo implícito en (μ + λ)
Eliminación sucesivaMulti-fidelidad: 27 configuraciones aleatorias con 1/9 de las filas, las 9 mejores con 1/3 y las 3 mejores con todas (η = 3); el costo equivale a unas 9 evaluaciones completas. La aplicamos a Random Forest y XGBoost, los modelos más costosos

Modelos. Todos los que dependen de la escala llevan StandardScaler dentro de un Pipeline, ajustado solo con las filas de cada entrenamiento. Ridge y Lasso pueden transformar el objetivo con log(1 + y). Para el SVR con núcleo RBF usamos una aproximación de Nyström de 300 componentes seguida de un SVR lineal en el primal, porque el núcleo completo es inviable con 400 mil filas (sección 5.3). Los rangos de búsqueda se fijaron por el tamaño de los datos, antes de ver resultados:

Loading...
Loading...

Los trimestres de validación de 2024 tienen pocas filas al principio del año (5 207 en el primero frente a 43 120 en el último) porque la red tuvo menos estaciones con las cuatro variables en ese periodo. Por eso el RMSE de los pliegues varía mucho y es más alto que en 2025: las estaciones de cada trimestre no son las mismas.

5.2 Resultados de la optimización

Desempeño en el bucle externo

Loading...
Loading...
<Figure size 1100x462 with 1 Axes>

Los cuatro optimizadores llegan a errores muy parecidos en cada modelo. La desviación estándar entre trimestres (≈ 0.09 m/s) es unas 20 veces mayor que la diferencia entre optimizadores dentro de un mismo modelo, lo que confirma que el trimestre (y su conjunto de estaciones) pesa más que la estrategia de búsqueda. Para cada modelo elegimos el optimizador con menor RMSE medio en el bucle externo, sin mirar 2025. Con esa regla el mejor modelo es XGBoost con búsqueda aleatoria (0.8950 ± 0.0932), seguido de cerca por Random Forest con Optuna (0.8961 ± 0.0931).

Tiempo de cómputo y número de evaluaciones

Loading...
Friedman entre optimizadores: χ² = 1.47, gl = 3, p = 0.690, N = 28 bloques, CD (Nemenyi, α = 0.05) = 0.89
Rangos promedio: {'Rejilla': 2.34, 'Aleatoria': 2.38, 'Bayesiana (Optuna)': 2.62, 'Genética (DEAP)': 2.66}

Los modelos lineales resuelven una búsqueda completa en segundos, mientras que XGBoost y Random Forest necesitan entre 1.5 y 2 horas de cómputo por optimizador para las cinco búsquedas. El algoritmo genético fue tres veces más barato en XGBoost (11.4 s por evaluación frente a ≈ 32 s): su población convergió hacia configuraciones con menos árboles (326 en promedio frente a ≈ 550) y menor profundidad (4.9 frente a 6.5), que son más baratas sin ser peores. La prueba de Friedman con los 28 bloques modelo × trimestre no encuentra diferencias entre optimizadores (χ² = 1.47, p = 0.69): los rangos promedio van de 2.34 (rejilla) a 2.66 (genético), todos dentro de la diferencia crítica de Nemenyi (CD = 0.89).

Curvas de convergencia (anytime)

<Figure size 1320x440 with 2 Axes>

Las curvas muestran el mejor RMSE interno alcanzado en función del tiempo de búsqueda. En XGBoost todas las estrategias bajan de 0.905 en los dos primeros minutos y después mejoran en milésimas: Optuna llega a 0.8985 a los 2 minutos y es la que termina más abajo (0.8965), mientras que la rejilla da su mejor salto a los 9. En Random Forest la rejilla llega a 0.900 en un minuto y la búsqueda aleatoria se queda en 0.902 después de 31 minutos. Con 40 evaluaciones, la ventaja de la optimización bayesiana sobre la aleatoria aparece al final de la búsqueda (es la que termina con el menor RMSE interno en ambos modelos), pero es pequeña. Los tiempos son de reloj y incluyen la competencia con los otros procesos que corrían en paralelo.

Eliminación sucesiva frente a búsqueda aleatoria con el mismo costo

Loading...

La eliminación sucesiva no ahorró trabajo en este problema. En Random Forest gastó 9.5 minutos y llegó al mismo RMSE interno (0.9035) que la búsqueda aleatoria en el mismo tiempo, y en XGBoost la búsqueda aleatoria encontró una configuración mejor (0.8976 frente a 0.8997) en menos tiempo. La razón es que con 1/9 de las filas (unas 4 400) el orden de las configuraciones no se mantiene con todos los datos: en XGBoost, la mejor combinación de número de árboles y tasa de aprendizaje depende del tamaño de la muestra, así que la primera ronda descarta configuraciones que habrían sido buenas con más filas. La multi-fidelidad conviene cuando la fidelidad baja ordena bien las configuraciones, y aquí no ocurre.

Diversidad de la población en el algoritmo genético

<Figure size 1320x418 with 2 Axes>
Operadores: {'seleccion': 'torneo (tamaño 3)', 'cruce': 'SBX acotado, η = 15, p = 0.7', 'mutacion': 'polinomial acotada, η = 20, p = 0.3, 1/d por gen', 'elitismo': '(μ + λ)', 'mu': 8, 'lambda': 8}

La diversidad (distancia media entre individuos en el espacio normalizado) cae de ≈ 0.27 a casi cero entre la segunda y la cuarta generación: con una población de 8, la presión de selección del torneo y el elitismo (μ + λ) concentran rápidamente la población alrededor del mejor individuo, y la mutación polinomial apenas la separa de nuevo (solo el SVR conserva algo de diversidad). El mejor individuo casi no mejora después de la generación 2. Con 40 evaluaciones la búsqueda genética explota mucho más de lo que explora; una población mayor o una mutación más fuerte la harían más exploratoria, a costa de más evaluaciones. Las generaciones son más de 4 porque las configuraciones repetidas salen de la caché y no consumen presupuesto.

Sensibilidad a la semilla

Loading...
Loading...

La semilla del modelo cambia el RMSE en 2025 en menos de una milésima en Random Forest y XGBoost (desviación de 0.0002 y 0.0003), y algo más en el árbol (0.0012). La semilla del optimizador pesa más: al repetir la búsqueda final de XGBoost con tres semillas adicionales, el RMSE en 2025 varía con una desviación de 0.0006 (aleatoria), 0.0011 (Optuna) y 0.0030 (genético). Esa variabilidad es del mismo orden que las diferencias entre optimizadores de la tabla anterior, otra señal de que ninguno es claramente superior. La búsqueda bayesiana es la más estable en el RMSE interno (0.8969 ± 0.0003).

5.3 Optimización computacional

Complejidad teórica y tiempos medidos

Loading...
<Figure size 1320x440 with 2 Axes>

El exponente empírico es la pendiente de log(tiempo) frente a log(n) entre 40 mil y 320 mil filas. Ridge, Lasso y el SVR lineal crecen de forma lineal (1.03–1.08), como predice O(n·p) con p fijo. El árbol, Random Forest y KNN con KD-Tree tienen exponentes de 1.16–1.20, coherentes con O(n log n). XGBoost en GPU casi no cambia (0.18): con estos tamaños domina el costo fijo de transferir los datos y lanzar los núcleos de la GPU, no el número de filas. En la predicción, KNN es la excepción: pronosticar 10 mil filas con 320 mil de referencia tarda 39 s, porque cada consulta recorre el árbol en 26 dimensiones, donde la estructura pierde su ventaja.

Variantes estándar frente a optimizadas

Loading...
<Figure size 660x385 with 1 Axes>
Nyström 300 + primal: exponente empírico 1.01
núcleo RBF completo: exponente empírico 1.96

Las variantes optimizadas dan el mismo resultado en mucho menos tiempo:

  • KNN: la búsqueda exhaustiva (brute) predice 10 veces más rápido que el KD-Tree (5.5 s frente a 53.8 s), con resultados idénticos. Con 26 predictoras los árboles de búsqueda podan poco (maldición de la dimensión) y la búsqueda exhaustiva aprovecha mejor las operaciones vectorizadas. Por eso los experimentos usan algorithm="auto", que elige brute.

  • Ridge: las ecuaciones normales con Cholesky son 11 veces más rápidas que SAGA (0.20 s frente a 2.2 s).

  • Lasso: el descenso por coordenadas es 2.5 veces más rápido que SGD con penalización L1.

  • XGBoost: el método por histogramas es 7.8 veces más rápido que el exacto con casi el mismo error. Con 160 mil filas y 300 árboles, la GPU (5.6 s) no supera a la CPU con 16 hilos (4.3 s), pero la usamos porque deja libres los núcleos de la CPU para los otros procesos que corrían en paralelo.

  • SVR con núcleo RBF: el núcleo completo crece con exponente 1.96 (cuadrático, como se espera de construir la matriz de núcleo) y Nyström con 1.01. Con 20 mil filas el núcleo completo tarda 14.7 s y Nyström 0.87 s, con un RMSE casi igual (0.6235 frente a 0.6246). Extrapolando, el núcleo completo con 400 mil filas tardaría horas por ajuste.

Parada temprana, paralelismo y perfilamiento

Loading...
Loading...
Loading...
 KNN 


         20368 function calls (20284 primitive calls) in 11.592 seconds

   Ordered by: cumulative time
   List reduced from 522 to 12 due to restriction <12>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        3    0.000    0.000   11.138    3.713 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\sklearn\pipeline.py:746(predict)
        3    0.005    0.002   11.134    3.711 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\sklearn\neighbors\_regression.py:229(predict)
        3    0.000    0.000   11.128    3.709 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\sklearn\neighbors\_base.py:756(kneighbors)
        3    0.000    0.000   11.120    3.707 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\sklearn\utils\parallel.py:54(__call__)
        3    0.000    0.000   11.120    3.707 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\joblib\parallel.py:1969(__call__)
       18    0.000    0.000   11.120    0.618 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\joblib\parallel.py:1670(_get_outputs)
       11    0.016    0.001   11.110    1.010 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\joblib\parallel.py:1776(_retrieve)
     1078   11.088    0.010   11.088    0.010 {built-in method time.sleep}
      9/3    0.000    0.000    0.451    0.150 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\sklearn\base.py:1381(wrapper)
        3    0.000    0.000    0.450    0.150 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\sklearn\pipeline.py:578(fit)
        3    0.000    0.000    0.410    0.137 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\sklearn\neighbors\_regression.py:205(fit)
        3    0.400    0.133    0.410    0.137 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\sklearn\neig 

 Random Forest 


         661562 function calls (650639 primitive calls) in 18.775 seconds

   Ordered by: cumulative time
   List reduced from 429 to 12 due to restriction <12>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        3    0.000    0.000   18.628    6.209 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\sklearn\base.py:1381(wrapper)
        3    0.000    0.000   18.626    6.209 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\sklearn\ensemble\_forest.py:302(fit)
        6    0.000    0.000   18.502    3.084 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\sklearn\utils\parallel.py:54(__call__)
        6    0.001    0.000   18.502    3.084 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\joblib\parallel.py:1969(__call__)
      912    0.001    0.000   18.501    0.020 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\joblib\parallel.py:1670(_get_outputs)
      803    0.030    0.000   18.475    0.023 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\joblib\parallel.py:1776(_retrieve)
     1787   18.435    0.010   18.435    0.010 {built-in method time.sleep}
        3    0.000    0.000    0.259    0.086 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\sklearn\ensemble\_forest.py:457(<listcomp>)
      450    0.002    0.000    0.259    0.001 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\sklearn\ensemble\_base.py:141(_make_estimator)
     2253    0.012    0.000    0.218    0.000 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\sklearn\base.py:248(get_params)
     2253    0.011    0.000    0.203    0.000 C:\Users\USER\miniconda3\envs\ml_env\Lib\site-packages\sklearn\base.py:218(_get_param_names)
     2253    0.001    0.000    0.160    0.000 C:\Users\USER\miniconda3\envs\ml_env\Lib\inspect.py:3261(sig 

  • Parada temprana: en XGBoost con tasa de aprendizaje 0.05, parar cuando 50 rondas seguidas no mejoran en un trimestre de validación reduce el ajuste de 26.7 s a 5.5 s (217 árboles en vez de 1 500) y además baja el error del trimestre siguiente de 0.798 a 0.767, porque evita el sobreajuste.

  • Paralelismo: Random Forest acelera 8.7 veces con 16 hilos, con eficiencia del 54 %: el procesador tiene 8 núcleos físicos, así que pasar de 8 a 16 hilos lógicos aporta poco (de 12.4 s a 9.2 s). Para seis ajustes de árbol independientes, los hilos (threading) son 4.5 veces más rápidos que en serie, porque scikit-learn libera el GIL al construir el árbol; los procesos (loky) apenas ganan, porque copian los datos a cada proceso. En los experimentos combinamos ambos niveles: 6 procesos para las búsquedas y 2–4 hilos dentro de cada modelo.

  • Perfilamiento: cProfile mide el hilo principal, que pasa casi todo el tiempo esperando a los hilos de trabajo (time.sleep dentro de joblib). Aun así, la distribución es clara: en KNN el 96 % del tiempo está en kneighbors (la predicción) y solo 0.45 s de 11.6 s en el ajuste, mientras que en Random Forest todo el tiempo está en fit. El cuello de botella de KNN es la consulta y el de los bosques, la construcción de árboles.

5.4 Evaluación en el conjunto de prueba (2025)

Loading...
Mejor modelo según el bucle externo: XGBoost con Aleatoria
Configuración: {'learning_rate': 0.011650335455725839, 'max_depth': 6, 'n_estimators': 993, 'min_child_weight': 60.72318357191729, 'subsample': 0.8743040097284747, 'colsample_bytree': 0.9453962454392624, 'reg_lambda': 3.747887608375425}
Diferencia máxima con la predicción guardada: 0.00e+00 m/s
RMSE entrenamiento (2020–2024): 0.7285 | RMSE prueba (2025): 0.7268

Todos los modelos superan a la persistencia: reducen su RMSE entre un 10 % (árbol) y un 14.5 % (Random Forest). En 2025, Random Forest es el mejor (RMSE = 0.7227, R² = 0.744), seguido de XGBoost (0.7268) y del SVR con núcleo RBF aproximado (0.7311). La ganancia de los modelos no lineales sobre los lineales es modesta: Random Forest mejora a Ridge en 0.010 m/s (1.4 %). Ridge y Lasso coinciden porque la regularización elegida es casi nula en Lasso y moderada en Ridge, y con 400 mil filas ambas dan prácticamente los mismos coeficientes. La última columna es el intervalo de confianza de la mejora diaria media sobre la persistencia, con bootstrap estacionario de bloques de 7 días.

Que Random Forest gane en 2025 mientras el bucle externo eligió XGBoost no es una contradicción: la diferencia en 2024 fue de 0.001 m/s a favor de XGBoost y en 2025 es de 0.004 m/s a favor de Random Forest, ambas dentro de la variabilidad que estimamos con las semillas. En la sección 5.7 comprobamos si esa diferencia es significativa. Mantenemos XGBoost como modelo elegido porque la regla se fijó antes de mirar 2025.

El RMSE del modelo reentrenado con 2020–2024 es 0.7285 en el propio entrenamiento y 0.7268 en 2025: no hay señales de sobreajuste. Reentrenarlo da exactamente las mismas predicciones que las guardadas.

Entrenamiento, validación y prueba

<Figure size 1320x770 with 2 Axes>

En azul están las predicciones sobre los datos de ajuste (2020–2023), en verde las de los cuatro pliegues externos de 2024 (cada trimestre pronosticado por un modelo que no lo vio) y en rojo las de 2025. Las tres siguen igual de bien la media diaria observada, otra señal de que el modelo no memoriza el entrenamiento. En la escala horaria, el pronóstico es mucho más suave que la persistencia: sigue el ciclo diario y los cambios de régimen de varios días, pero no anticipa los picos de una o dos horas.

Diagnóstico de residuos

White: LM = 4016.7, p = 0
BDS: rechaza independencia (5 %) en 100 % de 53 estaciones
Ljung-Box (24 rezagos): rechaza en 100 % de las estaciones
Media de los residuos: 0.0069 m/s | asimetría 0.91 | curtosis en exceso 4.04
<Figure size 1540x418 with 3 Axes>

Los residuos no son ruido blanco. La prueba de White rechaza la homocedasticidad, y el gráfico de la derecha muestra que la desviación del residuo crece con el pronóstico: el error es mayor cuando se espera más viento. BDS y Ljung-Box rechazan la independencia en todas las estaciones. La ACF de la estación mostrada decae despacio durante unas 20 horas: un error grande tiende a repetirse en las horas siguientes, porque los cambios de régimen que el modelo no anticipa duran varias horas. La distribución tiene asimetría positiva (0.91) y colas pesadas (curtosis en exceso de 4.04): las ráfagas fuertes se subestiman. La media del residuo es prácticamente cero (0.007 m/s). Este diagnóstico sugiere, para el modelo final, una pérdida o una transformación que tenga en cuenta la heterocedasticidad y el uso de errores recientes como predictoras.

5.5 Interpretabilidad

SHAP

Valor base: 1.993 m/s | suma de SHAP + base = predicción: True
<Figure size 990x660 with 2 Axes>
<Figure size 1650x374 with 4 Axes>
Loading...

Calculamos los valores SHAP con TreeSHAP exacto sobre 3 000 pronósticos de 2025; la suma de los valores SHAP y el valor base reproduce cada predicción. La velocidad actual (vel_l0) domina (0.45 m/s de contribución media absoluta), seguida de la media de las últimas 24 h (vel_m24) y de la velocidad de hace 23 h (vel_l23), que es la misma hora del día del objetivo. Después aparecen la hora del día (hy_cos) y la temperatura. Las gráficas de dependencia muestran que el efecto de vel_l0 y de vel_l1 es casi lineal, mientras que el de la media de 24 h se satura por encima de unos 4 m/s y el de vel_l23 se curva: son las no linealidades que un modelo lineal no captura, aunque su peso en el error total es pequeño. La importancia por ganancia de XGBoost concentra el 64 % en vel_l0 y el 16 % en vel_l1, y casi ignora vel_m24 (2 %). Como los rezagos están muy correlacionados, la ganancia se la lleva la variable que el árbol usa primero; SHAP reparte mejor el crédito entre variables redundantes.

LIME

<Figure size 1760x418 with 3 Axes>

LIME ajusta un modelo lineal local alrededor de cada pronóstico. En los tres casos, las condiciones sobre la velocidad actual, la de hace 23 h y la media de 24 h son las que más contribuyen, con el mismo signo que en SHAP. En calma, las velocidades bajas de todas las horas previas empujan el pronóstico hacia abajo; en el caso típico, la temperatura alta y la época del año lo compensan en parte; con viento fuerte, las velocidades altas lo empujan hacia arriba en casi 0.9 m/s. El R² local indica qué tan bien la aproximación lineal describe al modelo cerca de ese punto: es alto con viento fuerte (0.68) y bajo en el caso típico (0.12), donde la explicación local es solo orientativa.

5.6 Robustez y sobreajuste

Curva de aprendizaje

<Figure size 715x418 with 1 Axes>
Loading...

La curva de aprendizaje confirma que el modelo no sobreajusta. Con pocas filas, el error de entrenamiento es menor que el de prueba, y la brecha se cierra al crecer la muestra: con 200 mil filas los dos errores coinciden (0.724 y 0.725). Por encima de 100 mil filas el error de prueba ya no mejora, así que más datos de las mismas estaciones no ayudarían. Para bajar el error hacen falta predictoras con información nueva, no más filas.

Prueba de permutación de la respuesta

RMSE con la respuesta real: 0.7320 | con la respuesta permutada: 1.4450 ± 0.0096 (mín. 1.4235) | p = 0.02 con 49 permutaciones

En la prueba de permutación ajustamos el modelo con la respuesta desordenada al azar, lo que rompe toda relación entre predictoras y objetivo. Con la respuesta real el RMSE es 0.732; con la permutada, 1.445 ± 0.010, y ninguna de las 49 permutaciones se acerca. El p-valor es el mínimo posible con 49 permutaciones (0.02). El modelo aprende una relación real y no un artefacto del procedimiento.

5.7 Comparación estadística de los modelos

Friedman y Nemenyi con bloques estación × trimestre

Friedman: χ² = 126.2, gl = 6, p = 8.2e-25, N = 76 bloques (estación × trimestre con ≥ 48 horas), CD = 1.03
<Figure size 990x286 with 1 Axes>
Loading...

Para comparar los modelos en validación usamos como bloques cada estación en cada trimestre de 2024 (76 bloques con al menos 48 horas), que son unidades independientes del ajuste. Friedman rechaza que los siete modelos sean equivalentes (χ² = 126.2, p < 10⁻²⁴). El diagrama de diferencia crítica agrupa con una barra a los modelos cuyos rangos no difieren según Nemenyi (CD = 1.03). Random Forest, SVR y XGBoost forman el grupo líder y no se distinguen entre sí. Random Forest es significativamente mejor que Ridge y Lasso, y el árbol de decisión es peor que todos.

Model Confidence Set en 2025

311 días de 2025
Loading...

El Model Confidence Set con α = 0.10 (bootstrap estacionario con bloques de 7 días sobre las pérdidas diarias de 2025) conserva solo a Random Forest. XGBoost queda justo fuera (p = 0.094), igual que SVR, Ridge y Lasso (p ≈ 0.07–0.09); KNN, el árbol y la persistencia se descartan con p = 0. Como el umbral está muy cerca, lo prudente es decir que Random Forest es el mejor y que XGBoost, SVR y los lineales están cerca del límite.

Giacomini-White, Clark-West y Diebold-Mariano

Comparaciones de XGBoost (A) contra cada rival (B): pérdida diaria A − B; negativo favorece a XGBoost
Loading...

Comparamos el modelo elegido, XGBoost, con cada rival usando las pérdidas cuadráticas diarias de 2025:

  • Diebold-Mariano con la corrección de Harvey, Leybourne y Newbold: pregunta si la pérdida esperada es igual. Tras corregir por Holm, XGBoost solo es significativamente mejor que KNN y que el árbol. Frente a Random Forest la diferencia (+0.005, a favor de Random Forest) no es significativa (p = 0.44), y el intervalo del bootstrap estacionario incluye el cero.

  • Giacomini-White: pregunta si la diferencia de pérdidas es predecible con lo ocurrido el día anterior. Rechaza también frente a Ridge y Lasso (p = 0.021 con Holm): XGBoost no es mejor en promedio, pero sí en ciertos días, en particular después de días con errores grandes.

  • Tamaño del efecto: salvo frente al árbol (δ de Cliff pequeño, d = −0.81), todas las diferencias son despreciables. Aunque algunas sean significativas con 311 días, su magnitud práctica es pequeña.

Loading...
<Figure size 880x660 with 2 Axes>

Clark-West es la prueba adecuada cuando un modelo está anidado en otro: la persistencia es un caso particular de todos los modelos, porque vel(t) es una de sus predictoras. La prueba corrige el sesgo que tendría Diebold-Mariano en ese caso y rechaza con holgura en los siete modelos (p ≈ 0). El mapa de calor resume las 28 comparaciones por pares con Diebold-Mariano y Holm: los cinco mejores modelos no se distinguen entre sí, y todos difieren de la persistencia y del árbol.

Reality Check y SPA: control del data snooping

Loading...

Las comparaciones anteriores miran pares de modelos. El Reality Check de White y el SPA de Hansen responden otra pregunta: si, de todo el conjunto de modelos probados, alguno supera de verdad a una referencia o si la ventaja del mejor se explica por haber probado muchos. Con bootstrap estacionario de bloques de 7 días sobre las pérdidas diarias de 2025, todos los modelos superan a la persistencia (p = 0), pero ninguno supera de forma genuina a XGBoost: el SPA no rechaza (p = 0.25), ni tampoco el Reality Check (p = 0.42). La ventaja de Random Forest en 2025 es compatible con el azar de haber elegido el mejor entre siete modelos.

5.8 Tabla maestra

Loading...

La tabla maestra reúne, para cada combinación de modelo y optimizador, el RMSE del bucle externo (media ± desviación), el RMSE interno de la búsqueda final, las métricas en 2025, el número de evaluaciones, el tiempo total de búsqueda y la configuración final. La columna elegido marca el optimizador que seleccionó el bucle externo. La tabla se guarda en resultados/e3/tabla_maestra_resumen.csv, y la versión por tarea (una fila por búsqueda) en resultados/e3/tabla_maestra.csv.

5.9 Conclusiones

  1. Con validación anidada temporal y el mismo presupuesto de 40 evaluaciones, los cuatro optimizadores llegan a configuraciones de calidad equivalente (Friedman p = 0.69). La variabilidad entre trimestres y la debida a la semilla del optimizador son mayores que las diferencias entre estrategias. El algoritmo genético resultó el más barato en XGBoost porque converge hacia configuraciones más livianas, y la optimización bayesiana fue la más estable entre semillas.

  2. La eliminación sucesiva no mejoró a la búsqueda aleatoria con el mismo costo: con 1/9 de las filas el orden de las configuraciones no se mantiene con todos los datos.

  3. Los tiempos medidos siguen la complejidad teórica (exponentes cercanos a 1 en los modelos lineales y a n log n en los árboles; cuadrático en el SVR con núcleo completo). Las mayores ganancias computacionales vienen de elegir bien el algoritmo: búsqueda exhaustiva en KNN, Cholesky en Ridge, histogramas en XGBoost, Nyström en el SVR con núcleo RBF y parada temprana.

  4. El bucle externo eligió XGBoost con búsqueda aleatoria (RMSE = 0.7268 en 2025); Random Forest fue el mejor en 2025 (0.7227) y el único en el MCS al 10 %, pero la diferencia entre ambos no es significativa según Diebold-Mariano, el SPA no encuentra ningún modelo que supere a XGBoost y el tamaño de efecto es despreciable. Los dos mejoran al SVR lineal del capítulo anterior (0.733) y reducen el error de la persistencia en un 14 %.

  5. El modelo no sobreajusta (errores de entrenamiento y prueba iguales, curva de aprendizaje convergida, prueba de permutación con p = 0.02). Pero sus residuos son heterocedásticos y conservan dependencia temporal, y SHAP muestra que casi toda la información viene de la velocidad reciente. Para el modelo final, lo que puede mejorar el pronóstico no es otro algoritmo con las mismas predictoras, sino información nueva sobre el estado de la atmósfera.

Limitaciones. El presupuesto de 40 evaluaciones y las submuestras de 40 mil filas abaratan la búsqueda, pero pueden favorecer configuraciones que funcionan bien con pocos datos. Los trimestres de 2024 tienen entre 7 y 38 estaciones, lo que hace variable el RMSE externo. Los tiempos de las búsquedas son de reloj con varios procesos compartiendo la máquina, por lo que solo son comparables dentro de un mismo modelo; las mediciones de la sección 5.3 se hicieron con la máquina dedicada.

Versiones: {'numpy': '2.4.6', 'pandas': '3.0.6', 'scikit-learn': '1.9.1', 'xgboost': '3.2.0', 'optuna': '5.0.0', 'deap': '1.4.4', 'shap': '0.51.0', 'lime': '0.2.0.1', 'arch': '8.0.0', 'scikit-posthocs': '0.17.1', 'statsmodels': '0.15.0', 'scipy': '1.17.1'} | semilla: 42