Source code for RuleTree.stumps.transformers.ObliquePivotTransformer

"""ObliquePivotTransformer: oblique directions on the distances from the candidate pivots."""

import numpy as np

from RuleTree.stumps.transformers.BivariateTransformer import BivariateTransformer
from RuleTree.stumps.transformers.HouseholderTransformer import HouseholderTransformer
from RuleTree.stumps.transformers.PivotTransformer import PivotTransformer, scaler_from_state, scaler_state
from RuleTree.stumps.transformers._pivot_selection import PivotSelection
from RuleTree.utils.define import MODEL_TYPE_CLF


[docs] class ObliquePivotTransformer(PivotTransformer): """ Transformer of tabular data into oblique directions on the distances from the candidate pivots, e.g. ``0.7 * P_12 - 0.3 * P_57`` where ``P_i`` is the distance from the training instance ``i``. The candidate pivots are selected as in :class:`~RuleTree.stumps.transformers.PivotTransformer.PivotTransformer`, but the discriminative ones are the instances combined by an oblique direction (``oblique_split_type``, see PivotSelection). Then the distances of the instances of the node from the candidates are transformed by an oblique transformer of the same type (:class:`~RuleTree.stumps.transformers.HouseholderTransformer.HouseholderTransformer` or :class:`~RuleTree.stumps.transformers.BivariateTransformer.BivariateTransformer`), whose directions are the columns of the feature matrix. After the splitter has chosen a column, only its direction is kept (``select``); the candidates are all kept. Parameters ---------- ml_task : {MODEL_TYPE_CLF, MODEL_TYPE_REG}, default=MODEL_TYPE_CLF Task of the stump, which determines how the pivots and the directions are selected. distance_measure : str or callable, default='euclidean' Distance between the instances, computed on the standardized numerical features. k, reg_contrast_lambda, reg_max_thresholds Regression only: see PivotTransformer. tree_params : dict, default=None Parameters of the decision trees that select the pivots and the directions. oblique_split_type : {'householder', 'bivariate'}, default='householder' Type of the oblique directions. max_oblique_features : int, default=2 Householder only: maximum number of pivots combined by a direction. tau : float, default=1e-4 Householder only: threshold of the reflection. n_orientations : int, default=10 Bivariate only: number of angles. Attributes ---------- numerical_ : list of int Numerical columns of the feature matrix. categorical_ : list of int Categorical columns of the feature matrix (none). candidates_ : numpy.ndarray of shape (n_candidates, n_numerical_features) The candidate pivots, standardized. candidate_names_ : numpy.ndarray of shape (n_candidates,) Indices of the candidate pivots in the training set. oblique_ : HouseholderTransformer or BivariateTransformer The oblique transformer of the distances from the candidates. pivots_ : dict Indices in the training set of the ``descriptives``, ``discriminatives`` and ``candidates`` pivots of the node. scaler_ : StandardScaler Scaler of the numerical features, fitted on the whole data set. See Also -------- ObliquePivotTreeStumpClassifier : the stump that uses it. Examples -------- >>> from sklearn.datasets import load_iris >>> from RuleTree.stumps.transformers import ObliquePivotTransformer >>> X, y = load_iris(return_X_y=True) >>> transformer = ObliquePivotTransformer(tree_params={"random_state": 0}).fit(X, y) >>> transformer.transform(X).shape[1] == len(transformer.candidate_names_) # one direction per candidate True """ def __init__(self, ml_task=MODEL_TYPE_CLF, distance_measure='euclidean', k=None, reg_contrast_lambda=.2, reg_max_thresholds=None, tree_params=None, oblique_split_type='householder', max_oblique_features=2, tau=1e-4, n_orientations=10): """Initialize the ObliquePivotTransformer; see the class documentation for the parameters.""" super().__init__(ml_task=ml_task, distance_measure=distance_measure, k=k, reg_contrast_lambda=reg_contrast_lambda, reg_max_thresholds=reg_max_thresholds, tree_params=tree_params) self.oblique_split_type = oblique_split_type self.max_oblique_features = max_oblique_features self.tau = tau self.n_orientations = n_orientations def _selection(self): """A new PivotSelection whose discriminative pivots are found with ``oblique_split_type``.""" return PivotSelection(self.ml_task, k=self.k, reg_contrast_lambda=self.reg_contrast_lambda, reg_max_thresholds=self.reg_max_thresholds, discriminative=self.oblique_split_type, max_oblique_features=self.max_oblique_features, tau=self.tau, n_orientations=self.n_orientations, **(self.tree_params or {})) def _oblique(self): """A new, unfitted oblique transformer of the distances, of type ``oblique_split_type``.""" tree_params = self.tree_params or {} if self.oblique_split_type == "bivariate": return BivariateTransformer(self.ml_task, n_orientations=self.n_orientations, tree_params=tree_params) return HouseholderTransformer(max_oblique_features=self.max_oblique_features, tau=self.tau, random_state=tree_params.get("random_state")) def _distances(self, X): """Distances of the instances from the candidate pivots.""" return PivotTransformer.transform(self, X)
[docs] def fit(self, X, y, idx=None, data_type=None, cache=None, sample_weight=None): """ Select the candidate pivots of the node (see PivotTransformer.fit), then the oblique directions on the distances of the instances of the node from them. Parameters ---------- X : numpy.ndarray Tabular data, 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 Sample weights, all the instances. Returns ------- self : ObliquePivotTransformer Raises ------ NoSplitFoundWarning If the data have no numerical feature, or (bivariate) there are fewer than two candidates. """ if idx is None: idx = np.arange(len(X)) super().fit(X, y, idx, data_type=data_type, cache=cache, sample_weight=sample_weight) self.oblique_ = self._oblique().fit(self._distances(X[idx]), np.asarray(y)[idx], sample_weight=None if sample_weight is None else sample_weight[idx]) self.numerical_, self.categorical_ = self.oblique_.numerical_, [] return self
[docs] def transform(self, X): """ Projection of the distances from the candidates on the oblique directions (on the chosen one, after select). Parameters ---------- X : numpy.ndarray Tabular data. Returns ------- X_transformed : numpy.ndarray of shape (n_samples, n_directions) """ return self.oblique_.transform(self._distances(X))
[docs] def select(self, feature): """ Keep only the chosen direction. Parameters ---------- feature : int Column chosen by the splitter. Returns ------- feature : int Always 0: the projection on the only direction kept. """ feature = self.oblique_.select(feature) self.numerical_, self.categorical_ = [0], [] return feature
[docs] def combination(self, feature): """ Pivots and coefficients of a direction: the column is ``sum(c * P_i)``. Parameters ---------- feature : int Column of the feature matrix. Returns ------- pivots : list of int Indices in the training set of the pivots with a non-zero coefficient. coefficients : list of float Their coefficients. """ candidates, coefficients = self.oblique_.combination(feature) return [int(self.candidate_names_[c]) for c in candidates], coefficients
[docs] def feature_id(self, feature): """ Identifier of a direction: the indices of the pivots it combines. Parameters ---------- feature : int Column of the feature matrix. Returns ------- feature_id : tuple of int """ return tuple(self.combination(feature)[0])
[docs] def get_state(self): """ Fitted state: the candidate pivots and their indices, the numerical features, the scaler and the state of the oblique transformer. Returns ------- state : dict JSON-friendly dictionary, restored by set_state. """ return { 'num_pre_transformed': list(self.num_pre_transformed_), 'cat_pre_transformed': list(self.cat_pre_transformed_), 'candidates': np.asarray(self.candidates_).tolist(), 'candidate_names': [int(name) for name in self.candidate_names_], 'pivots': self.pivots_, 'scaler': scaler_state(self.scaler_), 'oblique': self.oblique_.get_state(), }
[docs] def set_state(self, state): """ Restore the fitted state saved by get_state. Parameters ---------- state : dict Dictionary created by get_state. Returns ------- self : ObliquePivotTransformer """ self.num_pre_transformed_ = state['num_pre_transformed'] self.cat_pre_transformed_ = state['cat_pre_transformed'] self.candidates_ = np.array(state['candidates']) self.candidate_names_ = np.array(state['candidate_names']) self.pivots_ = state['pivots'] self.scaler_ = scaler_from_state(state['scaler']) self.oblique_ = self._oblique().set_state(state['oblique']) self.numerical_, self.categorical_ = self.oblique_.numerical_, [] return self