def decision_function(self, X): """Compute the decision function of `X`. Parameters ---------- X : array-like of shape (n_samples, n_features, n_features) The input covariance features data. Returns ------- scores : :class:`numpy.ndarray` of shape (n_samples, k) The decision function of the input samples. The order of outputs is the same of that of the `classes_` attribute. Binary classification is a special cases with `k` = 1, otherwise `k` = `n_classes`. For binary classification, positive values indicate class 1 and negative values indicate class 0. """ check_is_fitted(self, "estimators_") if self.n_classes_ == 2: scores = [ estimator.predict(tangent_space(X, mu)) for estimator, mu in zip(self.estimators_, self.mean_spd_matrices) ] scores = np.sum(scores, axis=0) else: scores = [[ estimator.predict(tangent_space(X, mu)) for estimator, mu in zip(estimators, means) ] for estimators, means in zip(self.estimators_, self.mean_spd_matrices)] scores = np.sum(scores, axis=0).T return scores
def test_tangent_space(get_covmats): """Test tangent space projection""" n_matrices, n_channels = 6, 3 n_ts = (n_channels * (n_channels + 1)) // 2 covmats = get_covmats(n_matrices, n_channels) Xts = tangent_space(covmats, np.eye(n_channels)) assert Xts.shape == (n_matrices, n_ts)
def test_tangent_and_untangent_space(get_covmats): """Test tangent space projection and retro-projection should be the same""" n_matrices, n_channels = 10, 3 covmats = get_covmats(n_matrices, n_channels) Xts = tangent_space(covmats, np.eye(n_channels)) covmats_ut = untangent_space(Xts, np.eye(n_channels)) assert covmats_ut == approx(covmats)
def _fit_binary(self, X, y, random_state, fit_params): """Fit a binary LogitBoost model. This is Algorithm 3 in Friedman, Hastie, & Tibshirani (2000). """ # Initialize with uniform class probabilities p = np.full(shape=X.shape[0], fill_value=0.5, dtype=np.float64) # Initialize zero scores for each observation scores = np.zeros(X.shape[0], dtype=np.float64) # Do the boosting iterations to build the ensemble of estimators for i in range(self.n_estimators): # Update the working response and weights for this iteration sample_weight, z = self._weights_and_response(y, p) # Mapping the data to tangent space of the Riemannian mean mu = mean_riemann(X, sample_weight=sample_weight) self.mean_spd_matrices.append(mu) X_tspace = tangent_space(X, mu) # Create and fit a new base estimator X_train, z_train, kwargs = self._boost_fit_args( X_tspace, z, sample_weight, random_state) estimator = self._make_estimator(append=True, random_state=random_state) kwargs.update(fit_params) estimator.fit(X_train, z_train, **kwargs) # Update the scores and the probability estimates, unless we're # doing the last iteration if i < self.n_estimators - 1: new_scores = estimator.predict(X_tspace) scores += self.learning_rate * new_scores p = expit(scores)
def _fit_multiclass(self, X, y, random_state, fit_params): """Fit a multiclass LogitBoost model. This is Algorithm 6 in Friedman, Hastie, & Tibshirani (2000). """ # Initialize with uniform class probabilities p = np.full(shape=(X.shape[0], self.n_classes_), fill_value=(1. / self.n_classes_), dtype=np.float64) # Initialize zero scores for each observation scores = np.zeros((X.shape[0], self.n_classes_), dtype=np.float64) # Convert y to a one-hot-encoded vector y = np.eye(self.n_classes_)[y] # Do the boosting iterations to build the ensemble of estimators for iboost in range(self.n_estimators): # List of estimators for this boosting iteration new_estimators = [] new_mean_spd_matrices = [] # Create a new estimator for each class new_scores = [] for j in range(self.n_classes_): # Compute the working response and weights sample_weight, z = self._weights_and_response(y[:, j], p[:, j]) # Mapping the data to tangent space of the Riemannian mean mu = mean_riemann(X, sample_weight=sample_weight) new_mean_spd_matrices.append(mu) X_tspace = tangent_space(X, mu) # Fit a new base estimator X_train, z_train, kwargs = self._boost_fit_args( X_tspace, z, sample_weight, random_state) estimator = self._make_estimator(append=False, random_state=random_state) kwargs.update(fit_params) estimator.fit(X_train, z_train, **kwargs) new_estimators.append(estimator) # Update the scores and the probability estimates if iboost < self.n_estimators - 1: new_scores.append(estimator.predict(X_tspace)) if iboost < self.n_estimators - 1: new_scores = np.asarray(new_scores).T new_scores -= new_scores.mean(axis=1, keepdims=True) new_scores *= (self.n_classes_ - 1) / self.n_classes_ scores += self.learning_rate * new_scores p = softmax(scores, axis=1) self.estimators_.append(new_estimators) self.mean_spd_matrices.append(new_mean_spd_matrices)
def contributions(self, X, sample_weight=None): """Average absolute contribution of each estimator in the ensemble. This can be used to compare how much influence different estimators in the ensemble have on the final predictions made by the model. Parameters ---------- X : array-like of shape (n_samples, n_features, n_features) The input samples to average over. sample_weight : array-like of shape (n_samples,) (default=None) Weights for the samples, for averaging. Returns ------- contrib : :class:`numpy.ndarray` of shape (n_estimators,) Average absolute contribution of each estimator in the ensemble. """ check_is_fitted(self, 'estimators_') if self.n_classes_ == 2: predictions = [ estimator.predict(tangent_space(X, mu)) for estimator, mu in zip(self.estimators_, self.mean_spd_matrices) ] predictions = np.abs(predictions) else: predictions = [[ estimator.predict(tangent_space(X, mu)) for estimator, mu in zip(estimators, means) ] for estimators, means in zip( self.estimators_, self.mean_spd_matrices)] predictions = np.abs(predictions) predictions = np.mean(predictions, axis=1) return np.average(predictions, axis=-1, weights=sample_weight)
def staged_decision_function(self, X): """Compute decision function of `X` for each boosting iteration. This method allows monitoring (i.e. determine error on testing set) after each boosting iteration. Parameters ---------- X : array-like of shape (n_samples, n_features, n_features) The input data. Yields ------ scores : :class:`numpy.ndarray` of shape (n_samples, k) The decision function of the input samples. The order of outputs is the same of that of the `classes_` attribute. Binary classification is a special cases with `k` = 1, otherwise `k` = `n_classes`. For binary classification, positive values indicate class 1 and negative values indicate class 0. """ check_is_fitted(self, 'estimators_') if self.n_classes_ == 2: scores = 0 for estimator, mu in zip(self.estimators_, self.mean_spd_matrices): scores += estimator.predict(tangent_space(X, mu)) yield scores else: scores = 0 for estimators, means in zip(self.estimators_, self.mean_spd_matrices): new_scores = [ estimator.predict(tangent_space(X, mu)) for estimator, mu in zip(estimators, means) ] scores += np.asarray(new_scores).T yield scores
def fit_transform(self, X, y=None): """Fit and transform in a single function. Parameters ---------- X : ndarray, shape (n_trials, n_channels, n_channels) ndarray of SPD matrices. y : ndarray | None (default None) Not used, here for compatibility with sklearn API. Returns ------- ts : ndarray, shape (n_trials, n_ts) the tangent space projection of the matrices. """ # compute mean covariance self._check_reference_points(X) self.reference_ = mean_riemann(X, u_prime=self.u_prime) return tangent_space(X, self.reference_)
def transform(self, X): """Tangent space projection. Parameters ---------- X : ndarray, shape (n_trials, n_channels, n_channels) ndarray of SPD matrices. Returns ------- ts : ndarray, shape (n_trials, n_ts) the tangent space projection of the matrices. """ self._check_reference_points(X) if self.tsupdate: Cr = mean_riemann(X, u_prime=self.u_prime) else: Cr = self.reference_ return tangent_space(X, Cr)
def test_tangent_and_untangent_space(): """Test tangent space projection and retro-projection should be the same""" C = generate_cov(10,3) T = tangent_space(C,np.eye(3)) covmats = untangent_space(T,np.eye(3)) assert_array_equal(C,covmats)
def test_tangent_space(): """Test tangent space projection""" C = generate_cov(10,3) T = tangent_space(C,np.eye(3))