Loss Functions

fusionlab provides several custom loss functions tailored for advanced time series forecasting tasks, particularly those involving probabilistic (quantile) predictions and integrated anomaly detection. These functions are designed to be compatible with the Keras API (e.g., model.compile(loss=...)).

Understanding these losses is key to training models like TemporalFusionTransformer and XTFT effectively, especially when dealing with uncertainty estimation or anomaly-aware training strategies.

Quantile Loss Functions

These functions are used when the goal is to predict specific quantiles of the target distribution, enabling probabilistic forecasts. They typically return a callable loss function suitable for Keras compile.

quantile_loss

API Reference:

quantile_loss()

Purpose: Creates a Keras-compatible loss function that computes the quantile (pinball) loss for a single, specified quantile \(q\).

Functionality: Takes a single quantile \(q\) (between 0 and 1) as input and returns a function loss_fn(y_true, y_pred). This returned function calculates the pinball loss:

\[L_q(y_{true}, y_{pred}) = \text{mean}(\max(q \cdot error, (q - 1) \cdot error))\]

where \(error = y_{true} - y_{pred}\). The mean is typically taken across all dimensions except the last feature dimension.

Usage Context: Useful when you need to train a model to predict only one specific quantile of the target distribution. Pass the result of this function to model.compile. For example: model.compile(loss=quantile_loss(q=0.75)) would train the model to predict the 75th percentile.

Code Example:

 1import tensorflow as tf
 2import numpy as np
 3from fusionlab.nn.losses import quantile_loss
 4
 5# Config
 6batch_size = 4
 7horizon = 6
 8output_dim = 1
 9quantile_to_predict = 0.75
10
11# Dummy true values and predictions for a SINGLE quantile/output
12y_true = tf.random.normal((batch_size, horizon, output_dim))
13y_pred = y_true + tf.random.normal(tf.shape(y_true), stddev=0.5)
14
15# Create the loss function for the specific quantile
16loss_fn_q75 = quantile_loss(q=quantile_to_predict)
17
18# Calculate the loss
19loss_value = loss_fn_q75(y_true, y_pred)
20
21print(f"y_true shape: {y_true.shape}")
22print(f"y_pred shape (single quantile): {y_pred.shape}")
23print(f"Calculated Loss for q={quantile_to_predict}: {loss_value.numpy():.4f}")

quantile_loss_multi

API Reference:

quantile_loss_multi()

Purpose: Creates a Keras-compatible loss function that computes the average quantile (pinball) loss across a list of specified quantiles.

Functionality: Takes a list of quantiles (e.g., [0.1, 0.5, 0.9]) as input and returns a function loss_fn(y_true, y_pred). The model’s prediction y_pred is expected to have a final dimension matching the number of quantiles. The returned function calculates the pinball loss \(L_q\) (as defined in quantile_loss()) for each quantile \(q\) and corresponding prediction slice, then computes the average of these individual quantile losses.

\[L_{multi} = \frac{1}{|Q|} \sum_{q \in Q} L_q(y_{true}, \hat{y}_{pred, q})\]

where \(Q\) is the set of specified quantiles.

Usage Context: Intended for training models that output predictions for multiple quantiles simultaneously. The model’s output layer should typically have a final dimension whose size equals the number of quantiles. This function provides one way to achieve multi-quantile training. Ensure the model output shape is compatible.

Code Example:

 1import tensorflow as tf
 2import numpy as np
 3from fusionlab.nn.losses import quantile_loss_multi
 4
 5# Config
 6batch_size = 4
 7horizon = 6
 8output_dim = 1 # Univariate target
 9quantiles = [0.1, 0.5, 0.9]
10num_quantiles = len(quantiles)
11
12# Dummy true values (B, H, O=1)
13y_true = tf.random.normal((batch_size, horizon, output_dim))
14# Dummy predicted quantiles (B, H, Q) - Assuming O=1 is squeezed
15y_pred_multi_q = tf.random.normal((batch_size, horizon, num_quantiles))
16
17# Create the loss function
18loss_fn_multi = quantile_loss_multi(quantiles=quantiles)
19
20# Calculate the loss
21loss_value = loss_fn_multi(y_true, y_pred_multi_q)
22
23print(f"y_true shape: {y_true.shape}")
24print(f"y_pred shape (multi-quantile): {y_pred_multi_q.shape}")
25print(f"Calculated Multi-Quantile Loss: {loss_value.numpy():.4f}")

combined_quantile_loss

API Reference:

combined_quantile_loss()

Purpose: Creates a Keras-compatible loss function that calculates the mean quantile loss (pinball loss) averaged across multiple specified quantiles. This is the primary recommended loss function for multi-quantile forecasting in fusionlab.

Functionality: This function takes a list of target quantiles (e.g., [0.1, 0.5, 0.9]) and returns another function loss_fn(y_true, y_pred) suitable for Keras. The returned loss_fn performs the following calculation:

  1. Calculates the prediction error: \(error = y_{true} - y_{pred}\). Note that \(y_{true}\) (shape \((B, H, O)\)) is typically expanded and broadcasted internally to match the shape of \(y_{pred}\) which includes the quantile dimension (e.g., shape \((B, H, Q)\) or \((B, H, Q, O)\)).

  2. For each specified quantile \(q\) in the quantiles list, it computes the pinball loss:

    \[\text{Loss}_q(error) = \max(q \cdot error, (q - 1) \cdot error)\]
  3. It averages the loss across all dimensions (batch B, horizon H, quantiles Q, output O if present).

Usage Context: This is the standard loss function to use with model.compile when training a model (like TFT or XTFT) that is configured to output predictions for multiple quantiles. The use of @register_keras_serializable within the factory ensures models compiled with this loss can often be saved and loaded correctly.

Code Example:

 1import tensorflow as tf
 2import numpy as np
 3from fusionlab.nn.losses import combined_quantile_loss
 4
 5# Config
 6batch_size = 4
 7horizon = 6
 8output_dim = 1 # Univariate target
 9quantiles = [0.1, 0.5, 0.9]
10num_quantiles = len(quantiles)
11
12# Dummy true values (B, H, O=1)
13y_true = tf.random.normal((batch_size, horizon, output_dim))
14# Dummy predicted quantiles (B, H, Q) - Assuming O=1 is squeezed
15y_pred_multi_q = tf.random.normal((batch_size, horizon, num_quantiles))
16
17# Create the loss function using the factory
18loss_fn_combined = combined_quantile_loss(quantiles=quantiles)
19
20# Calculate the loss
21loss_value = loss_fn_combined(y_true, y_pred_multi_q)
22
23print(f"y_true shape: {y_true.shape}")
24print(f"y_pred shape (multi-quantile): {y_pred_multi_q.shape}")
25print(f"Calculated Combined Quantile Loss: {loss_value.numpy():.4f}")
26
27# Typical compilation:
28# model.compile(optimizer='adam', loss=loss_fn_combined)

Anomaly & Combined Loss Functions

These functions integrate anomaly detection signals into the training objective, often combining them with a primary forecasting loss like the quantile loss. They typically return callable functions suitable for Keras compile.

anomaly_loss

API Reference:

anomaly_loss()

Purpose: Creates a Keras-compatible loss function based on fixed, pre-provided anomaly scores. This allows incorporating an anomaly penalty into the total loss where the anomaly scores themselves are static inputs captured when the loss function is created.

Functionality: Takes a tensor of anomaly_scores and an anomaly_loss_weight during initialization. It returns a Keras loss function \(loss\_fn(y_{true}, y_{pred})\). Crucially, this returned function ignores \(y_{true}\) and \(y_{pred}\) and computes the loss only based on the anomaly_scores provided when the loss function was created:

\[L_{anomaly} = w_{anomaly} \cdot \text{mean}(\text{anomaly\_{scores}}^2)\]

where \(w_{anomaly}\) is the anomaly_loss_weight.

Usage Context: This function differs significantly from the AnomalyLoss layer (which processes dynamic scores). This function captures scores at definition time. It might be used in specific scenarios where anomaly scores are fixed throughout training and treated purely as an additional static penalty term. Its direct use might be less common than using the AnomalyLoss layer within combined loss strategies like combined_total_loss().

Code Example:

 1import tensorflow as tf
 2import numpy as np
 3from fusionlab.nn.losses import anomaly_loss
 4
 5# Config
 6batch_size = 4
 7horizon = 6
 8output_dim = 1
 9anomaly_weight = 0.1
10
11# Dummy anomaly scores (fixed for the loss function)
12# Shape needs to be considered carefully based on how mean is taken
13dummy_scores = tf.constant(
14    np.random.rand(batch_size, horizon, output_dim) * 0.5,
15    dtype=tf.float32
16)
17
18# Create the loss function, capturing the scores
19loss_fn_anomaly = anomaly_loss(
20    anomaly_scores=dummy_scores,
21    anomaly_loss_weight=anomaly_weight
22)
23
24# Dummy y_true/y_pred (ignored by this specific loss function)
25y_true = tf.random.normal((batch_size, horizon, output_dim))
26y_pred = tf.random.normal((batch_size, horizon, output_dim))
27
28# Calculate the loss (depends only on captured scores and weight)
29loss_value = loss_fn_anomaly(y_true, y_pred)
30
31print(f"Captured anomaly scores shape: {dummy_scores.shape}")
32print(f"Calculated Anomaly Loss (fixed scores): {loss_value.numpy():.4f}")

prediction_based_loss

API Reference:

prediction_based_loss()

Purpose: Creates a Keras-compatible loss function specifically for the ‘prediction_based’ anomaly detection strategy used in XTFT. This strategy defines anomalies based on the magnitude of prediction errors.

Functionality: This function takes optional quantiles and an anomaly_loss_weight and returns a Keras loss function \(loss\_fn(y_{true}, y_{pred})\). The returned \(loss\_fn\) computes two components internally:

  1. Prediction Loss (:math:`L_{pred}`):

  2. Anomaly Loss (:math:`L_{anomaly}`):

    • Calculates prediction error \(|y_{true} - y_{pred}|\). If predicting quantiles, the error relative to the median (or average across quantiles) might be used.

    • Anomaly loss is the mean squared value of these errors: \(L_{anomaly} = \text{mean}(\text{error}^2)\).

  3. Total Loss: Weighted sum:

    \[L_{total} = L_{pred} + w_{anomaly} \cdot L_{anomaly}\]

    where \(w_{anomaly}\) is the anomaly_loss_weight.

Usage Context: This function should be used to create the loss for model.compile only when using the ‘prediction_based’ anomaly detection strategy in XTFT. It allows the model to simultaneously minimize forecasting error and penalize large prediction errors (treating them as anomalies).

Code Example:

 1import tensorflow as tf
 2import numpy as np
 3from fusionlab.nn.losses import prediction_based_loss
 4
 5# Config
 6batch_size = 4
 7horizon = 6
 8output_dim = 1
 9quantiles = [0.1, 0.5, 0.9] # Example for quantile mode
10num_quantiles = len(quantiles)
11anomaly_weight = 0.05
12
13# Create the loss function for quantile + prediction-based anomaly
14loss_fn_pred_based = prediction_based_loss(
15    quantiles=quantiles,
16    anomaly_loss_weight=anomaly_weight
17)
18
19# Dummy true values (B, H, O=1)
20y_true = tf.random.normal((batch_size, horizon, output_dim))
21# Dummy predicted quantiles (B, H, Q)
22y_pred_quantiles = tf.random.normal((batch_size, horizon, num_quantiles))
23
24# Calculate the combined loss
25loss_value = loss_fn_pred_based(y_true, y_pred_quantiles)
26
27print(f"y_true shape: {y_true.shape}")
28print(f"y_pred shape (multi-quantile): {y_pred_quantiles.shape}")
29print(f"Calculated Prediction-Based Loss: {loss_value.numpy():.4f}")
30
31# Example for point forecast mode
32loss_fn_point = prediction_based_loss(quantiles=None, anomaly_loss_weight=0.1)
33y_pred_point = tf.random.normal((batch_size, horizon, output_dim))
34loss_value_point = loss_fn_point(y_true, y_pred_point)
35print(f"\nCalculated Prediction-Based Loss (Point): {loss_value_point.numpy():.4f}")

combined_total_loss

API Reference:

combined_total_loss()

Purpose: Creates a Keras-compatible loss function that combines a standard quantile loss with an anomaly loss derived from pre-computed or externally provided anomaly scores captured at the time of loss creation. This is primarily used for the ‘from_config’ anomaly detection strategy.

Functionality: This function takes the quantiles list, an instance of the AnomalyLoss layer (anomaly_layer), and a tensor of fixed anomaly_scores as input. It returns a Keras loss function \(loss\_fn(y_{true}, y_{pred})\). The returned \(loss\_fn\) computes:

  1. Quantile Loss (:math:`L_{quantile}`): Calculated using the internal combined_quantile_loss() logic based on quantiles, \(y_{true}\), and \(y_{pred}\).

  2. Anomaly Loss (:math:`L_{anomaly}`): Calculated by calling the provided anomaly_layer with the fixed anomaly_scores tensor that was passed during the creation of this loss function. Typically: \(L_{anomaly} = w \cdot \text{mean}(\text{anomaly\_{scores}}^2)\).

  3. Total Loss: \(L_{total} = L_{quantile} + L_{anomaly}\)

Usage Context: Used to create the loss for model.compile when using the ‘from_config’ anomaly detection strategy in XTFT. Requires providing the anomaly_scores tensor when creating the loss function. (Note: Aligning these fixed scores with training batches within `model.fit` can be complex; using `model.add_loss` in a custom `train_step` might be more robust for `’from_config’`).

Code Example:

 1import tensorflow as tf
 2import numpy as np
 3from fusionlab.nn.losses import combined_total_loss
 4from fusionlab.nn.components import AnomalyLoss
 5
 6# Config
 7batch_size = 4
 8horizon = 6
 9output_dim = 1
10quantiles = [0.1, 0.5, 0.9]
11num_quantiles = len(quantiles)
12anomaly_weight = 0.05
13
14# 1. Instantiate the anomaly loss layer component
15anomaly_loss_layer = AnomalyLoss(weight=anomaly_weight)
16
17# 2. Provide FIXED anomaly scores (e.g., for training data)
18#    Shape needs careful handling based on loss implementation
19#    Assuming (B, H, O) or compatible shape for demo
20dummy_scores_train = tf.constant(
21    np.random.rand(batch_size, horizon, output_dim) * 0.2,
22    dtype=tf.float32
23)
24
25# 3. Create the combined loss function, capturing scores
26loss_fn_total = combined_total_loss(
27    quantiles=quantiles,
28    anomaly_layer=anomaly_loss_layer,
29    anomaly_scores=dummy_scores_train # Pass fixed scores
30)
31
32# 4. Dummy data for calculation demo
33y_true = tf.random.normal((batch_size, horizon, output_dim))
34y_pred_quantiles = tf.random.normal((batch_size, horizon, num_quantiles))
35
36# 5. Calculate the loss
37#    Uses y_true/y_pred for quantile part, uses captured scores for anomaly part
38loss_value = loss_fn_total(y_true, y_pred_quantiles)
39
40print(f"Captured anomaly scores shape: {dummy_scores_train.shape}")
41print(f"y_true shape: {y_true.shape}")
42print(f"y_pred shape: {y_pred_quantiles.shape}")
43print(f"Calculated Combined Total Loss: {loss_value.numpy():.4f}")

Loss Function Wrappers/Factories

These functions help in constructing or wrapping loss components for use with Keras.

objective_loss

API Reference:

objective_loss()

Purpose: To create a standard Keras-compatible loss function signature \(loss(y_{true}, y_{pred})\) from a pre-configured MultiObjectiveLoss layer instance, potentially incorporating fixed anomaly_scores captured at creation time.

Functionality: This function acts as a bridge or factory. It takes an instantiated multi_obj_loss layer (which internally holds other loss layers like AdaptiveQuantileLoss and AnomalyLoss) and optional fixed anomaly_scores. It returns a standard Keras loss function _loss_fn(y_true, y_pred). When Keras calls _loss_fn, it internally invokes the multi_obj_loss layer’s call method, passing along y_true, y_pred, and the captured anomaly_scores in a way the MultiObjectiveLoss layer expects (requiring careful design of the MultiObjectiveLoss.call signature or data format).

Usage Context: Provides a way to package a configured MultiObjectiveLoss layer and potentially fixed anomaly scores into the standard loss(y_true, y_pred) format expected by model.compile. This might be used to simplify compilation when dealing with multi-task objectives managed by the MultiObjectiveLoss layer, particularly for strategies like ‘from_config’ where scores are fixed.

Code Example (Instantiation):

 1import tensorflow as tf
 2from fusionlab.nn.losses import objective_loss
 3from fusionlab.nn.components import (
 4    MultiObjectiveLoss, AdaptiveQuantileLoss, AnomalyLoss
 5)
 6
 7# Config
 8quantiles = [0.1, 0.5, 0.9]
 9anomaly_weight = 0.05
10batch_size, horizon, output_dim = 4, 6, 1
11
12# 1. Instantiate individual loss components
13quantile_loss_comp = AdaptiveQuantileLoss(quantiles=quantiles)
14anomaly_loss_comp = AnomalyLoss(weight=anomaly_weight)
15
16# 2. Instantiate the multi-objective loss layer
17multi_loss_layer = MultiObjectiveLoss(
18    quantile_loss_fn=quantile_loss_comp,
19    anomaly_loss_fn=anomaly_loss_comp
20)
21
22# 3. Provide FIXED anomaly scores (if needed by multi_loss_layer's logic)
23dummy_scores = tf.constant(
24    np.random.rand(batch_size, horizon, output_dim) * 0.2,
25    dtype=tf.float32
26)
27
28# 4. Create the Keras-compatible loss function using the factory
29keras_loss_fn = objective_loss(
30    multi_obj_loss=multi_loss_layer,
31    anomaly_scores=dummy_scores # Pass scores to be captured by the wrapper
32)
33
34print("Keras-compatible objective_loss function created.")
35# Now use this in compile:
36# model.compile(optimizer='adam', loss=keras_loss_fn)
37# Note: model.fit needs to provide y_true/y_pred in a format
38# that the internal MultiObjectiveLoss understands.