Exercise: Basic Point Forecasting with Flexible TFT

Welcome! This exercise will guide you through the fundamentals of point forecasting using the flexible TemporalFusionTransformer from fusionlab-learn. We’ll focus on a simple scenario using only dynamic (past observed) features to predict a single future time step.

Learning Objectives:

  • Generate a simple synthetic time series.

  • Prepare input sequences and targets for a forecasting model using create_sequences().

  • Understand how to instantiate and compile the flexible TemporalFusionTransformer for point forecasting (i.e., with quantiles=None).

  • Correctly format inputs for the model when only dynamic features are used.

  • Train the model and make basic predictions.

  • Visualize the forecast against actual values.

Let’s get started!

Prerequisites

Before you begin, ensure you have fusionlab-learn and its common dependencies installed. For visualizations, matplotlib is also needed.

pip install fusionlab-learn matplotlib

Step 1: Imports and Setup

First, we import all the necessary libraries. This includes pandas for data handling, numpy for numerical operations, tensorflow for the model, matplotlib for plotting, and the relevant components from fusionlab.

 1import numpy as np
 2import pandas as pd
 3import tensorflow as tf
 4import matplotlib.pyplot as plt
 5import warnings
 6import os
 7
 8# FusionLab imports
 9from fusionlab.nn.transformers import TemporalFusionTransformer
10from fusionlab.nn.utils import create_sequences
11# Import for Keras to recognize custom loss if model was saved with it
12from fusionlab.nn.losses import combined_quantile_loss
13
14# Suppress warnings and TF logs for cleaner output
15warnings.filterwarnings('ignore')
16tf.get_logger().setLevel('ERROR')
17if hasattr(tf, 'autograph'): # Check for autograph availability
18    tf.autograph.set_verbosity(0)
19
20# Directory for saving any output images from this exercise
21exercise_output_dir_basic = "./basic_forecasting_exercise_outputs"
22os.makedirs(exercise_output_dir_basic, exist_ok=True)
23
24print("Libraries imported and setup complete for basic forecasting exercise.")

Expected Output 1.1:

Libraries imported and setup complete for basic forecasting exercise.

Step 2: Generate Synthetic Time Series Data

We’ll create a simple sine wave with some added random noise. This will be our univariate time series that we want to forecast.

 1# For reproducibility
 2np.random.seed(42)
 3tf.random.set_seed(42)
 4
 5time_ex = np.arange(0, 100, 0.1)
 6amplitude_ex = np.sin(time_ex) + np.random.normal(
 7    0, 0.15, len(time_ex)
 8    )
 9df_ex = pd.DataFrame({'Value': amplitude_ex})
10print(f"Generated data shape for exercise: {df_ex.shape}")
11print("Sample of generated data:")
12print(df_ex.head())

Expected Output 2.2:

Generated data shape for exercise: (1000, 1)
Sample of generated data:
      Value
0  0.074540
1  0.070004
2  0.140878
3  0.312668
4  0.208073

Step 3: Prepare Input Sequences and Targets

Forecasting models learn from sequences of past data to predict future values. We’ll use the create_sequences() utility to transform our time series into these input-output pairs. We will use the past 10 time steps to predict the single next time step.

 1sequence_length_ex = 10    # How many past steps to look at (lookback window)
 2forecast_horizon_ex = 1    # How many steps ahead to predict
 3target_col_name_ex = 'Value'
 4
 5# Create sequences
 6# `sequences_ex` will be our X (input features)
 7# `targets_ex` will be our y (what we want to predict)
 8sequences_ex, targets_ex = create_sequences(
 9    df=df_ex,
10    sequence_length=sequence_length_ex,
11    target_col=target_col_name_ex,
12    forecast_horizon=forecast_horizon_ex,
13    verbose=0 # Keep output clean
14)
15
16# Ensure data types are float32 for TensorFlow
17sequences_ex = sequences_ex.astype(np.float32)
18# Reshape targets for Keras: (Samples, Horizon, OutputDim)
19# Here, OutputDim is 1 as we predict one feature ('Value')
20targets_ex = targets_ex.reshape(
21    -1, forecast_horizon_ex, 1
22    ).astype(np.float32)
23
24print(f"\nInput sequences shape (X): {sequences_ex.shape}")
25print(f"Target values shape (y): {targets_ex.shape}")
Expected Output 3.3:

(The number of samples will be `len(df_ex) - sequence_length_ex - forecast_horizon_ex + 1` if `forecast_horizon > 0` in `create_sequences` logic, or `len(df_ex) - sequence_length_ex` if `forecast_horizon=0` means reconstruct. For `forecast_horizon=1`, it’s typically `len(df_ex) - sequence_length_ex`) For `create_sequences` as typically implemented for forecasting, it should be `len(df) - sequence_length - forecast_horizon + 1`. So, 1000 - 10 - 1 + 1 = 990 samples.

Input sequences shape (X): (990, 10, 1)
Target values shape (y): (990, 1, 1)

Step 4: Define and Compile the Flexible TFT Model

Now, we instantiate the TemporalFusionTransformer. Since this exercise uses only dynamic past features: * dynamic_input_dim is set to the number of features in our sequences_ex. * static_input_dim and future_input_dim are left as None (their defaults). * quantiles is set to None for point forecasting. * output_dim=1 as we are predicting a single target value.

We compile the model with ‘adam’ optimizer and ‘mse’ (Mean Squared Error) loss, suitable for regression tasks like point forecasting.

 1num_dynamic_features_ex = sequences_ex.shape[-1] # Should be 1
 2
 3tft_model_ex = TemporalFusionTransformer(
 4    dynamic_input_dim=num_dynamic_features_ex,
 5    # static_input_dim and future_input_dim default to None
 6    forecast_horizon=forecast_horizon_ex,
 7    output_dim=1,
 8    hidden_units=16,        # Using smaller values for faster demo
 9    num_heads=2,
10    num_lstm_layers=1,      # A single LSTM layer in the encoder
11    lstm_units=16,
12    quantiles=None          # Crucial for point forecasting
13)
14print("\nFlexible TemporalFusionTransformer instantiated for point forecast.")
15
16tft_model_ex.compile(optimizer='adam', loss='mse')
17print("Model compiled successfully with MSE loss.")

Expected Output 4.4:

Flexible TemporalFusionTransformer instantiated for point forecast.
Model compiled successfully with MSE loss.

Step 5: Train the Model

We train the model using the .fit() method. The TemporalFusionTransformer expects its inputs as a list of three elements: [static_inputs, dynamic_inputs, future_inputs]. Since we only have dynamic inputs for this exercise, the static and future inputs will be None in this list.

 1# Prepare inputs for the model's fit method in the correct order
 2# [Static, Dynamic, Future]
 3# since static and Future are None, then
 4# pass directly sequences_ex as dynamic only.
 5train_inputs_list_ex = [sequences_ex]
 6
 7print("\nStarting model training (this may take a few moments)...")
 8history_obj_ex = tft_model_ex.fit(
 9    train_inputs_list_ex, # Pass the 3-element list
10    targets_ex,           # Target shape (Samples, Horizon, OutputDim)
11    epochs=10,            # Train for more epochs for better results
12    batch_size=32,
13    validation_split=0.2, # Use last 20% of data for validation
14    verbose=1             # Show training progress per epoch
15)
16print("Training finished.")
17if history_obj_ex and history_obj_ex.history.get('val_loss'):
18    final_val_loss = history_obj_ex.history['val_loss'][-1]
19    print(f"Final validation loss: {final_val_loss:.4f}")
Expected Output 5.5:

(Output will show Keras training progress for 5 epochs. The final validation loss will vary.)

Starting model training (this may take a few moments)...
Epoch 1/10
25/25 [==============================] - 5s 37ms/step - loss: 0.4707 - val_loss: 0.2404
Epoch 2/10
25/25 [==============================] - 0s 8ms/step - loss: 0.2550 - val_loss: 0.1804
Epoch 3/10
25/25 [==============================] - 0s 8ms/step - loss: 0.2153 - val_loss: 0.1285
Epoch 4/10
25/25 [==============================] - 0s 8ms/step - loss: 0.1804 - val_loss: 0.0970
Epoch 5/10
25/25 [==============================] - 0s 8ms/step - loss: 0.1599 - val_loss: 0.0901
Epoch 6/10
25/25 [==============================] - 0s 9ms/step - loss: 0.1536 - val_loss: 0.0911
Epoch 7/10
25/25 [==============================] - 0s 9ms/step - loss: 0.1449 - val_loss: 0.0924
Epoch 8/10
25/25 [==============================] - 0s 9ms/step - loss: 0.1366 - val_loss: 0.0919
Epoch 9/10
25/25 [==============================] - 0s 8ms/step - loss: 0.1298 - val_loss: 0.0907
Epoch 10/10
25/25 [==============================] - 0s 8ms/step - loss: 0.1306 - val_loss: 0.0869
Training finished.
Final validation loss: 0.0869

Step 6: Make Predictions and Visualize Results

Let’s use the trained model to predict on the validation portion of our data and plot these predictions against the actual values.

 1# Prepare validation data for prediction
 2num_total_samples = sequences_ex.shape[0]
 3val_start_index_ex = int(num_total_samples * (1 - 0.2)) # From validation_split
 4
 5val_dynamic_data = sequences_ex[val_start_index_ex:]
 6val_actual_targets = targets_ex[val_start_index_ex:]
 7val_actuals_for_plot = val_actual_targets
 8
 9# Package validation inputs in the [Static, Dynamic, Future] format
10# Static and Future are None then pass Dynamic only as
11val_inputs_list = [val_dynamic_data] # this way , the flexible can handle
12
13print("\nMaking predictions on the validation set...")
14val_predictions = tft_model_ex.predict(val_inputs_list, verbose=0)
15# val_predictions shape: (NumValSamples, Horizon, OutputDim)
16
17print(f"Validation predictions shape: {val_predictions.shape}")
18# For H=1, O=1, this will be (NumValSamples, 1, 1)
19print("Sample prediction (first validation sample):",
20      val_predictions[0, 0, 0]) # Accessing the scalar value
21
22# --- Visualization ---
23# Align time axis for plotting the validation results
24# The target for sequence `i` corresponds to data point `time_ex[i + sequence_length_ex]`
25plot_val_time_axis_ex = time_ex[
26    val_start_index_ex + sequence_length_ex : \
27    val_start_index_ex + sequence_length_ex + len(val_actuals_for_plot)
28    ]
29# Ensure the time axis matches the number of validation predictions/actuals
30num_plot_points_ex = min(len(plot_val_time_axis_ex), len(val_actuals_for_plot))
31
32plt.figure(figsize=(14, 7))
33# Plot a portion of original data for context
34context_end_idx_ex = val_start_index_ex + sequence_length_ex + \
35                     num_plot_points_ex + forecast_horizon_ex
36plt.plot(time_ex[:context_end_idx_ex],
37         amplitude_ex[:context_end_idx_ex],
38         label='Original Data Context', alpha=0.6, color='lightblue')
39
40# Plot actuals from validation set (H=1, O=1)
41plt.plot(plot_val_time_axis_ex[:num_plot_points_ex],
42         val_actuals_for_plot[:num_plot_points_ex, 0, 0],
43         label=f'Actual Validation Data (H={forecast_horizon_ex})',
44         linestyle=':', marker='o', color='blue')
45
46# Plot predictions on validation set (H=1, O=1)
47plt.plot(plot_val_time_axis_ex[:num_plot_points_ex],
48         val_predictions[:num_plot_points_ex, 0, 0],
49         label=f'Predicted Validation Data (H={forecast_horizon_ex})',
50         marker='x', color='red')
51
52plt.title('Flexible TFT Point Forecast Exercise (Dynamic Input Only)')
53plt.xlabel('Time')
54plt.ylabel('Value')
55plt.legend()
56plt.grid(True)
57plt.tight_layout()
58# To save the figure for documentation:
59# fig_path_ex = os.path.join(
60# exercise_output_dir_basic,
61# "exercise_basic_tft_point_forecast.png"
62# )
63# plt.savefig(fig_path_ex)
64# print(f"\nPlot saved to {fig_path_ex}")
65plt.show() # Display plot
66
67print("\nBasic TFT point forecasting exercise complete.")

Expected Plot 6.6:

Basic TFT Point Forecast Exercise Results

Visualization of the point forecast from the flexible TemporalFusionTransformer against actual validation data.

Discussion of Exercise

In this exercise, you learned how to:

  • Prepare simple time series data for a forecasting model using create_sequences``.

  • Instantiate the flexible TemporalFusionTransformer for a point forecasting task, specifying only the dynamic_input_dim and setting quantiles=None.

  • Correctly provide inputs to the model’s fit and predict methods as a list [None, dynamic_array, None] when only dynamic features are used, adhering to the expected [static, dynamic, future] order.

  • Compile the model with an appropriate loss function (mse) for point forecasts.

  • Train the model and generate predictions.

This forms the foundation for more complex forecasting tasks. You can extend this by adding static and future known covariates, exploring multi-step forecasting, or moving to probabilistic (quantile) forecasts as shown in other examples.