Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 35 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ The archive is described in
Classification](https://arxiv.org/abs/2603.20352) (arXiv:2603.20352). If you use the
archive, please cite it; `CITATION.cff` has the full entry.

The wider literature the archive is measured against is reviewed in
[Benchmark-Driven Multivariate Time Series Classification: The Role of the UEA MTSC
Archive](https://eprints.soton.ac.uk/512258/), a survey of 142 papers using the UEA
archive. The accuracies it collected are in
[`published_results/`](published_results/), and are what our runs are compared against.

The current paper version describes:

- 133 unique MTSC problems
Expand All @@ -32,33 +38,34 @@ The current paper version describes:
### Multiverse-core leaderboard

<!-- LEADERBOARD:START -->
| # | Estimator | Accuracy | Balanced accuracy | AUROC | F1 | Log loss &darr; | Sensitivity | Specificity | Accuracy rank |
| # | Estimator | Accuracy rank | Accuracy | Balanced accuracy | AUROC | F1 | Log loss &darr; | Sensitivity | Specificity |
|---|---|---|---|---|---|---|---|---|---|
| 1 | HC2 | **0.7909** | 0.7518 | **0.8990** | 0.7273 | **0.5383** | 0.7459 | **0.7943** | **7.64** |
| 2 | MRHydra | 0.7837 | **0.7564** | 0.8105 | **0.7316** | 7.7974 | **0.7642** | 0.7757 | 8.14 |
| 3 | RDST | 0.7734 | 0.7333 | 0.7912 | 0.6991 | 8.1667 | 0.7109 | 0.7874 | 9.02 |
| 4 | RIST | 0.7720 | 0.7397 | 0.8748 | 0.7147 | 0.6218 | 0.7408 | 0.7655 | 9.60 |
| 5 | DrCIF | 0.7747 | 0.7429 | 0.8813 | 0.7173 | 0.6484 | 0.7397 | 0.7708 | 9.86 |
| 6 | FreshPRINCE | 0.7743 | 0.7487 | 0.8745 | 0.7211 | 0.6007 | 0.7414 | 0.7770 | 9.87 |
| 7 | CIF | 0.7781 | 0.7471 | 0.8908 | 0.7212 | 0.6430 | 0.7441 | 0.7753 | 9.94 |
| 8 | QUANT | 0.7720 | 0.7462 | 0.8831 | 0.7189 | 0.6175 | 0.7521 | 0.7581 | 10.29 |
| 9 | Arsenal | 0.7680 | 0.7321 | 0.8457 | 0.7024 | 3.8631 | 0.7257 | 0.7732 | 10.41 |
| 10 | ROCKET | 0.7690 | 0.7326 | 0.7925 | 0.7019 | 8.3249 | 0.7200 | 0.7764 | 10.58 |
| 11 | LITETime-MV | 0.7506 | 0.7299 | 0.8513 | 0.6820 | 1.3206 | 0.7132 | 0.7637 | 10.92 |
| 12 | STSF | 0.7724 | 0.7477 | 0.8804 | 0.7080 | 0.6432 | 0.7345 | 0.7826 | 11.29 |
| 13 | H-InceptionTime | 0.7408 | 0.7190 | 0.8496 | 0.6838 | 1.3227 | 0.7223 | 0.7378 | 11.39 |
| 14 | LiteTIME | 0.7341 | 0.7104 | 0.8394 | 0.6680 | 1.4776 | 0.7113 | 0.7336 | 12.08 |
| 15 | PatchMTSC | 0.7428 | 0.6897 | 0.8261 | 0.6533 | 0.7655 | 0.6852 | 0.7352 | 12.77 |
| 16 | ConvTran | 0.7462 | 0.7102 | 0.8592 | 0.6767 | 0.8190 | 0.7159 | 0.7345 | 12.89 |
| 17 | Catch22 | 0.7475 | 0.7181 | 0.8697 | 0.6922 | 0.7147 | 0.7240 | 0.7374 | 12.93 |
| 18 | STC | 0.7545 | 0.7172 | 0.8744 | 0.6940 | 0.6391 | 0.7185 | 0.7537 | 13.63 |
| 19 | TSF | 0.7515 | 0.7236 | 0.8740 | 0.6883 | 0.7252 | 0.7093 | 0.7606 | 13.63 |
| 20 | TDE | 0.7262 | 0.6813 | 0.8374 | 0.6382 | 0.8869 | 0.6714 | 0.7344 | 14.21 |
| 21 | Summary | 0.6858 | 0.6574 | 0.8268 | 0.6230 | 0.9123 | 0.6574 | 0.6844 | 16.12 |
| 22 | 1NN-DTW | 0.6712 | 0.6454 | 0.7197 | 0.6136 | 11.8506 | 0.6521 | 0.6636 | 17.82 |
| 23 | Dummy | 0.3645 | 0.3029 | 0.5000 | 0.1507 | 1.4067 | 0.2855 | 0.3816 | 20.95 |

Test results for default train/test split. All classifiers trained with default settings. Average over the 52 Multiverse-core datasets with results for every estimator on every metric, ordered by average accuracy rank. Best in each column in bold.
| 1 | HC2 | **7.86** | **0.7909** | 0.7518 | **0.8990** | 0.7273 | **0.5383** | 0.7459 | **0.7943** |
| 2 | MRHydra | 8.31 | 0.7837 | **0.7564** | 0.8105 | **0.7316** | 7.7974 | **0.7642** | 0.7757 |
| 3 | RDST | 9.16 | 0.7734 | 0.7333 | 0.7912 | 0.6991 | 8.1667 | 0.7109 | 0.7874 |
| 4 | RIST | 9.76 | 0.7720 | 0.7397 | 0.8748 | 0.7147 | 0.6218 | 0.7408 | 0.7655 |
| 5 | DrCIF | 10.07 | 0.7747 | 0.7429 | 0.8813 | 0.7173 | 0.6484 | 0.7397 | 0.7708 |
| 6 | FreshPRINCE | 10.10 | 0.7743 | 0.7487 | 0.8745 | 0.7211 | 0.6007 | 0.7414 | 0.7770 |
| 7 | CIF | 10.13 | 0.7781 | 0.7471 | 0.8908 | 0.7212 | 0.6430 | 0.7441 | 0.7753 |
| 8 | QUANT | 10.55 | 0.7720 | 0.7462 | 0.8831 | 0.7189 | 0.6175 | 0.7521 | 0.7581 |
| 9 | Arsenal | 10.70 | 0.7680 | 0.7321 | 0.8457 | 0.7024 | 3.8631 | 0.7257 | 0.7732 |
| 10 | ROCKET | 10.82 | 0.7690 | 0.7326 | 0.7925 | 0.7019 | 8.3249 | 0.7200 | 0.7764 |
| 11 | LITETime-MV | 11.14 | 0.7506 | 0.7299 | 0.8513 | 0.6820 | 1.3206 | 0.7132 | 0.7637 |
| 12 | STSF | 11.55 | 0.7724 | 0.7477 | 0.8804 | 0.7080 | 0.6432 | 0.7345 | 0.7826 |
| 13 | H-InceptionTime | 11.63 | 0.7408 | 0.7190 | 0.8496 | 0.6838 | 1.3227 | 0.7223 | 0.7378 |
| 14 | LiteTIME | 12.34 | 0.7341 | 0.7104 | 0.8394 | 0.6680 | 1.4776 | 0.7113 | 0.7336 |
| 15 | PatchMTSC | 13.12 | 0.7428 | 0.6897 | 0.8261 | 0.6533 | 0.7655 | 0.6852 | 0.7352 |
| 16 | ConvTran | 13.14 | 0.7462 | 0.7102 | 0.8592 | 0.6767 | 0.8190 | 0.7159 | 0.7345 |
| 17 | Catch22 | 13.15 | 0.7475 | 0.7181 | 0.8697 | 0.6922 | 0.7147 | 0.7240 | 0.7374 |
| 18 | STC | 13.95 | 0.7545 | 0.7172 | 0.8744 | 0.6940 | 0.6391 | 0.7185 | 0.7537 |
| 19 | TSF | 14.00 | 0.7515 | 0.7236 | 0.8740 | 0.6883 | 0.7252 | 0.7093 | 0.7606 |
| 20 | TDE | 14.63 | 0.7262 | 0.6813 | 0.8374 | 0.6382 | 0.8869 | 0.6714 | 0.7344 |
| 21 | Summary | 16.66 | 0.6858 | 0.6574 | 0.8268 | 0.6230 | 0.9123 | 0.6574 | 0.6844 |
| 22 | TimesURL | 16.95 | 0.6958 | 0.6533 | 0.7906 | 0.5967 | 1.0055 | 0.6257 | 0.6973 |
| 23 | 1NN-DTW | 18.45 | 0.6712 | 0.6454 | 0.7197 | 0.6136 | 11.8506 | 0.6521 | 0.6636 |
| 24 | Dummy | 21.81 | 0.3645 | 0.3029 | 0.5000 | 0.1507 | 1.4067 | 0.2855 | 0.3816 |

Average over the 52 Multiverse-core datasets with results for every estimator on every metric, ordered by average accuracy rank. Best in each column in bold.
<!-- LEADERBOARD:END -->

Rebuilt with `python -m multiverse.experiments.tables`, which also writes a sortable
Expand Down Expand Up @@ -206,7 +213,8 @@ multiverse/
├── docs/ # Documentation
├── img/ # Images used in the documentation
├── results/ # Benchmark results, one directory per classifier
├── survey/ # Data behind the MTSC survey
├── published_results/ # Accuracies reported in the MTSC literature
├── survey/ # Signpost only: the directory moved to published_results/
└── multiverse/ # Python package
├── classification/ # Classifiers not available in aeon
├── examples/ # Short runnable examples
Expand Down
83 changes: 73 additions & 10 deletions docs/classifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ from multiverse.classification import (
PatchMTSCClassifier,
TimesNetClassifier,
TimesURLClassifier,
TS2VecClassifier,
XCMClassifier,
)
```

Expand Down Expand Up @@ -66,14 +68,61 @@ Wei, Y., et al. "PatchMTSC: patch-based multivariate time series classification"

TimesURL is a self-supervised representation learner rather than an end-to-end
classifier. A contrastive objective pretrains an encoder on the training collection, the
collection is encoded, and a logistic regression probe is fitted on those
representations; at prediction time the fitted encoder embeds the new series and the
probe classifies them. Ported from the authors'
[implementation](https://github.com/Alrash/TimesURL).
collection is encoded, and a probe is fitted on those representations; at prediction time
the fitted encoder embeds the new series and the probe classifies them. Ported from the
authors' [implementation](https://github.com/Alrash/TimesURL).

The adapter follows the authors' UEA experiment: channels are standardised using
training data only, a normalised time coordinate is appended, and an RBF SVM is fitted to
the full-series representations. `eval_protocol` selects the probe, defaulting to their
`"svm"`, with `"linear"` and `"knn"` also available.

Liu, J. and Chen, S. "TimesURL: Self-supervised Contrastive Learning for Universal Time
Series Representation Learning." AAAI, 2024.

## TS2Vec

TS2Vec is a self-supervised representation learner: a hierarchical contrastive objective
pretrains an encoder, the collection is encoded to one vector per series, and a
classifier is fitted on the representations. It is the direct comparator to TimesURL,
which is built on its codebase. Ported from the authors'
[implementation](https://github.com/zhihanyue/ts2vec).

Yue, Z., Wang, Y., Duan, J., Yang, T., Huang, C., Tong, Y. and Xu, B. "TS2Vec: Towards
Universal Representation of Time Series." AAAI, 2022.

For UEA the authors evaluate with a support vector machine chosen by grid search over C
(`train.py` passes `eval_protocol='svm'`), so `probe="svm"` is the default. `probe=
"logistic"` selects their linear alternative, which is the probe TimesURL uses, so the
two encoders can be compared without the probe differing between them.

## XCM

XCM is an explainable convolutional network. Two parallel branches see the input
differently: a 2D branch convolves along time within each channel separately, so its
activations stay attributable to individual channels, and a 1D branch convolves along
time across all channels together. The branches are concatenated, passed through a
further 1D convolution, globally average pooled and classified. The channel
attributability of the 2D branch is what the paper's explanations rest on, so the
layer names it refers to are preserved. Ported from the authors'
[implementation](https://github.com/XAIseries/XCM).

Fauvel, K., Lin, T., Masson, V., Fromont, E. and Termier, A. "XCM: An Explainable
Convolutional Neural Network for Multivariate Time Series Classification."
Mathematics, 9(23), 2021.

This is the only Keras port here, following the authors, so it needs `tensorflow`
rather than `torch`. Both are in the `deep-learning` extra.

The authors tune `window_size` per dataset. Their results table carries a `Win_pct`
column spread over a five point grid: 20, 40 and 60 on five datasets each, 80 on
thirteen, and 100 on two. The default here is **0.8**, the value they use most often.
The 0.2 in their `config.yml` is the worked example for BasicMotions, not a default.

Because `window_size` is a fraction, the kernel grows with the series, and 0.8 of
EigenWorms' 17984 points would be a 14387 point kernel. `max_window` bounds the kernel
at 100 points, and it is floored at 1 for very short series.

## Notes on the ports

All three wrappers take aeon's ``numpy3D`` collections, shape
Expand All @@ -91,11 +140,18 @@ package: install it with ``pip install aeon-multiverse[deep-learning]``.
| ConvTran | [Navidfoumani/ConvTran](https://github.com/Navidfoumani/ConvTran), commit `148afb6` | `Models/model.py`, `Models/Attention.py`, `Models/AbsolutePositionalEncoding.py`, `Models/optimizers.py` |
| PatchMTSC | [YanxuanWei/PatchMTSC](https://github.com/YanxuanWei/PatchMTSC) | `Models/model.py`, `Models/Attention.py`, `Models/AbsolutePositionalEncoding.py` |
| TimesURL | [Alrash/TimesURL](https://github.com/Alrash/TimesURL) | the whole model package, vendored under `_timesurl_original` |

ConvTran, PatchMTSC and TimesNet are each a single self-contained module. TimesURL is
the exception: its implementation spans 11 modules and about 2,300 lines, so it is
vendored under `multiverse/classification/_timesurl_original` and driven by a thin
wrapper, rather than inlined. Only the components the published
| TS2Vec | [zhihanyue/ts2vec](https://github.com/zhihanyue/ts2vec) | `ts2vec.py`, `utils.py`, `models/`, vendored under `_ts2vec_original` |
| XCM | [XAIseries/XCM](https://github.com/XAIseries/XCM) | `models/xcm.py` |

ConvTran, PatchMTSC and TimesNet are each a single self-contained module. TimesURL and
TS2Vec are vendored instead, under `_timesurl_original` and `_ts2vec_original`, and
driven by thin wrappers: they are whole model packages rather than single networks, at
about 2,300 and 630 lines respectively.

Note that TimesURL is a fork of TS2Vec, not a user of it. Its copies of `encoder.py` and
`losses.py` are modified: the loss calls mixup variants that TS2Vec does not have, and
the encoder carries a reconstruction head. The two vendored packages are therefore kept
separate rather than sharing a base. Only the components the published
architecture actually reaches are reproduced: for ConvTran and PatchMTSC that is the
``tAPE`` fixed encoding and ``eRPE`` relative encoding path, so the alternative
encodings (``Sin``, ``Learn``, ``Vector``) and the unused ``Transformer`` and
Expand All @@ -120,7 +176,10 @@ faithful transcription.
| Sibling imports rewritten as relative imports | TimesURL | Upstream is laid out for ``sys.path`` insertion; as a subpackage it needs relative imports. A stray package-level ``from .encoder import TSEncoder`` is dropped, since ``encoder`` lives under ``models`` and the import only went unnoticed because the ``sys.path`` route bypassed it |
| One unconditional ``print`` silenced | TimesURL | ``lib.py`` printed the training tensor shape on every fit, which would pollute benchmark logs. ``verbose`` controls training output instead |
| Python's ``random`` seeded alongside numpy and torch | TimesURL | The authors' collator draws from ``random`` for segment masking and index shuffling, so seeding numpy and torch alone left runs irreproducible |
| ``multi_class`` dropped from the probe | TimesURL | The authors pass ``multi_class="auto"``, deprecated in scikit-learn 1.5 and removed in 1.8. The default already matches its behaviour here |
| Probe probabilities from ``decision_function`` | TimesURL | An ``SVC`` without ``probability=True`` cannot produce probability estimates, and aeon classifiers must implement ``predict_proba``. The decision scores are softmaxed instead, which avoids the internal cross-validation that Platt scaling would add |
| ``probability=True`` on the SVM probe | TS2Vec | The authors' grid sets it False, which leaves an ``SVC`` unable to produce probability estimates. aeon classifiers must implement ``predict_proba``, so it is enabled, adding Platt scaling fitted by internal cross-validation on the training data |
| Layer imports taken from ``tensorflow.keras.layers`` | XCM | The original imports ``Conv1D`` and ``Conv2D`` from ``keras.layers.convolutional``, a path removed in Keras 3. The layers and their arguments are unchanged |
| Kernel length floored at one point | XCM | The original computes ``int(window_size * n)``, which is zero for series shorter than five points and builds an invalid layer |
| Validation split moved inside ``fit`` | all three | The originals split train/validation outside the model, which risks leakage between train and test. TSLib is explicit about it: ``exp_classification.py`` sets ``vali_data = self._get_data(flag='TEST')``, so it selects the retained epoch on the test set. See the note at the top of this page |
| Test data scaled with training statistics | TimesNet | TSLib fits its normaliser separately per split, so its test set is scaled by its own statistics. The port fits on train and applies to test |

Expand Down Expand Up @@ -167,6 +226,8 @@ tests record which ports have which:
|---|---|---|
| ConvTran | yes | yes |
| TimesURL | vendored verbatim, so identical by construction | n/a |
| TS2Vec | vendored verbatim, so identical by construction | n/a |
| XCM | yes, the built graph matches layer for layer with equal parameter counts | n/a, Keras rather than torch |
| PatchMTSC | yes | no, the original's head is an ``nn.LazyLinear`` that draws its weights on the first forward pass rather than at construction |
| TimesNet | yes | no, TSLib always builds a temporal embedding that the classification path never applies; dropping it removes one weight tensor and shifts every later draw |

Expand All @@ -182,6 +243,8 @@ export MULTIVERSE_CONVTRAN_SRC=/path/to/ConvTran
export MULTIVERSE_PATCHMTSC_SRC=/path/to/PatchMTSC
export MULTIVERSE_TIMESNET_SRC=/path/to/Time-Series-Library
export MULTIVERSE_TIMESURL_SRC=/path/to/TimesURL/model/modules
export MULTIVERSE_TS2VEC_SRC=/path/to/ts2vec
export MULTIVERSE_XCM_SRC=/path/to/XCM
pytest multiverse/classification/tests/test_original_equivalence.py -v
```

Expand Down
4 changes: 4 additions & 0 deletions multiverse/classification/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@
"ConvTranClassifier",
"PatchMTSCClassifier",
"TimesNetClassifier",
"TS2VecClassifier",
"XCMClassifier",
"TimesURLClassifier",
]

from multiverse.classification._convtran import ConvTranClassifier
from multiverse.classification._patchmtsc import PatchMTSCClassifier
from multiverse.classification._timesnet import TimesNetClassifier
from multiverse.classification._ts2vec import TS2VecClassifier
from multiverse.classification._xcm import XCMClassifier
from multiverse.classification._timesurl import TimesURLClassifier
23 changes: 16 additions & 7 deletions multiverse/classification/_timesnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,22 @@ class TimesNetClassifier(BaseClassifier):
Maximum number of training epochs.
learning_rate : float, default=1e-3
Learning rate for RAdam.
lr_adjust : {"type1", "cosine", None}, default="type1"
lr_adjust : {"type1", "cosine", None}, default=None
Learning rate schedule, applied every five epochs as in the original
TSLib training loop. ``"type1"``, the TSLib default, sets the rate to
``learning_rate * 0.5 ** (epoch - 1)`` at epochs 5, 10, 15, and so on,
which decays it to near zero part way through a default 30 epoch run.
``"cosine"`` follows TSLib's cosine option. ``None`` disables the
schedule and trains at a constant rate.
TSLib training loop. ``"type1"`` is TSLib's own default and sets the
rate to ``learning_rate * 0.5 ** (epoch - 1)`` at epochs 5, 10, 15 and
so on, which from the published ``learning_rate=0.001`` reaches 2.0e-6
by epoch 10 and 6.1e-8 by epoch 15, so training effectively stops
about a third of the way through a 30 epoch run.

The default here is None, which departs from TSLib deliberately. Their
schedule is harmless in their pipeline because they select the
retained epoch on the *test* set, so an early epoch from before the
collapse is kept anyway. This wrapper selects on a held-out split of
the training data, so it keeps a model that stopped learning. On ERing
the difference is 0.578 with the schedule against 0.933 without, where
the published result is 0.915. Set ``"type1"`` to reproduce TSLib's
behaviour, or ``"cosine"`` for their cosine option.
patience : int, default=10
Early stopping patience based on internal validation accuracy.
validation_size : float, default=0.2
Expand Down Expand Up @@ -178,7 +187,7 @@ def __init__(
batch_size: int = 16,
n_epochs: int = 30,
learning_rate: float = 1e-3,
lr_adjust: str | None = "type1",
lr_adjust: str | None = None,
patience: int = 10,
validation_size: float = 0.2,
gradient_clip: float = 4.0,
Expand Down
Loading