Home News A Coding Guide to Build Flexible Multi-Model Workflows in GluonTS with Synthetic...

A Coding Guide to Build Flexible Multi-Model Workflows in GluonTS with Synthetic Data, Evaluation, and Advanced Visualizations

0

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.

Exit mobile version