sktime.classification.dictionary_based._tde 源代码

"""TDE classifiers.

Dictionary based TDE classifiers based on SFA transform. Contains a single IndividualTDE
and TDE.
"""

__author__ = ["MatthewMiddlehurst"]
__all__ = ["TemporalDictionaryEnsemble", "IndividualTDE", "histogram_intersection"]

import math
import time
from collections import defaultdict

import numpy as np
from joblib import Parallel, delayed
from sklearn import preprocessing
from sklearn.kernel_ridge import KernelRidge
from sklearn.utils import check_random_state

from sktime.classification.base import BaseClassifier
from sktime.transformations.panel.dictionary_based import SFA
from sktime.utils.validation.panel import check_X_y
from sktime.utils.warnings import warn


[文档]class TemporalDictionaryEnsemble(BaseClassifier): """Temporal Dictionary Ensemble (TDE). Implementation of the dictionary based Temporal Dictionary Ensemble as described in [1]_. Overview: Input "n" series length "m" with "d" dimensions TDE searches "k" parameter values selected using a Gaussian processes regressor, evaluating each with a LOOCV. It then retains "s" ensemble members. There are six primary parameters for individual classifiers: - alpha: alphabet size - w: window length - l: word length - p: normalise/no normalise - h: levels - b: MCB/IGB For any combination, an individual TDE classifier slides a window of length w along the series. The w length window is shortened to an l length word through taking a Fourier transform and keeping the first l/2 complex coefficients. These lcoefficients are then discretised into alpha possible values, to form a word length l using breakpoints found using b. A histogram of words for each series is formed and stored, using a spatial pyramid of h levels. For multivariate series, accuracy from a reduced histogram is used to select dimensions. fit involves finding n histograms. predict uses 1 nearest neighbour with a the histogram intersection distance function. Parameters ---------- n_parameter_samples : int, default=250 Number of parameter combinations to consider for the final ensemble. max_ensemble_size : int, default=50 Maximum number of estimators in the ensemble. max_win_len_prop : float, default=1 Maximum window length as a proportion of series length, must be between 0 and 1. min_window : int, default=10 Minimum window length. randomly_selected_params: int, default=50 Number of parameters randomly selected before the Gaussian process parameter selection is used. bigrams : boolean or None, default=None Whether to use bigrams, defaults to true for univariate data and false for multivariate data. dim_threshold : float, default=0.85 Dimension accuracy threshold for multivariate data, must be between 0 and 1. max_dims : int, default=20 Max number of dimensions per classifier for multivariate data. time_limit_in_minutes : int, default=0 Time contract to limit build time in minutes, overriding n_parameter_samples. Default of 0 means n_parameter_samples is used. contract_max_n_parameter_samples : int, default=np.inf Max number of parameter combinations to consider when time_limit_in_minutes is set. typed_dict : bool, default=True Use a numba typed Dict to store word counts. May increase memory usage, but will be faster for larger datasets. As the Dict cannot be pickled currently, there will be some overhead converting it to a python dict with multiple threads and pickling. save_train_predictions : bool, default=False Save the ensemble member train predictions in fit for use in _get_train_probs leave-one-out cross-validation. n_jobs : int, default=1 The number of jobs to run in parallel for both ``fit`` and ``predict``. ``-1`` means using all processors. random_state : int or None, default=None Seed for random number generation. Attributes ---------- n_classes_ : int The number of classes. classes_ : list The classes labels. n_instances_ : int The number of train cases. n_dims_ : int The number of dimensions per case. series_length_ : int The length of each series. n_estimators_ : int The final number of classifiers used (<= max_ensemble_size) estimators_ : list of shape (n_estimators) of IndividualTDE The collections of estimators trained in fit. weights_ : list of shape (n_estimators) of float Weight of each estimator in the ensemble. See Also -------- IndividualTDE, ContractableBOSS Notes ----- For the Java version, see `TSML <https://github.com/uea-machine-learning/tsml/blob/master/src/main/java/ tsml/classifiers/dictionary_based/TDE.java>`_. References ---------- .. [1] Matthew Middlehurst, James Large, Gavin Cawley and Anthony Bagnall "The Temporal Dictionary Ensemble (TDE) Classifier for Time Series Classification", in proceedings of the European Conference on Machine Learning and Principles and Practice of Knowledge Discovery in Databases, 2020. Examples -------- >>> from sktime.classification.dictionary_based import TemporalDictionaryEnsemble >>> from sktime.datasets import load_unit_test >>> X_train, y_train = load_unit_test(split="train", return_X_y=True) >>> X_test, y_test = load_unit_test(split="test", return_X_y=True) # doctest: +SKIP >>> clf = TemporalDictionaryEnsemble( ... n_parameter_samples=10, ... max_ensemble_size=3, ... randomly_selected_params=5, ... ) # doctest: +SKIP >>> clf.fit(X_train, y_train) # doctest: +SKIP TemporalDictionaryEnsemble(...) >>> y_pred = clf.predict(X_test) # doctest: +SKIP """ _tags = { # packaging info # -------------- "authors": ["MatthewMiddlehurst"], "python_dependencies": "numba", # estimator type # -------------- "capability:multivariate": True, "capability:train_estimate": True, "capability:contractable": True, "capability:multithreading": True, "capability:predict_proba": True, "classifier_type": "dictionary", } def __init__( self, n_parameter_samples=250, max_ensemble_size=50, max_win_len_prop=1, min_window=10, randomly_selected_params=50, bigrams=None, dim_threshold=0.85, max_dims=20, time_limit_in_minutes=0.0, contract_max_n_parameter_samples=np.inf, typed_dict=True, save_train_predictions=False, n_jobs=1, random_state=None, ): self.n_parameter_samples = n_parameter_samples self.max_ensemble_size = max_ensemble_size self.max_win_len_prop = max_win_len_prop self.min_window = min_window self.randomly_selected_params = randomly_selected_params self.bigrams = bigrams # multivariate self.dim_threshold = dim_threshold self.max_dims = max_dims self.time_limit_in_minutes = time_limit_in_minutes self.contract_max_n_parameter_samples = contract_max_n_parameter_samples self.typed_dict = typed_dict self.save_train_predictions = save_train_predictions self.random_state = random_state self.n_jobs = n_jobs self.n_instances_ = 0 self.n_dims_ = 0 self.series_length_ = 0 self.n_estimators_ = 0 self.estimators_ = [] self.weights_ = [] self._word_lengths = [16, 14, 12, 10, 8] self._norm_options = [True, False] self._levels = [1, 2, 3] self._igb_options = [True, False] self._alphabet_size = 4 self._weight_sum = 0 self._prev_parameters_x = [] self._prev_parameters_y = [] self._min_window = min_window super().__init__() def _fit(self, X, y): """Fit an ensemble on cases (X,y), where y is the target variable. Build an ensemble of base TDE classifiers from the training set (X, y), through an optimised selection over the para space to make a fixed size ensemble of the best. Parameters ---------- X : 3D np.array of shape = [n_instances, n_dimensions, series_length] The training data. y : array-like, shape = [n_instances] The class labels. Returns ------- self : Reference to self. Notes ----- Changes state by creating a fitted model that updates attributes ending in "_" and sets is_fitted flag to True. """ if self.n_parameter_samples <= self.randomly_selected_params: warn( "TemporalDictionaryEnsemble warning: n_parameter_samples <= " "randomly_selected_params, ensemble member parameters will be " "fully randomly selected.", obj=self, stacklevel=2, ) self.n_instances_, self.n_dims_, self.series_length_ = X.shape self.estimators_ = [] self.weights_ = [] self._prev_parameters_x = [] self._prev_parameters_y = [] # Window length parameter space dependent on series length max_window_searches = self.series_length_ / 4 max_window = int(self.series_length_ * self.max_win_len_prop) if self.min_window >= max_window: self._min_window = max_window warn( f"TemporalDictionaryEnsemble warning: min_window = " f"{self.min_window} is larger than max_window = {max_window}." f" min_window has been set to {max_window}.", obj=self, stacklevel=2, ) win_inc = int((max_window - self._min_window) / max_window_searches) if win_inc < 1: win_inc = 1 possible_parameters = self._unique_parameters(max_window, win_inc) num_classifiers = 0 subsample_size = int(self.n_instances_ * 0.7) lowest_acc = 1 lowest_acc_idx = 0 time_limit = self.time_limit_in_minutes * 60 start_time = time.time() train_time = 0 if time_limit > 0: n_parameter_samples = 0 contract_max_n_parameter_samples = self.contract_max_n_parameter_samples else: n_parameter_samples = self.n_parameter_samples contract_max_n_parameter_samples = np.inf rng = check_random_state(self.random_state) if self.bigrams is None: if self.n_dims_ > 1: use_bigrams = False else: use_bigrams = True else: use_bigrams = self.bigrams # use time limit or n_parameter_samples if limit is 0 while ( ( train_time < time_limit and num_classifiers < contract_max_n_parameter_samples ) or num_classifiers < n_parameter_samples ) and len(possible_parameters) > 0: if num_classifiers < self.randomly_selected_params: parameters = possible_parameters.pop( rng.randint(0, len(possible_parameters)) ) else: scaler = preprocessing.StandardScaler() scaler.fit(self._prev_parameters_x) gp = KernelRidge(kernel="poly", degree=1) gp.fit( scaler.transform(self._prev_parameters_x), self._prev_parameters_y ) preds = gp.predict(scaler.transform(possible_parameters)) parameters = possible_parameters.pop( rng.choice(np.flatnonzero(preds == preds.max())) ) subsample = rng.choice( self.n_instances_, size=subsample_size, replace=False ) X_subsample = X[subsample] y_subsample = y[subsample] tde = IndividualTDE( *parameters, alphabet_size=self._alphabet_size, bigrams=use_bigrams, dim_threshold=self.dim_threshold, max_dims=self.max_dims, typed_dict=self.typed_dict, n_jobs=self._threads_to_use, random_state=self.random_state, ) tde.fit(X_subsample, y_subsample) tde._subsample = subsample tde._accuracy = self._individual_train_acc( tde, y_subsample, subsample_size, 0 if num_classifiers < self.max_ensemble_size else lowest_acc, ) if tde._accuracy > 0: weight = math.pow(tde._accuracy, 4) else: weight = 0.000000001 if num_classifiers < self.max_ensemble_size: if tde._accuracy < lowest_acc: lowest_acc = tde._accuracy lowest_acc_idx = num_classifiers self.weights_.append(weight) self.estimators_.append(tde) elif tde._accuracy > lowest_acc: self.weights_[lowest_acc_idx] = weight self.estimators_[lowest_acc_idx] = tde lowest_acc, lowest_acc_idx = self._worst_ensemble_acc() self._prev_parameters_x.append(parameters) self._prev_parameters_y.append(tde._accuracy) num_classifiers += 1 train_time = time.time() - start_time self.n_estimators_ = len(self.estimators_) self._weight_sum = np.sum(self.weights_) return self def _predict(self, X) -> np.ndarray: """Predict class values of n instances in X. Parameters ---------- X : 3D np.array of shape = [n_instances, n_dimensions, series_length] The data to make predictions for. Returns ------- y : array-like, shape = [n_instances] Predicted class labels. """ rng = check_random_state(self.random_state) return np.array( [ self.classes_[int(rng.choice(np.flatnonzero(prob == prob.max())))] for prob in self._predict_proba(X) ] ) def _predict_proba(self, X) -> np.ndarray: """Predict class probabilities for n instances in X. Parameters ---------- X : 3D np.array of shape = [n_instances, n_dimensions, series_length] The data to make predict probabilities for. Returns ------- y : array-like, shape = [n_instances, n_classes_] Predicted probabilities using the ordering in classes_. """ _, _, series_length = X.shape if series_length != self.series_length_: raise TypeError( "ERROR number of attributes in the train does not match " "that in the test data" ) sums = np.zeros((X.shape[0], self.n_classes_)) for n, clf in enumerate(self.estimators_): preds = clf.predict(X) for i in range(0, X.shape[0]): sums[i, self._class_dictionary[preds[i]]] += self.weights_[n] return sums / (np.ones(self.n_classes_) * self._weight_sum) def _worst_ensemble_acc(self): min_acc = 1.0 min_acc_idx = 0 for c, classifier in enumerate(self.estimators_): if classifier._accuracy < min_acc: min_acc = classifier._accuracy min_acc_idx = c return min_acc, min_acc_idx def _unique_parameters(self, max_window, win_inc): possible_parameters = [ [win_size, word_len, normalise, levels, igb] for normalise in self._norm_options for win_size in range(self._min_window, max_window + 1, win_inc) for word_len in self._word_lengths for levels in self._levels for igb in self._igb_options ] return possible_parameters def _get_train_probs(self, X, y, train_estimate_method="loocv") -> np.ndarray: from sktime.datatypes import convert_to self.check_is_fitted() if not isinstance(X, np.ndarray): X = convert_to(X, "numpy3D") X, y = check_X_y(X, y, coerce_to_numpy=True) n_instances, n_dims, series_length = X.shape if ( n_instances != self.n_instances_ or n_dims != self.n_dims_ or series_length != self.series_length_ ): raise ValueError( "n_instances, n_dims, series_length mismatch. X should be " "the same as the training data used in fit for generating train " "probabilities." ) results = np.zeros((n_instances, self.n_classes_)) divisors = np.zeros(n_instances) if train_estimate_method.lower() == "loocv": for i, clf in enumerate(self.estimators_): subsample = clf._subsample preds = ( clf._train_predictions if self.save_train_predictions else Parallel(n_jobs=self._threads_to_use, prefer="threads")( delayed(clf._train_predict)( i, ) for i in range(len(subsample)) ) ) for n, pred in enumerate(preds): results[subsample[n]][self._class_dictionary[pred]] += ( self.weights_[i] ) divisors[subsample[n]] += self.weights_[i] elif train_estimate_method.lower() == "oob": indices = range(n_instances) for i, clf in enumerate(self.estimators_): oob = [n for n in indices if n not in clf._subsample] if len(oob) == 0: continue preds = clf.predict(X[oob]) for n, pred in enumerate(preds): results[oob[n]][self._class_dictionary[pred]] += self.weights_[i] divisors[oob[n]] += self.weights_[i] else: raise ValueError( "Invalid train_estimate_method. Available options: loocv, oob" ) for i in range(n_instances): results[i] = ( np.ones(self.n_classes_) * (1 / self.n_classes_) if divisors[i] == 0 else results[i] / (np.ones(self.n_classes_) * divisors[i]) ) return results def _individual_train_acc(self, tde, y, train_size, lowest_acc): correct = 0 required_correct = int(lowest_acc * train_size) if self._threads_to_use > 1: c = Parallel(n_jobs=self._threads_to_use, prefer="threads")( delayed(tde._train_predict)( i, ) for i in range(train_size) ) for i in range(train_size): if correct + train_size - i < required_correct: return -1 elif c[i] == y[i]: correct += 1 if self.save_train_predictions: tde._train_predictions.append(c[i]) else: for i in range(train_size): if correct + train_size - i < required_correct: return -1 c = tde._train_predict(i) if c == y[i]: correct += 1 if self.save_train_predictions: tde._train_predictions.append(c) return correct / train_size
[文档] @classmethod def get_test_params(cls, parameter_set="default"): """Return testing parameter settings for the estimator. Parameters ---------- parameter_set : str, default="default" Name of the set of test parameters to return, for use in tests. If no special parameters are defined for a value, will return ``"default"`` set. For classifiers, a "default" set of parameters should be provided for general testing, and a "results_comparison" set for comparing against previously recorded results if the general set does not produce suitable probabilities to compare against. Returns ------- params : dict or list of dict, default={} Parameters to create testing instances of the class. Each dict are parameters to construct an "interesting" test instance, i.e., ``MyClass(**params)`` or ``MyClass(**params[i])`` creates a valid test instance. ``create_test_instance`` uses the first (or only) dictionary in ``params``. """ if parameter_set == "results_comparison": return { "n_parameter_samples": 10, "max_ensemble_size": 5, "randomly_selected_params": 5, } else: return { "n_parameter_samples": 5, "max_ensemble_size": 2, "randomly_selected_params": 3, "save_train_predictions": True, }
[文档]class IndividualTDE(BaseClassifier): """Single TDE classifier, an extension of the Bag of SFA Symbols (BOSS) model. Base classifier for the TDE classifier. Implementation of single TDE base model from Middlehurst (2021). [1]_ Overview: input "n" series of length "m" and IndividualTDE performs a SFA transform to form a sparse dictionary of discretised words. The resulting dictionary is used with the histogram intersection distance function in a 1-nearest neighbor. fit involves finding "n" histograms. predict uses 1 nearest neighbor with the histogram intersection distance function. Parameters ---------- window_size : int, default=10 Size of the window to use in the SFA transform. word_length : int, default=8 Length of word to use to use in the SFA transform. norm : bool, default=False Whether to normalize SFA words by dropping the first Fourier coefficient. levels : int, default=1 The number of spatial pyramid levels for the SFA transform. igb : bool, default=False Whether to use Information Gain Binning (IGB) or Multiple Coefficient Binning (MCB) for the SFA transform. alphabet_size : default=4 Number of possible letters (values) for each word. bigrams : bool, default=False Whether to record word bigrams in the SFA transform. dim_threshold : float, default=0.85 Accuracy threshold as a proportion of the highest accuracy dimension for words extracted from each dimensions. Only applicable for multivariate data. max_dims : int, default=20 Maximum number of dimensions words are extracted from. Only applicable for multivariate data. typed_dict : bool, default=True Use a numba TypedDict to store word counts. May increase memory usage, but will be faster for larger datasets. n_jobs : int, default=1 The number of jobs to run in parallel for both ``fit`` and ``predict``. ``-1`` means using all processors. random_state : int or None, default=None Seed for random, integer. Attributes ---------- n_classes_ : int The number of classes. classes_ : list The classes labels. n_instances_ : int The number of train cases. n_dims_ : int The number of dimensions per case. series_length_ : int The length of each series. See Also -------- TemporalDictionaryEnsemble, SFA Notes ----- For the Java version, see `TSML <https://github.com/uea-machine-learning/tsml/blob/master/src/main/java/ tsml/classifiers/dictionary_based/IndividualTDE.java>`_. References ---------- .. [1] Matthew Middlehurst, James Large, Gavin Cawley and Anthony Bagnall "The Temporal Dictionary Ensemble (TDE) Classifier for Time Series Classification", in proceedings of the European Conference on Machine Learning and Principles and Practice of Knowledge Discovery in Databases, 2020. Examples -------- >>> from sktime.classification.dictionary_based import IndividualTDE >>> from sktime.datasets import load_unit_test >>> X_train, y_train = load_unit_test(split="train", return_X_y=True) >>> X_test, y_test = load_unit_test(split="test", return_X_y=True) # doctest: +SKIP >>> clf = IndividualTDE() # doctest: +SKIP >>> clf.fit(X_train, y_train) # doctest: +SKIP IndividualTDE(...) >>> y_pred = clf.predict(X_test) # doctest: +SKIP """ _tags = { # packaging info # -------------- "authors": ["MatthewMiddlehurst"], "python_dependencies": "numba", # estimator type # -------------- "capability:multivariate": True, "capability:multithreading": True, } def __init__( self, window_size=10, word_length=8, norm=False, levels=1, igb=False, alphabet_size=4, bigrams=True, dim_threshold=0.85, max_dims=20, typed_dict=True, n_jobs=1, random_state=None, ): self.window_size = window_size self.word_length = word_length self.norm = norm self.levels = levels self.igb = igb self.alphabet_size = alphabet_size self.bigrams = bigrams # multivariate self.dim_threshold = dim_threshold self.max_dims = max_dims self.typed_dict = typed_dict self.n_jobs = n_jobs self.random_state = random_state self.n_instances_ = 0 self.n_dims_ = 0 self.series_length_ = 0 self._transformers = [] self._transformed_data = [] self._class_vals = [] self._dims = [] self._highest_dim_bit = 0 self._accuracy = 0 self._subsample = [] self._train_predictions = [] super().__init__() # todo remove along with BOSS and SFA workarounds when Dict becomes serialisable. def __getstate__(self): """Return state as dictionary for pickling, required for typed Dict objects.""" state = self.__dict__.copy() if self.typed_dict: nl = [None] * len(self._transformed_data) for i, ndict in enumerate(state["_transformed_data"]): pdict = dict() for key, val in ndict.items(): pdict[key] = val nl[i] = pdict state["_transformed_data"] = nl return state def __setstate__(self, state): """Set current state using input pickling, required for typed Dict objects.""" from numba import types from numba.typed import Dict self.__dict__.update(state) if self.typed_dict: nl = [None] * len(self._transformed_data) for i, pdict in enumerate(self._transformed_data): ndict = ( Dict.empty( key_type=types.UniTuple(types.int64, 2), value_type=types.uint32 ) if self.levels > 1 or self.n_dims_ > 1 else Dict.empty(key_type=types.int64, value_type=types.uint32) ) for key, val in pdict.items(): ndict[key] = val nl[i] = ndict self._transformed_data = nl def _fit(self, X, y): """Fit a single base TDE classifier on n_instances cases (X,y). Parameters ---------- X : 3D np.array of shape = [n_instances, n_dimensions, series_length] The training data. y : array-like, shape = [n_instances] The class labels. Returns ------- self : Reference to self. Notes ----- Changes state by creating a fitted model that updates attributes ending in "_" and sets is_fitted flag to True. """ from numba import types from numba.typed import Dict self.n_instances_, self.n_dims_, self.series_length_ = X.shape self._class_vals = y # select dimensions using accuracy estimate if multivariate if self.n_dims_ > 1: self._dims, self._transformers = self._select_dims(X, y) words = ( [ Dict.empty( key_type=types.UniTuple(types.int64, 2), value_type=types.uint32 ) for _ in range(self.n_instances_) ] if self.typed_dict else [defaultdict(int) for _ in range(self.n_instances_)] ) for i, dim in enumerate(self._dims): X_dim = X[:, dim, :].reshape(self.n_instances_, 1, self.series_length_) dim_words = self._transformers[i].transform(X_dim, y) dim_words = dim_words[0] for n in range(self.n_instances_): if self.typed_dict: for word, count in dim_words[n].items(): if self.levels > 1: words[n][ (word[0], word[1] << self._highest_dim_bit | dim) ] = count else: words[n][(word, dim)] = count else: for word, count in dim_words[n].items(): words[n][word << self._highest_dim_bit | dim] = count self._transformed_data = words else: self._transformers.append( SFA( word_length=self.word_length, alphabet_size=self.alphabet_size, window_size=self.window_size, norm=self.norm, levels=self.levels, binning_method="information-gain" if self.igb else "equi-depth", bigrams=self.bigrams, remove_repeat_words=True, lower_bounding=False, save_words=False, use_fallback_dft=True, typed_dict=self.typed_dict, n_jobs=self._threads_to_use, ) ) sfa = self._transformers[0].fit_transform(X, y) self._transformed_data = sfa[0] def _predict(self, X): """Predict class values of all instances in X. Parameters ---------- X : 3D np.array of shape = [n_instances, n_dimensions, series_length] The data to make predictions for. Returns ------- y : array-like, shape = [n_instances] Predicted class labels. """ from numba import types from numba.typed import Dict num_cases = X.shape[0] if self.n_dims_ > 1: words = ( [ Dict.empty( key_type=types.UniTuple(types.int64, 2), value_type=types.uint32 ) for _ in range(num_cases) ] if self.typed_dict else [defaultdict(int) for _ in range(num_cases)] ) for i, dim in enumerate(self._dims): X_dim = X[:, dim, :].reshape(num_cases, 1, self.series_length_) dim_words = self._transformers[i].transform(X_dim) dim_words = dim_words[0] for n in range(num_cases): if self.typed_dict: for word, count in dim_words[n].items(): if self.levels > 1: words[n][ (word[0], word[1] << self._highest_dim_bit | dim) ] = count else: words[n][(word, dim)] = count else: for word, count in dim_words[n].items(): words[n][word << self._highest_dim_bit | dim] = count test_bags = words else: test_bags = self._transformers[0].transform(X) test_bags = test_bags[0] classes = Parallel(n_jobs=self._threads_to_use, prefer="threads")( delayed(self._test_nn)( test_bag, ) for test_bag in test_bags ) return np.array(classes) def _test_nn(self, test_bag): rng = check_random_state(self.random_state) best_sim = -1 nn = None for n, bag in enumerate(self._transformed_data): sim = histogram_intersection(test_bag, bag) if sim > best_sim or (sim == best_sim and rng.random() < 0.5): best_sim = sim nn = self._class_vals[n] return nn def _select_dims(self, X, y): self._highest_dim_bit = (math.ceil(math.log2(self.n_dims_))) + 1 accs = [] transformers = [] # select dimensions based on reduced bag size accuracy for i in range(self.n_dims_): self._dims.append(i) transformers.append( SFA( word_length=self.word_length, alphabet_size=self.alphabet_size, window_size=self.window_size, norm=self.norm, levels=self.levels, binning_method="information-gain" if self.igb else "equi-depth", bigrams=self.bigrams, remove_repeat_words=True, lower_bounding=False, save_words=False, keep_binning_dft=True, use_fallback_dft=True, typed_dict=self.typed_dict, n_jobs=self._threads_to_use, ) ) X_dim = X[:, i, :].reshape(self.n_instances_, 1, self.series_length_) transformers[i].fit(X_dim, y) sfa = transformers[i].transform( X_dim, y, ) transformers[i].keep_binning_dft = False transformers[i].binning_dft = None correct = 0 for i in range(self.n_instances_): if self._train_predict(i, sfa[0]) == y[i]: correct = correct + 1 accs.append(correct) max_acc = max(accs) dims = [] fin_transformers = [] for i in range(self.n_dims_): if accs[i] >= max_acc * self.dim_threshold: dims.append(i) fin_transformers.append(transformers[i]) if len(dims) > self.max_dims: rng = check_random_state(self.random_state) idx = rng.choice(len(dims), self.max_dims, replace=False).tolist() dims = [dims[i] for i in idx] fin_transformers = [fin_transformers[i] for i in idx] return dims, fin_transformers def _train_predict(self, train_num, bags=None): if bags is None: bags = self._transformed_data test_bag = bags[train_num] best_sim = -1 nn = None for n, bag in enumerate(bags): if n == train_num: continue sim = histogram_intersection(test_bag, bag) if sim > best_sim: best_sim = sim nn = self._class_vals[n] return nn
def histogram_intersection(first, second): """Find the distance between two histograms using the histogram intersection. This distance function is designed for sparse matrix, represented as a dictionary or numba Dict, but can accept arrays. Parameters ---------- first : dict, numba.Dict or array First dictionary used in distance measurement. second : dict, numba.Dict or array Second dictionary that will be used to measure distance from ``first``. Returns ------- dist : float The histogram intersection distance between the first and second dictionaries. """ from numba.typed import Dict from sktime.classification.dictionary_based._tde_numba import ( _histogram_intersection_dict, ) if isinstance(first, dict): sim = 0 for word, val_a in first.items(): val_b = second.get(word, 0) sim += min(val_a, val_b) return sim elif isinstance(first, Dict): return _histogram_intersection_dict(first, second) else: return np.sum( [ 0 if first[n] == 0 else np.min(first[n], second[n]) for n in range(len(first)) ] )