Source code for RuleTree.stumps.transformers.ProximityTransformer
"""ProximityTransformer: which of two shapelets is closer to each time series."""
import itertools
import numpy as np
from RuleTree.exceptions import NoSplitFoundWarning
from RuleTree.stumps.transformers.ShapeletTransformer import ShapeletTransformer
[docs]
def closer_to_second(distances: np.ndarray, pairs: list) -> np.ndarray:
"""
For each pair ``(i, j)`` of columns of ``distances``, 1 where ``distances[:, i] > distances[:, j]``
(the instance is closer to the second one), 0 otherwise.
A split ``column <= t`` with ``0 <= t < 1`` sends left the instances closer to the first one
(or at the same distance from both).
Parameters
----------
distances : numpy.ndarray of shape (n_samples, n_references)
Distance of each instance from each reference (shapelet, pivot, ...).
pairs : list of tuple
Pairs of columns.
Returns
-------
comparisons : numpy.ndarray of shape (n_samples, n_pairs)
Examples
--------
>>> import numpy as np
>>> from RuleTree.stumps.transformers.ProximityTransformer import closer_to_second
>>> closer_to_second(np.array([[1.0, 2.0], [3.0, 0.5]]), [(0, 1)])
array([[0.],
[1.]])
"""
pairs = np.asarray(pairs, dtype=int).reshape(-1, 2)
return (distances[:, pairs[:, 0]] > distances[:, pairs[:, 1]]).astype(float)
[docs]
class ProximityTransformer(ShapeletTransformer):
"""
Transformer of time series into proximity comparisons: for each pair of shapelets, whether the
time series is closer to the second one than to the first.
The shapelets are selected as in
:class:`~RuleTree.stumps.transformers.ShapeletTransformer.ShapeletTransformer`; the feature matrix
has one 0/1 column for each pair of them (see :func:`closer_to_second`), so that a split sends
left the time series closer to the first shapelet of a pair. After the splitter has chosen a
pair, only its two shapelets are kept (``select``).
Parameters
----------
n_shapelets : int, default=8
Number of shapelets selected at each node (at least 2).
n_shapelets_for_selection : int, numpy.inf or 'stratified', default=500
Number of candidate subsequences, among which the shapelets are selected.
n_ts_for_selection_per_class : int or numpy.inf, default=100
Number of time series of each class from which the candidates are extracted.
sliding_window : int, default=50
Length of the shapelets.
selection : {'random', 'mi_clf', 'mi_reg', 'cluster'}, default='mi_clf'
How the shapelets are selected among the candidates.
distance : {'euclidean', 'sqeuclidean', 'cosine', 'cityblock'} or callable, default='euclidean'
Distance between a shapelet and a subsequence.
mi_n_neighbors : int, default=100
Number of neighbors used to estimate the mutual information.
random_state : int, default=42
Seed of the random choices of the candidates and of the shapelets.
n_jobs : int, default=1
Number of threads used to compute the distances (-1: all the CPUs).
Attributes
----------
numerical_ : list of int
Numerical columns of the feature matrix.
categorical_ : list of int
Categorical columns of the feature matrix (none).
st_ : Shapelets
The fitted shapelet transform (only the two shapelets of the chosen pair after ``select``).
pairs_ : list of tuple
Pairs of shapelets of the columns of the feature matrix.
See Also
--------
ProximityTreeStumpClassifier, ProximityTreeStumpRegressor : the stumps that use it.
PartialProximityTransformer : the same comparisons with partial pivots, for tabular data.
Examples
--------
>>> import numpy as np
>>> from RuleTree.stumps.transformers import ProximityTransformer
>>> rng = np.random.default_rng(0)
>>> X_ts, y = rng.normal(size=(20, 1, 30)), rng.integers(0, 2, size=20)
>>> transformer = ProximityTransformer(n_shapelets=4, sliding_window=5, selection="random").fit(X_ts, y)
>>> transformer.transform(X_ts).shape # one column for each of the 6 pairs of shapelets
(20, 6)
"""
[docs]
def fit(self, X, y, idx=None, data_type=None, cache=None, sample_weight=None):
"""
Select the shapelets among the subsequences of the time series in the node.
Parameters
----------
X : numpy.ndarray
Time series, all the instances.
y : numpy.ndarray
Target, all the instances.
idx : numpy.ndarray, default=None
Indices of the instances in the node. If None, all the instances.
data_type : str, default=None
Type of X, used as key of the cached values.
cache : dict, default=None
Values shared among the splits of the tree.
sample_weight : numpy.ndarray, default=None
Not supported: must be None.
Returns
-------
self : ProximityTransformer
Raises
------
ValueError
If sample_weight is given.
NoSplitFoundWarning
If fewer than two shapelets are selected.
"""
super().fit(X, y, idx, data_type=data_type, cache=cache, sample_weight=sample_weight)
self.pairs_ = list(itertools.combinations(range(len(self.st_.shapelets)), 2))
if not self.pairs_:
raise NoSplitFoundWarning(f"{self.__class__.__name__} needs at least two shapelets")
self.numerical_, self.categorical_ = list(range(len(self.pairs_))), []
return self
[docs]
def transform(self, X):
"""
Proximity comparisons of each time series for each pair of shapelets (for the chosen pair, after
select).
Parameters
----------
X : numpy.ndarray
Time series, of shape (n_samples, n_signals, length).
Returns
-------
X_transformed : numpy.ndarray of shape (n_samples, n_pairs)
1 if the series is closer to the second shapelet of the pair, 0 otherwise.
"""
return closer_to_second(self.st_.transform(X), self.pairs_)
[docs]
def select(self, feature):
"""
Keep only the two shapelets of the chosen pair.
Parameters
----------
feature : int
Column chosen by the splitter.
Returns
-------
feature : int
Always 0: the comparison of the only pair kept.
"""
self.st_.shapelets = self.st_.shapelets[list(self.pairs_[feature])]
self.pairs_ = [(0, 1)]
self.numerical_, self.categorical_ = [0], []
return 0
[docs]
def get_state(self):
"""
Fitted state: the shapelets and the pairs.
Returns
-------
state : dict
JSON-friendly dictionary, restored by set_state.
"""
return super().get_state() | {"pairs": [[int(i), int(j)] for i, j in self.pairs_]}
[docs]
def set_state(self, state):
"""
Restore the fitted state saved by get_state.
Parameters
----------
state : dict
Dictionary created by get_state.
Returns
-------
self : ProximityTransformer
"""
super().set_state(state)
self.pairs_ = [tuple(pair) for pair in state["pairs"]]
self.numerical_, self.categorical_ = list(range(len(self.pairs_))), []
return self