Comprehensive Guide to Multi-Model Time Series Forecasting with GluonTS
This article presents a hands-on approach to generating intricate synthetic time series data, preparing it for analysis, and deploying multiple forecasting models concurrently. We emphasize integrating diverse estimators within a unified pipeline, managing missing dependencies smoothly, and ensuring meaningful output. By embedding evaluation and visualization stages, we establish a streamlined workflow that facilitates training, benchmarking, and interpreting models cohesively.
Setting Up the Environment and Dependencies
We start by importing essential libraries for data manipulation, visualization, and GluonTS functionalities. To maintain flexibility, conditional imports for PyTorch and MXNet-based estimators are implemented, enabling the use of whichever deep learning backend is accessible in the current environment.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
import warnings
warnings.filterwarnings('ignore')
from gluonts.dataset.pandas import PandasDataset
from gluonts.dataset.split import split
from gluonts.evaluation import make_evaluation_predictions, Evaluator
from gluonts.dataset.artificial import ComplexSeasonalTimeSeries
try:
from gluonts.torch import DeepAREstimator
TORCH_AVAILABLE = True
except ImportError:
TORCH_AVAILABLE = False
try:
from gluonts.mx import DeepAREstimator as MXDeepAREstimator
from gluonts.mx import SimpleFeedForwardEstimator
MX_AVAILABLE = True
except ImportError:
MX_AVAILABLE = False
Generating Synthetic Multi-Variate Time Series Data
To simulate realistic time series, we create a function that synthesizes multiple series incorporating trends, seasonal patterns, and stochastic noise. The design ensures reproducibility by fixing the random seed and outputs a well-structured DataFrame suitable for modeling.
def generate_synthetic_time_series(num_series=50, length=365, prediction_length=30):
"""
Create synthetic multi-variate time series data with embedded trend, weekly and annual seasonality, plus noise.
"""
np.random.seed(42)
series_collection = []
for idx in range(num_series):
# Generate a cumulative trend with slight variation per series
trend_component = np.cumsum(np.random.normal(0.1 + idx * 0.01, 0.1, length))
# Weekly seasonality (7-day cycle)
weekly_seasonality = 10 * np.sin(2 * np.pi * np.arange(length) / 7)
# Annual seasonality (365.25-day cycle)
annual_seasonality = 20 * np.sin(2 * np.pi * np.arange(length) / 365.25)
# Random noise
noise_component = np.random.normal(0, 5, length)
# Combine all components and ensure values remain positive
combined_values = np.maximum(trend_component + weekly_seasonality + annual_seasonality + noise_component + 100, 1)
# Create date index starting from Jan 1, 2020
date_index = pd.date_range(start='2020-01-01', periods=length, freq='D')
series_collection.append(pd.Series(combined_values, index=date_index, name=f'series_{idx}'))
return pd.concat(series_collection, axis=1)
Preparing Data and Initializing Forecasting Models
We generate a dataset with 10 time series, wrap it into GluonTS’s PandasDataset, and split it into training and testing segments. Subsequently, we attempt to initialize several estimators-PyTorch DeepAR, MXNet DeepAR, and a FeedForward model-depending on backend availability. If none are accessible, we revert to a built-in artificial dataset to maintain workflow continuity.
print("🚀 Generating synthetic multi-series dataset...")
df = generate_synthetic_time_series(num_series=10, length=200, prediction_length=30)
dataset = PandasDataset(df, target=df.columns.tolist())
training_data, test_generator = split(dataset, offset=-60)
test_data = test_generator.generate_instances(prediction_length=30, windows=2)
print("🔧 Setting up forecasting models...")
models = {}
if TORCH_AVAILABLE:
try:
models['DeepAR_PyTorch'] = DeepAREstimator(freq='D', prediction_length=30)
print("✅ PyTorch DeepAR model loaded successfully")
except Exception as e:
print(f"❌ Failed to load PyTorch DeepAR: {e}")
if MX_AVAILABLE:
try:
models['DeepAR_MXNet'] = MXDeepAREstimator(freq='D', prediction_length=30, trainer=dict(epochs=5))
print("✅ MXNet DeepAR model loaded successfully")
except Exception as e:
print(f"❌ Failed to load MXNet DeepAR: {e}")
try:
models['FeedForward_MXNet'] = SimpleFeedForwardEstimator(freq='D', prediction_length=30, trainer=dict(epochs=5))
print("✅ MXNet FeedForward model loaded successfully")
except Exception as e:
print(f"❌ Failed to load MXNet FeedForward: {e}")
if not models:
print("🔄 No external models available, switching to built-in artificial dataset...")
artificial_dataset = ComplexSeasonalTimeSeries(
num_series=10,
prediction_length=30,
freq='D',
length_low=150,
length_high=200
).generate()
training_data, test_generator = split(artificial_dataset, offset=-60)
test_data = test_generator.generate_instances(prediction_length=30, windows=2)
Training Models and Generating Forecasts
Each accessible model is trained on the training data, and probabilistic forecasts are generated for the test set. We store both the trained predictors and their forecasts for subsequent evaluation and visualization. Robust error handling ensures that failures in training one model do not interrupt the overall process.
trained_models = {}
forecast_results = {}
if models:
for model_name, estimator in models.items():
print(f"🎯 Training model: {model_name}...")
try:
predictor = estimator.train(training_data)
trained_models[model_name] = predictor
forecasts = list(predictor.predict(test_data.input))
forecast_results[model_name] = forecasts
print(f"✅ Training completed for {model_name}")
except Exception as e:
print(f"❌ Training failed for {model_name}: {e}")
continue
Evaluating Forecast Accuracy with Standard Metrics
We assess model performance using established metrics such as Mean Absolute Scaled Error (MASE), Symmetric Mean Absolute Percentage Error (sMAPE), and weighted quantile loss. This evaluation provides a quantitative basis for comparing models on a consistent scale.
print("📊 Evaluating model performance...")
evaluator = Evaluator(quantiles=[0.1, 0.5, 0.9])
evaluation_summary = {}
for model_name, forecasts in forecast_results.items():
if forecasts:
try:
agg_metrics, _ = evaluator(test_data.label, forecasts)
evaluation_summary[model_name] = agg_metrics
print(f"n{model_name} Performance Metrics:")
print(f" MASE: {agg_metrics['MASE']:.4f}")
print(f" sMAPE: {agg_metrics['sMAPE']:.4f}")
print(f" Mean Weighted Quantile Loss: {agg_metrics['mean_wQuantileLoss']:.4f}")
except Exception as e:
print(f"❌ Evaluation failed for {model_name}: {e}")
Visualizing Forecasts, Residuals, and Model Comparisons
To gain deeper insights, we create advanced visualizations that juxtapose forecasts from multiple models, display uncertainty intervals, and analyze residual distributions. Additionally, a comparative bar chart summarizes key evaluation metrics across models, facilitating intuitive performance assessment.
def visualize_forecast_comparisons(test_data, forecasts_dict, series_index=0):
"""
Generate comprehensive plots comparing multiple model forecasts, including uncertainty bands and residuals.
"""
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
fig.suptitle('Multi-Model Forecasting Analysis with GluonTS', fontsize=16, fontweight='bold')
if not forecasts_dict:
fig.text(0.5, 0.5, 'No forecasts available for visualization', ha='center', va='center', fontsize=20)
return fig
if series_index 1 else x_pos)
ax4.set_xticklabels(metrics)
ax4.legend()
ax4.grid(alpha=0.3)
else:
ax4.text(0.5, 0.5, 'No evaluation results to display', ha='center', va='center', transform=ax4.transAxes, fontsize=14)
plt.tight_layout()
return fig
if forecast_results and test_data.label:
print("📈 Generating detailed forecast visualizations...")
fig = visualize_forecast_comparisons(test_data, forecast_results, series_index=0)
plt.show()
print(f"n🎉 Tutorial completed successfully!")
print(f"📊 Trained {len(trained_models)} model(s) on {len(df.columns) if 'df' in locals() else 10} time series")
print(f"🎯 Forecast horizon: 30 days")
if evaluation_summary:
best_model_name, best_metrics = min(evaluation_summary.items(), key=lambda x: x[1]['MASE'])
print(f"🏆 Top performing model: {best_model_name} (MASE: {best_metrics['MASE']:.4f})")
print("n🔧 Environment status:")
print(f" PyTorch support: {'✅' if TORCH_AVAILABLE else '❌'}")
print(f" MXNet support: {'✅' if MX_AVAILABLE else '❌'}")
else:
print("⚠️ No models available, displaying demonstration plot with synthetic data...")
fig, ax = plt.subplots(figsize=(12, 6))
demo_dates = pd.date_range('2020-01-01', periods=100, freq='D')
demo_series = 100 + np.cumsum(np.random.normal(0, 2, 100)) + 20 * np.sin(np.arange(100) * 2 * np.pi / 30)
ax.plot(demo_dates[:70], demo_series[:70], 'b-', label='Historical Data', linewidth=2)
ax.plot(demo_dates[70:], demo_series[70:], 'r--', label='Future (Example)', linewidth=2)
ax.fill_between(demo_dates[70:], demo_series[70:] - 5, demo_series[70:] + 5, color='red', alpha=0.3)
ax.set_title('GluonTS Probabilistic Forecasting Demonstration', fontsize=14, fontweight='bold')
ax.set_xlabel('Date')
ax.set_ylabel('Value')
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.show()
print("n📚 This tutorial covers advanced GluonTS concepts including:")
print(" • Multi-series synthetic data generation")
print(" • Probabilistic forecasting techniques")
print(" • Model evaluation and comparative analysis")
print(" • Sophisticated visualization methods")
print(" • Robust error and dependency handling")
Summary
This guide demonstrates a resilient framework that integrates synthetic data creation, multi-model experimentation, and thorough performance evaluation. By avoiding reliance on a single model or backend, it encourages adaptability and comprehensive analysis. The visualization tools enhance interpretability, making it easier to compare forecasting approaches. These principles provide a solid foundation for applying GluonTS to real-world datasets while maintaining modularity and extensibility.
