Más sobre forecasting en: cienciadedatos.net
- Forecasting series temporales con machine learning
- Modelos ARIMA y SARIMAX
- Forecasting series temporales con gradient boosting: XGBoost, LightGBM y CatBoost
- Global Forecasting: Multi-series forecasting
- Forecasting de la demanda eléctrica con machine learning
- Forecasting con deep learning
- Forecasting con modelos fundacionales
- Forecasting de visitas a página web con machine learning
- Forecasting del precio de Bitcoin
- Forecasting probabilístico
- Forecasting de demanda intermitente
- Reducir el impacto del Covid en modelos de forecasting
- Modelar series temporales con tendencia utilizando modelos de árboles
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.
Modelos Fundacionales Soportados¶
| - | Chronos | TimesFM | Moirai | TabICL |
|---|---|---|---|---|
| Proveedor | Amazon | 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) | Sí | No | No | Sí |
| 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
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
# 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
# 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
# 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
# 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! 😊
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.
