Source code for geomstats.learning.exponential_barycenter

"""Exponential barycenter.

Lead author: Nicolas Guigui.
"""

import logging

from sklearn.base import BaseEstimator

import geomstats.backend as gs
from geomstats.geometry.euclidean import Euclidean
from geomstats.learning.frechet_mean import BaseGradientDescent, LinearMean


[docs] class GradientDescent(BaseGradientDescent): """Gradient descent for exponential barycenter."""
[docs] def minimize(self, group, points, weights=None): """Compute the (weighted) group exponential barycenter of `points`. Parameters ---------- group : LieGroup Instance of the class LieGroup. points : array-like, shape=[n_samples, dim, dim] Input points lying in the Lie Group. weights : array-like, shape=[n_samples,] Weights associated to the points. Optional, defaults to 1 for each point if None. Returns ------- exp_bar : array-like, shape=[dim, dim] Exponential barycenter of the input points. """ n_samples = points.shape[0] if n_samples == 1: return points[0] if weights is None: weights = gs.ones((n_samples,)) sum_weights = gs.sum(weights) mean = points[0] if self.init_point is None else self.init_point grad_norm = 0.0 for iteration in range(self.max_iter): if not (grad_norm > self.epsilon or iteration == 0): break inv_mean = group.inverse(mean) centered_points = group.compose(inv_mean, points) logs = group.log(point=centered_points) tangent_mean = self.init_step_size * gs.einsum( "n, nk...->k...", weights / sum_weights, logs ) mean_next = group.compose(mean, group.exp(tangent_vec=tangent_mean)) grad_norm = gs.linalg.norm(tangent_mean) mean = mean_next else: logging.warning( f"Maximum number of iterations {self.max_iter} reached. " "The mean may be inaccurate" ) if self.verbose: logging.info(f"n_iter: {iteration}, final gradient norm: {grad_norm}") return mean
[docs] class ExponentialBarycenter(BaseEstimator): """Empirical exponential barycenter for matrix groups. Parameters ---------- space : LieGroup Lie group instance on which the data lie. Attributes ---------- estimate_ : array-like, shape=[dim, dim] If fit, exponential barycenter. """ def __new__(cls, space): """Interface for instantiating proper algorithm.""" if isinstance(space, Euclidean): return LinearMean(space) return super().__new__(cls) def __init__(self, space): self.space = space self.optimizer = GradientDescent() self.estimate_ = None
[docs] def set(self, **kwargs): """Set optimizer parameters. Especially useful for one line instantiations. """ for param_name, value in kwargs.items(): if not hasattr(self.optimizer, param_name): raise ValueError(f"Unknown parameter {param_name}.") setattr(self.optimizer, param_name, value) return self
[docs] def fit(self, X, y=None, weights=None): """Compute the empirical weighted exponential barycenter. Parameters ---------- X : array-like, shape=[n_samples, dim, dim] Training input samples. y : None Target values. Ignored. weights : array-like, shape=[n_samples,] Weights associated to the samples. Optional, default: None, in which case it is equally weighted. Returns ------- self : object Returns self. """ # TODO (nguigs): use closed form expression for special euclidean # group as before PR #537 self.estimate_ = self.optimizer.minimize( group=self.space, points=X, weights=weights, ) return self