[ENH] Speed up interval-based forest regressors#3628
Conversation
_SB_MotifThree_quantile_hh (entropy_pairs): replace the r1/r2 per-symbol position-list machinery and terminal-position trim with a direct 3x3 adjacent-transition count. The trim is just excluding the last position as a transition source, which range(n-1) handles, so this is a straight simplification that drops ~7 temporary length-n arrays. ~1.5x faster in isolation. _sb_coarsegrain: collapse the num_groups labelling passes into a single pass with an early break, since the group ranges are disjoint. Output is bit-for-bit identical: max abs diff 0.0 vs a reference set and a 5760-case fuzz of _SB_MotifThree_quantile_hh against the original r1/r2 implementation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_fluct_prop: bespoke least squares over the fixed grid d=[1..tau] - the grid moments are closed-form and computed once per tau, and the DFA fluctuation is a residual-sum-of-squares from sums instead of detrending each window. ~1.6x on rs_range/dfa. _compute_autocorrelations: factor out the FFT twiddle table (depends only on the series length) and precompute it once per transform via _ac_twiddle_cache, reused for the shared autocorrelation features (2, 3, 8, 10, 12) instead of rebuilding it per call. ~1.15x on the acf cluster. Output is bit-for-bit identical to the previous implementation (verified against the original over fixed-seed panels incl. multivariate and edge cases, plus 4200 direct _fluct_prop fuzz cases); catch22 tests pass. Together ~1.2x on the full 22-feature transform. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_FC_LocalSimple_mean1_tauresrat computes the autocorrelation of the differenced series; pass it the transform's precomputed twiddle table (added for the other autocorrelation features) so it reuses them instead of rebuilding, falling back to computing its own when the differenced series lands on a different FFT size (length 2**k + 1). Bit-for-bit identical; ~1.1x on whiten_timescale. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Change the default base estimator criterion for interval-based forest regressors (DrCIF, CIF, TSF, RISE, IntervalForest) from absolute_error to squared_error. The sklearn absolute_error criterion scales quadratically with the number of cases, making fitting infeasible for larger datasets (84s vs 2.3s per tree at 10000 cases). Also avoid joblib overhead in Catch22 transform when n_jobs=1. Expected test results regenerated for the new default. Doctest outputs are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Thank you for contributing to
|
Add a numba batch function mirroring _transform_case that transforms all cases of an equal length collection in one call, removing Python-level per-case overhead. Used for single-threaded 3D numpy input, the hot path for interval-based forests. The FFT for the SP_Summaries features is precomputed for all cases with a vectorised np.fft call, with the series means calculated to exactly match the per-case path as the FFT DC term is sensitive to catastrophic cancellation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-forest-regressors # Conflicts: # aeon/transformations/collection/feature_based/_catch22.py
…l-forest-regressors
|
those speed ups for decision trees regression are insane. I suggest rebase off #3620, make the change to squared and I will run the timing and accuracy comparison as main -> 3620 (x2.5 faster) -> squared error regression/ gini for classification |
What does this implement/fix? Explain your changes.
Speeds up the interval-based forest regressors (
DrCIFRegressor,CanonicalIntervalForestRegressor,TimeSeriesForestRegressor,RandomIntervalSpectralEnsembleRegressor,IntervalForestRegressor), which currently become infeasibly slow on larger datasets (e.g. windowed forecasting problems with 1000-10000 training cases).1. Change the default base estimator for regression from
DecisionTreeRegressor(criterion="absolute_error")toDecisionTreeRegressor(criterion="squared_error")inBaseIntervalForest.sklearn's
absolute_errorcriterion scales roughly quadratically with the number of cases (a long-standing sklearn limitation, see scikit-learn/scikit-learn#9626), whilesquared_errorisO(n log n). Fitting a single tree on DrCIF-transformed features (~210 features):With the default 200 estimators, tree fitting alone at 10000 cases drops from ~4.7 hours to ~8 minutes per fit. Test regression quality is essentially unchanged in a quick check (DrCIF, 50 trees): CardanoSentiment test MAE 0.224 (absolute_error) vs 0.226 (squared_error); on a synthetic windowed forecasting problem squared_error was slightly more accurate (MAE 1.62 vs 1.82).
This is a behavioural change for anyone relying on the old default: results produced with defaults will differ slightly from previous versions (e.g. the TSER bake-off reference results). The old behaviour is available by passing
base_estimator=DecisionTreeRegressor(criterion="absolute_error"), and this is noted in the docstrings. Classification defaults are untouched.2. Transform whole collections in a single numba call in
Catch22.Catch22is called once per interval per tree inside DrCIF/CIF (classification and regression) withn_jobs=1. Previously each case was transformed by a Python-level_transform_casecall (via a joblibdelayedwrapper), paying Python branching and numba dispatch overhead tens of millions of times in a large forest fit. For single-threaded equal-length 3D input, the per-case loop now runs inside a single numba function per transform call (_transform_collection_3d), which mirrors_transform_caseexactly. The FFT used by the twoSP_Summariesfeatures is precomputed for all cases with a vectorisednp.fft.fftcall (verified bit-identical to the per-case calls), asnp.fftis unsupported in numba. The per-case path is kept for unequal length input,n_jobs > 1andpycatch22.One caveat: numba compiles the feature functions a second time when they are called from the batch function, and with
fastmath=Truethis can differ in the last float digits from the Python-called versions (max observed relative difference3.5e-13over a large grid of inputs, feature subsets,catch24andoutlier_normsettings, NaN patterns identical). Care was taken to avoid this where it matters: the FFT input mean is computed exactly as the per-case path does, since the FFT DC term is a catastrophic cancellation that would otherwise meaningfully change theSP_Summariesfeatures for short series. In testing,DrCIFRegressorpredictions and the stored expected results outputs forCatch22Classifier,Catch22Regressor,CanonicalIntervalForest(both),DrCIFClassifierandRISTClassifierwere byte-identical with and without the batch path. The multithreading and datatype consistency estimator checks (which usenp.allclose) all pass.End-to-end
DrCIFRegressor.fit(default params, univariate, series length 100, all runs in a single session):The advantage grows with n_cases as the quadratic tree fitting cost takes over on main. At 10000 cases with the default 200 estimators this works out to roughly 6 hours on main vs 1 hour with this PR per fit.
Expected test results for the four affected regressors with stored results were regenerated using the
_write_estimator_results.pyprocedure. Doctest outputs are unchanged (the examples predict on their training data, where fully-grown trees reproduce the targets exactly regardless of criterion). Allcheck_estimatorchecks pass for the five affected regressors plusCatch22,Catch22ClassifierandDrCIFClassifier, and the interval-forest/regression/catch22 test suites pass.Does your contribution introduce a new dependency? If yes, which one?
No.
Any other comments?
The criterion change alters default behaviour, so flagging for maintainer opinion:
absolute_errorhas been the regression default since the base class was introduced, but it makes the regressors unusable beyond a few thousand cases (a week-long fit on a windowed forecasting problem motivated this PR). sklearn's ownRandomForestRegressordefaults tosquared_error.The two changes are independent — happy to split the PR if preferred.
PR checklist
For all contributions
🤖 Generated with Claude Code