HIVE-COTE - a meta-ensemble over distinct time-series representations.

Classes

HIVECOTEClassifier

class algorithms.timeseries.classification.hive_cote.HIVECOTEClassifier(TimeSeriesClassifier)

Combine different representations of a series, weighted by competence.

Every other classifier in this package looks at a series one way: elastic alignment, convolutional response, local subsequences, symbolic word counts, interval statistics. Each is strong somewhere and blind elsewhere. HIVE-COTE's insight is that those blind spots barely overlap, so combining the views beats tuning any one of them.

Crucially it is not a majority vote. Each component's probabilities are weighted by its own cross-validated accuracy raised to a power, so a component that is right on this dataset dominates one that is not — and the ensemble degrades gracefully when a member is simply unsuited.

Constructor
__init__(
    self,
    components: Optional[List[Tuple[str, Any]]] = None,
    alpha: float = 4.0,
    cv: int = 3,
    random_state: Optional[int] = None,
)

Overview

  1. Cross-validate every component on the training data.
  2. Raise each accuracy to alpha to get its weight, so the gap between
a good and a mediocre component is amplified.
  1. Fit every component on the full training set.
  2. At prediction time, take the weighted sum of the components'
probabilities.

Theory

With component c achieving cross-validated accuracy a_c and predicting P_c(y \mid x), the ensemble predicts

P(y \mid x) \ \propto \ \sum_c a_c^{\alpha} \ P_c(y \mid x)

The exponent \alpha controls how sharply competence is rewarded. At \alpha = 0 every component counts equally; as \alpha \to \infty only the best survives. The published value of 4 sits deliberately between: a component 10% more accurate than another receives roughly 1.46 times the weight, enough to matter without letting one noisy cross-validation estimate take over.

The weights are estimated by cross-validation on the training set, not on the training predictions themselves — a component that memorises its training data would otherwise earn a weight of 1 and swamp the rest.

Parameters

components
list of tuple
(name, classifier) pairs. Defaults to one member per representation: MINIROCKET (convolutional), BOSS (dictionary), TimeSeriesForest (interval) and DTW-1NN (elastic distance).
alpha
float = 4.0
Exponent applied to each component's cross-validated accuracy.
cv
int = 3
Folds used to estimate the weights. Raising it steadies the weights and multiplies the fitting cost.
random_state
int
Seed passed to the default components and the fold split.

Attributes

components_
list of tuple
(name, fitted classifier) pairs.
weights_
np.ndarray of shape (n_components,)
Normalised ensemble weights.
component_accuracy_
dict
Cross-validated accuracy per component name — the diagnostic worth reading, since it says which view the data actually rewards.
classes_
np.ndarray of shape (n_classes,)
Class labels seen during fit.

Notes

Complexity. Fitting costs cv + 1 fits of every component, so it is the most expensive classifier here by a wide margin. Prediction costs one pass per component. This buys robustness, not speed: if a single model is wanted, use MiniRocketClassifier.

When to use — and be sceptical. The case for HIVE-COTE is that you cannot say in advance whether the signal is a motif, a frequency, a trend in one stretch, or a global shape. Measured across three synthetic problems built to favour different views, 120 train / 120 test:

============== ======== ========== ======== ======== ========== problem rocket dictionary interval distance HIVE-COTE ============== ======== ========== ======== ======== ========== localised 1.000 0.825 1.000 1.000 1.000 trend local motif, 1.000 0.783 0.958 1.000 1.000 random position frequency 1.000 0.983 1.000 0.983 0.992 under noise worst case 1.000 0.783 0.958 0.983 0.992 ============== ======== ========== ======== ======== ==========

The weighting works as designed — the dictionary component was down-weighted to 0.10-0.13 where it was weak and to 0.25 where all four were equal — and the ensemble tracks the best member without being told which it is. But MINIROCKET alone matched or beat it on every row, at roughly a quarter of the cost. That is the usual outcome when one component is already near-perfect: an ensemble insures against picking wrong, and insurance is a loss when you would have picked right.

So: fit the components individually first and read component_accuracy_. If one dominates, use it and keep the compute. Reach for the ensemble when the components disagree, when several are close, or when the deployment will see data unlike the sample you tuned on — the case the table above cannot show.

The published HIVE-COTE 2.0 uses four specific components with tuned internals. This class keeps the structure — cross-validated competence weighting over diverse representations — while letting the components be chosen, because the structure is what carries the benefit and a fixed component list would rot.

References

Lines2018
Lines, J., Taylor, S., & Bagnall, A. (2018). Time Series Classification with HIVE-COTE: The Hierarchical Vote Collective of Transformation-Based Ensembles. ACM Transactions on Knowledge Discovery from Data, 12(5), 1-35. :doi:`10.1145/3182382`
Middlehurst2021
Middlehurst, M., Large, J., Flynn, M., Lines, J., Bostrom, A., & Bagnall, A. (2021). HIVE-COTE 2.0: A New Meta Ensemble for Time Series Classification. Machine Learning, 110(11), 3211-3243. :doi:`10.1007/s10994-021-06057-9`
python
>>> import numpy as np
>>> from tuiml.algorithms.timeseries.classification import (
...     HIVECOTEClassifier, MiniRocketClassifier, TimeSeriesForestClassifier)
>>> rng = np.random.default_rng(0)
>>> X = rng.normal(0, 1.0, (60, 80))
>>> y = np.arange(60) % 2
>>> X[y == 1, 20:50] += np.linspace(0, 4, 30)
>>> model = HIVECOTEClassifier(
...     components=[("rocket", MiniRocketClassifier(n_features=840, random_state=0)),
...                 ("interval", TimeSeriesForestClassifier(n_estimators=50, random_state=0))],
...     cv=2, random_state=0).fit(X, y)
>>> float((model.predict(X) == y).mean())
1.0
>>> sorted(model.component_accuracy_)
['interval', 'rocket']

Methods

get_parameter_schema (cls) -> Dict[str, Dict[str, Any]]

Return JSON Schema for algorithm parameters.

get_capabilities (cls) -> List[str]

Return supported capabilities.

get_complexity (cls) -> str

Return complexity analysis.

get_references (cls) -> List[str]

Return academic citations.

fit (self, X: np.ndarray, y: np.ndarray) -> 'HIVECOTEClassifier'

Weight the components by cross-validated accuracy, then fit them.

Parameters
X
np.ndarray of shape (n_samples, n_timepoints) or (n_samples, n_channels, n_timepoints)
Training series.
y
np.ndarray of shape (n_samples,)
Training labels.
Returns
self
HIVECOTEClassifier
The fitted ensemble.
predict (self, X: np.ndarray) -> np.ndarray

Classify each series by the weighted component vote.

Parameters
X
np.ndarray of shape (n_samples, n_timepoints) or (n_samples, n_channels, n_timepoints)
Series to classify.
Returns
y_pred
np.ndarray of shape (n_samples,)
Predicted labels.
predict_proba (self, X: np.ndarray) -> np.ndarray

Return the weighted average of the components' probabilities.

Parameters
X
np.ndarray of shape (n_samples, n_timepoints) or (n_samples, n_channels, n_timepoints)
Series to classify.
Returns
proba
np.ndarray of shape (n_samples, n_classes)
Ensemble probabilities, rows summing to one.
__repr__ (self) -> str

Return a readable representation of the ensemble.