Package

The xai4tsc package is the importable use case. Install it with pip and use the public API in your own code, notebooks, or scripts to load datasets, train classifiers, generate explanations, and evaluate them quantitatively. All three registries (MODELS, EXPLAINERS, METRICS) support runtime extension via the register_* functions.

Installation

# editable install from a local clone (not yet published to PyPI)
pip install -e PATH/TO/XAI4TSC

Getting Started

The typical workflow follows four steps: load data, train a model, generate explanations, and evaluate them. The example below shows the full sequence using a UCR dataset and a built-in model.

Example

import xai4tsc
from xai4tsc.data import load_dataset
from xai4tsc.evaluation.evaluate import evaluate
from xai4tsc.models.models import load_model
from xai4tsc.xai.explain import generate_explanation


def main(out_dir: str) -> None:
    """
    Run an example to showcase package capabilities.

    Parameters
    ----------
    out_dir : str
        Directory to safe the output to.
    """
    # Enable package logging to the console and to <out_dir>/xai4tsc.log
    xai4tsc.enable_logging(out_dir)

    # Load data (UCR download or local numpy files)
    ds = load_dataset("GunPoint", use_predefined_splits=True)
    splits, encoder = ds.split(train_split=0.8, val_split=0.1, random_state=42)
    ds.save_splits(out_dir)
    train_data, train_labels, _ = splits[0]
    test_data, test_labels, _ = splits[1]

    # Load a model
    model = load_model(
        {"model": "FCN", "init_params": {"in_channels": 1, "num_classes": 2}},
        device="cpu",
    )

    # Set up hyperparameters
    hyperparams = {
        "epochs": 10,
        "batchsize": 32,
        "loss_func": "CrossEntropy",
        "optimizer": "adam",
        "learn_rate": 0.001,
        "patience": 3,
    }

    # Train the model
    model.train_model(
        train_data,
        train_labels,
        hyperparams,
        save_path=out_dir,  # best checkpoint + training plots land here
    )

    # Evaluate on test data
    model.evaluate_model(
        test_data,
        test_labels,
        hyperparams,
        save_path=out_dir,  # save model performance data
    )

    # Generate explanations
    exp = generate_explanation(
        method="integrated_gradients",
        model=model,
        data=test_data,
        labels=test_labels,
        encoder=encoder,
        indices=[0, 1, 2],
        device="cpu",
    )

    # Evaluate the explanations
    _ = evaluate(
        model=model,
        metric="Complexity",
        explanation=exp,
        data=test_data[exp.indices],
        labels=test_labels[exp.indices],
        metric_class_params={"normalise": True, "abs": True, "disable_warnings": True},
        device="cpu",
    )


if __name__ == "__main__":
    main("experiments/results/getting_started")

API Reference

The package is organised into the following submodules:

Key functions

The four functions a package user reaches for, in pipeline order. Each is also re-exported at the top level (e.g. from xai4tsc import generate_explanation) and from its submodule (e.g. from xai4tsc.models import load_model).

Function

Purpose

load_dataset()

Load a UCR/UEA, local, or synthetic dataset.

load_model()

Instantiate a model by name or from a checkpoint.

generate_explanation()

Produce an Explanation for chosen samples.

evaluate()

Score an explanation with a metric from the registry.

Built-in components

These are the concrete classes you select at runtime — by registry key in a YAML config or in the package API (load_model, generate_explanation(method=...), evaluate(metric=...)). The key is what you pass; the class is where its parameters and behaviour are documented. Extend any registry at runtime with the matching register_* function.

Models (xai4tsc.MODELS)

Registry key

Class

fcn

FCN

lenet

LeNet

resnet

ResNet

lstm

LSTM

patchtst

PatchTST

xlstm

XLSTM

Explainers (xai4tsc.EXPLAINERS)

Registry key

Class

integrated_gradients

IntegratedGradientsExplainer

guided_backpropagation

GuidedBackpropagationExplainer

deconvolution

DeconvolutionExplainer

deeplift

DeepLiftExplainer

occlusion

OcclusionExplainer

tshap

TSHAPExplainer

sign

SignExplainer

freqrise

FreqRISEExplainer

frequency

FrequencyExplainer

timefrequency

TimeFrequencyExplainer

random_frequency

RandomFrequencyExplainer

random_timefrequency

RandomTimeFreqExplainer

Metrics (xai4tsc.METRICS)

Every METRICS value is a callable that produces an EvaluatorBase. Most registry keys map to Quantus metric classes (listed in xai4tsc.QUANTUS_METRICS, e.g. "Complexity", "Faithfulness Correlation", "ROAD"), adapted by the single QuantusEvaluator (bound to the metric name); see the Quantus documentation for those. The xai4tsc-native metrics are:

Registry key

Class

Frequency Perturbation

FrequencyEvaluator

Time-Frequency Perturbation

TimeFrequencyEvaluator

Time-Frequency Perturbation Gaussian

TimeFrequencyEvaluatorGaussian

Time-Frequency AUC

TimeFrequencyAUCEvaluator

Data domain

A dataset declares the signal domain its arrays are expressed in via xai4tsc.data.DatasetBase.data_domain, and that is the domain the model trained on it consumes - the model-native domain. It is the time domain for raw series, which is every dataset shipped here; a dataset of (real-valued) spectra declares "frequency". A LocalDataset takes it as a constructor argument, because only the caller knows what is in the file, while UCR/UEA data is always raw series and a synthetic dataset declares its own on the class. Unlike layout, which describes a file and is resolved away at load time, this one travels: pass it to generate_explanation() and it lands on xai4tsc.Explanation.data_domain, where it decides two things.

First, what a plain attributor’s explanation domain is: relevance is assigned to the features the model was given, so it comes out in the data’s own domain, and Integrated_Gradients on a spectra-trained model yields a frequency explanation. Explainers that deliberately change domain (Frequency, TimeFrequency, FreqRISE, the Random_* baselines) set explains_in_data_domain to False and name their output instead.

Second, which explainers can read the data at all: accepts_data_domains declares the domains an explainer can consume (empty means any). The domain-lifting explainers consume time-domain data only, so pointing one at frequency data is refused rather than transforming it a second time.

Metric domains

Which explanation domains a metric can score is declared on the registry value, and required_domains_for() is the reader. A metric defined for a domain names it in required_domains (an empty set means any domain); one that perturbs the input and re-queries the model instead sets requires_native_domain, which ties it to the model-native domain - the domain the model’s inputs live in, xai4tsc.Explanation.data_domain. That is the time domain for a model trained on raw series and the frequency domain for one trained on spectra, so these metrics follow the model rather than being fixed to time.

For the Quantus metrics the choice follows from what each one consumes, recorded in QUANTUS_GROUPS:

Group

Its own code reads

n

Explanation domain

ATTRIBUTION_ONLY

a_batch

3

any

MODEL_PERTURBATION

x_batch + model

16

model-native

LOCALISATION

x_batch + s_batch

6

model-native

RE_EXPLAIN

+ explain_func

12

model-native

So "Complexity", "Effective Complexity" and "Sparseness" score an explanation in any domain, including one lifted out of the data’s own (the adapter supplies inputs on the same coefficient grid), while a metric that perturbs the input needs the explanation in the domain the model predicts in.

The RE_EXPLAIN metrics re-run the explainer on perturbed or randomised inputs, so they need a callable rather than just the stored attribution. The adapter builds one from xai4tsc.Explanation.explainer and xai4tsc.Explanation.explainer_params, which is why the explanation records how it was produced: a re-explanation has to use the same method with the same settings, or the metric measures a configuration difference instead of the property it claims to.

Seven metrics across the groups are refused rather than scored, each with its reason in BLOCKED_BY_BACKEND. Six of them index a second spatial axis a (B, C, T) series does not have - super-pixels, quadrants, 2-D padding, pixel neighbourhoods - and raise inside the library, so refusing them replaces an obscure IndexError with a stated reason. The seventh, "Smooth MPRT", is the one worth knowing about: it returns a number, because it calls the library’s own explain function instead of the one it is given, so without the entry it would report a plausible score for an explainer it never ran.

Ground-truth localization

The LOCALISATION metrics compare attributions to a supplied ground truth (GT). That means the dataset has to supply the GT, as per-sample metadat. A localization metric on a dataset without GT is reported as skipped-no-ground-truth with the reason, via ground_truth_missing().

A dataset that does know records a list of regions under ground_truth in each sample’s metadata. A region is one interval: pos and len define it, and every other field is a scope that broadcasts over the whole of it.

Field

Meaning

pos, len

The interval, in samples. All a time-domain dataset needs

channels

Channels it applies to; omitted means all

classes

Classes it discriminates; omitted means all

freqs

Components in cycles per series - frequency / time-frequency only

phases

Paired with freqs; recorded only

Every field is optional and an omitted one means all, so a time-domain dataset writes {"pos": ..., "len": ...} and the classes. A region must still localize something, and an unrecognized field is an error rather than ignored - a misspelled len would otherwise read as no interval, which means “the whole series”, and quietly widen the mask over the entire sample.

Stored metadata is never rewritten to match this schema: from_mapping() is the one place that parses it, and it accepts the singular channel / freq / phase spellings and the per-class {class: [region, ...]} mapping alongside the forms above.

An attribution is always for a class, so it is scored against the evidence for that class. xai4tsc.Explanation.explained_targets records which class each attribution explains, and regions_for_class() keeps only the regions scoped to it; a region that declares no classes still applies to every class, which is the right default for a single-label dataset whose sample label already says which class it is. This matters most for a multi-label dataset: without it every class would be scored against the union of the sample’s regions, so each class would get an identical mask.

Three consequences worth knowing when reading a score. Quantus requires its masks to have a single channel, so a region’s channels scope is unioned away on that path and the score cannot say whether an attribution found the right channel, it warns when that collapse discards something, which needs multivariate ground truth. Samples with no region for the class in question are dropped rather than scored: an all-False mask passes Quantus’ own guard, which counts array dimensions rather than elements, and would score 0 like a genuine result.

That dropping has a consequence for targets: predicted: a sample carrying no evidence for the class the model predicted is excluded, so the scored subset depends on model accuracy. Use targets: label when the sample set has to be fixed across models.

evaluate() also takes channel_first, the axis order of the inputs, which defaults to True for the framework’s canonical (B, C, T). It is passed on to the Quantus metric; left unset there it is inferred from the shape, and the guess is wrong for an input with more channels than timesteps. Pass it through metric_call_params when calling evaluate(), or set it under evaluation_config.default_settings.metric_call_params in a runner config.

Extending the package

If you want to add a custom dataset, model, explainer or evaluator, subclass the relevant base class:

See the full xai4tsc for complete details.