Probabilistic Forecasting and Information-Theoretic Analysis of Multivariate fMRI Dynamics.
The 20 matches
- [1] § 2. Materials and Methods › 2.4. Forecasting Models › 2.4.4. Transformer ↔ models/transformer.py, lines 19–93 · score 0.81 · transformer encoder layers, attention heads, embedded, positions, vector, stacked
- [2] § 2. Materials and Methods › 2.5. Training and Evaluation Protocol ↔ utils/training.py, lines 86–235 · score 0.80 · learning rate scheduling, AdamW, weight decay, optimizer, loss, training
- [3] § 2. Materials and Methods › 2.4. Forecasting Models › 2.4.3. Long Short-Term Memory Network ↔ models/lstm.py, lines 18–67 · score 0.80 · LSTM layers, recurrent layers, hidden state, multi layer, dropout, batch
- [4] § 2. Materials and Methods › 2.4. Forecasting Models › 2.4.3. Long Short-Term Memory Network ↔ models/lstm.py, lines 18–67 · score 0.74 · recurrent layer, hidden state, full forecasting, reshaped, maps, linear
- [5] § 2. Materials and Methods › 2.4. Forecasting Models › 2.4.4. Transformer ↔ scripts/cross_validation.py, lines 60–108 · score 0.73 · transformer encoder layers, attention heads, forecasting horizon, dropout, dimension, training
- [6] § 2. Materials and Methods › 2.4. Forecasting Models › 2.4.4. Transformer ↔ models/transformer.py, lines 19–93 · score 0.73 · Learned positional embeddings, ROI vector, layer, space, transformer, linear
- [7] § 2. Materials and Methods › 2.2. Forecasting Problem Formulation ↔ utils/measures.py, lines 741–806 · score 0.69 · Empirical residual histograms, predictive distribution, model predicted, binning, discretized, forecasting models
- [8] § 2. Materials and Methods › 2.1. Dataset and Region-of-Interest Definition ↔ utils/nsd_utils.py, lines 353–433 · score 0.66 · MNI space, ROI atlas, ANTs, affine, scan, warped
- [9] § 2. Materials and Methods › 2.1. Dataset and Region-of-Interest Definition ↔ utils/nsd_utils.py, lines 353–433 · score 0.66 · MNI space, ROI atlas, ANTs, affine, scan, warped
- [10] § 2. Materials and Methods › 2.5. Training and Evaluation Protocol ↔ utils/training.py, lines 430–488 · score 0.66 · LOSO folds, LOSO CV, cross validation, forecasting models, training
- [11] § 2. Materials and Methods › 2.6. Directed Information Post Analysis ↔ utils/measures.py, lines 741–806 · score 0.65 · empirical residual histogram, predictive distributions, model prediction, probability, horizon, ROI
- [12] § 2. Materials and Methods › 2.1. Dataset and Region-of-Interest Definition ↔ utils/nsd_utils.py, lines 1095–1143 · score 0.65 · LO1, VO1, LO2, PPA, V3A, VO2
- [13] § 2. Materials and Methods › 2.1. Dataset and Region-of-Interest Definition ↔ utils/nsd_utils.py, lines 1090–1138 · score 0.65 · LO1, VO1, LO2, PPA, V3A, VO2
- [14] § 2. Materials and Methods › 2.2. Forecasting Problem Formulation ↔ utils/measures.py, lines 168–172 · score 0.64 · Squared Scaled Error, step forecasting, RMSSE, Root, predictive
- [15] § 3. Results › Post Hoc Directed Information Analysis ↔ scripts/compute_di.py, lines 75–135 · score 0.59 · DI computation, source ROI, target ROI, likelihoods, probabilistic, training
- [16] § 2. Materials and Methods › 2.5. Training and Evaluation Protocol ↔ scripts/cross_validation.py, lines 111–234 · score 0.58 · cross validation, LOSO CV, fold, held, training, Model
- [17] § 2. Materials and Methods › 2.4. Forecasting Models › 2.4.2. Exponential Smoothing ↔ models/exponential_smoothing.py, lines 1–12 · score 0.57 · exponential smoothing model, fMRI, global, predictions, forecasting
- [18] § 3. Results ↔ utils/parse_data.py, lines 334–393 · score 0.56 · phase randomization, amplitude, power, spectrum, surrogate, variance
- [19] § 2. Materials and Methods › 2.3. Data Parsing and Sliding-Window Construction ↔ utils/parse_data.py, lines 243–331 · score 0.53 · sliding window, target windows, overlapping, Parsing
- [20] § 2. Materials and Methods › 2.5. Training and Evaluation Protocol ↔ utils/training.py, lines 430–488 · score 0.51 · LOSO folds, cross validated, Training, predictability, Model, forecasting
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Python · 643 lines · 21 KB · Apache-2.0 · 3 matches
- import copy
- import inspect
- import pandas as pd
- import numpy as np
- from tqdm.auto import tqdm
- from pathlib import Path
- import torch
- import torch.nn as nn
- from torch.utils.data import Dataset, DataLoader
- from .measures import compute_eta_gauss, compute_rmse, compute_rmsse
- from .parse_data import split_by_subject, normalize_items, build_sliding_windows
- try:
- from models.naive_models import last_value_model_generator
- except ImportError:
- from ..models.naive_models import last_value_model_generator
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
- class FMRIWindowDataset(Dataset):
- """PyTorch dataset for fMRI windowed sequences."""
- def __init__(self, X, Y=None):
- self.X = torch.tensor(X, dtype=torch.float32)
- self.Y = None
- if Y is not None:
- self.Y = torch.tensor(Y, dtype=torch.float32)
- def __len__(self):
- return len(self.X)
- def __getitem__(self, idx):
- if self.Y is not None:
- return self.X[idx], self.Y[idx]
- return self.X[idx]
- class DeltaAwareLoss(nn.Module):
- """
- Combined loss: HuberLoss + delta (change) penalty.
- Combines a robust HuberLoss on absolute values with a penalty on incorrect
- forecast dynamics (differences between consecutive timesteps). This encourages
- the model to not only predict accurate values but also preserve the temporal
- structure and rate of change in the fMRI time series.
- Attributes:
- alpha (float): Weight for the delta loss term. Controls the trade-off between
- prediction accuracy and dynamics preservation.
- base (nn.HuberLoss): Huber loss instance used for both absolute and delta terms.
- Input shapes:
- - pred: (batch_size, horizon, n_roi) predicted fMRI connectivity values
- - target: (batch_size, horizon, n_roi) ground truth fMRI connectivity values
- Output:
- - scalar tensor: weighted combination of base loss and delta loss
- Example:
- >>> criterion = DeltaAwareLoss(alpha=0.3, delta=0.5)
- >>> pred = torch.randn(32, 5, 100) # batch=32, horizon=5, n_roi=100
- >>> target = torch.randn(32, 5, 100)
- >>> loss = criterion(pred, target) # returns scalar
- """
- def __init__(self, alpha=0.3, delta=0.5):
- super().__init__()
- self.alpha = alpha
- self.base = nn.HuberLoss(delta=delta)
- def forward(self, pred, target):
- base_loss = self.base(pred, target)
- pred_delta = pred[:, 1:, :] - pred[:, :-1, :]
- target_delta = target[:, 1:, :] - target[:, :-1, :]
- delta_loss = self.base(pred_delta, target_delta)
- return base_loss + self.alpha * delta_loss
- def train_model(
- model,
- train_loader,
- val_loader=None,
- num_epochs=30,
- device=None,
- patience=5,
- checkpoint_dir=None,
- checkpoint_prefix="forecast_model",
- checkpoint_every=None,
- save_best=True,
- save_last=False,
- verbose=True
- ):
- if device is None:
- device = torch.device("cpu")
- elif isinstance(device, str):
- device = torch.device(device)
- if type(checkpoint_dir) is str:
- checkpoint_dir = Path(checkpoint_dir)
- if checkpoint_dir is not None:
- checkpoint_dir.mkdir(parents=True, exist_ok=True)
- optimizer = torch.optim.AdamW(
- model.parameters(), lr=5e-4, weight_decay=1e-5
- )
- scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
- optimizer, mode="min", factor=0.5, patience=patience
- )
- criterion = DeltaAwareLoss(alpha=0.3, delta=0.5)
- best_val_loss = float("inf")
- patience_counter = 0
- best_state = None
- def _save_checkpoint(path, epoch, train_loss, val_loss=None, is_best=False):
- if checkpoint_dir is None:
- return
- torch.save(
- {
- "epoch": epoch,
- "model_state_dict": model.state_dict(),
- "optimizer_state_dict": optimizer.state_dict(),
- "scheduler_state_dict": scheduler.state_dict(),
- "train_loss": train_loss,
- "val_loss": val_loss,
- "best_val_loss": best_val_loss,
- "is_best": is_best,
- },
- path,
- )
- for epoch in range(num_epochs):
- model.train()
- total_loss = 0.0
- loop = tqdm(train_loader, desc=f"Epoch {epoch+1}/{num_epochs}", leave=False)
- for X_batch, Y_batch in loop:
- X_batch = X_batch.to(device, non_blocking=True)
- Y_batch = Y_batch.to(device, non_blocking=True)
- optimizer.zero_grad()
- loss = criterion(model(X_batch), Y_batch)
- loss.backward()
- optimizer.step()
- total_loss += loss.item()
- loop.set_postfix(loss=f"{loss.item():.4f}")
- avg_loss = total_loss / len(train_loader)
- scheduler.step(avg_loss)
- if val_loader is not None:
- model.eval()
- val_loss = 0.0
- with torch.no_grad():
- for xb, yb in val_loader:
- val_loss += criterion(
- model(xb.to(device)), yb.to(device)
- ).item()
- val_loss /= len(val_loader)
- if verbose:
- print(f" Epoch {epoch+1:2d} | train: {avg_loss:.6f} | val: {val_loss:.6f}")
- if val_loss < best_val_loss:
- best_val_loss = val_loss
- best_state = {k: v.clone() for k, v in model.state_dict().items()}
- patience_counter = 0
- if save_best and checkpoint_dir is not None:
- _save_checkpoint(
- checkpoint_dir / f"{checkpoint_prefix}_best.pt",
- epoch=epoch + 1,
- train_loss=avg_loss,
- val_loss=val_loss,
- is_best=True,
- )
- else:
- patience_counter += 1
- if verbose:
- print(f" No improvement ({patience_counter}/{patience})")
- if patience_counter >= patience:
- if verbose:
- print(f" Early stopping at epoch {epoch+1}")
- break
- else:
- if verbose:
- print(f" Epoch {epoch+1:2d} | train loss: {avg_loss:.6f}")
- if avg_loss < best_val_loss:
- best_val_loss = avg_loss
- best_state = {k: v.detach().cpu().clone() for k, v in model.state_dict().items()}
- if save_best and checkpoint_dir is not None:
- _save_checkpoint(
- checkpoint_dir / f"{checkpoint_prefix}_best.pt",
- epoch=epoch + 1,
- train_loss=avg_loss,
- val_loss=None,
- is_best=True,
- )
- if (
- checkpoint_dir is not None
- and checkpoint_every is not None
- and checkpoint_every > 0
- and (epoch + 1) % checkpoint_every == 0
- ):
- _save_checkpoint(
- checkpoint_dir / f"{checkpoint_prefix}_epoch{epoch+1:03d}.pt",
- epoch=epoch + 1,
- train_loss=avg_loss,
- val_loss=val_loss if val_loader is not None else None,
- is_best=False,
- )
- if save_last and checkpoint_dir is not None:
- _save_checkpoint(
- checkpoint_dir / f"{checkpoint_prefix}_last.pt",
- epoch=epoch + 1,
- train_loss=avg_loss,
- val_loss=val_loss if val_loader is not None else None,
- is_best=False,
- )
- if best_state is not None:
- model.load_state_dict(best_state)
- if verbose:
- print(f" Best val loss: {best_val_loss:.6f} | weights restored")
- return model
- def get_predictions(model, loader, device=None):
- """Runs inference and returns concatenated predictions."""
- if device is None:
- device = next(model.parameters()).device
- model = model.to(device)
- model.eval()
- all_preds = []
- with torch.no_grad():
- for batch in loader:
- if isinstance(batch, (tuple, list)):
- xb = batch[0]
- else:
- xb = batch
- xb = xb.to(device, non_blocking=True)
- preds = model(xb)
- all_preds.append(preds.detach().cpu().numpy())
- return np.concatenate(all_preds, axis=0)
- def _is_torch_model(model):
- return isinstance(model, nn.Module)
- def _flatten_model_inputs(X):
- """Convert windowed inputs (N, M, ROI) into tabular features for sklearn models."""
- return X.reshape(X.shape[0], -1)
- def _flatten_model_targets(Y):
- """Convert forecasting targets into 2D multi-output targets for sklearn models."""
- return Y.reshape(Y.shape[0], -1)
- def _reshape_predictions(preds, target_shape):
- """Restore flattened sklearn predictions back to forecasting shape."""
- preds = np.asarray(preds, dtype=np.float32)
- if preds.shape == target_shape:
- return preds
- if preds.ndim == 1:
- preds = preds[:, None]
- if preds.ndim == 2:
- return preds.reshape(target_shape)
- raise ValueError(
- f"Could not reshape predictions from {preds.shape} to {target_shape}"
- )
- def _expects_windowed_input(model):
- return getattr(model, "expects_windowed_input", False)
- def _clone_model(model):
- """Best-effort clone for either PyTorch modules or sklearn estimators."""
- if _is_torch_model(model):
- return {k: v.detach().cpu().clone() for k, v in model.state_dict().items()}
- return copy.deepcopy(model)
- def train_forecasting_model(
- model,
- X_train,
- Y_train,
- X_val=None,
- Y_val=None,
- batch_size=512,
- num_epochs=30,
- device=None,
- patience=5,
- checkpoint_dir=None,
- checkpoint_prefix="forecast_model",
- checkpoint_every=None,
- save_best=True,
- save_last=False,
- verbose=True
- ):
- """
- Train either a PyTorch forecasting model or an sklearn-style estimator.
- """
- if _is_torch_model(model):
- train_loader = DataLoader(
- FMRIWindowDataset(X_train, Y_train),
- batch_size=batch_size,
- shuffle=True,
- pin_memory=True,
- )
- val_loader = None
- if X_val is not None and Y_val is not None and len(X_val) > 0:
- val_loader = DataLoader(
- FMRIWindowDataset(X_val, Y_val),
- batch_size=batch_size,
- shuffle=False,
- pin_memory=True,
- )
- return train_model(
- model,
- train_loader,
- val_loader=val_loader,
- num_epochs=num_epochs,
- device=device,
- patience=patience,
- checkpoint_dir=checkpoint_dir,
- checkpoint_prefix=checkpoint_prefix,
- checkpoint_every=checkpoint_every,
- save_best=save_best,
- save_last=save_last,
- verbose=verbose,
- )
- if hasattr(model, "fit") and hasattr(model, "predict"):
- if _expects_windowed_input(model):
- model.fit(X_train, Y_train)
- else:
- X_train_flat = _flatten_model_inputs(X_train)
- Y_train_flat = _flatten_model_targets(Y_train)
- model.fit(X_train_flat, Y_train_flat)
- return model
- raise TypeError(
- "Unsupported model type. Expected a torch.nn.Module or an estimator "
- "with fit/predict methods."
- )
- def predict_forecasting_model(model, X, batch_size=512, device=None):
- """Run inference for either a PyTorch forecasting model or an sklearn estimator."""
- if _is_torch_model(model):
- test_loader = DataLoader(
- FMRIWindowDataset(X),
- batch_size=batch_size,
- shuffle=False,
- pin_memory=True,
- )
- return get_predictions(model, test_loader, device)
- if hasattr(model, "predict"):
- if _expects_windowed_input(model):
- preds = model.predict(X)
- else:
- preds = model.predict(_flatten_model_inputs(X))
- return preds
- raise TypeError(
- "Unsupported model type. Expected a torch.nn.Module or an estimator "
- "with a predict method."
- )
- def _make_model(model_gen, n_roi=None, M=None, H=None):
- """
- Instantiate model_gen while preserving compatibility with zero-arg notebook
- lambdas and newer factories that accept fold dimensions.
- """
- try:
- signature = inspect.signature(model_gen)
- except (TypeError, ValueError):
- return model_gen()
- kwargs = {}
- for name in signature.parameters:
- if name in {"n_roi", "input_size", "input_dim"} and n_roi is not None:
- kwargs[name] = n_roi
- elif name in {"M", "window_size"} and M is not None:
- kwargs[name] = M
- elif name in {"H", "output_horizon", "horizon"} and H is not None:
- kwargs[name] = H
- if kwargs:
- return model_gen(**kwargs)
- return model_gen()
- def horizon_rmse(y_true, y_pred):
- """Compute and print RMSE separately for each forecast step."""
- horizon_scores = []
- print("\nHorizon-wise RMSE:")
- for h in range(y_true.shape[1]):
- r = compute_rmse(y_true[:, h, :], y_pred[:, h, :])
- horizon_scores.append(r)
- print(f" Step {h+1} RMSE: {r:.6f}")
- return horizon_scores
- def run_loso_cv(dataset_raw, model_gen, M=50, H=3, stride=1,
- num_epochs=20, batch_size=512, device=device,
- checkpoint_dir=None, checkpoint_prefix="forecast_model",
- checkpoint_every=None, save_best=True, save_last=False,
- results_path="loso_results.csv", patience=5,
- compute_naive_rmse=True):
- """
- Leave-One-Subject-Out Cross Validation (LOSO-CV).
- Supports both:
- - PyTorch forecasting models with the existing training loop
- - sklearn-style estimators exposing fit(X, y) and predict(X)
- Set ``compute_naive_rmse=False`` when a caller computes the last-value
- baseline once and joins those scores onto multiple model result tables.
- Returns:
- df - LOSO summary dataframe
- last_trained_model - model from the last fold
- last_X_test - test windows from the last fold
- last_Y_test - test targets from the last fold
- best_model - model with highest eta across all folds
- best_X_test - test windows of the best eta fold
- best_Y_test - test targets of the best eta fold
- """
- if checkpoint_dir is not None:
- checkpoint_dir = Path(checkpoint_dir)
- checkpoint_dir.mkdir(parents=True, exist_ok=True)
- subjects = sorted(set(d["subject"] for d in dataset_raw))
- n_subjects = len(subjects)
- print(f"\n{'='*60}")
- print(f"LOSO-CV | {n_subjects} subjects | M={M}, H={H}")
- print(f"{'='*60}")
- fold_results = []
- last_trained_model = None
- last_X_test = None
- last_Y_test = None
- best_eta_model = None
- best_eta_score = -float("inf")
- best_eta_subject = None
- best_X_test = None
- best_Y_test = None
- best_n_roi = None
- for fold_i, test_subj in enumerate(tqdm(subjects, desc="LOSO Folds")):
- print(f"\nFold {fold_i+1}/{n_subjects} | Test subject: {test_subj}")
- train_items, test_items = split_by_subject(
- dataset_raw,
- test_subjects=[test_subj]
- )
- train_norm = normalize_items(train_items)
- test_norm = normalize_items(test_items)
- X_tr, Y_tr = build_sliding_windows(train_norm, M, H, stride)
- X_te, Y_te = build_sliding_windows(test_norm, M, H, stride)
- if len(X_tr) == 0 or len(X_te) == 0:
- print("Skipping fold (no valid windows)")
- continue
- val_split = int(len(X_tr) * 0.9)
- X_val, Y_val = X_tr[val_split:], Y_tr[val_split:]
- X_tr, Y_tr = X_tr[:val_split], Y_tr[:val_split]
- n_roi = X_tr.shape[2]
- print(f"Train windows: {len(X_tr)} | Val windows: {len(X_val)} "
- f"| Test windows: {len(X_te)} | ROIs: {n_roi}")
- try:
- model = _make_model(model_gen, n_roi=n_roi, M=M, H=H)
- if _is_torch_model(model):
- model = model.to(device)
- except AttributeError as e:
- print(f"Model is not CUDA compatible: {e}\nContinuing with CPU...")
- model = _make_model(model_gen, n_roi=n_roi, M=M, H=H)
- fold_checkpoint_dir = None
- if checkpoint_dir is not None and _is_torch_model(model):
- fold_checkpoint_dir = checkpoint_dir
- print(f"Training (max {num_epochs} epochs, early stopping patience={patience})...")
- model = train_forecasting_model(
- model,
- X_tr,
- Y_tr,
- X_val=X_val,
- Y_val=Y_val,
- batch_size=batch_size,
- num_epochs=num_epochs,
- device=device,
- patience=patience,
- checkpoint_dir=fold_checkpoint_dir,
- checkpoint_prefix=f"{checkpoint_prefix}_fold{fold_i+1:02d}_{test_subj}",
- checkpoint_every=checkpoint_every,
- save_best=save_best,
- save_last=save_last,
- )
- all_preds = predict_forecasting_model(
- model,
- X_te,
- batch_size=batch_size,
- device=device,
- )
- all_preds = _reshape_predictions(all_preds, Y_te.shape)
- all_targets = Y_te
- model_r = compute_rmse(all_targets, all_preds)
- model_rmsse = compute_rmsse(all_targets, all_preds)
- eta = compute_eta_gauss(all_targets, all_preds)
- naive_r = np.nan
- beat_naive = pd.NA
- if compute_naive_rmse:
- naive_model = last_value_model_generator(H=H)
- naive_model = train_forecasting_model(
- naive_model,
- X_tr,
- Y_tr,
- verbose=False,
- )
- naive_preds = predict_forecasting_model(
- naive_model,
- X_te,
- batch_size=batch_size,
- device=device,
- )
- naive_preds = _reshape_predictions(naive_preds, Y_te.shape)
- naive_r = compute_rmse(all_targets, naive_preds)
- beat_naive = model_r < naive_r
- print(f"\nResults:")
- print(f" MODEL RMSE : {model_r:.6f}")
- if compute_naive_rmse:
- print(f" Naive RMSE : {naive_r:.6f}")
- print(f" eta : {eta:.4f}")
- if compute_naive_rmse:
- print(f" Beat naive : {'YES' if beat_naive else 'NO'}")
- hor_rmse = horizon_rmse(all_targets, all_preds)
- fold_results.append({
- "test_subject": test_subj,
- "Model_RMSE": round(model_r, 6),
- "Naive_RMSE": round(naive_r, 6),
- "Model_RMSSE": round(model_rmsse, 6),
- "eta": round(eta, 4),
- "beat_naive": beat_naive,
- })
- last_trained_model = model
- last_X_test = X_te
- last_Y_test = Y_te
- if eta > best_eta_score:
- best_eta_score = eta
- best_eta_subject = test_subj
- best_eta_model = _clone_model(model)
- best_X_test = X_te.copy()
- best_Y_test = Y_te.copy()
- best_n_roi = n_roi
- print(f" New best eta model saved: {test_subj} (eta={eta:.4f})")
- del X_tr, Y_tr, X_val, Y_val
- del train_norm, test_norm
- torch.cuda.empty_cache()
- print(f"\n{'='*60}")
- print("LOSO-CV SUMMARY")
- print(f"{'='*60}")
- df = pd.DataFrame(fold_results)
- if df.empty:
- raise ValueError(
- "LOSO-CV produced no valid folds. Check subject count and window "
- f"settings M={M}, H={H}, stride={stride}."
- )
- print(df.to_string(index=False))
- print(f"\nMean Model RMSE : {df['Model_RMSE'].mean():.6f}")
- if compute_naive_rmse:
- print(f"Mean Naive RMSE : {df['Naive_RMSE'].mean():.6f}")
- print(f"Mean eta : {df['eta'].mean():.4f}")
- print(f"Mean RMSSE : {df['Model_RMSSE'].mean():.6f}")
- if compute_naive_rmse:
- print(f"Folds beat naive : {df['beat_naive'].sum()} / {len(df)}")
- if results_path is not None:
- results_path = Path(results_path)
- results_path.parent.mkdir(parents=True, exist_ok=True)
- df.to_csv(results_path, index=False)
- print(f"\nResults saved to {results_path}")
- print(f"\nBest eta fold : {best_eta_subject} (eta={best_eta_score:.4f})")
- best_model = _make_model(model_gen, n_roi=best_n_roi, M=M, H=H)
- if _is_torch_model(best_model):
- best_model = best_model.to(device)
- if best_eta_model is not None:
- best_model.load_state_dict(best_eta_model)
- best_model.eval()
- elif best_eta_model is not None:
- best_model = best_eta_model
- return df, last_trained_model, last_X_test, last_Y_test, \
- best_model, best_X_test, best_Y_test
training.py at commit a064b79, under Apache-2.0 · at the source
Overview
- Department of Electrical & Computer Engineering, Rice University, Houston, TX 77005, USA; (Z.Z.); (B.A.)
- Department of Electrical & Electronics Engineering, Özyeğin University, 34794 Istanbul, Türkiye
- Department of Urology, Houston Methodist, Houston, TX 77030, USA
Abstract
Functional magnetic resonance imaging (fMRI) signals exhibit complex temporal structure arising from multivariate neural dynamics, physiological variability, and measurement uncertainty. In this work, we formulate region-of-interest-level
Reproduced under the paper's license (CC BY), from the paper cited above.
Repositories
Its files are read in the Code ↔ Paper reader above, with 20 matches between paragraphs and lines of code.
ab126/fmri_forecasting
a064b79113462d62a1c3808f3e3008fe8cf8df31, 15 September 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
30 files
- __init__.py, Python, 1 line
- compute_di.ipynb, Jupyter, 342 lines
- fetch_NSD_data.ipynb, Jupyter, 130 lines
- forecast_models.ipynb, Jupyter, 156 lines
- miscellaneous.ipynb, Jupyter, 263 lines
- models/
.ipynb_checkpoints/ , Jupyter, 90 linesforecast_models-checkpoi nt.ipynb - models/
__init__.py , Python, 1 line - models/
exponential_smoothing.py , Python, 165 lines, 1 match - models/
linear.py , Python, 24 lines - models/
lstm.py , Python, 140 lines, 2 matches - models/
naive_models.py , Python, 133 lines - models/
transformer.py , Python, 296 lines, 2 matches - permutation_test.ipynb, Jupyter, 112 lines
- scripts/
compute_di.py , Python, 136 lines, 1 match - scripts/
cross_validation.py , Python, 238 lines, 2 matches - scripts/
fetch_data.py , Python, 63 lines - scripts/
test_models.py , Python, 423 lines - test/
__init__.py , Python, 1 line - test/
test_compute_di.py , Python, 77 lines - test/
test_nsd_utils.py , Python, 77 lines - utils/
__init__.py , Python, 1 line - utils/
connectivity.py , Python, 71 lines - utils/
measures.py , Python, 866 lines, 3 matches - utils/
nsd_utils.py , Python, 1,239 lines, 2 matches - utils/
parse_data.py , Python, 414 lines, 2 matches - utils/
plotting.py , Python, 718 lines - utils/
training.py , Python, 643 lines, 3 matches - utils/
validation.py , Python, 132 lines - LICENSE, License, 201 lines
- README.md, Text, 324 lines
Zenodo 20341604
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
- 27 September 2026: the link answers (HTTP 200)
29 files
- __init__.py, Python, 1 line
- compute_di.ipynb, Jupyter, 342 lines
- fetch_NSD_data.ipynb, Jupyter, 130 lines
- forecast_models.ipynb, Jupyter, 156 lines
- miscellaneous.ipynb, Jupyter, 263 lines
- models/
.ipynb_checkpoints/ , Jupyter, 90 linesforecast_models-checkpoi nt.ipynb - models/
__init__.py , Python, 1 line - models/
exponential_smoothing.py , Python, 165 lines - models/
linear.py , Python, 24 lines - models/
lstm.py , Python, 140 lines - models/
naive_models.py , Python, 133 lines - models/
transformer.py , Python, 296 lines - permutation_test.ipynb, Jupyter, 112 lines
- scripts/
compute_di.py , Python, 136 lines - scripts/
cross_validation.py , Python, 238 lines - scripts/
fetch_data.py , Python, 49 lines - scripts/
test_models.py , Python, 423 lines - test/
__init__.py , Python, 1 line - test/
test_compute_di.py , Python, 77 lines - utils/
__init__.py , Python, 1 line - utils/
connectivity.py , Python, 70 lines - utils/
measures.py , Python, 866 lines - utils/
nsd_utils.py , Python, 1,233 lines, 2 matches - utils/
parse_data.py , Python, 414 lines - utils/
plotting.py , Python, 718 lines - utils/
training.py , Python, 643 lines - utils/
validation.py , Python, 132 lines - LICENSE, License, 201 lines
- README.md, Text, 202 lines
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 55 scripts, each with its path and the digest of its content;
- 20 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data Availability Statement
The Natural Scenes Dataset (NSD) analyzed in this study is publicly available from the original NSD release [17]. The code used for forecasting, entropy estimation, and directed information analysis is publicly available at https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 5 authors, 10 keywords, 1 funder, 21 references.
Cite
This paper
Bayer, A., Zhang, Z., Ipek, A. E., Khavari, R., & Aazhang, B. (2026). Probabilistic Forecasting and Information-Theoretic Analysis of Multivariate fMRI Dynamics. Entropy (Basel, Switzerland), 28(7), 738. https://
BibTeX
@article{bayer2026probab
author = {Bayer, Arda and Zhang, Zhiyao and Ipek, Ahmet Emre and Khavari, Rose and Aazhang, Behnaam},
title = {{Probabilistic Forecasting and Information-Theoretic Analysis of Multivariate fMRI Dynamics}},
journal = {Entropy (Basel, Switzerland)},
year = {2026},
month = jul,
volume = {28},
number = {7},
pages = {738},
publisher = {Multidisciplinary Digital Publishing Institute (MDPI)},
issn = {1099-4300},
doi = {10.3390/
url = {https://
pmid = {42511348},
pmcid = {PMC13409728}
}
RIS
TY - JOUR
AU - Bayer, Arda
AU - Zhang, Zhiyao
AU - Ipek, Ahmet Emre
AU - Khavari, Rose
AU - Aazhang, Behnaam
TI - Probabilistic Forecasting and Information-Theoretic Analysis of Multivariate fMRI Dynamics
T2 - Entropy (Basel, Switzerland)
J2 - Entropy (Basel)
PY - 2026
DA - 2026/
VL - 28
IS - 7
SP - 738
SN - 1099-4300
PB - Multidisciplinary Digital Publishing Institute (MDPI)
DO - 10.3390/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.3390/
"type": "article-journal",
"title": "Probabilistic Forecasting and Information-Theoretic Analysis of Multivariate fMRI Dynamics",
"container-title": "Entropy (Basel, Switzerland)",
"author": [
{
"family": "Bayer",
"given": "Arda"
},
{
"family": "Zhang",
"given": "Zhiyao"
},
{
"family": "Ipek",
"given": "Ahmet Emre"
},
{
"family": "Khavari",
"given": "Rose"
},
{
"family": "Aazhang",
"given": "Behnaam"
}
],
"container-title-short":
"volume": "28",
"issue": "7",
"page": "738",
"DOI": "10.3390/
"PMID": "42511348",
"PMCID": "PMC13409728",
"ISSN": "1099-4300",
"publisher": "Multidisciplinary Digital Publishing Institute (MDPI)",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
1
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.7554/elife.107933 [code]
- Modality-agnostic decoding of vision and language from fMRI.Journal: eLifeIn common: Nilearn, statsmodels, NiBabel, 6 other tools, fMRI, 3 references
- [2] doi:10.1038/s41597-026-07248-6 [code]
- A large-scale fMRI dataset for vision-language semantic association.Journal: Scientific dataIn common: ANTs, Nilearn, NiBabel, 5 other tools, fMRI, 2 references
- [3] doi:10.1162/netn.a.547 [code]
- An evaluation of the efficacy of single-echo and multi-echo fMRI denoising strategies.Journal: Network neuroscience (Cambridge, Mass.)In common: ANTs, Nilearn, statsmodels, 6 other tools, fMRI, 1 reference
- [4] doi:10.1016/j.neuroimage.2026.122171 [code]
- A conserved node degree-based backbone and flexible hub organization of brain connectome during naturalistic movie watching.Journal: NeuroImageIn common: ANTs, Nilearn, NiBabel, 4 other tools, fMRI, 2 references
- [5] doi:10.1162/imag.a.1286 [code]
- Behavioral imitation with artificial neural networks leads to personalized models of brain dynamics during videogame play.Journal: Imaging neuroscience (Cambridge, Mass.)In common: Nilearn, statsmodels, NiBabel, 6 other tools, fMRI, 1 reference
- [6] doi:10.1162/imag.a.1256 [code]
- Gamer in the scanner: Event-related analysis of fMRI activity during retro videogame play guided by automated annotations of game content.Journal: Imaging neuroscience (Cambridge, Mass.)In common: Nilearn, statsmodels, NiBabel, 6 other tools, fMRI, 1 reference
- [7] doi:10.1038/s41467-026-76452-0 [code]
- Music evokes shared neural representations of imagined narratives across sensory modalities.Journal: Nature communicationsIn common: ANTs, Nilearn, statsmodels, 7 other tools
- [8] doi:10.1371/journal.pbio.3003856 [code]
- Aging and metabolism contribute separately to brain-body health.Journal: PLoS biologyIn common: ANTs, Nilearn, statsmodels, 7 other tools
- [9] doi:10.1038/s41467-026-71151-2 [code]
- Common and distinct neural correlates of social interaction processing and theory of mind in narratives.Journal: Nature communicationsIn common: ANTs, Nilearn, statsmodels, 6 other tools, 1 reference
- [10] doi:10.1162/imag.a.1252 [code]
- Does the brain's E:I balance really shape long-range temporal correlations? Lessons learned from 3T MRI.Journal: Imaging neuroscience (Cambridge, Mass.)In common: ANTs, NiBabel, seaborn, 4 other tools, fMRI, 2 references
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 2 repositories of the authors' code, each at its verified commit and with its license, 55 scripts, and 20 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:5c7380980f0b2f1d…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
