Más sobre forecasting en: cienciadedatos.net


Forecasting con 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 pasos 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.

Diagrama de Arquitectura del Modelo Fundacional

Modelos Fundacionales Soportados

- Chronos TimesFM Moirai TabICL
Proveedor Amazon Google Salesforce Soda-Inria
GitHub chronos-forecasting timesfm uni2ts tabicl
Documentación Chronos models TimesFM models Moirai-R models TabICL Docs
IDs de modelos disponibles amazon/chronos-2
autogluon/chronos-2-small
autogluon/chronos-2-synth
google/timesfm-2.5-200m-pytorch Salesforce/moirai-2.0-R-small soda-inria/tabicl
Backend PyTorch PyTorch PyTorch PyTorch
Tipo de pronóstico Zero-shot Zero-shot Zero-shot Zero-shot
Longitud de contexto predeterminada 8192 512 2048 4096
Longitud de contexto máxima 8192 16384 2048 4096
max_horizon Sin límite estricto, se define con steps en predict 512 Sin límite estricto, se define con steps en predict Sin límite estricto, se define con steps en predict
Pronóstico puntual Mediana (cuantil 0.5) Media (array de salida dedicado) Mediana (cuantil 0.5) Media (predeterminado, configurable a mediana)
Soporte de covariables (exog) No No
Parámetro cross_learning Sí (solo en modo multi-series) No No No
Comando de instalación pip install chronos-forecasting pip install git+https://github.com/google-research/timesfm.git pip install uni2ts pip install tabicl[forecast]

💡 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
)
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()}")
torch version: 2.6.0+cu124
  Cuda available : True
  MPS available  : 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)})")
Train dates: 2012-01-01 00:00:00 --- 2014-11-30 23:00:00  (n=25560)
Test dates : 2014-12-01 00:00:00 --- 2014-12-30 23:00:00  (n=720)

Pronóstico de serie única con Chronos

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-04-24 16:15:38
  • Last fit date: 2026-04-24 16:15:38
  • Skforecast version: 0.22.0
  • Python version: 3.12.13
  • 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:
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$ pasos: 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
# ==============================================================================
pasos = 24
predicciones = pronosticador.predict(
                  steps = pasos,
                  exog  = datos_test[["Temperature", "Holiday"]]
              )
predicciones.head(3)
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    = pasos,
                            exog     = datos_test[["Temperature", "Holiday"]],
                            interval = [10, 90],  # 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
# ==============================================================================
vc = 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                = vc,
    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 completed in 1.4381 seconds.
Backtest metrics
mean_absolute_error
0 171.266953
Backtest predictions
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)

pronosticador

ForecasterFoundation

General Information
  • Model ID: autogluon/chronos-2-small
  • Context length: 500
  • Window size: 500
  • Series names: None
  • Exogenous included: False
  • Creation date: 2026-04-24 16:15:59
  • Last fit date: None
  • Skforecast version: 0.22.0
  • Python version: 3.12.13
  • Forecaster id: None
Exogenous Variables

None

Training Information
  • Context range: Not fitted
  • Training index type: Not fitted
  • Training index frequency: Not fitted
Model Parameters
  • cross_learning: False
  • context_length: 500
  • device_map: auto
  • torch_dtype: None
  • predict_kwargs: None

📖 API Reference    📝 User Guide

# Predicciones para todas las series (niveles)
# ==============================================================================
pasos = len(datos_multiseries_test)
predicciones_articulos = pronosticador.predict(
                        steps   = pasos, 
                        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_22_py12\Lib\site-packages\skforecast\uti 
 ls\utils.py:2799                                                                     
 Suppress : warnings.simplefilter('ignore', category=InputTypeWarning)                
╰──────────────────────────────────────────────────────────────────────────────────────╯
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 = [10, 90],  # 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_22_py12\Lib\site-packages\skforecast\uti 
 ls\utils.py:2799                                                                     
 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

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"])
pronosticador

ForecasterFoundation

General Information
  • Model ID: google/timesfm-2.5-200m-pytorch
  • Context length: 500
  • Window size: 500
  • Series names: Demand
  • Exogenous included: False
  • Creation date: 2026-04-24 16:16:02
  • Last fit date: 2026-04-24 16:16:02
  • Skforecast version: 0.22.0
  • Python version: 3.12.13
  • Forecaster id: None
Exogenous Variables

None

Training Information
  • Context range: 'Demand': ['2012-01-01 00:00:00', '2014-11-30 23:00:00']
  • Training index type: DatetimeIndex
  • Training index frequency:
Model Parameters
  • context_length: 500
  • max_horizon: 512
  • forecast_config_kwargs: None

📖 API Reference    📝 User Guide

# Predicciones: pronóstico puntual
# ==============================================================================
pasos = 24
predicciones = pronosticador.predict(steps=pasos)
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    = pasos,
    interval = [10, 90],  # 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
# ==============================================================================
vc = 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                = vc,
    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 completed in 56.3338 seconds.
Backtest metrics
mean_absolute_error
0 160.357018
Backtest predictions
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"])
pronosticador

ForecasterFoundation

General Information
  • Model ID: Salesforce/moirai-2.0-R-small
  • Context length: 500
  • Window size: 500
  • Series names: Demand
  • Exogenous included: False
  • Creation date: 2026-04-24 16:17:01
  • Last fit date: 2026-04-24 16:17:01
  • Skforecast version: 0.22.0
  • Python version: 3.12.13
  • Forecaster id: None
Exogenous Variables

None

Training Information
  • Context range: 'Demand': ['2012-01-01 00:00:00', '2014-11-30 23:00:00']
  • Training index type: DatetimeIndex
  • Training index frequency:
Model Parameters
  • context_length: 500
  • device: auto

📖 API Reference    📝 User Guide

# Predicciones: pronóstico puntual
# ==============================================================================
pasos = 24
predicciones = pronosticador.predict(steps=pasos)
predicciones.head(3)
level pred
2014-12-01 00:00:00 Demand 5731.725098
2014-12-01 01:00:00 Demand 5870.827148
2014-12-01 02:00:00 Demand 5959.207031
# Predicciones: intervalos
# ==============================================================================
predicciones_intervalos = pronosticador.predict_interval(
    steps    = pasos,
    interval = [10, 90],  # 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
# ==============================================================================
vc = 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                = vc,
    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"]]
)
pronosticador

ForecasterFoundation

General Information
  • Model ID: soda-inria/tabicl
  • Context length: 500
  • Window size: 500
  • Series names: Demand
  • Exogenous included: True
  • Creation date: 2026-04-24 16:17:14
  • Last fit date: 2026-04-24 16:17:14
  • Skforecast version: 0.22.0
  • Python version: 3.12.13
  • 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:
Model Parameters
  • context_length: 500
  • point_estimate: mean
  • tabicl_config: None
  • temporal_features: None

📖 API Reference    📝 User Guide

# Predicciones: pronóstico puntual
# ==============================================================================
pasos = 24
predicciones = pronosticador.predict(
                  steps = pasos,
                  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.487305
2014-12-01 02:00:00 Demand 5661.673828
# Predicciones: intervalos
# ==============================================================================
predicciones_intervalos = pronosticador.predict_interval(
                            steps    = pasos,
                            exog     = datos_test[["Temperature", "Holiday"]],
                            interval = [10, 90],  # 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.955078
2014-12-01 01:00:00 Demand 5678.133301 5215.843750 6059.711914
2014-12-01 02:00:00 Demand 5677.455078 5131.911133 6149.854492
# Backtesting
# ==============================================================================
vc = 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                = vc,
    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)

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
# ==============================================================================
comparacion = pd.DataFrame({
    "Modelo": [
        "Chronos-2 (small)*",
        "TimesFM-2.5 (200m)",
        "Moirai-2.0-R (small)",
        "TabICLv2*"
    ],
    "mean_absolute_error": [
        metricas_chronos["mean_absolute_error"].iloc[0],
        metricas_timesfm["mean_absolute_error"].iloc[0],
        metricas_moirai["mean_absolute_error"].iloc[0],
        metricas_tabicl["mean_absolute_error"].iloc[0]
    ],
    "Tiempo transcurrido": [
        tiempo_transcurrido_chronos,
        tiempo_transcurrido_timesfm,
        tiempo_transcurrido_moirai,
        tiempo_transcurrido_tabicl
    ]
})

display(
    comparacion.style.highlight_min(subset="mean_absolute_error", color="green").format(precision=4)
)
print("* Chronos-2 (small) y TabICLv2 son los únicos que permiten incluir características exógenas.")
  Model mean_absolute_error Elapsed time
0 Chronos-2 (small)* 171.2670 1.4381
1 TimesFM-2.5 (200m) 160.3570 56.3338
2 Moirai-2.0-R (small) 161.6911 9.3907
3 TabICLv2* 170.1016 196.0960
* Chronos-2 (small) and TabICLv2 are the only ones that allow to include exogenous features.

⚠️ 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.

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.

# Influencia de la longitud de contexto en la precisión y velocidad del pronóstico
# ==============================================================================
longitudes_contexto = [100, 500, 1000, 5000]
resultados_metricas = {
    'autogluon/chronos-2-small': [],
    'google/timesfm-2.5-200m-pytorch': [],
    'Salesforce/moirai-2.0-R-small': [],
    'soda-inria/tabicl': [],
}
resultados_tiempo = {
    'autogluon/chronos-2-small': [],
    'google/timesfm-2.5-200m-pytorch': [],
    'Salesforce/moirai-2.0-R-small': [],
    'soda-inria/tabicl': [],
}

for id_modelo in resultados_metricas.keys():

    if id_modelo in {'autogluon/chronos-2-small', 'soda-inria/tabicl'}:
        variables_exogenas = datos[["Temperature", "Holiday"]]
    else:
        variables_exogenas = None
    
    for longitud_contexto in longitudes_contexto:

        estimador = FoundationModel(model_id=id_modelo, context_length=longitud_contexto)
        pronosticador = ForecasterFoundation(estimator=estimador)
        vc = TimeSeriesFold(
                steps              = 24,
                initial_train_size = len(datos.loc[:fin_entrenamiento]),
                refit              = False
            )
        inicio = time.perf_counter()
        metricas, predicciones_backtest = backtesting_foundation(
            forecaster        = pronosticador,
            series            = datos['Demand'],
            exog              = variables_exogenas,
            cv                = vc,
            metric            = 'mean_absolute_error',
            suppress_warnings = True,
            show_progress     = False
        )
        tiempo_transcurrido = time.perf_counter() - inicio
        resultados_metricas[id_modelo].append(metricas.at[0, 'mean_absolute_error'])
        resultados_tiempo[id_modelo].append(tiempo_transcurrido)

resultados_metricas = pd.DataFrame(resultados_metricas, index=longitudes_contexto)
resultados_tiempo = pd.DataFrame(resultados_tiempo, index=longitudes_contexto)
# Graficar resultados
# ==============================================================================
fig, ax = plt.subplots(figsize=(7, 3))
resultados_metricas.plot(ax = ax)
ax.set_title("Error de predicción vs longitud de contexto")
ax.set_xlabel("longitud de contexto")
ax.set_ylabel("error absoluto medio")

fig, ax = plt.subplots(figsize=(7, 3))
resultados_tiempo.plot(ax = ax)
ax.set_title("Tiempo transcurrido vs longitud de contexto")
ax.set_xlabel("longitud de contexto")
ax.set_ylabel("Tiempo transcurrido");

Para los cuatro modelos, hay una caída masiva en el Error Absoluto Medio (MAE) al aumentar la longitud de contexto de 100 a 500. Sin embargo, aumentar la longitud de contexto a 1000 o 5000 solo produce mejoras marginales. Esto indica un "punto óptimo" alrededor de 500-1000 donde los modelos tienen suficientes datos históricos para capturar el patrón, y proporcionarles más historial no ayuda significativamente.

autogluon/chronos-2-small y Salesforce/moirai-2.0-R-small exhiben una excelente escalabilidad computacional. Su tiempo de ejecución permanece prácticamente plano y muy bajo en todas las longitudes de contexto, haciéndolos altamente eficientes incluso con 5000 puntos de datos históricos. google/timesfm-2.5-200m-pytorch muestra un incremento lineal en el tiempo de procesamiento a medida que crece la longitud de contexto. soda-inria/tabicl muestra peor escalabilidad que los otros tres.

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.22.0). Zenodo. https://doi.org/10.5281/zenodo.8382788

APA:

Amat Rodrigo, J., & Escobar Ortiz, J. (2024). skforecast (Version 0.22.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.22.0}, month = {04}, 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.