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
Example #8
0
 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_)
Example #9
0
 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))