Más sobre forecasting en: cienciadedatos.net


Modelos fundacionales

Los modelos fundacionales (FMs) han desencadenado un cambio de paradigma fundamental en el pronóstico de series temporales, alejando el campo del modelado por conjunto de datos individual hacia el aprendizaje de representaciones generalizadas. Impulsados por los mismos avances arquitectónicos que potencian los Grandes Modelos de Lenguaje (LLMs), los FMs aportan capacidades de aprendizaje zero-shot y en contexto a los datos temporales.

En el contexto del pronóstico, un modelo fundacional es una red neuronal de enorme escala (típicamente basada en Transformers) que ha sido preentrenada en conjuntos de datos altamente diversos entre dominios, abarcando finanzas, meteorología, tráfico web, comercio minorista y más.

Modelos como AWS Chronos, Google TimesFM 2.5 y Salesforce Moirai plantean el pronóstico temporal como un problema de modelado de secuencias y procesan datos temporales ya sea como tokens discretos cuantizados (como en Chronos, que aplica cuantización escalar) o como embeddings de parches continuos (como en TimesFM y Moirai, que agrupan steps de tiempo consecutivos en parches de longitud fija antes de codificarlos). Habiendo ya internalizado los priors estructurales de millones de series durante el preentrenamiento, pueden inferir instantáneamente tendencias, estacionalidad y dinámicas complejas en datos completamente nuevos, eliminando la necesidad de actualizaciones de pesos específicas del dominio.

Modelos Fundacionales vs. Modelos de Machine Learning

Los modelos fundacionales y los modelos de machine learning tradicionales abordan el pronóstico de maneras fundamentalmente diferentes. Comprender estas distinciones es crucial para saber cuándo y cómo desplegar cada método.

Predicción Zero-Shot

Los modelos de machine learning requieren una fase de entrenamiento. Debes ajustar el modelo sobre tus datos históricos objetivo para que el algoritmo pueda aprender los pesos y parámetros óptimos para tu serie temporal específica. Los modelos fundacionales, sin embargo, son capaces de inferencia zero-shot. Dado que sus pesos altamente generalizados ya están congelados desde la masiva fase de preentrenamiento, pueden generar pronósticos precisos sobre tus datos de inmediato, aprovechando sus representaciones latentes preexistentes en lugar de aprender tu conjunto de datos desde cero.

El Rol del Método fit

Los modelos de machine learning deben ser entrenados: llamar a .fit() optimiza los parámetros internos del modelo minimizando una función de pérdida sobre tus datos históricos. Los modelos fundacionales, por el contrario, llegan preentrenados: sus pesos están fijos y nunca se actualizan. Llamar a .fit() en un modelo fundacional no es un paso de entrenamiento; simplemente almacena el contexto histórico (observaciones, frecuencia y cualquier factor de escala) necesario en el momento de inferencia. En algunas implementaciones, llamar a .fit() es completamente opcional antes de la predicción.

Ventana de Contexto vs. Lags Diseñados

Los modelos de machine learning dependen de características explícitamente diseñadas; requieren crear un conjunto de datos tabular donde los valores pasados se usan como columnas para predecir el objetivo. Los modelos fundacionales dependen de una ventana de contexto. Pasas directamente al modelo un fragmento secuencial sin procesar de datos históricos recientes (por ejemplo, las últimas 512 observaciones) en el momento de inferencia. El mecanismo de atención dentro del modelo decide automáticamente qué puntos de datos pasados son más relevantes.

En resumen, los modelos fundacionales representan un cambio de paradigma fundamental, reemplazando el pipeline tradicional de entrenar -> predecir por un enfoque de preentrenar -> (contexto + predecir). Mientras las principales instituciones de investigación con acceso a millones de series temporales diversas llevan a cabo la fase de preentrenamiento computacionalmente intensiva, los usuarios finales quedan completamente liberados del entrenamiento del modelo.

Sin embargo, en machine learning no existe nada gratuito. Saltarse la fase de entrenamiento supone una mayor carga durante la fase de inferencia. Dado que sus pesos están congelados, estos modelos no pueden adaptarse a tus datos mediante el entrenamiento. En cambio, se adaptan implícitamente en el momento de inferencia procesando el contexto histórico a través de su mecanismo de atención. Por lo tanto, cada predicción requiere ingerir y atender sobre una larga secuencia de observaciones sin procesar en tiempo real. En consecuencia, la principal desventaja del pronóstico zero-shot es que el proceso de inferencia es significativamente más lento, más costoso computacionalmente y requiere que tu pipeline de datos proporcione continuamente grandes cantidades de contexto histórico en tiempo de ejecución.

- Modelo ML Modelo Fundacional
fit Entrena el modelo, actualiza pesos Almacena contexto y metadatos
predict Usa pesos aprendidos Procesa contexto vía atención
Datos requeridos en entrenamiento Historial completo No requeridos
Datos requeridos en predicción Últimas lags observaciones Ventana de contexto completa
Coste computacional En entrenamiento En inferencia

✏️ Nota

Para más detalles sobre el pronóstico con modelos fundacionales, visita Forecasting: Principles and Practice, the Pythonic Way.

Modelos Fundacionales en skforecast

La integración de Skforecast se basa en dos capas. Primero, FoundationModel actúa como un wrapper unificado que adapta la API nativa de cada modelo (Chronos-2, TimesFM 2.5, Moirai-2, TabICL) detrás de una interfaz familiar de scikit-learn (fit, predict, get_params). Segundo, ForecasterFoundation envuelve dicho estimador para desbloquear el ecosistema completo de skforecast. Expone la misma interfaz que cualquier otro pronosticador de skforecast, lo que significa que los usuarios pueden utilizar backtesting, intervalos de predicción y soporte multi-series con exactamente el mismo código.

skforecast ecosystem

ForecasterFoundation (forecasting)

FoundationModel (skforecast Wrapper)

.fit() (Store Context)
</div>
.predict() (Zero-Shot Run)
</div> </div>

Internal Model Adapters
Chronos
TimesFM
Moirai
TabICL
TabPFN
TFC-T0
Nori
TS-ICL
</div> </div>

</div>

Modelos Fundacionales Soportados

Model Provider GitHub Documentation Available model IDs Backend Default context length Max context length Max horizon Point forecast Covariate support (exog) Cross learning Install command
Chronos Amazon GitHub Docs amazon/chronos-2
autogluon/chronos-2-small
autogluon/chronos-2-synth
PyTorch 8192 8192 No hard limit, set via steps Median (0.5 quantile) Yes Multi-series only
pip install chronos-forecasting
TimesFM Google GitHub Docs google/timesfm-2.5-200m-pytorch PyTorch 512 16384 512 Mean (dedicated output) No No
pip install timesfm
Moirai Salesforce GitHub Docs Salesforce/moirai-2.0-R-small PyTorch 2048 2048 No hard limit, set via steps Median (0.5 quantile) No No
pip install uni2ts
TabICL Soda-Inria GitHub Docs soda-inria/tabicl PyTorch 4096 4096 No hard limit, set via steps Mean (default) Yes No
pip install tabicl[forecast]
TabPFN-TS Prior Labs GitHub Docs priorlabs/tabpfn-ts PyTorch 32768 No hard limit (~65536) No hard limit, set via steps Median (default) Yes No
pip install tabpfn-time-series
T0 The Forecasting Company GitHub Docs theforecastingcompany/t0-alpha PyTorch 8192 No hard limit No hard limit, set via steps Median (0.5 quantile) Yes No
pip install tfc-t0
Nori Synthefy GitHub Docs Synthefy/Nori PyTorch 4096 No hard limit No hard limit, set via steps Mean (default) Yes No
pip install synthefy-nori
TS-ICL EDF Lab GitHub Docs taharnbl/TS-ICL PyTorch 4096 No hard limit No hard limit, set via steps Median (0.5) Yes No
pip install tsicl

💡 Consejo

Los cuatro modelos funcionan en CPU. Sin embargo, se recomienda una GPU CUDA para una inferencia más rápida, especialmente con ventanas de contexto largas. El backend MPS también es detectado automáticamente por PyTorch y puede beneficiar a los usuarios de Apple Silicon.

Es importante tener en cuenta que la longitud del contexto afecta significativamente a la velocidad de inferencia. Los contextos más largos proporcionan a los modelos más información, pero aumentan el tiempo de procesamiento. Aunque estos modelos presumen de enormes capacidades de contexto, los contextos más cortos suelen lograr resultados similares mucho más rápido en la mayoría de los casos de uso.

Formatos de Datos de Entrada

ForecasterFoundation acepta varios formatos de datos tanto para la serie objetivo como para las variables exógenas.

Serie Objetivo (series)

El parámetro series en el método .fit() admite configuraciones tanto de serie única como de múltiples series (modelo global).

Modo Tipo de Dato Permitido Descripción
Serie Única pd.Series Una sola serie temporal con un índice con nombre.
Multi-Series (Wide) pd.DataFrame Cada columna representa una serie temporal independiente.
Multi-Series (Long) pd.DataFrame MultiIndex (Nivel 0: ID de serie, Nivel 1: DatetimeIndex).
Multi-Series (Dict) dict[str, pd.Series] Las claves son identificadores de series, los valores son Series de pandas.

💡 Consejo

Aunque los DataFrames en formato Long están soportados, se convierten internamente a diccionarios. Para un mejor rendimiento, pasa directamente un dict[str, pd.Series].

Variables Exógenas (exog)

Las variables exógenas deben estar alineadas con el índice de la serie objetivo. Actualmente, solo Chronos y TabICL admiten covariables (ver la tabla de Modelos Fundacionales Soportados). TimesFM 2.5 y Moirai-2 no aceptan variables exógenas.

Modo Tipo de Dato Permitido Descripción
Serie Única pd.Series o pd.DataFrame Alineado al índice de la serie objetivo.
Multi-Series (Dict) dict[str, pd.Series | pd.DataFrame | None] Una entrada por serie.
Multi-Series (Broadcast) pd.Series o pd.DataFrame Aplicado automáticamente a todas las series.
Multi-Series (Long) pd.DataFrame MultiIndex (Nivel 0: ID de serie, Nivel 1: DatetimeIndex).

Librerías y datos

# Librerías
# ==============================================================================
import pandas as pd
import time
import torch
import matplotlib.pyplot as plt
from skforecast.datasets import fetch_dataset
from skforecast.foundation import FoundationModel, ForecasterFoundation
from skforecast.model_selection import (
    TimeSeriesFold,
    backtesting_foundation,
    bayesian_search_foundation
)
from skforecast.plot import set_dark_theme

color = '\033[1m\033[38;5;208m' 
print(f"{color}versión de torch: {torch.__version__}")
print(f"  Cuda disponible : {torch.cuda.is_available()}")
print(f"  MPS disponible  : {torch.backends.mps.is_available()}")
versión de torch: 2.8.0+cu128
  Cuda disponible : True
  MPS disponible  : False
# Descarga de datos
# ==============================================================================
datos = fetch_dataset(name='vic_electricity')

# Agregando en intervalos de 1H
# ==============================================================================
# Se elimina la columna Date para evitar errores al agregar.
datos = datos.drop(columns="Date")
datos = (
    datos
    .resample(rule="h", closed="left", label="right")
    .agg({
        "Demand": "mean",
        "Temperature": "mean",
        "Holiday": "mean",
    })
)
datos.head(3)
╭──────────────────────────── vic_electricity ─────────────────────────────╮
│ Description:                                                             │
│ Half-hourly electricity demand for Victoria, Australia                   │
│                                                                          │
│ Source:                                                                  │
│ O'Hara-Wild M, Hyndman R, Wang E, Godahewa R (2022).tsibbledata: Diverse │
│ Datasets for 'tsibble'. https://tsibbledata.tidyverts.org/,              │
│ https://github.com/tidyverts/tsibbledata/.                               │
│ https://tsibbledata.tidyverts.org/reference/vic_elec.html                │
│                                                                          │
│ URL:                                                                     │
│ https://raw.githubusercontent.com/skforecast/skforecast-                 │
│ datasets/main/data/vic_electricity.csv                                   │
│                                                                          │
│ Shape: 52608 rows x 4 columns                                            │
╰──────────────────────────────────────────────────────────────────────────╯
Demand Temperature Holiday
Time
2011-12-31 14:00:00 4323.095350 21.225 1.0
2011-12-31 15:00:00 3963.264688 20.625 1.0
2011-12-31 16:00:00 3950.913495 20.325 1.0
# División de datos en entrenamiento-test
# ==============================================================================
datos = datos.loc['2012-01-01 00:00:00':'2014-12-30 23:00:00', :].copy()
fin_entrenamiento = '2014-11-30 23:59:00'
datos_entrenamiento = datos.loc[: fin_entrenamiento, :].copy()
datos_test  = datos.loc[fin_entrenamiento:, :].copy()

print(f"Fechas de entrenamiento: {datos_entrenamiento.index.min()} --- {datos_entrenamiento.index.max()}  (n={len(datos_entrenamiento)})")
print(f"Fechas de test         : {datos_test.index.min()} --- {datos_test.index.max()}  (n={len(datos_test)})")
Fechas de entrenamiento: 2012-01-01 00:00:00 --- 2014-11-30 23:00:00  (n=25560)
Fechas de test         : 2014-12-01 00:00:00 --- 2014-12-30 23:00:00  (n=720)

Forecasting de una única serie

Se crea un ForecasterFoundation utilizando el modelo Chronos-2-small de Amazon.

# Crear ForecasterFoundation
# ==============================================================================
estimador = FoundationModel(model_id="autogluon/chronos-2-small", context_length=500)
pronosticador = ForecasterFoundation(estimator=estimador)

Cada adaptador acepta argumentos adicionales que controlan el comportamiento específico del modelo (por ejemplo, context_length, device_map, torch_dtype). Estos pueden pasarse directamente a través del constructor de FoundationModel.

Para la lista completa de parámetros disponibles, consulta la referencia de la API: ChronosAdapter, TimesFMAdapter, MoiraiAdapter, TabICLAdapter.

💡 Consejo

Aunque aquí se utiliza .fit() para almacenar el contexto histórico y los metadatos, no es estrictamente necesario. Los modelos fundacionales pueden generar pronósticos pasando el contexto directamente a .predict() mediante el parámetro context. Sin embargo, llamar a .fit() primero simplifica las llamadas posteriores a .predict(), .predict_interval() y .predict_quantiles().

# Entrenar ForecasterFoundation
# ==============================================================================
pronosticador.fit(
    series = datos_entrenamiento["Demand"], 
    exog   = datos_entrenamiento[["Temperature", "Holiday"]]
)
pronosticador

ForecasterFoundation

General Information
  • Model ID: autogluon/chronos-2-small
  • Context length: 500
  • Window size: 500
  • Series names: Demand
  • Exogenous included: True
  • Creation date: 2026-07-28 12:59:21
  • Last fit date: 2026-07-28 12:59:21
  • Skforecast version: 0.24.0
  • Python version: 3.13.14
  • Forecaster id: None
Exogenous Variables

Temperature, Holiday

Training Information
  • Context range: 'Demand': ['2012-01-01 00:00:00', '2014-11-30 23:00:00']
  • Training index type: DatetimeIndex
  • Training index frequency: h
Model Parameters
  • cross_learning: False
  • context_length: 500
  • device_map: auto
  • torch_dtype: None
  • predict_kwargs: None

📖 API Reference    📝 User Guide

Se pueden usar tres métodos para predecir los próximos $n$ steps: predict(), predict_interval() y predict_quantiles(). Todos estos métodos permiten pasar context y context_exog para reemplazar el contexto histórico utilizado por el modelo subyacente al generar predicciones.

# Predicciones: pronóstico puntual
# ==============================================================================
steps = 24
predicciones = pronosticador.predict(
                  steps = steps,
                  exog  = datos_test[["Temperature", "Holiday"]]
              )
predicciones.head(3)
Loading weights:   0%|          | 0/92 [00:00<?, ?it/s]
level pred
2014-12-01 00:00:00 Demand 5527.678711
2014-12-01 01:00:00 Demand 5511.500977
2014-12-01 02:00:00 Demand 5457.791992
# Predicciones: intervalos
# ==============================================================================
predicciones_intervalos = pronosticador.predict_interval(
                            steps    = steps,
                            exog     = datos_test[["Temperature", "Holiday"]],
                            interval = [0.1, 0.9],  # intervalo de predicción al 80%
                        )

predicciones_intervalos.head(3)
level pred lower_bound upper_bound
2014-12-01 00:00:00 Demand 5527.678711 5372.815918 5689.793457
2014-12-01 01:00:00 Demand 5511.500977 5318.045410 5733.492188
2014-12-01 02:00:00 Demand 5457.791992 5241.040527 5717.424805
# Backtesting
# ==============================================================================
cv = TimeSeriesFold(
         steps              = 24,
         initial_train_size = len(datos.loc[:fin_entrenamiento]),
         refit              = False
     )

inicio = time.perf_counter()
metricas_chronos, predicciones_backtest = backtesting_foundation(
    forecaster        = pronosticador,
    series            = datos['Demand'],
    exog              = datos[["Temperature", "Holiday"]],
    cv                = cv,
    metric            = 'mean_absolute_error',
    suppress_warnings = True
)
tiempo_transcurrido_chronos = time.perf_counter() - inicio
print(f"Backtesting completado en {tiempo_transcurrido_chronos:.4f} segundos.")
print("Métricas de backtesting")
display(metricas_chronos)
print("")
print("Predicciones del backtesting")
predicciones_backtest.head(4)
  0%|          | 0/30 [00:00<?, ?it/s]
Backtesting completado en 0.7737 segundos.
Métricas de backtesting
mean_absolute_error
0 171.266953
Predicciones del backtesting
level fold pred
2014-12-01 00:00:00 Demand 0 5527.678711
2014-12-01 01:00:00 Demand 0 5511.500977
2014-12-01 02:00:00 Demand 0 5457.791992
2014-12-01 03:00:00 Demand 0 5402.819336
# Graficar predicciones
# ==============================================================================
set_dark_theme()
fig, ax = plt.subplots(figsize=(7, 3))
datos_test['Demand'].plot(ax=ax, label='test')
predicciones_backtest['pred'].plot(ax=ax, label='predicciones')
ax.legend();

Múltiples series (modelo global)

La clase ForecasterFoundation permite modelar y pronosticar múltiples series con un único modelo.

# Datos
# ==============================================================================
datos_multiseries = fetch_dataset(name="items_sales")
display(datos_multiseries.head(3))
╭─────────────────────── items_sales ───────────────────────╮
│ Description:                                              │
│ Simulated time series for the sales of 3 different items. │
│                                                           │
│ Source:                                                   │
│ Simulated data.                                           │
│                                                           │
│ URL:                                                      │
│ https://raw.githubusercontent.com/skforecast/skforecast-  │
│ datasets/main/data/simulated_items_sales.csv              │
│                                                           │
│ Shape: 1097 rows x 3 columns                              │
╰───────────────────────────────────────────────────────────╯
item_1 item_2 item_3
date
2012-01-01 8.253175 21.047727 19.429739
2012-01-02 22.777826 26.578125 28.009863
2012-01-03 27.549099 31.751042 32.078922
# División de datos en entrenamiento-test
# ==============================================================================
fin_entrenamiento = '2014-07-15 23:59:00'
datos_multiseries_entrenamiento = datos_multiseries.loc[:fin_entrenamiento, :]
datos_multiseries_test  = datos_multiseries.loc[fin_entrenamiento:, :]
# Graficar series temporales
# ==============================================================================
set_dark_theme()
fig, ejes = plt.subplots(nrows=3, ncols=1, figsize=(7, 5), sharex=True)

for i, columna in enumerate(datos_multiseries.columns):
    datos_multiseries_entrenamiento[columna].plot(ax=ejes[i], label='entrenamiento')
    datos_multiseries_test[columna].plot(ax=ejes[i], label='test')
    ejes[i].set_title(columna)
    ejes[i].set_ylabel('ventas')
    ejes[i].set_xlabel('')
    ejes[i].legend(loc='upper left')

fig.tight_layout()
plt.show();

En este ejemplo, en lugar de llamar a fit(), el contexto se pasa directamente al método predict().

# Crear y entrenar ForecasterFoundation
# ==============================================================================
estimador = FoundationModel(model_id = "autogluon/chronos-2-small", context_length=500)
pronosticador = ForecasterFoundation(estimator = estimador)

# fit() es opcional; el contexto se pasa directamente a predict()
# pronosticador.fit(series=datos_multiseries_entrenamiento)
# Predicciones para todas las series (niveles)
# ==============================================================================
steps = len(datos_multiseries_test)
predicciones_articulos = pronosticador.predict(
                        steps   = steps, 
                        levels  = None,  # Se predicen todos los niveles
                        context = datos_multiseries_entrenamiento
                    )
predicciones_articulos.head()
╭────────────────────────────────── InputTypeWarning ──────────────────────────────────╮
 Passing a DataFrame (either wide or long format) as `series` requires additional     
 internal transformations, which can increase computational time. It is recommended   
 to use a dictionary of pandas Series instead. For more details, see:                 
 https://skforecast.org/latest/user_guides/independent-multi-time-series-forecasting. 
 html#input-data                                                                      
                                                                                      
 Category : skforecast.exceptions.InputTypeWarning                                    
 Location :                                                                           
 c:\Users\Joaquin\miniconda3\envs\skforecast_24_py13\Lib\site-packages\skforecast\uti 
 ls\utils.py:3388                                                                     
 Suppress : warnings.simplefilter('ignore', category=InputTypeWarning)                
╰──────────────────────────────────────────────────────────────────────────────────────╯
Loading weights:   0%|          | 0/92 [00:00<?, ?it/s]
level pred
2014-07-16 item_1 25.523064
2014-07-16 item_2 10.456666
2014-07-16 item_3 11.862236
2014-07-17 item_1 25.296782
2014-07-17 item_2 10.701235
# Graficar predicciones
# ==============================================================================
set_dark_theme()
fig, ejes = plt.subplots(nrows=3, ncols=1, figsize=(7, 5), sharex=True)

for i, columna in enumerate(datos_multiseries.columns):
    
    datos_multiseries_entrenamiento[columna].plot(ax=ejes[i], label='entrenamiento')
    datos_multiseries_test[columna].plot(ax=ejes[i], label='test')
    predicciones_articulos.query(f"level == '{columna}'").plot(
        ax=ejes[i], label='predicciones', color='white'
    )

    ejes[i].set_title(columna)
    ejes[i].set_ylabel('ventas')
    ejes[i].set_xlabel('')
    ejes[i].legend(loc='upper left')

fig.tight_layout()
plt.show();
# Predicciones de intervalo para item_1 e item_2
# ==============================================================================
predicciones_intervalos = pronosticador.predict_interval(
                            steps    = 24,
                            levels   = ['item_1', 'item_2'],
                            context  = datos_multiseries_entrenamiento,
                            interval = [0.1, 0.9],  # intervalo de predicción al 80%
                        )

predicciones_intervalos.head()
╭────────────────────────────────── InputTypeWarning ──────────────────────────────────╮
 Passing a DataFrame (either wide or long format) as `series` requires additional     
 internal transformations, which can increase computational time. It is recommended   
 to use a dictionary of pandas Series instead. For more details, see:                 
 https://skforecast.org/latest/user_guides/independent-multi-time-series-forecasting. 
 html#input-data                                                                      
                                                                                      
 Category : skforecast.exceptions.InputTypeWarning                                    
 Location :                                                                           
 c:\Users\Joaquin\miniconda3\envs\skforecast_24_py13\Lib\site-packages\skforecast\uti 
 ls\utils.py:3388                                                                     
 Suppress : warnings.simplefilter('ignore', category=InputTypeWarning)                
╰──────────────────────────────────────────────────────────────────────────────────────╯
level pred lower_bound upper_bound
2014-07-16 item_1 25.464174 24.582005 26.430853
2014-07-16 item_2 10.649370 8.679634 13.302807
2014-07-17 item_1 25.270247 24.255964 26.327969
2014-07-17 item_2 10.834244 8.717453 13.826941
2014-07-18 item_1 25.175861 24.079086 26.286255

Impacto de la longitud de contexto

Dado que los modelos fundacionales de pronóstico son altamente generalizados, carecen de conocimiento intrínseco de tu conjunto de datos específico. Para compensar, dependen de una "ventana de contexto", un período específico de datos históricos recientes, para adaptarse a tu escenario único en tiempo real. Este contexto actúa como la memoria a corto plazo del modelo, permitiéndole calcular la trayectoria actual de tus datos e identificar si la serie tiene tendencia al alza, está acelerando o aplanándose.

La longitud de esta ventana de contexto es absolutamente crítica para capturar la estacionalidad y los eventos recurrentes. Para predecir con precisión un patrón, como un pico semanal en ventas o un ciclo anual, el modelo debe observar efectivamente ese patrón dentro del historial proporcionado. Por ejemplo, si tus datos tienen una estacionalidad de 365 días, proporcionar 400 días de contexto permite al modelo reconocer y proyectar el ciclo, mientras que una ventana de 30 días haría que el modelo perdiera el patrón por completo, resultando en un pronóstico plano o inexacto.

Sin embargo, aumentar la longitud del contexto para mejorar la precisión introduce un compromiso computacional significativo. Dado que la mayoría de los modelos fundacionales están construidos sobre arquitecturas Transformer, la complejidad computacional de su mecanismo de atención escala cuadráticamente ($O(N^2)$) con la longitud de la secuencia de entrada. En consecuencia, duplicar la ventana de contexto puede cuadruplicar la memoria y el procesamiento requeridos. Este crecimiento cuadrático significa que llevar las longitudes de contexto a su máximo teórico a menudo produce ganancias de precisión marginales con costos computacionales rápidamente crecientes.

En definitiva, utilizar eficazmente los modelos fundacionales requiere evaluar cuidadosamente este compromiso. La mejor práctica es analizar el tamaño del contexto y seleccionar la ventana más corta posible que aún logre un alto rendimiento predictivo. Encontrar este equilibrio garantiza pronósticos precisos y conscientes de patrones, evitando el desperdicio innecesario de recursos computacionales y ancho de banda de transferencia de datos.

El siguiente código muestra cómo evaluar el impacto de la longitud del contexto en la precisión predictiva y el tiempo de inferencia de un modelo base. El ejemplo utiliza el modelo Amazon Chronos-2-small, pero el mismo enfoque puede aplicarse a cualquier otro modelo base compatible con skforecast.

# Influencia de la longitud del contexto en la precisión y la velocidad
# ==============================================================================
model_id = 'autogluon/chronos-2-small'
context_lengths = [100, 500, 1000, 5000]

model_ids_allow_exog = {
    'autogluon/chronos-2-small', 'soda-inria/tabicl',
    'priorlabs/tabpfn-ts', 'theforecastingcompany/t0-alpha',
    'Synthefy/Nori', 'taharnbl/TS-ICL'
}
if model_id in model_ids_allow_exog:
    exog = datos[["Temperature", "Holiday"]]
else:
    exog = None

cv = TimeSeriesFold(
         steps              = 24,
         initial_train_size = len(datos.loc[:fin_entrenamiento]),
         refit              = False
     )

results_metrics = []
results_elapsed_time = []
for context_length in context_lengths:

    estimator = FoundationModel(model_id=model_id, context_length=context_length)
    forecaster = ForecasterFoundation(estimator=estimator)

    start = time.perf_counter()
    metrics, predicciones_backtest = backtesting_foundation(
        forecaster        = forecaster,
        series            = datos['Demand'],
        exog              = exog,
        cv                = cv,
        metric            = 'mean_absolute_error',
        suppress_warnings = True,
        show_progress     = False
    )
    elapsed_time = time.perf_counter() - start

    results_metrics.append(metrics.at[0, 'mean_absolute_error'])
    results_elapsed_time.append(elapsed_time)

results = pd.DataFrame(
    {
        'metric': results_metrics,
        'elapsed_time': results_elapsed_time
    },
    index=context_lengths
)
results
Loading weights:   0%|          | 0/92 [00:00<?, ?it/s]
Loading weights:   0%|          | 0/92 [00:00<?, ?it/s]
Loading weights:   0%|          | 0/92 [00:00<?, ?it/s]
Loading weights:   0%|          | 0/92 [00:00<?, ?it/s]
metric elapsed_time
100 213.935341 4.489171
500 132.459974 4.577228
1000 127.277556 4.782205
5000 123.635550 4.924833
# Plot
# ==============================================================================
set_dark_theme()
fig, ax = plt.subplots(figsize=(6, 2.5))
results['metric'].plot(ax = ax, marker = 'o')
ax.set_title("Prediction error vs context length")
ax.set_xlabel("context length")
ax.set_ylabel("mean absolute error")

fig, ax = plt.subplots(figsize=(6, 2.5))
results['elapsed_time'].plot(ax = ax, marker = 'o')
ax.set_title("Elapsed time vs context length")
ax.set_xlabel("context length")
ax.set_ylabel("Elapsed time");

El ejemplo anterior era ilustrativo y mostraba cómo la longitud del contexto puede afectar a los resultados, pero no es la mejor estrategia de búsqueda. Skforecast permite optimizar la longitud del contexto junto con otros parámetros de FoundationModel mediante la búsqueda bayesiana.

# Dividir los datos en conjuntos de entrenamiento, validación y prueba
# ==============================================================================
datos = datos.loc['2012-01-01 00:00:00':'2014-12-30 23:00:00', :].copy()
end_train = '2014-10-30 23:59:00'
end_validation = '2014-11-30 23:59:00'

datos_train = datos.loc[: end_train, :].copy()
datos_val = datos.loc[end_train : end_validation, :].copy()
datos_test  = datos.loc[end_validation:, :].copy()

print(f"Train dates: {datos_train.index.min()} --- {datos_train.index.max()}  (n={len(datos_train)})")
print(f"Train val  : {datos_val.index.min()} --- {datos_val.index.max()}  (n={len(datos_val)})")
print(f"Test dates : {datos_test.index.min()} --- {datos_test.index.max()}  (n={len(datos_test)})")
Train dates: 2012-01-01 00:00:00 --- 2014-10-30 23:00:00  (n=24816)
Train val  : 2014-10-31 00:00:00 --- 2014-11-30 23:00:00  (n=744)
Test dates : 2014-12-01 00:00:00 --- 2014-12-30 23:00:00  (n=720)
# Bayesian search
# ==============================================================================
cv = TimeSeriesFold(
         steps              = 24,
         initial_train_size = len(datos.loc[:end_train]),
         refit              = False
     )

estimator = FoundationModel(model_id="autogluon/chronos-2-small", context_length=150)
forecaster = ForecasterFoundation(estimator=estimator)

# Search space
def search_space(trial):
    search_space  = {
        'context_length': trial.suggest_int('context_length', 100, 5000, step=100),
    }
    
    return search_space

results, study = bayesian_search_foundation(
                     forecaster   = forecaster,
                     series       = datos.loc[:end_validation, 'Demand'],
                     exog         = datos.loc[:end_validation, ['Temperature', 'Holiday']],
                     search_space = search_space,
                     cv           = cv,
                     metric       = 'mean_absolute_error',
                     n_trials     = 20
                 )
results
  0%|          | 0/20 [00:00<?, ?it/s]
Loading weights:   0%|          | 0/92 [00:00<?, ?it/s]
trial_number levels params mean_absolute_error context_length
0 4 [Demand] {'context_length': 3600} 139.470447 3600
1 6 [Demand] {'context_length': 5000} 139.935649 5000
2 12 [Demand] {'context_length': 5000} 139.935649 5000
3 11 [Demand] {'context_length': 5000} 139.935649 5000
4 18 [Demand] {'context_length': 5000} 139.935649 5000
5 13 [Demand] {'context_length': 4000} 140.103792 4000
6 14 [Demand] {'context_length': 4100} 140.149167 4100
7 10 [Demand] {'context_length': 4300} 140.168050 4300
8 16 [Demand] {'context_length': 4900} 140.202668 4900
9 2 [Demand] {'context_length': 1200} 142.167335 1200
10 5 [Demand] {'context_length': 2200} 142.248174 2200
11 1 [Demand] {'context_length': 1500} 142.699084 1500
12 9 [Demand] {'context_length': 2000} 143.176399 2000
13 3 [Demand] {'context_length': 2800} 143.253429 2800
14 7 [Demand] {'context_length': 3500} 143.339500 3500
15 0 [Demand] {'context_length': 3500} 143.339500 3500
16 17 [Demand] {'context_length': 3500} 143.339500 3500
17 19 [Demand] {'context_length': 3500} 143.339500 3500
18 8 [Demand] {'context_length': 2500} 143.521964 2500
19 15 [Demand] {'context_length': 100} 252.934367 100

Otros modelos fundacionales

Los ejemplos anteriores utilizan el modelo Amazon Chronos, pero la misma estructura de código se aplica a cualquier otro modelo fundacional compatible con skforecast. Las siguientes subsecciones demuestran que el pipeline es idéntico independientemente del modelo subyacente; solo cambia el model_id. Para utilizar un modelo diferente, simplemente pásalo al instanciar el wrapper FoundationModel.

TimesFM 2.5

Se crea un ForecasterFoundation utilizando el modelo TimesFM-2.5-200m de Google.

# Crear ForecasterFoundation
# ==============================================================================
estimador = FoundationModel(model_id="google/timesfm-2.5-200m-pytorch", context_length=500)
pronosticador = ForecasterFoundation(estimator = estimador)
# Entrenar ForecasterFoundation
# ==============================================================================
pronosticador.fit(series=datos_entrenamiento["Demand"])
# Predicciones: pronóstico puntual
# ==============================================================================
steps = 24
predicciones = pronosticador.predict(steps=steps)
predicciones.head(3)
level pred
2014-12-01 00:00:00 Demand 5658.415039
2014-12-01 01:00:00 Demand 5671.861816
2014-12-01 02:00:00 Demand 5747.938477
# Predicciones: intervalos
# ==============================================================================
predicciones_intervalos = pronosticador.predict_interval(
    steps    = steps,
    interval = [0.1, 0.9],  # intervalo de predicción al 80%
)
predicciones_intervalos.head(3)
level pred lower_bound upper_bound
2014-12-01 00:00:00 Demand 5658.415039 5541.004883 5790.344238
2014-12-01 01:00:00 Demand 5671.861816 5470.189453 5899.113281
2014-12-01 02:00:00 Demand 5747.938477 5450.534180 6064.879883
# Backtesting
# ==============================================================================
cv = TimeSeriesFold(
         steps              = 24,
         initial_train_size = len(datos.loc[:fin_entrenamiento]),
         refit              = False
     )

inicio = time.perf_counter() 
metricas_timesfm, predicciones_backtest = backtesting_foundation(
    forecaster        = pronosticador,
    series            = datos['Demand'],
    cv                = cv,
    metric            = 'mean_absolute_error',
    suppress_warnings = True
)
tiempo_transcurrido_timesfm = time.perf_counter() - inicio
print(f"Backtesting completado en {tiempo_transcurrido_timesfm:.4f} segundos.")
print("Métricas de backtesting")
display(metricas_timesfm)
print("")
print("Predicciones del backtesting")
predicciones_backtest.head(4)
  0%|          | 0/168 [00:00<?, ?it/s]
Backtesting completado en 24.8245 segundos.
Métricas de backtesting
mean_absolute_error
0 160.357018
Predicciones del backtesting
level fold pred
2014-07-16 00:00:00 Demand 0 6189.843750
2014-07-16 01:00:00 Demand 0 5988.112793
2014-07-16 02:00:00 Demand 0 5830.692383
2014-07-16 03:00:00 Demand 0 5696.288086

Moirai

Se crea un ForecasterFoundation utilizando el modelo Moirai-2.0-R-small de Salesforce.

# Crear ForecasterFoundation
# ==============================================================================
estimador = FoundationModel(model_id="Salesforce/moirai-2.0-R-small", context_length=500)
pronosticador = ForecasterFoundation(estimator=estimador)
# Entrenar ForecasterFoundation
# ==============================================================================
pronosticador.fit(series=datos_entrenamiento["Demand"])
# Predicciones: pronóstico puntual
# ==============================================================================
steps = 24
predicciones = pronosticador.predict(steps=steps)
predicciones.head(3)
# Predicciones: intervalos
# ==============================================================================
predicciones_intervalos = pronosticador.predict_interval(
    steps    = steps,
    interval = [0.1, 0.9],  # intervalo de predicción al 80%
)
predicciones_intervalos.head(3)
level pred lower_bound upper_bound
2014-12-01 00:00:00 Demand 5731.725098 5517.737793 5940.646484
2014-12-01 01:00:00 Demand 5870.827148 5548.743164 6176.801270
2014-12-01 02:00:00 Demand 5959.207031 5599.376953 6323.206055
# Backtesting
# ==============================================================================
cv = TimeSeriesFold(
         steps              = 24,
         initial_train_size = len(datos.loc[:fin_entrenamiento]),
         refit              = False
     )
inicio = time.perf_counter()
metricas_moirai, predicciones_backtest = backtesting_foundation(
    forecaster        = pronosticador,
    series            = datos['Demand'],
    cv                = cv,
    metric            = 'mean_absolute_error',
    suppress_warnings = True
)
tiempo_transcurrido_moirai = time.perf_counter() - inicio
print(f"Backtesting completado en {tiempo_transcurrido_moirai:.4f} segundos.")
print("Métricas de backtesting")
display(metricas_moirai)
print("")
print("Predicciones del backtesting")
predicciones_backtest.head(4)
  0%|          | 0/168 [00:00<?, ?it/s]
Backtesting completed in 9.3907 seconds.
Backtest metrics
mean_absolute_error
0 161.691106
Backtest predictions
level fold pred
2014-07-16 00:00:00 Demand 0 6222.097656
2014-07-16 01:00:00 Demand 0 6114.366699
2014-07-16 02:00:00 Demand 0 5969.839844
2014-07-16 03:00:00 Demand 0 5920.479492

TabICL

Se crea un ForecasterFoundation utilizando el modelo TabICL de Soda-Inria.

# Crear ForecasterFoundation
# ==============================================================================
estimador = FoundationModel(model_id="soda-inria/tabicl", context_length=500)
pronosticador = ForecasterFoundation(estimator=estimador)
# Entrenar ForecasterFoundation
# ==============================================================================
pronosticador.fit(
    series = datos_entrenamiento["Demand"], 
    exog   = datos_entrenamiento[["Temperature", "Holiday"]]
)
# Predicciones: pronóstico puntual
# ==============================================================================
steps = 24
predicciones = pronosticador.predict(
                  steps = steps,
                  exog  = datos_test[["Temperature", "Holiday"]]
              )

predicciones.head(3)
level pred
2014-12-01 00:00:00 Demand 5590.919922
2014-12-01 01:00:00 Demand 5662.485840
2014-12-01 02:00:00 Demand 5661.672852
# Predicciones: intervalos
# ==============================================================================
predicciones_intervalos = pronosticador.predict_interval(
                            steps    = steps,
                            exog     = datos_test[["Temperature", "Holiday"]],
                            interval = [0.1, 0.9],  # intervalo de predicción al 80%
                        )

predicciones_intervalos.head(3)
level pred lower_bound upper_bound
2014-12-01 00:00:00 Demand 5617.177246 5159.889160 5952.954590
2014-12-01 01:00:00 Demand 5678.132812 5215.843262 6059.710938
2014-12-01 02:00:00 Demand 5677.454590 5131.910156 6149.853027
# Backtesting
# ==============================================================================
cv = TimeSeriesFold(
         steps              = 24,
         initial_train_size = len(datos.loc[:fin_entrenamiento]),
         refit              = False
     )
inicio = time.perf_counter()
metricas_tabicl, predicciones_backtest = backtesting_foundation(
    forecaster        = pronosticador,
    series            = datos['Demand'],
    exog              = datos[["Temperature", "Holiday"]],
    cv                = cv,
    metric            = 'mean_absolute_error',
    suppress_warnings = True
)
tiempo_transcurrido_tabicl = time.perf_counter() - inicio
print(f"Backtesting completado en {tiempo_transcurrido_tabicl:.4f} segundos.")
print("Métricas de backtesting")
display(metricas_tabicl)
print("")
print("Predicciones del backtesting")
predicciones_backtest.head(4)
  0%|          | 0/168 [00:00<?, ?it/s]
Backtesting completado en 157.4076 segundos.
Métricas de backtesting
mean_absolute_error
0 170.101609
Predicciones del backtesting
level fold pred
2014-07-16 00:00:00 Demand 0 6371.091797
2014-07-16 01:00:00 Demand 0 6311.226562
2014-07-16 02:00:00 Demand 0 6289.089355
2014-07-16 03:00:00 Demand 0 6268.155762

TabPFN-TS

Un ForecasterFoundation se crea utilizando el modelo TabPFN-TS de Prior Labs. TabPFN-TS requiere una clave API gratuita de Prior Labs para ejecutar la inferencia. Puedes obtenerla registrándote en https://priorlabs.ai. Por defecto, la inferencia se ejecuta de forma local (mode='local', CUDA > MPS > CPU); pasa mode='client' para utilizar en su lugar la API en la nube de Prior Labs (no requiere GPU, necesita una clave API).

# Crear ForecasterFoundation
# ==============================================================================
estimator = FoundationModel(model_id="priorlabs/tabpfn-ts", context_length=500)
forecaster = ForecasterFoundation(estimator=estimator)
# Entrenar ForecasterFoundation
# ==============================================================================
forecaster.fit(
    series = datos_entrenamiento["Demand"], 
    exog   = datos_entrenamiento[["Temperature", "Holiday"]]
)
# Predicciones: pronóstico puntual
# ==============================================================================
steps = 24
predictions = forecaster.predict(
                  steps = steps,
                  exog  = datos_test[["Temperature", "Holiday"]]
              )

predictions.head(3)
level pred
2014-12-01 00:00:00 Demand 5530.493652
2014-12-01 01:00:00 Demand 5536.577148
2014-12-01 02:00:00 Demand 5492.600586
# Predicciones: intervalos
# ==============================================================================
predictions_intervals = forecaster.predict_interval(
                            steps    = steps,
                            exog     = datos_test[["Temperature", "Holiday"]],
                            interval = [0.1, 0.9],  # 80% prediction interval
                        )

predictions_intervals.head(3)
level pred lower_bound upper_bound
2014-12-01 00:00:00 Demand 5530.493652 5016.985840 5742.127441
2014-12-01 01:00:00 Demand 5536.577148 5077.645020 5806.521973
2014-12-01 02:00:00 Demand 5492.600586 4959.099121 5818.618164
# Backtesting
# ==============================================================================
cv = TimeSeriesFold(
         steps              = 24,
         initial_train_size = len(datos.loc[:fin_entrenamiento]),
         refit              = False
     )
start = time.perf_counter()
metricas_tabpfn, predicciones_backtest = backtesting_foundation(
    forecaster        = forecaster,
    series            = datos['Demand'],
    exog              = datos[["Temperature", "Holiday"]],
    cv                = cv,
    metric            = 'mean_absolute_error',
    suppress_warnings = True
)
tiempo_transcurrido_tabpfn = time.perf_counter() - inicio
print(f"Backtesting completado en {tiempo_transcurrido_tabpfn:.4f} segundos.")
print("Métricas de backtesting")
display(metricas_tabpfn)
print("")
print("Predicciones del backtesting")
predicciones_backtest.head(4)
  0%|          | 0/168 [00:00<?, ?it/s]
Backtesting completado en 408.7843 segundos.
Métricas de backtesting
mean_absolute_error
0 159.175397
Predicciones del backtesting
level fold pred
2014-07-16 00:00:00 Demand 0 6208.879395
2014-07-16 01:00:00 Demand 0 6112.437500
2014-07-16 02:00:00 Demand 0 6066.314453
2014-07-16 03:00:00 Demand 0 6032.233398

The Forecasting Company T0

Warning

theforecastingcompany/t0* los puntos de control están restringidos en Hugging Face Hub. Antes de ejecutar las celdas que aparecen a continuación, inicia sesión en la página del modelo (por ejemplo, huggingface.co/theforecastingcompany/t0-alpha) para aceptar su licencia y, a continuación, autentícate localmente, por ejemplo, con hf auth login o configurando la variable de entorno HF_TOKEN.
# Crear ForecasterFoundation
# ==============================================================================
estimator = FoundationModel(model_id="theforecastingcompany/t0-alpha", context_length=500)
forecaster = ForecasterFoundation(estimator=estimator)
# Backtesting
# ==============================================================================
cv = TimeSeriesFold(
         steps              = 24,
         initial_train_size = len(datos.loc[:end_train]),
         refit              = False
     )
start = time.perf_counter()
metricas_t0, predicciones_backtest = backtesting_foundation(
    forecaster        = forecaster,
    series            = datos['Demand'],
    exog              = datos[["Temperature", "Holiday"]],
    cv                = cv,
    metric            = 'mean_absolute_error',
    suppress_warnings = True
)

tiempo_transcurrido_t0 = time.perf_counter() - start
print(f"Backtesting completado en {tiempo_transcurrido_t0:.4f} seconds.")
print("Métricas de backtesting")
display(metricas_t0)
print("")
print("Predicciones del backtesting")
predicciones_backtest.head(4)
  0%|          | 0/61 [00:00<?, ?it/s]
Backtesting completado en 5.2062 seconds.
Métricas de backtesting
mean_absolute_error
0 144.131344
Predicciones del backtesting
level fold pred
2014-10-31 00:00:00 Demand 0 4904.634766
2014-10-31 01:00:00 Demand 0 4869.083496
2014-10-31 02:00:00 Demand 0 4887.306641
2014-10-31 03:00:00 Demand 0 4923.373047

Synthefy Nori

# Crear ForecasterFoundation
# ==============================================================================
estimator = FoundationModel(model_id="Synthefy/Nori", context_length=500)
forecaster = ForecasterFoundation(estimator=estimator)
# Backtesting
# ==============================================================================
cv = TimeSeriesFold(
         steps              = 24,
         initial_train_size = len(datos.loc[:end_train]),
         refit              = False
     )
start = time.perf_counter()
metricas_nori, predicciones_backtest = backtesting_foundation(
    forecaster        = forecaster,
    series            = datos['Demand'],
    exog              = datos[["Temperature", "Holiday"]],
    cv                = cv,
    metric            = 'mean_absolute_error',
    suppress_warnings = True
)
tiempo_transcurrido_nori = time.perf_counter() - start
print(f"Backtesting completado en {tiempo_transcurrido_nori:.4f} seconds.")
print("Métricas de backtesting")
display(metricas_nori)
print("")
print("Predicciones del backtesting")
predicciones_backtest.head(4)
  0%|          | 0/61 [00:00<?, ?it/s]
Backtesting completado en 71.3532 seconds.
Métricas de backtesting
mean_absolute_error
0 193.843538
Predicciones del backtesting
level fold pred
2014-10-31 00:00:00 Demand 0 4867.585570
2014-10-31 01:00:00 Demand 0 5005.078139
2014-10-31 02:00:00 Demand 0 5091.125827
2014-10-31 03:00:00 Demand 0 5124.809975

EDF-Lab TS-ICL

# Create ForecasterFoundation
# ==============================================================================
estimator = FoundationModel(model_id="taharnbl/TS-ICL", context_length=500)
forecaster = ForecasterFoundation(estimator=estimator)
# Backtesting
# ==============================================================================
cv = TimeSeriesFold(
         steps              = 24,
         initial_train_size = len(datos.loc[:end_train]),
         refit              = False
     )
start = time.perf_counter()
metricas_tsicl, predicciones_backtest = backtesting_foundation(
    forecaster        = forecaster,
    series            = datos['Demand'],
    exog              = datos[["Temperature", "Holiday"]],
    cv                = cv,
    metric            = 'mean_absolute_error',
    suppress_warnings = True
)
tiempo_transcurrido_tsicl = time.perf_counter() - start
print(f"Backtesting completado en {tiempo_transcurrido_tsicl:.4f} seconds.")
print("Métricas de backtesting")
display(metricas_tsicl)
print("")
print("Predicciones del backtesting")
predicciones_backtest.head(4)
  0%|          | 0/61 [00:00<?, ?it/s]
Backtesting completado en 3.6210 seconds.
Métricas de backtesting
mean_absolute_error
0 139.935565
Predicciones del backtesting
level fold pred
2014-10-31 00:00:00 Demand 0 4934.014648
2014-10-31 01:00:00 Demand 0 4937.680176
2014-10-31 02:00:00 Demand 0 4976.268555
2014-10-31 03:00:00 Demand 0 5069.974121

Comparación de modelos

La siguiente tabla resume los resultados del backtesting (Error Absoluto Medio) para los cuatro modelos fundacionales sobre el mismo conjunto de datos.

# Comparación de métricas de backtesting
# ==============================================================================
comparison = pd.DataFrame({
    "Model": [
        "Chronos-2 (small)*",
        "TimesFM-2.5 (200m)",
        "Moirai-2.0-R (small)",
        "TabICLv2*",
        "TabPFN-TS*",
        "T0",
        "Nori",
        "TS-ICL"
    ],
    "mean_absolute_error": [
        metricas_chronos["mean_absolute_error"].iloc[0],
        metricas_timesfm["mean_absolute_error"].iloc[0],
        metricas_moirai["mean_absolute_error"],
        metricas_tabicl["mean_absolute_error"].iloc[0],
        metricas_tabpfn["mean_absolute_error"].iloc[0],
        metricas_t0["mean_absolute_error"].iloc[0],
        metricas_nori["mean_absolute_error"].iloc[0],
        metricas_tsicl["mean_absolute_error"].iloc[0],
    ],
    "Elapsed time": [
        tiempo_transcurrido_chronos,
        tiempo_transcurrido_timesfm,
        tiempo_transcurrido_moirai,
        tiempo_transcurrido_tabicl,
        tiempo_transcurrido_tabpfn,
        tiempo_transcurrido_t0,
        tiempo_transcurrido_nori,
        tiempo_transcurrido_tsicl
        

    ]
}).sort_values(by="mean_absolute_error")

display(
    comparison.style.highlight_min(
        subset="mean_absolute_error", color="green"
    ).format(precision=4)
)
print("* Chronos-2 (small), TabICL, TabPFN-TS, TFC T0, Nori and TS-ICL permiten variables exógenas.")
  Model mean_absolute_error Elapsed time
7 TS-ICL 139.9356 3.6210
5 T0 144.1313 5.2062
4 TabPFN-TS* 159.1754 408.7843
1 TimesFM-2.5 (200m) 160.3570 24.8245
2 Moirai-2.0-R (small) 161.6911 9.3907
3 TabICLv2* 170.1016 157.4076
0 Chronos-2 (small)* 171.2670 0.7737
6 Nori 193.8435 71.3532
* Chronos-2 (small), TabICL, TabPFN-TS, TFC T0, Nori and TS-ICL permiten variables exógenas.

⚠️ Advertencia

Este ejemplo utiliza un conjunto de datos público ampliamente disponible con fines ilustrativos. Es muy probable que los modelos fundacionales (Chronos, TimesFM, Moirai...) hayan sido expuestos a estos datos durante su fase de preentrenamiento. Como resultado, las predicciones pueden ser más optimistas de lo que se lograría en un entorno de producción real con datos privados o nuevos.

Información de la sesión

import session_info
session_info.show(html=False)
-----
matplotlib          3.10.8
pandas              2.3.3
session_info        v1.0.1
skforecast          0.22.0
torch               2.6.0+cu124
-----
IPython             9.12.0
jupyter_client      8.8.0
jupyter_core        5.9.1
-----
Python 3.12.13 | packaged by conda-forge | (main, Mar  5 2026, 16:36:12) [MSC v.1944 64 bit (AMD64)]
Windows-11-10.0.26200-SP0
-----
Session information updated at 2026-04-24 17:03

Instrucciones para citar

Cómo citar este documento

Si utilizas este documento o cualquier parte del mismo, por favor reconoce la fuente, ¡muchas gracias!

Forecasting con modelos fundacionales por Joaquín Amat Rodrigo y Javier Escobar Ortiz, disponible bajo una licencia Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0 DEED) en https://cienciadedatos.net/documentos/py79-forecasting-con-modelos-fundacionales.html

¿Cómo citar skforecast?

Si utilizas skforecast, te agradeceríamos mucho que lo cites. ¡Muchas gracias!

Zenodo:

Amat Rodrigo, Joaquin, & Escobar Ortiz, Javier. (2024). skforecast (v0.24.0). Zenodo. https://doi.org/10.5281/zenodo.8382788

APA:

Amat Rodrigo, J., & Escobar Ortiz, J. (2024). skforecast (Version 0.24.0) [Computer software]. https://doi.org/10.5281/zenodo.8382788

BibTeX:

@software{skforecast, author = {Amat Rodrigo, Joaquin and Escobar Ortiz, Javier}, title = {skforecast}, version = {0.24.0}, month = {07}, year = {2026}, license = {BSD-3-Clause}, url = {https://skforecast.org/}, doi = {10.5281/zenodo.8382788} }


¿Te gustó el artículo? Tu apoyo es importante

Tu contribución me ayudará a seguir generando contenido educativo gratuito. ¡Muchas gracias! 😊

Conviértete en patrocinador de GitHub Conviértete en patrocinador de GitHub

Creative Commons Licence

Este documento creado por Joaquín Amat Rodrigo y Javier Escobar Ortiz tiene licencia Attribution-NonCommercial-ShareAlike 4.0 International.

Se permite:

  • Compartir: copiar y redistribuir el material en cualquier medio o formato.

  • Adaptar: remezclar, transformar y crear a partir del material.

Bajo los siguientes términos:

  • Atribución: Debes otorgar el crédito adecuado, proporcionar un enlace a la licencia e indicar si se realizaron cambios. Puedes hacerlo de cualquier manera razonable, pero no de una forma que sugiera que el licenciante te respalda o respalda tu uso.

  • No-Comercial: No puedes utilizar el material para fines comerciales.

  • Compartir-Igual: Si remezclas, transformas o creas a partir del material, debes distribuir tus contribuciones bajo la misma licencia que el original.