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:
xai4tsc.data— dataset loading and splittingxai4tsc.models— model base class and implementationsxai4tsc.xai— explainer base classes and methodsxai4tsc.evaluation— evaluation metrics
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 a UCR/UEA, local, or synthetic dataset. |
|
Instantiate a model by name or from a checkpoint. |
|
Produce an |
|
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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
Explainers (xai4tsc.EXPLAINERS)
Registry key |
Class |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|---|---|
|
|
|
|
|
|
|
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 |
|---|---|---|---|
|
|
3 |
any |
|
|
16 |
model-native |
|
|
6 |
model-native |
|
|
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 |
|---|---|
|
The interval, in samples. All a time-domain dataset needs |
|
Channels it applies to; omitted means all |
|
Classes it discriminates; omitted means all |
|
Components in cycles per series - frequency / time-frequency only |
|
Paired with |
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:
xai4tsc.data.base.DatasetBase— subclass to add a datasetxai4tsc.models.base.ModelBase— subclass to add a modelxai4tsc.xai.base.ExplainerBase— subclass to add an explainerxai4tsc.evaluation.base.EvaluatorBase— subclass to add a metric
See the full xai4tsc for complete details.