Tasks
A task pairs a model prototype with an evaluation protocol: it slices the data into windows, initializes the model from the shapes it finds, trains it, scores it and calibrates prediction intervals. The model is a parameter of the task, so moving a model between tasks means swapping the task class, not the model.
Task |
Question it answers |
Default metrics |
|---|---|---|
|
What do the next |
MSE / MAE / RMSE |
|
The last few days are still being reported – what will the final counts be? |
MSE / MAE / RMSE |
|
What would have happened under a different intervention? |
PEHE / ATE error |
|
Which nodes are the outbreak sources? |
accuracy / macro-F1 |
All four are importable from epilearn.tasks; the two new tasks also have
short aliases:
from epilearn.tasks import Forecast, Detection, NowcastTask, ScenarioTask
from epilearn.tasks import Nowcast, Scenario # aliases of the two above
This page is the reference: keywords, return shapes, and the trap each task carries. The runnable scripts live elsewhere – Quickstart for EpiLearn for a complete forecasting pipeline, Pipeline for Epidemic Modeling for a step-by-step walkthrough of all four tasks with the output each one prints.
Constructing a task
task = Forecast(prototype=STGCN, lookback=12, horizon=3, device='cpu')
Argument |
Meaning |
|---|---|
|
The model class, re-instantiated per fold with |
|
Input window length, steps to predict, and the gap between the two ( |
|
|
Rolling-window training
rolling_train is the primary entry point for every task, and it replaces the old
train_model(dataset=..., train_rate=..., val_rate=...) call, which no longer
splits the data for you (see MIGRATION.md). One call walks a rolling origin across
the series, training a fresh model per fold, calibrating a conformal quantile on that
fold’s validation window and scoring its test window:
fold 1: |--- train ---|- val -|- test -|
fold 2: |------ train ------|- val -|- test -| (expanding=True)
fold 3: |-------- train --------|- val -|- test -|
result = task.rolling_train(dataset=dataset, train_size=400, val_size=50,
test_size=50, epochs=50, batch_size=5)
print(result['aggregate_metrics'])
Argument |
Meaning |
|---|---|
|
Timesteps in the first training window, in the window held out for early
stopping and conformal calibration, and scored per fold. Keep
|
|
How far the origin advances per fold (default |
|
Miscoverage target, |
|
Tune hyperparameters per fold with Optuna. Search ranges come from
|
|
Metric names to compute, default |
The return value is a dictionary with seven entries: fold_results (per fold:
its metrics, conformal_quantile, coverage, interval_width,
val_residuals, test_split, process_history, best_params),
aggregate_metrics (n_folds plus <metric>_mean / <metric>_std
across folds), conformal_alpha, all_predictions, all_targets,
conformal_intervals and runtime.
Warning
Seven entries only on success. A single failing fold is caught and reported, but
if every fold fails the return value degrades to {'fold_results': [],
'aggregate_metrics': None}, so aggregate_metrics['mse_mean'] raises
TypeError and result['all_predictions'] raises KeyError.
Note
Split-conformal intervals come for free – there is no separate calibration
step – but the two coverage numbers EpiLearn reports are not the same
quantity: the per-fold coverage here is marginal (fraction of
individual (sample, region, horizon-step) entries inside their interval)
while epilearn.utils.uncertainty.static_conformal()['coverage'] is
joint (fraction of samples whose whole horizon is inside the band), and
joint coverage is always the smaller number.
Note
Metrics are computed on whatever scale the model trained on: with
transforms.normalize_target() attached they are in normalized units, and
only evaluate_model(inverse_normalize=True) reports original units.
- BaseTask.rolling_train(dataset, train_size: int, test_size: int, val_size: int = 0, step_size: int | None = None, expanding: bool = True, max_folds: int | None = None, train_loss='mse', val_loss='mse', epochs=100, batch_size=32, lr=0.001, weight_decay=0, patience=50, verbose=False, model_args={}, conformal_alpha=0.1, regions=None, device=None, use_optuna=False, n_trials=10, optimizer_params=None, optuna_model_args=None, residual_type=None, report_metrics=None)
Rolling/walk-forward evaluation for time series forecasting.
Trains and evaluates using a rolling window approach to avoid data leakage. Optionally uses Optuna for per-fold hyperparameter tuning.
For each fold: 1. Train model on training window 2. Compute predictions on validation set → calibrate conformal quantile 3. Compute predictions on test set → apply conformal intervals
This gives per-fold uncertainty estimation using the validation data that is temporally closest to the test data.
- Parameters:
dataset – Dataset object with timestamps
train_size – Initial training window size
test_size – Test window size for each fold
val_size – Validation window size (required for conformal prediction)
step_size – Timesteps to advance each fold (default = test_size)
expanding – If True, training window expands; if False, slides
max_folds – Maximum number of folds (None = all possible)
train_loss – Loss functions (str like ‘mse’ or nn.Module)
val_loss – Loss functions (str like ‘mse’ or nn.Module)
epochs – Training params
batch_size – Training params
lr – Training params
weight_decay – Training params
patience – Training params
verbose – Print training progress
model_args – Fixed model arguments
conformal_alpha – Significance level for conformal prediction (default 0.1 = 90% coverage)
regions – Optional list of regions
device – Training device
use_optuna – Enable Optuna tuning per fold
n_trials – Optuna trials per fold
optimizer_params – Optimizer hyperparameter ranges (can include custom loss)
optuna_model_args – Model hyperparameter ranges (can include ‘lookback’)
residual_type – How to compute residuals for conformal prediction: - None: Standard
|preds - targets|- callable: Custom function (preds, targets) -> residualsreport_metrics – List of metric names to compute and report (default: [‘mse’, ‘mae’, ‘rmse’]) Available metrics: ‘mse’, ‘mae’, ‘rmse’, ‘mape’, ‘r2’, ‘acc’ or custom callables
- Returns:
fold_results: Per-fold metrics + conformal results
aggregate_metrics: Mean/std of specified metrics + coverage
conformal_intervals: Per-fold prediction intervals
all_predictions/all_targets: Concatenated predictions and targets
- Return type:
Dictionary with
Forecast
Predicts the next horizon steps from the previous lookback steps; see
Quickstart for EpiLearn for the full script.
- class epilearn.tasks.forecast.Forecast(prototype=None, model=None, dataset=None, lookback=None, horizon=None, ahead=0, device='cpu')
The Forecast class extends the BaseTask class, focusing on the training and evaluation of forecast models for time-series prediction tasks. It includes functionalities specific to handling time-series data, especially in settings that involve spatial-temporal dynamics. The class supports model initialization, training, evaluation, and preprocessing, facilitating the application of various neural network architectures and configurations.
- evaluate_model(model=None, dataset=None, process_history=None, use_conformal=True, conformal_quantile=None, inverse_normalize=False, residue_func=None)
Evaluate the trained model and compute metrics with adaptive conformal prediction intervals.
- Parameters:
model – Model to evaluate (uses self.model if None)
dataset – Dataset dictionary with ‘features’, ‘targets’, ‘graph’, etc.
process_history – Dict with ‘target_mean’, ‘target_std’ for inverse normalization
use_conformal – Whether to compute conformal prediction intervals
conformal_quantile – Conformal quantile (uses self.conformal_quantile if None)
inverse_normalize – Whether to inverse normalize predictions/targets
residue_func – Custom residual function for misaligned predictions
- Returns:
Point metrics: mse, mae, rmse, mape, r2
Residual statistics: residual_mean, residual_std
Raw outputs: predictions, targets, residuals
Conformal results: adaptive_lower, adaptive_upper, coverage stats (if use_conformal=True)
- Return type:
Dictionary containing
- inverse_norm(data, mean, std, node_indices=None)
Apply inverse normalization to data.
Handles multiple cases: - Global normalization (scalar mean/std) - Per-node normalization with data that still has node dimension - Per-node normalization with flattened data and node_indices - Per-node normalization with flattened data without node_indices (uses global average)
- Parameters:
data – Tensor to denormalize
mean – Either scalar or array of means (one per node)
std – Either scalar or array of stds (one per node)
node_indices – Optional tensor of node indices for flattened data
- Returns:
Denormalized tensor
- plot_preds(eval_results, n_show=None, figsize=(15, 7), save_path=None, backend='matplotlib', region_idx=0, horizon_idx=-1, interactive=False)
Plot predictions with adaptive uncertainty intervals in a clean white style.
- Parameters:
eval_results – Dictionary returned from evaluate_model containing predictions, targets, and uncertainty estimates
n_show – Number of time samples to display (None plots all samples, default: None)
figsize – Figure size as (width, height) tuple for matplotlib (default: (15, 7))
save_path – Optional path to save the figure (e.g., ‘plot.png’ or ‘plot.html’)
backend – Plotting backend - ‘matplotlib’ or ‘plotly’ (default: ‘matplotlib’)
region_idx – Index of region to plot (default: 0)
horizon_idx – Index of horizon step to plot (default: -1, last step)
interactive – Whether to make plotly plots interactive (default: False)
- Returns:
fig, ax For plotly: fig
- Return type:
For matplotlib
dataset.transforms is fitted on each fold’s training window only and applied
from there to that fold’s validation and test windows, so the statistics never leak
forward in time. Each fold returns its fitted constants as process_history,
which is what evaluate_model needs to undo them:
last_fold = result['fold_results'][-1]
evaluation = task.evaluate_model(dataset=last_fold['test_split'], # split dict
process_history=last_fold['process_history'],
inverse_normalize=True)
task.plot_preds(evaluation, region_idx=0, horizon_idx=-1, save_path='f.png')
evaluate_model returns mse, mae, rmse, mape, r2,
median_ae, max_error, residual_mean, residual_std and the raw
predictions / targets / residuals tensors. Its dataset= is a split
dict (features, targets, graph, dynamic_graph, states) from
Dataset.generate_dataset or fold_results[i]['test_split']; hand it a
Dataset, or nothing at all, and it fails on the lookup instead.
Warning
Do not pass conformal_quantile= to evaluate_model: the helper it
dispatched to was removed and the call now raises NotImplementedError. Read
the calibrated value from fold_results[i]['conformal_quantile'], or apply
the strategies in Utils to fold_results[i]['val_residuals'].
Detection
Classifies nodes – typically outbreak source detection – from a spatial
snapshot, so it pairs with the Spatial models.
Warning
horizon here is the number of classes, not a number of future steps: it
reaches the model as num_classes, and the target window length is set
separately by horizon_size=1 in generate_dataset. That also rules out
rolling_train, which builds windows with horizon_size=self.horizon: the
targets come out the wrong shape and every fold dies in the cross-entropy loss
with ValueError: Expected input batch_size (...) to match target batch_size
(...). Use the single-split train_model instead.
- class epilearn.tasks.detection.Detection(prototype=None, model=None, dataset=None, lookback=None, horizon=None, ahead=0, device='cpu')
The Detection class extends the BaseTask class, focusing on the training and evaluation of source detection models for spatial-temporal prediction tasks. It predicts the source node given future spatial-temporal observations. The class supports model initialization, training, evaluation with conformal prediction, and comprehensive metrics for classification tasks including accuracy, precision, recall, and F1 score.
- evaluate_model(model=None, dataset=None, process_history=None, use_conformal=True, conformal_quantile=None, n_bootstrap=100, compute_bootstrap_ci=True)
Comprehensive evaluation of the trained detection model with uncertainty quantification.
- Parameters:
model – Model to evaluate (uses self.model if None)
dataset – Dataset dict with ‘features’, ‘targets’, ‘graph’, etc.
process_history – Processing history (not used for detection, kept for API consistency)
use_conformal – Whether to compute conformal prediction intervals
conformal_quantile – Conformal quantile (uses self.conformal_quantile if None)
n_bootstrap – Number of bootstrap samples for uncertainty estimation
compute_bootstrap_ci – Whether to compute bootstrap confidence intervals
- Returns:
Dictionary containing comprehensive evaluation metrics and uncertainty estimates
- plot_preds(eval_results, n_show=None, figsize=(15, 7), save_path=None, backend='matplotlib', sample_idx=0, interactive=False)
Plot source detection predictions with confidence scores.
- Parameters:
eval_results – Dictionary returned from evaluate_model containing predictions, targets, and probabilities
n_show – Number of nodes to display (None plots all nodes, default: None)
figsize – Figure size as (width, height) tuple for matplotlib (default: (15, 7))
save_path – Optional path to save the figure (e.g., ‘plot.png’ or ‘plot.html’)
backend – Plotting backend - ‘matplotlib’ or ‘plotly’ (default: ‘matplotlib’)
sample_idx – Index of sample to plot (default: 0)
interactive – Whether to make plotly plots interactive (default: False)
- Returns:
fig, ax For plotly: fig
- Return type:
For matplotlib
task = Detection(prototype=GCN, dataset=dataset, lookback=1, horizon=2) # 2 classes
task.train_model(train_split=train_split, val_split=val_split,
test_split=test_split, train_loss='ce', val_loss='ce')
evaluation = task.evaluate_model(dataset=test_split) # a split dict, not a Dataset
Build those splits with generate_dataset(..., horizon_size=1), passing adj=
or the graph reaching the model is silently None
(Pipeline for Epidemic Modeling has the script); train_model needs all three
splits, and it catches training exceptions, prints them and returns None.
evaluate_model returns accuracy, macro_precision, macro_recall,
macro_f1, per-class precision_per_class / recall_per_class /
f1_per_class, mean_confidence, predictions / targets /
probabilities, and a bootstrap confidence interval per metric (turn the
resampling off with compute_bootstrap_ci=False).
NowcastTask
Nowcasting corrects for reporting delay: recent counts are still incomplete, and the
task learns how much each day will be revised upward. The input is a reporting
triangle – row t holds the counts known about day t after delays
d = min_delay ... max_delay, not-yet-observable entries marked -1.
lookback is how many past days of the triangle the model sees, horizon how
many recent incomplete days it nowcasts per sample, and
n_delays = max_delay - min_delay + 1 becomes the model’s feature dimension.
- class epilearn.tasks.nowcast.NowcastTask(prototype=None, model=None, lookback: int = 30, horizon: int = 7, min_delay: int = 3, max_delay: int | None = None, device: str = 'cpu')
Nowcasting task for epidemiological reporting triangles.
Extends BaseTask to use the same model interface as Forecast. Supports ALL models from epilearn/models: - Temporal: GRU, LSTM, MLP, Transformer, etc. - Statistical: ARIMA, etc.
Data format: - Features: (batch, lookback, n_regions, n_delays)
At each time step, input the n_delays vector
Unobserved values are marked as -1
n_delays = max_delay - min_delay + 1
Targets: (batch, n_regions, horizon) - Final counts for the last horizon days
- Usage:
from epilearn.tasks import Nowcast from epilearn.models.Temporal import GRUModel
# Create task with any model task = NowcastTask(prototype=GRUModel, lookback=30, horizon=7,
min_delay=3, max_delay=30)
# Load triangle data and create dataset data = task.load_triangle(‘data.npz’) dataset = task.create_dataset(data[‘triangle’], data[‘final_counts’])
# Run training with rolling evaluation (uses base.py’s rolling_train) results = task.rolling_train(
dataset=dataset, train_size=500, test_size=100, val_size=100,
)
- compute_naive_baseline(dataset: Dataset) Dict
Compute naive baseline metrics for comparison.
The naive baseline uses the latest available observation as the prediction.
- create_dataset(triangle: ndarray, final_counts: ndarray, delays: ndarray | None = None) Dataset
Convert reporting triangle to Dataset for use with rolling_train.
- Parameters:
triangle – (n_days, total_delays) reporting triangle
final_counts – (n_days,) final values for each day
delays – Optional array of delay values (e.g., [3, 4, 5, …, 60]). Used to map min_delay/max_delay to column indices.
- Returns:
x: (n_samples, lookback, n_regions=1, n_delays) - features
y: (n_samples, n_regions=1, horizon) - targets
- Return type:
Dataset with
Note
n_delays = max_delay - min_delay + 1 (or all columns if max_delay not set)
Unobserved values in features are marked as -1
Models should handle -1 appropriately (mask or replace)
- static load_triangle(path: str) Dict[str, ndarray]
Load reporting triangle from .npz file.
create_dataset shapes features as (n_samples, lookback, 1, n_delays) and
targets as (n_samples, 1, horizon), ready for rolling_train;
load_triangle expects a .npz holding triangle, final_counts,
delays and time_values. compute_naive_baseline returns
{'naive_mae': ..., 'n_samples': ...} for the “just trust the latest report”
baseline – the number a nowcast has to beat – computed over every sample in the
dataset rather than the rolling test windows, so read it as a reference level and
not a fold-matched comparison.
Note
Neither NowcastTask nor BaseTask defines evaluate_model, so
task.evaluate_model(...) raises AttributeError: 'NowcastTask' object has no
attribute 'evaluate_model'. Score nowcasts from the rolling_train return
value: aggregate_metrics, fold_results[i], or all_predictions /
all_targets for raw values.
ScenarioTask
Scenario modeling answers counterfactual questions: several intervention policies
share the same observed history, then diverge over the projection horizon. Each
sample holds n_scenarios futures, one of which (baseline_scenario_idx,
default 0) is the no-intervention control, and the task is scored on the
difference from that control, measured on target_compartment ('I' by
default): PEHE (sqrt(mean((tau_pred - tau_true)**2)), error on the
per-sample treatment effect tau = Y_intervention - Y_baseline) and ATE
error (|mean(tau_pred) - mean(tau_true)|, error on its average).
- class epilearn.tasks.scenario_modeling.ScenarioTask(prototype=None, model=None, lookback: int = 60, horizon: int = 30, n_scenarios: int = 4, compartmental_model: SEIRVIModel | None = None, baseline_scenario_idx: int = 0, target_compartment: str = 'I', device: str = 'cpu')
Scenario Modeling task for epidemiological counterfactual analysis.
Extends BaseTask to support multi-scenario predictions with shared history.
Data format: - Features X: (batch, lookback, n_scenarios, N+4)
N compartments + 4 intervention features (vacc_rate, vacc_delay, isol_rate, isol_delay)
Compartments are IDENTICAL across scenarios (shared history)
Intervention features can differ for dynamic interventions
Targets Y: (batch, horizon, n_scenarios, N) - Future compartment values per scenario - Scenarios diverge based on different intervention policies
Metrics (Treatment Effect): - PEHE (Precision in Estimation of Heterogeneous Effects):
Measures accuracy in predicting the difference between intervention and baseline
ATE Error (Average Treatment Effect Error): Measures accuracy in predicting the average effect of intervention
- Usage:
from epilearn.tasks import ScenarioTask from epilearn.models.Temporal import GRUModel
# Create task with any model task = ScenarioTask(prototype=GRUModel, lookback=60, horizon=30, n_scenarios=4)
# Generate dataset from compartmental model dataset = task.generate_dataset(n_samples=100)
# Run training with rolling evaluation results = task.rolling_train(dataset=dataset, …)
- evaluate_model(model=None, dataset=None, baseline_scenario_idx: int | None = None, target_compartment: str | None = None, **kwargs) Dict
Evaluate scenario model with treatment effect metrics.
Unlike standard forecasting metrics (MSE, MAE), scenario modeling uses metrics that measure how well the model captures the EFFECT of interventions.
Metrics: 1. PEHE (Precision in Estimation of Heterogeneous Effects):
sqrt(E[(tau_pred - tau_true)^2]) where tau = Y_intervention - Y_baseline
ATE Error (Average Treatment Effect Error):
|E[tau_pred] - E[tau_true]|
- Parameters:
model – Model to evaluate
dataset – Dataset dict with ‘features’, ‘targets’
baseline_scenario_idx – Index of baseline scenario (default: uses self.baseline_scenario_idx)
target_compartment – Compartment name or index (default: uses self.target_compartment)
- Returns:
Dictionary with PEHE, ATE Error, and predictions
- generate_dataset(n_samples: int = 100, population: float = 1000000.0, process_noise: float | None = None, seed: int = 42, **kwargs) Dataset
Generate scenario modeling dataset using compartmental model.
- Parameters:
n_samples – Number of samples to generate
population – Population size
process_noise – Optional noise in simulation
seed – Random seed
**kwargs – Additional parameters for generate_multi_scenario_dataset
- Returns:
x: (n_samples, lookback, n_scenarios, N+4)
y: (n_samples, horizon, n_scenarios, N)
- Return type:
Dataset with
- plot_scenario_comparison(eval_results: Dict, sample_idx: int = 0, compartment_idx: int = 2, scenario_names: List[str] | None = None, figsize: Tuple[int, int] = (14, 5))
Plot scenario comparison for a single sample.
- Parameters:
eval_results – Results from evaluate_model
sample_idx – Which sample to plot
compartment_idx – Which compartment to plot (default: 2 = Infectious)
scenario_names – Names for each scenario
figsize – Figure size
task = ScenarioTask(prototype=GRUModel, lookback=20, horizon=10,
n_scenarios=4, target_compartment='I', device='cpu')
dataset = task.generate_dataset(n_samples=150, population=1e6, seed=42)
result = task.rolling_train(dataset, train_size=60, val_size=45, test_size=45,
report_metrics=['pehe', 'ate_error'])
ScenarioTask needs no data of your own: generate_dataset simulates
trajectories with the built-in SEIRVIModel
(epilearn.utils.compartmental_models, replaceable via compartmental_model=),
sampling vaccination and isolation rates/delays per scenario. Features come out as
(n_samples, lookback, n_scenarios, N + 4) – N compartments plus four
intervention descriptors – and targets as (n_samples, horizon, n_scenarios, N).
Those “timesteps” are independent simulated samples, so train_size /
val_size / test_size count samples rather than days, and both metrics carry
the units of the simulation (people out of population). Passing a single split
dict to evaluate_model returns pehe, ate_error, mse,
predictions / targets and the treatment-effect tensors tau_pred /
tau_true of shape (n_samples, n_scenarios - 1, horizon), which
plot_scenario_comparison draws.
Important
PEHE and ATE error are the only metrics ScenarioTask computes, and they are
not in the default report_metrics. Pass
report_metrics=['pehe', 'ate_error'] to rolling_train; otherwise it
goes looking for 'mse', finds nothing, and aggregate_metrics comes back
holding only the conformal numbers. The per-fold pehe / ate_error are
in fold_results either way – it is the aggregation and the printed summary
that go missing.