Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Modular Volatility Flow Predictor Trials

This notebook demonstrates the modular workflow for volatility modeling using the codebase in src/. We use both synthetic HAR data and real ETF data, and reproduce the key plots and model training steps from the original workflow.

VolatilityFlow-HAR: Co-trained neural distributional transform + HAR

Implements an approach inspired by Co-Training Realized Volatility Prediction with Neural Distributional Transformation (Du et al., ICAIF'23) in a modular, OOP codebase:

  • Transform (embedder): learnable invertible 1D monotonic neural transform f_θ with closed-form derivative and numerical inverse.
  • Predictor: HAR on the transformed series z_t = f_θ(x_t) with daily/weekly/monthly components.
  • Joint training:
    • mle_flow: Gaussian residual likelihood on z plus change‑of‑variables term -log|f'(x)|.
    • mse_x: MSE on original scale after inversion x̂_{t+1} = f_θ^{-1}(ẑ_{t+1}).
  • Data: reads Parquet with OHLCV & Adj Close; computes realized volatility proxy (Garman–Klass) if rv not supplied.
# Imports for modular workflow
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import torch

from src.data import gen_HAR_ts
from src.data import load_parquet_data,load_csv_data
from src.data import realised_vol_for_ticker, realised_vol_for_tickers
from src.embedder.NormalisingFlow1D import NormalizingFlowEmbedder
from src.predictor.HARPredictor import NeuralHARMLP

What the main classes and functions do

  • NeuralHARMLP (src/predictor/HARPredictor.py)

    • A small MLP that takes HAR-style features computed from a rolling window (lookback) of past values:
      • mean over last d1 steps, mean over last d2 steps, mean over last d3 steps.
    • You control the window length with lookback and the aggregation spans with d1, d2, d3.
    • Trained with a standard regression loss (e.g., MSE) to predict the next-step target.
  • gen_HAR_ts (src/data/gen_synth.py)

    • Generates a synthetic series consistent with a HAR process and returns a design matrix X_synth (lag features) and y_target_synth (the next-step target).
    • Use this to quickly prototype the training pipeline.
  • realised_vol_for_ticker (src/data/rv.py)

    • Computes realized volatility features from raw OHLCV data for a selected ticker.
    • Returns a 2D numpy array of [daily_vol, garman_klass_vol] that can be converted to a torch tensor.
# Generate synthetic HAR data
d1, d2, d3 = 2, 10, 30
b_0, b_1, b_2, b_3 = -0.5, 0.9, -0.7, -0.8
X_synth,y_target_synth = gen_HAR_ts(N=5000, d1=d1, d2=d2, d3=d3
                           , b_0=b_0, b1=b_1, b2=b_2, b3=b_3,
                             noise_std=0.05, seed=42)
X_synth
tensor([[-0.2144, -0.2811, -0.2995],
        [-0.2349, -0.2670, -0.3193],
        [-0.2448, -0.2515, -0.3038],
        ...,
        [-0.9247, -0.2766, -0.2643],
        [-0.9504, -0.4165, -0.3019],
        [-0.8997, -0.5410, -0.3384]])

Synthetic data generation (HAR)

We generate a synthetic series using a HAR process. The generator creates lagged features (X_synth) and a next-step target (y_target_synth). These are used to sanity-check our model training flow before moving to real data.

etf_dataset = load_csv_data('../data_download/etf_prices_1hour_adjusted.csv')

Load raw ETF data

We load raw OHLCV data from CSV into a pandas DataFrame. For realized volatility features, we later select a specific ticker and compute [daily_vol, garman_klass_vol].

vol_data = realised_vol_for_ticker(etf_dataset, 'VOO')
vol_data
/home/yagyik/Dropbox/finance_trials/volatility_modelling/VolatilityFlowPredictor/src/data/rv.py:26: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
  gk_vol = df_ticker.groupby('date_only').apply(garman_klass_vol)





array([[0.        , 0.00257693],
       [0.        , 0.00257693],
       [0.        , 0.00257693],
       ...,
       [0.00334623, 0.00294795],
       [0.00334623, 0.00294795],
       [0.00334623, 0.00294795]], shape=(12724, 2))

Compute realized volatility for a specific ticker

We compute realized volatility features for the selected ticker (e.g., 'VOO') using the simplified function. The output vol_data contains two columns: [daily_vol, garman_klass_vol]. You can convert it to a torch tensor for modeling.

  • No lookback or batch_size is involved here; this step prepares features from raw data.
import matplotlib.pyplot as plt

fig, axs = plt.subplots(2, 1, figsize=(12, 10), sharex=True)

# Plot X columns
for i in range(X_synth.shape[1]):
    axs[0].plot(X_synth[:, i].detach().numpy(), label=f'X[:, {i}]')
axs[0].set_ylabel('Lagged Features',fontsize=20)
axs[0].set_title('Lagged Features (X)',fontsize=20)
axs[0].legend(fontsize=20)

# Plot y_target
axs[1].plot(y_target_synth.detach().numpy(), color='tab:orange', label='y_target')
axs[1].set_ylabel('Target',fontsize=20)
axs[1].set_title('Target (y_target)',fontsize=20)
axs[1].legend(fontsize=20)


plt.tight_layout()
plt.show()

png

Visualize synthetic design matrix and target

We plot the HAR lagged features X_synth and the corresponding target y_target_synth to understand the structure of the synthetic dataset before training.

  • No train/test split here; this is exploratory visualization.
import torch
from torch.utils.data import DataLoader, TensorDataset

import torch.optim as optim





# X_synth: (T, L) or (T, F)? From gen_HAR_ts, it returns X_synth (T, 3) of HAR lags and y_target_synth (T,)
# For NeuralHARMLP, we want raw windows x of length L to compute HAR features internally.
# We'll use the first column of X_synth as a proxy time series, or build windows directly from y_target_synth.

# Build windows from y_target_synth as the base series
series = y_target_synth.detach().clone()
print(len(series))
lookback = 1000
X_list, y_list = [], []
for t in range(lookback, len(series) - 1):
    X_list.append(series[t - lookback:t])
    y_list.append(series[t + 1])

Xw = torch.stack(X_list)  # (N, lookback)
yw = torch.stack(y_list).unsqueeze(-1)  # (N, 1)

# Time-based split 80:20
N = Xw.shape[0]
print(N)
split = int(0.8 * N)
X_tr, X_te = Xw[:split], Xw[split:]
y_tr, y_te = yw[:split], yw[split:]

tr_loader = DataLoader(TensorDataset(X_tr, y_tr), batch_size=64, shuffle=False)
te_loader = DataLoader(TensorDataset(X_te, y_te), batch_size=64, shuffle=False)

model = NeuralHARMLP(input_dim=lookback, output_dim=1, 
                     hidden_dim=64, d1=1, d2=5, d3=22,
                      optimizer_cls=optim.Adam,
                        loss_fn=torch.nn.MSELoss(), lr=1e-3)
model.configure_optimizer()

# Train
epochs = 200
for epoch in range(epochs):
    for xb, yb in tr_loader:
        xb = xb.squeeze(-1) if xb.dim() == 3 else xb  # Ensure shape (batch, lookback)
        yb = yb.squeeze(-1) if yb.dim() == 3 else yb  # Ensure shape (batch, 1)
        model.optimizer.zero_grad()
        pred = model(xb)
        loss = model.loss_fn(pred, yb)
        loss.backward()
        model.optimizer.step()
    if (epoch + 1) % 10 == 0:
        print(f"Epoch {epoch+1}/{epochs} - Train MSE: {loss.item():.6f}")

with torch.no_grad():
    preds = []
    gts = []
    for xb, yb in te_loader:
        xb = xb.squeeze(-1) if xb.dim() == 3 else xb
        yb = yb.squeeze(-1) if yb.dim() == 3 else yb
        p = model(xb)
        preds.append(p)
        gts.append(yb)
    preds = torch.cat(preds, dim=0)
    gts = torch.cat(gts, dim=0)
    mse = torch.mean((preds - gts) ** 2).item()
    rmse = mse ** 0.5
print(f"Test RMSE: {rmse:.6f}")
4969
3968
Epoch 10/200 - Train MSE: 0.008031
Epoch 10/200 - Train MSE: 0.008031
Epoch 20/200 - Train MSE: 0.005620
Epoch 20/200 - Train MSE: 0.005620
Epoch 30/200 - Train MSE: 0.005666
Epoch 30/200 - Train MSE: 0.005666
Epoch 40/200 - Train MSE: 0.005373
Epoch 40/200 - Train MSE: 0.005373
Epoch 50/200 - Train MSE: 0.004018
Epoch 50/200 - Train MSE: 0.004018
Epoch 60/200 - Train MSE: 0.003535
Epoch 60/200 - Train MSE: 0.003535
Epoch 70/200 - Train MSE: 0.003862
Epoch 70/200 - Train MSE: 0.003862
Epoch 80/200 - Train MSE: 0.003491
Epoch 80/200 - Train MSE: 0.003491
Epoch 90/200 - Train MSE: 0.003777
Epoch 90/200 - Train MSE: 0.003777
Epoch 100/200 - Train MSE: 0.004041
Epoch 100/200 - Train MSE: 0.004041
Epoch 110/200 - Train MSE: 0.002831
Epoch 110/200 - Train MSE: 0.002831
Epoch 120/200 - Train MSE: 0.003675
Epoch 120/200 - Train MSE: 0.003675
Epoch 130/200 - Train MSE: 0.003496
Epoch 130/200 - Train MSE: 0.003496
Epoch 140/200 - Train MSE: 0.002738
Epoch 140/200 - Train MSE: 0.002738
Epoch 150/200 - Train MSE: 0.002860
Epoch 150/200 - Train MSE: 0.002860
Epoch 160/200 - Train MSE: 0.002775
Epoch 160/200 - Train MSE: 0.002775
Epoch 170/200 - Train MSE: 0.002634
Epoch 170/200 - Train MSE: 0.002634
Epoch 180/200 - Train MSE: 0.002952
Epoch 180/200 - Train MSE: 0.002952
Epoch 190/200 - Train MSE: 0.002731
Epoch 190/200 - Train MSE: 0.002731
Epoch 200/200 - Train MSE: 0.002575
Test RMSE: 0.092184
Epoch 200/200 - Train MSE: 0.002575
Test RMSE: 0.092184

Train/test split and windowing (NeuralHARMLP)

We create rolling windows of length lookback from the synthetic target series and perform an 80:20 time-based split:

  • lookback: number of past steps used to compute HAR features (means over the last d1, d2, d3 points). Larger values may capture longer memory but reduce the number of samples.
  • batch_size: number of windows per optimization step; set via the DataLoader. Higher batch_size stabilizes gradients but uses more memory.
  • Split policy: chronological; the first 80% of windows for training, last 20% for testing. This avoids leakage from shuffling.

We report Train MSE during epochs and final Test RMSE after training.

# Plot predictions vs targets
# with torch.no_grad():
#     preds = model(X_te).squeeze().numpy()
#     targets = y_te.squeeze().numpy()

plt.plot(gts, label='Targets')
plt.plot(preds, label='Predictions')
plt.xlabel('Time Step')
plt.ylabel('Value')
plt.title('Targets vs Predictions')
plt.legend()
plt.tight_layout()
plt.show()

# Scatter plot: predictions vs targets
plt.figure(figsize=(6, 6))
plt.scatter(gts, preds, alpha=0.5)
plt.xlabel('Targets')
plt.ylabel('Predictions')
plt.title('Scatter: Predictions vs Targets')
plt.tight_layout()
plt.show()

png

png

# Prepare vol_tensor from vol_data
vol_tensor = torch.tensor(vol_data, dtype=torch.float32)
if vol_tensor.ndim == 1:
    vol_tensor = vol_tensor.unsqueeze(1)
if vol_tensor.shape[1] == 1:
    # Tile to 2D for flow if needed
    vol_tensor = torch.cat([vol_tensor, vol_tensor], dim=1)

# Instantiate and configure NormalizingFlowEmbedder
flow = NormalizingFlowEmbedder(num_layers=8,
                                features=vol_tensor.shape[1],
                                hidden_features=32,
                                  optimizer_cls=optim.Adam, 
                                  lr=1e-3)
flow.configure_optimizer()

# Train the flow model
inputs = vol_tensor
for i in range(1000):
    x = inputs[torch.randperm(inputs.size(0))[:128]]
    flow.optimizer.zero_grad()
    flow.loss_fn = -flow.flow.log_prob(inputs=x).mean()
    loss = -flow.flow.log_prob(inputs=x).mean()
    loss.backward()
    flow.optimizer.step()
    if (i+1) % 200 == 0:
        print(f"Epoch {i+1}: Loss={loss.item():.4f}")

# Transform and inverse transform
vol_transformed = flow.transform(vol_tensor)
inverse_transformed = flow.inverse_transform(vol_transformed)

fig, axs = plt.subplots(3, 2, figsize=(14, 14))

# Row 1: Original vol_tensor
axs[0, 0].hist(vol_tensor[:, 0].detach().numpy(), bins=50, color='tab:green', alpha=0.7)
axs[0, 0].set_title('Histogram of daily_vol (original)', fontsize=20)
axs[0, 0].set_xlabel('daily_vol', fontsize=20)
axs[0, 0].set_ylabel('Frequency', fontsize=20)
axs[0, 0].tick_params(axis='x', labelsize=20)
axs[0, 0].tick_params(axis='y', labelsize=20)

axs[0, 1].hist(vol_tensor[:, 1].detach().numpy(), bins=50, color='tab:red', alpha=0.7)
axs[0, 1].set_title('Histogram of garman_klass_vol (original)', fontsize=20)
axs[0, 1].set_xlabel('garman_klass_vol', fontsize=20)
axs[0, 1].set_ylabel('Frequency', fontsize=20)
axs[0, 1].tick_params(axis='x', labelsize=20)
axs[0, 1].tick_params(axis='y', labelsize=20)

# Row 2: Transformed volatilities
axs[1, 0].hist(vol_transformed[:, 0].detach().numpy(), bins=50, color='tab:purple', alpha=0.7)
axs[1, 0].set_title('Histogram of vol_transformed[:, 0]', fontsize=20)
axs[1, 0].set_xlabel('vol_transformed[:, 0]', fontsize=20)
axs[1, 0].set_ylabel('Frequency', fontsize=20)
axs[1, 0].tick_params(axis='x', labelsize=20)
axs[1, 0].tick_params(axis='y', labelsize=20)

axs[1, 1].hist(vol_transformed[:, 1].detach().numpy(), bins=50, color='tab:brown', alpha=0.7)
axs[1, 1].set_title('Histogram of vol_transformed[:, 1]', fontsize=20)
axs[1, 1].set_xlabel('vol_transformed[:, 1]', fontsize=20)
axs[1, 1].set_ylabel('Frequency', fontsize=20)
axs[1, 1].tick_params(axis='x', labelsize=20)
axs[1, 1].tick_params(axis='y', labelsize=20)

# Row 3: Inverse transformed
axs[2, 0].hist(inverse_transformed[:, 0].detach().numpy(), bins=50, color='tab:blue', alpha=0.7)
axs[2, 0].set_title('Histogram of inverse_transformed[:, 0]',fontsize=20)
axs[2, 0].set_xlabel('inverse_transformed[:, 0]', fontsize=20)
axs[2, 0].set_ylabel('Frequency', fontsize=20)
axs[2, 0].tick_params(axis='x', labelsize=20)
axs[2, 0].tick_params(axis='y', labelsize=20)

axs[2, 1].hist(inverse_transformed[:, 1].detach().numpy(), bins=50, color='tab:orange', alpha=0.7)
axs[2, 1].set_title('Histogram of inverse_transformed[:, 1]',fontsize=20)
axs[2, 1].set_xlabel('inverse_transformed[:, 1]', fontsize=20)
axs[2, 1].set_ylabel('Frequency', fontsize=20)
axs[2, 1].tick_params(axis='x', labelsize=20)
axs[2, 1].tick_params(axis='y', labelsize=20)

# If you add legends, use fontsize=20:
# axs[row, col].legend(fontsize=20)

plt.tight_layout()
plt.show()
Epoch 200: Loss=-7.1608
Epoch 400: Loss=-3.7269
Epoch 400: Loss=-3.7269
Epoch 600: Loss=-4.5926
Epoch 600: Loss=-4.5926
Epoch 800: Loss=-6.7962
Epoch 800: Loss=-6.7962
Epoch 1000: Loss=-8.3836
Epoch 1000: Loss=-8.3836

png

import importlib

importlib.reload(importlib)  # Ensure importlib itself is fresh

modules_to_reload = [
    'src.data',
    'src.data.utils',
    'src.embedder.NormalisingFlow1D',
    'src.predictor.HARPredictor',
    'src.joint_models'
]

for mod in modules_to_reload:
    try:
        importlib.reload(importlib.import_module(mod))
    except Exception as e:
        print(f"Could not reload {mod}: {e}")

from src.data.utils import make_dataloaders
from src.joint_models.FlowHAR import FlowHARModel
# Use vol_tensor from cell 8 and utility functions from previous cell

lookback = 44
old_mean = vol_tensor.mean(dim=0)
old_std = vol_tensor.std(dim=0)
print(old_mean, old_std)
normalised_vol_tensor = (vol_tensor - old_mean) / old_std


train_loader, val_loader = make_dataloaders(normalised_vol_tensor, lookback=lookback, split_ratio=0.8, batch_size=128)

# Instantiate model
flow_embedder = NormalizingFlowEmbedder(num_layers=8,
                                features=normalised_vol_tensor.shape[1],
                                hidden_features=32,
                                  optimizer_cls=optim.Adam, 
                                  lr=1e-3)

har_predictor = NeuralHARMLP(input_dim=3 * normalised_vol_tensor.shape[1],
                              output_dim=normalised_vol_tensor.shape[1], 
                     hidden_dim=64, d1=1, d2=5, d3=22,
                      optimizer_cls=optim.Adam,
                        loss_fn=torch.nn.MSELoss(), lr=1e-3)

model = FlowHARModel(flow_embedder, har_predictor,
                     loss_fn = torch.nn.MSELoss(),
                     lr=1e-3)


flow_embedder.configure_optimizer()
har_predictor.configure_optimizer()
model.configure_optimizer()


num_epochs = 50
train_losses, val_losses = [], []
best_val_loss = float('inf')
best_state_dict = None

for epoch in range(num_epochs):
    model.train()
    train_loss = 0.0
    for xb, yb in train_loader:
        model.optimizer.zero_grad()
        preds = model(xb)
        loss = torch.sqrt(model.loss_fn(preds, yb))
        loss.backward()
        model.optimizer.step()
        train_loss += loss.item() * xb.size(0)
    train_loss /= len(train_loader.dataset)
    train_losses.append(train_loss)

    model.eval()
    val_loss = 0.0
    with torch.no_grad():
        for xb, yb in val_loader:
            preds = model(xb)
            loss = torch.sqrt(model.loss_fn(preds, yb))
            val_loss += loss.item() * xb.size(0)
    val_loss /= len(val_loader.dataset)
    val_losses.append(val_loss)

    print(f"Epoch {epoch+1}: Train RMSE={train_loss:.4f}, Val RMSE={val_loss:.4f}")

    if val_loss < best_val_loss:
        best_val_loss = val_loss
        best_state_dict = {k: v.cpu() for k, v in model.state_dict().items()}

# Save learned weights
torch.save(best_state_dict, "flow_har_model_best.pth")

# Store predictions and targets from the test set for downstream evaluation
model.eval()
preds_list, targets_list = [], []
with torch.no_grad():
    for xb, yb in val_loader:
        preds = model(xb)
        # Rescale predictions and targets to original scale
        preds_rescaled = preds * old_std + old_mean
        targets_rescaled = yb * old_std + old_mean
        preds_list.append(preds_rescaled)
        targets_list.append(targets_rescaled)
preds_test = torch.cat(preds_list, dim=0)
targets_test = torch.cat(targets_list, dim=0)
tensor([0.0030, 0.0053]) tensor([0.0040, 0.0041])
Epoch 1: Train RMSE=2.0638, Val RMSE=0.7075
Epoch 1: Train RMSE=2.0638, Val RMSE=0.7075
Epoch 2: Train RMSE=0.6694, Val RMSE=0.5534
Epoch 2: Train RMSE=0.6694, Val RMSE=0.5534
Epoch 3: Train RMSE=0.5586, Val RMSE=0.4725
Epoch 3: Train RMSE=0.5586, Val RMSE=0.4725
Epoch 4: Train RMSE=0.5195, Val RMSE=0.4487
Epoch 4: Train RMSE=0.5195, Val RMSE=0.4487
Epoch 5: Train RMSE=0.5137, Val RMSE=0.4386
Epoch 5: Train RMSE=0.5137, Val RMSE=0.4386
Epoch 6: Train RMSE=0.5051, Val RMSE=0.4373
Epoch 6: Train RMSE=0.5051, Val RMSE=0.4373
Epoch 7: Train RMSE=0.5011, Val RMSE=0.4343
Epoch 7: Train RMSE=0.5011, Val RMSE=0.4343
Epoch 8: Train RMSE=0.4912, Val RMSE=0.4348
Epoch 8: Train RMSE=0.4912, Val RMSE=0.4348
Epoch 9: Train RMSE=0.4892, Val RMSE=0.4344
Epoch 9: Train RMSE=0.4892, Val RMSE=0.4344
Epoch 10: Train RMSE=0.4918, Val RMSE=0.4538
Epoch 10: Train RMSE=0.4918, Val RMSE=0.4538
Epoch 11: Train RMSE=0.4855, Val RMSE=0.4291
Epoch 11: Train RMSE=0.4855, Val RMSE=0.4291
Epoch 12: Train RMSE=0.4977, Val RMSE=0.4436
Epoch 12: Train RMSE=0.4977, Val RMSE=0.4436
Epoch 13: Train RMSE=0.4822, Val RMSE=0.4301
Epoch 13: Train RMSE=0.4822, Val RMSE=0.4301
Epoch 14: Train RMSE=0.4816, Val RMSE=0.4298
Epoch 14: Train RMSE=0.4816, Val RMSE=0.4298
Epoch 15: Train RMSE=0.4732, Val RMSE=0.4462
Epoch 15: Train RMSE=0.4732, Val RMSE=0.4462
Epoch 16: Train RMSE=0.4834, Val RMSE=0.4316
Epoch 16: Train RMSE=0.4834, Val RMSE=0.4316
Epoch 17: Train RMSE=0.4755, Val RMSE=0.4298
Epoch 17: Train RMSE=0.4755, Val RMSE=0.4298
Epoch 18: Train RMSE=0.4727, Val RMSE=0.4261
Epoch 18: Train RMSE=0.4727, Val RMSE=0.4261
Epoch 19: Train RMSE=0.4730, Val RMSE=0.4254
Epoch 19: Train RMSE=0.4730, Val RMSE=0.4254
Epoch 20: Train RMSE=0.4751, Val RMSE=0.4175
Epoch 20: Train RMSE=0.4751, Val RMSE=0.4175
Epoch 21: Train RMSE=0.4764, Val RMSE=0.4158
Epoch 21: Train RMSE=0.4764, Val RMSE=0.4158
Epoch 22: Train RMSE=0.4656, Val RMSE=0.4181
Epoch 22: Train RMSE=0.4656, Val RMSE=0.4181
Epoch 23: Train RMSE=0.4697, Val RMSE=0.4270
Epoch 23: Train RMSE=0.4697, Val RMSE=0.4270
Epoch 24: Train RMSE=0.4707, Val RMSE=0.4173
Epoch 24: Train RMSE=0.4707, Val RMSE=0.4173
Epoch 25: Train RMSE=0.4681, Val RMSE=0.4224
Epoch 25: Train RMSE=0.4681, Val RMSE=0.4224
Epoch 26: Train RMSE=0.4753, Val RMSE=0.4380
Epoch 26: Train RMSE=0.4753, Val RMSE=0.4380
Epoch 27: Train RMSE=0.4643, Val RMSE=0.4150
Epoch 27: Train RMSE=0.4643, Val RMSE=0.4150
Epoch 28: Train RMSE=0.4666, Val RMSE=0.4238
Epoch 28: Train RMSE=0.4666, Val RMSE=0.4238
Epoch 29: Train RMSE=0.4619, Val RMSE=0.4219
Epoch 29: Train RMSE=0.4619, Val RMSE=0.4219
Epoch 30: Train RMSE=0.4577, Val RMSE=0.4119
Epoch 30: Train RMSE=0.4577, Val RMSE=0.4119
Epoch 31: Train RMSE=0.4555, Val RMSE=0.4137
Epoch 31: Train RMSE=0.4555, Val RMSE=0.4137
Epoch 32: Train RMSE=0.4545, Val RMSE=0.4155
Epoch 32: Train RMSE=0.4545, Val RMSE=0.4155
Epoch 33: Train RMSE=0.4626, Val RMSE=0.4487
Epoch 33: Train RMSE=0.4626, Val RMSE=0.4487
Epoch 34: Train RMSE=0.4587, Val RMSE=0.4189
Epoch 34: Train RMSE=0.4587, Val RMSE=0.4189
Epoch 35: Train RMSE=0.4566, Val RMSE=0.4269
Epoch 35: Train RMSE=0.4566, Val RMSE=0.4269
Epoch 36: Train RMSE=0.4544, Val RMSE=0.4204
Epoch 36: Train RMSE=0.4544, Val RMSE=0.4204
Epoch 37: Train RMSE=0.4500, Val RMSE=0.4174
Epoch 37: Train RMSE=0.4500, Val RMSE=0.4174
Epoch 38: Train RMSE=0.4527, Val RMSE=0.4140
Epoch 38: Train RMSE=0.4527, Val RMSE=0.4140
Epoch 39: Train RMSE=0.4547, Val RMSE=0.4100
Epoch 39: Train RMSE=0.4547, Val RMSE=0.4100
Epoch 40: Train RMSE=0.4593, Val RMSE=0.4418
Epoch 40: Train RMSE=0.4593, Val RMSE=0.4418
Epoch 41: Train RMSE=0.4632, Val RMSE=0.4087
Epoch 41: Train RMSE=0.4632, Val RMSE=0.4087
Epoch 42: Train RMSE=0.4533, Val RMSE=0.4139
Epoch 42: Train RMSE=0.4533, Val RMSE=0.4139
Epoch 43: Train RMSE=0.4569, Val RMSE=0.4186
Epoch 43: Train RMSE=0.4569, Val RMSE=0.4186
Epoch 44: Train RMSE=0.4497, Val RMSE=0.4256
Epoch 44: Train RMSE=0.4497, Val RMSE=0.4256
Epoch 45: Train RMSE=0.4537, Val RMSE=0.4246
Epoch 45: Train RMSE=0.4537, Val RMSE=0.4246
Epoch 46: Train RMSE=0.4489, Val RMSE=0.4049
Epoch 46: Train RMSE=0.4489, Val RMSE=0.4049
Epoch 47: Train RMSE=0.4426, Val RMSE=0.4034
Epoch 47: Train RMSE=0.4426, Val RMSE=0.4034
Epoch 48: Train RMSE=0.4380, Val RMSE=0.4139
Epoch 48: Train RMSE=0.4380, Val RMSE=0.4139
Epoch 49: Train RMSE=0.4521, Val RMSE=0.4021
Epoch 49: Train RMSE=0.4521, Val RMSE=0.4021
Epoch 50: Train RMSE=0.4417, Val RMSE=0.4077
Epoch 50: Train RMSE=0.4417, Val RMSE=0.4077
fig, axs = plt.subplots(2, 2, figsize=(14, 10))

# Line plots for each dimension
for i in range(preds_test.shape[1]):
    axs[0, i].plot(targets_test[:, i].detach().numpy(), label='Target', color='tab:blue')
    axs[0, i].plot(preds_test[:, i].detach().numpy(), label='Prediction', color='tab:orange')
    axs[0, i].set_title(f'Lineplot: Dimension {i+1}', fontsize=16)
    axs[0, i].set_xlabel('Time Step', fontsize=14)
    axs[0, i].set_ylabel('Value', fontsize=14)
    axs[0, i].legend(fontsize=12)

# Scatter plots for each dimension
for i in range(preds_test.shape[1]):
    axs[1, i].scatter(targets_test[:, i].detach().numpy(), preds_test[:, i].detach().numpy(), alpha=0.5)
    axs[1, i].set_title(f'Scatter: Pred vs Target (Dim {i+1})', fontsize=16)
    axs[1, i].set_xlabel('Target', fontsize=14)
    axs[1, i].set_ylabel('Prediction', fontsize=14)

plt.tight_layout()
plt.show()

png

Diagnostics and plots

We visualize distributions of transformed features, predictions vs targets, and residuals. These help assess calibration and model fit quality.

  • No train/test split changes here; evaluation pertains to the synthetic split defined earlier.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages