Skip to content

[ENH] speed up RandomShapeletTransform #3574

Draft
TonyBagnall wants to merge 22 commits into
mainfrom
ajb/rst
Draft

[ENH] speed up RandomShapeletTransform #3574
TonyBagnall wants to merge 22 commits into
mainfrom
ajb/rst

Conversation

@TonyBagnall

@TonyBagnall TonyBagnall commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Not dependent on #3631, but obviously STC sill be dependent on both that and this,

Speed up RST without changing outcome. May also redesign and change threading

Stage 1: _online_shapelet_distance scalar rewrite

Replace traverse, sums, and sums2 list state with scalar left/right state

compute the normalised value on demand, avoid unnecessary slicing. Move unchanging values out of loop

Stage 2: code restructure

I feel it could benefit from a redesign,. _process_fit_batch has too many responsibilities, fixed and contracted has duplication, _class_dictionary is populated but never read etc

Stage 3: optimisations

threading at the moment makes things worse. n_jobs=2/4 is slower than n_jobs=1 for both fit and transform, consistent
with per-candidate Joblib overhead and Numba entry points not being configured to release the GIL

  1. nogil=True on the scoring/transform kernels — the big one: fit and transform ~3.3x on 4 cores (was ~1.3x; the threads were GIL-bound).
  2. Transform kernel njit'd — one kernel over the whole block instead of per-pair Python dispatch.
  3. Per-candidate fusion — z-norm + argsort folded into the scoring kernel (one numba crossing per candidate; np.argsort(kind="mergesort") reproduces the old sort exactly).
  4. Single reused RNG — reseed-per-candidate instead of 10k RandomState allocations (~16x on that line, bit-exact), plus a clean sampling/scoring split.
  5. Hard code IG constants

@TonyBagnall TonyBagnall added the transformations Transformations package label Jun 28, 2026
@aeon-actions-bot aeon-actions-bot Bot added the enhancement New feature, improvement request or other non-bug code enhancement label Jun 28, 2026
@aeon-actions-bot

Copy link
Copy Markdown
Contributor

Thank you for contributing to aeon

I have added the following labels to this PR based on the title: [ enhancement ].
I would have added the following labels to this PR based on the changes made: [ transformations ], however some package labels are already present.

The Checks tab will show the status of our automated tests. You can click on individual test runs in the tab or "Details" in the panel below to see more information if there is a failure.

If our pre-commit code quality check fails, please run pre-commit locally and push the fixes to your PR branch.

Don't hesitate to ask questions on the aeon Discord channel if you have any.

PR CI actions

These checkboxes will add labels to enable or disable CI functionality for this PR. This may not take effect immediately, and a new commit may be required to run the new configuration.

  • Run pre-commit checks for all files
  • Run mypy typecheck tests
  • Run all pytest tests and configurations
  • Run all notebook example tests
  • Run numba-disabled codecov tests
  • Disable numba cache loading
  • Regenerate expected results for testing
  • Push an empty commit to re-run CI checks

Comment thread aeon/transformations/collection/shapelet_based/_shapelet_transform.py Outdated
Comment thread aeon/transformations/collection/shapelet_based/_shapelet_transform.py Outdated

@patrickzib patrickzib left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a review, just a comment. Maybe calling if on every value is not super efficient? normalization once outside the loops or using / std=1.0 might be faster?

TonyBagnall and others added 10 commits July 10, 2026 23:29
Stage 1 of the rotation forest speed up plan. Extracts the code duplicated
between RotationForestClassifier and RotationForestRegressor into a new
BaseRotationForest in aeon/base/_estimators/sklearn, following the
BaseIntervalForest pattern of switching task-specific behaviour on
is_classifier. The public classes are now thin subclasses keeping their
docstrings, signatures and public methods.

Outputs are bit-identical to the previous implementations for fit/predict/
predict_proba/fit_predict on both estimators, with one deliberate fix: the
regressor previously ignored its pca_solver parameter and always used the
'full' SVD solver. It now honours the parameter like the classifier, so the
expected values in test_rotf_output (which passes pca_solver='randomized')
have been regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n forest (#3630)

Optimisation stage 1 of the rotation forest speed up plan. Attribute groups
are only a few columns wide, so the sklearn PCA object per group is almost
all validation and wrapper overhead (measured 0.43ms vs 0.05ms per group).
Each group rotation is now a minimal _GroupPCA computing an exact
eigendecomposition of the group covariance matrix, storing only the mean and
components.

The components match the sklearn 'full' SVD solver bit-for-bit, including
its sign convention, except for rank-deficient inputs where trailing
components are mathematically arbitrary. Predictions on the default settings
are unchanged; the docstring examples and downstream STC/HC2/FreshPRINCE
outputs are identical. The pca_solver parameter is deprecated and has no
effect, so the pinned values in the two test_rotf_output tests (which passed
pca_solver='randomized') have been regenerated. Old pickles still predict
correctly since stored sklearn PCA objects are used via the same transform
interface.

Measured against the previous commit (500 cases, single thread unless noted):
- predict: 3.2-4.4x faster across shapes
- fit: regressor 1.4x, classifier 1.0-1.1x (tree building dominated)
- fit with n_jobs: scaling plateau lifted from 2x to 4.2x on 8 threads
  (500x400, 40 trees: 6.0s -> 2.2s)
- fit_predict_proba: 1.6x

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t trees (#3630)

Optimisation stages 2 and 4 of the rotation forest speed up plan.

When base_estimator is the default, trees are now constructed directly
instead of via clone, and fit/predict on them pass check_input=False since
the rotated X_t is always a finite float32 array built internally. The
default tree is seeded with an int drawn from the ensemble rng, consuming
exactly one draw as sklearn's _set_random_states did on the clone path, so
all outputs remain bit-identical. User-supplied base estimators keep the
fully validated path.

The out-of-bag index computation in fit_predict previously scanned the
subsample array once per case (O(n^2)); it now uses a boolean mask (O(n)).
Measured at n=20,000: 0.12s -> 0.15ms per tree, saving ~24s across a
200-tree fit_predict. The OOB result scatter is also vectorised.

Measured overall: fit ~8% faster single-threaded (500x800, 20 trees:
11.1s -> 10.2s min-of-2 interleaved); predict unchanged (tree traversal
dominated); fit_predict unchanged at small n, large savings at large n.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…3630)

Optimisation stage 3a. The per-estimator rotation previously built a list of
per-group transformed arrays, concatenated them, cast to float32 and always
ran nan_to_num (three full-array scans for nan/+inf/-inf). It now writes each
group's rotation straight into a preallocated float32 array (the cast happens
on assignment) and runs the full nan_to_num replacement only when a single
np.isfinite scan finds a non-finite value, which cannot happen for the
normalised inputs and finite _GroupPCA components in the common path.

Output is bit-identical. Shared by predict and the fit tail via
_transform_for_estimator. Measured 1.5x on predict (500x800, 50 trees:
0.33s -> 0.22s, interleaved min-of-6).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ion skip, n_jobs=1 path (#3630)

A batch of small, bit-identical optimisations from the speed up plan:

- Cache is_classifier(self) once as self._is_classifier instead of calling it
  in the per-group and per-estimator inner loops.
- Extend check_input=False to any sklearn BaseDecisionTree base estimator
  (self._skip_tree_checks), not just the None default, so user-supplied plain
  decision trees (as in the tests and FreshPRINCE-style usage) also skip
  redundant input validation. Non-tree estimators keep the validated path.
- Build the classifier PCA sampling matrix with a single np.concatenate over
  the selected class blocks rather than growing it one class at a time.
- Skip joblib dispatch entirely when n_jobs == 1 via a small _parallel helper,
  used by fit, predict and fit_predict. Random draw order is preserved, so
  results are unchanged.

All paths remain bit-identical to before (verified against a saved reference
for fit/predict/predict_proba/fit_predict on both estimators, and n_jobs=1
equals n_jobs=4). Individually these are within measurement noise (~1-3%);
the main value is removing the base_estimator-is-None coupling and joblib
overhead. All 175 sklearn tests and doctests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…uctors (#3630)

Adds criterion, splitter, max_features, max_depth, max_leaf_nodes and
min_samples_leaf to both RotationForestClassifier and RotationForestRegressor
(and the shared base). Each defaults to the value previously hard-coded for
the default tree, so behaviour is bit-identical unless a user sets them:

- criterion: 'entropy' (classifier) / 'squared_error' (regressor)
- splitter: 'best', max_features: None, max_depth: None,
  max_leaf_nodes: None, min_samples_leaf: 1

They are only used when base_estimator is None (a user-supplied estimator
carries its own parameters). Tree construction is factored into a single
_new_default_tree helper used by both the fit template and the per-estimator
_make_tree path.

These let users trade accuracy for speed without swapping in a full
base_estimator, e.g. criterion='gini' (~1.5x fit), splitter='random'
(~5x fit) or max_features='sqrt' (~7.5x fit) measured on 500x400/30 trees.

Defaults verified bit-identical against the saved reference for both
estimators; all 175 sklearn tests and doctests pass; get_params/clone
round-trip the new parameters.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address recurring test-review points on the rotation forest tests:

- Fix docstrings that did not match the test: test_rotf_output claimed
  'contracting and train estimate' but only checks predict output;
  test_contracted_rotf claimed a train estimate it never computes.
- Derive the expected probability width from the number of classes in the
  data instead of hard-coding 2.
- Replace exact error-string assertions with short, intention-revealing
  substring matches ("not a time series", "same value").
- Drop the deprecated pca_solver argument from the output tests (its no-op
  behaviour is covered by test_rotf_pca_solver_is_noop).
- Add test_rotf_tree_parameters to both estimators, checking the newly
  exposed criterion/splitter/max_depth/min_samples_leaf reach the fitted
  default trees and that the defaults are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sion (#3630)

The regressor output test pinned 15 exact continuous predictions, which vary
with the scikit-learn version: 1.8 changed decision-tree handling of
almost-constant features (already noted in the class docstring), shifting
individual predictions by up to ~0.005 and failing the test on 1.8 while
passing on 1.7. The aggregate error is stable across versions (MSE differs by
~1e-5), so test_rotf_output now asserts the test-set MSE instead of exact
predictions, over the full test set (removing the magic 15-case slice).

test_contracted_rotf pinned its MSE at 0.002 (decimal=4) while the observed
value is 0.00213, leaving it one version bump from failing; re-centred on the
observed value so the tolerance has room for cross-version drift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@TonyBagnall TonyBagnall changed the title [ENH] RandomShapeletTransform speed ups [ENH] speed up RandomShapeletTransform Jul 13, 2026
@TonyBagnall TonyBagnall requested a review from dguijo July 13, 2026 14:48
@TonyBagnall

TonyBagnall commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

back to this draft properly. Priorities

  1. Clean ups (e.g. std dev in loops)
  2. njit whole transform block
  3. sort out threading
  4. optimise

@TonyBagnall

Copy link
Copy Markdown
Contributor Author

almost done, modest gains on sequential but significant gains on parallel, nogil ftw

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature, improvement request or other non-bug code enhancement transformations Transformations package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants