pymanopt
repository·master·Indexed 21 days ago
https://github.com/pymanopt/pymanoptA Python toolbox for optimization on Riemannian manifolds with support for automatic differentiation. It provides built-in support for various manifolds (e.g., Sphere, Stiefel, Grassmann, SPD) and a variety of optimizers, including gradient-based methods like Conjugate Gradients and Steepest Descent, as well as derivative-free methods like Nelder-Mead and Particle Swarms. Pymanopt supports multiple autodiff backends including autograd, jax, tensorflow, and torch.
What's inside pymanopt
- Pymanopt is a Python toolbox designed for optimization on Riemannian manifolds. It provides support for automatic differentiation, allowing users to perform complex optimization tasks on non-Euclidean spaces efficiently.
Available Optimizers in Pymanopt
masterPymanopt provides a variety of optimization algorithms designed for Riemannian manifolds. You can choose from gradient-based methods, derivative-free methods, and population-based methods depending on your problem requirements.
Gradient-based methods:
Conjugate GradientsSteepest DescentRiemannian Trust Regions AlgorithmLine-Search Methods
Derivative-free methods:
Nelder-Mead AlgorithmParticle Swarms
Define cost functions and derivatives using Automatic Differentiation
masterIn Pymanopt, cost functions, gradients, and Hessian-vector products (hvps) must be defined as Python callables. To enable automatic differentiation, you must annotate these callables with a backend decorator.
Decorating a callable wraps it in a
pymanopt.autodiff.Functioninstance. This provides a backend-agnostic API to thepymanopt.core.problem.Problemclass, allowing Pymanopt to compute derivatives automatically.Important: Signature Matching The signature of your decorated callable must match the point layout of the manifold it is defined on. For example, if using the
pymanopt.manifolds.fixed_rank.FixedRankEmbeddedmanifold, points are represented via singular value decomposition rather than a single matrix. Therefore, your cost function must accept three arguments (u,s, andvt) instead of one matrix. Always check the specific manifold's documentation to determine the correct argument signature for your cost function.# Example conceptual usage: # @backend_decorator # def cost_function(u, s, vt): # ... # return valueHow to use Pymanopt for optimization
masterPymanopt follows a modular four-step workflow to solve optimization problems on manifolds:
- Instantiate a manifold: Select a manifold from the
pymanopt.manifoldspackage (e.g.,Sphere). - Define a cost function: Create a function $f: \mathcal{M} \to \mathbb{R}$ to minimize. Use a backend decorator from
pymanopt.function(like@pymanopt.function.autograd) to enable automatic differentiation. - Create a Problem: Instantiate a
pymanopt.Problemby tying the manifold and the cost function together. - Run an optimizer: Instantiate an optimizer from
pymanopt.optimizersand call.run(problem).
import autograd.numpy as anp import pymanopt # 1. Instantiate manifold manifold = pymanopt.manifolds.Sphere(3) # 2. Define cost function with backend decorator @pymanopt.function.autograd(manifold) def cost(point): return -point @ matrix @ point # 3. Create problem problem = pymanopt.Problem(manifold, cost) # 4. Run optimizer optimizer = pymanopt.optimizers.SteepestDescent() result = optimizer.run(problem)- Instantiate a manifold: Select a manifold from the
How to implement a custom manifold in Pymanopt
masterIf you need to optimize over a search space not currently supported by Pymanopt, you can implement your own manifold by inheriting from one of the following base classes:
pymanopt.manifolds.manifold.Manifold: Use this for general manifold implementations.pymanopt.manifolds.manifold.RiemannianSubmanifold: Use this if your manifold is a smooth subset of a Euclidean space.
Install Pymanopt with automatic differentiation backends
masterPymanopt requires Python 3.8+, NumPy, and SciPy. To use Pymanopt's recommended automatic differentiation, you must install it with a specific backend. You can choose from
autograd,jax,tensorflow, ortorch.If you are unsure which to use,
autogradis recommended as it wraps NumPy and is very simple to use.$ pip install "pymanopt[autograd]" # or [jax], [tensorflow], [torch]Create a custom Automatic Differentiation backend
masterTo implement a new autodiff backend, you must:
- Inherit from the
pymanopt.autodiff.backends._backend.Backendclass. - Create a backend decorator using the
pymanopt.autodiff.backend_decorator_factoryfunction.
from pymanopt.autodiff import backend_decorator_factory from pymanopt.autodiff.backends._backend import Backend class MyCustomBackend(Backend): # Implement backend logic here pass my_backend_decorator = backend_decorator_factory(MyCustomBackend)- Inherit from the
Implement Riemannian submanifolds using RiemannianSubmanifold
masterIf your manifold is a submanifold of a Euclidean space, subclass
RiemannianSubmanifold. This class provides automatic implementations for converting Euclidean gradients and Hessians to their Riemannian counterparts, provided you implement the necessary geometric components.Requirements
To use the automatic conversions, you must implement:
projection(point, vector): To project vectors onto the tangent space.weingarten(point, tangent_vector, normal_vector): The Weingarten map (shape operator), which takes a tangent vector and a normal vector to produce a tangent vector.
Automatic Methods Provided
euclidean_to_riemannian_gradient(point, euclidean_gradient): Usesprojectionto convert the gradient.euclidean_to_riemannian_hessian(point, euclidean_gradient, euclidean_hessian, tangent_vector): Uses bothprojectionandweingartento convert the Hessian.
from pymanopt.manifolds import RiemannianSubmanifold class MySubmanifold(RiemannianSubmanifold): def __init__(self): super().__init__(name="Submanifold", dimension=2) # Implement abstract Manifold methods (inner_product, projection, etc.) def weingarten(self, point, tangent_vector, normal_vector): # Implementation of the Weingarten map return tangent_vectorDefine a custom manifold by subclassing Manifold
masterTo implement a new Riemannian manifold in Pymanopt, you must create a subclass of the
Manifoldbase class.Initialization
When initializing your subclass, provide:
name: A string representation.dimension: The dimension of the tangent spaces.point_layout: An integer or sequence of integers describing how points are represented. For standard numpy arrays, use1. For manifolds representing points as a tuple/list of $n$ arrays, usen.
Required Implementations
Every custom manifold must implement these abstract methods:
inner_product(point, tangent_vector_a, tangent_vector_b): The Riemannian inner product.projection(point, vector): Projects an ambient space vector onto the tangent space.norm(point, tangent_vector): Computes the norm of a tangent vector.random_point(): Returns a random point on the manifold.random_tangent_vector(point): Returns a random tangent vector at a point.zero_vector(point): Returns the origin of the tangent space.
Optimizer-Specific Requirements
Depending on the optimizer you intend to use, you may need to implement additional methods:
- First-order optimizers (e.g.,
SteepestDescent,ConjugateGradient): Requireeuclidean_to_riemannian_gradient. - Second-order optimizers (e.g.,
TrustRegions): Requireeuclidean_to_riemannian_hessian.
from pymanopt.manifolds import Manifold import numpy as np class MyCustomManifold(Manifold): def __init__(self): super().__init__(name="MyManifold", dimension=3, point_layout=1) def inner_product(self, point, tangent_vector_a, tangent_vector_b): return np.dot(tangent_vector_a, tangent_vector_b) def projection(self, point, vector): # Implementation of projection return vector def norm(self, point, tangent_vector): return np.linalg.norm(tangent_vector) def random_point(self): return np.array([0.0, 0.0, 0.0]) def random_tangent_vector(self, point): return np.array([1.0, 0.0, 0.0]) def zero_vector(self, point): return np.array([0.0, 0.0, 0.0])Use RetrAsExpMixin to fallback to retraction
masterIf your manifold does not have an efficient or available exponential map (
exp), you can use theRetrAsExpMixinto automatically fallback to theretractionmethod whenexpis called. This will trigger aRuntimeWarningnotifying the user that the exponential map is unavailable and the retraction is being used instead.from pymanopt.manifolds import Manifold, RetrAsExpMixin class MyManifold(Manifold, RetrAsExpMixin): # ... implementation ... def retraction(self, point, tangent_vector): # Implementation of retraction return point + tangent_vectorHandle singular covariance matrices with custom Line Search
masterIn MoG models, a common issue is a Gaussian component collapsing onto a single data point, causing singular covariance matrices. You can mitigate this by implementing a custom line search rule that detects
np.linalg.LinAlgErrorduring the retraction step and resets the problematic component to a random point on the manifold.To implement this, create a class that implements a
searchmethod (similar toBackTrackingLineSearcher) and a helper method (e.g.,_newxnewf) that wraps themanifold.retractioncall in atry-exceptblock.class LineSearchMoG: def __init__(self, contraction_factor=0.5, optimism=2, sufficient_decrease=1e-4, max_iterations=25, initial_step_size=1): # ... initialization ... pass def search(self, objective, manifold, x, d, f0, df0): # ... backtracking logic ... pass def _newxnewf(self, x, d, objective, manifold): newx = manifold.retraction(x, d) try: newf = objective(newx) except np.linalg.LinAlgError: # Reset component to a random point if matrix is singular replace = np.asarray([ np.linalg.matrix_rank(newx[0][k, :, :]) != newx[0][0, :, :].shape[0] for k in range(newx[0].shape[0]) ]) x[0][replace, :, :] = manifold.random_point()[0][replace, :, :] return x, objective(x), True return newx, newf, FalseSolve for the dominant eigenvector of a symmetric matrix
masterThis example demonstrates finding the dominant eigenvector of a real symmetric matrix by minimizing the function $f(x) = -x^T A x$ on the unit sphere manifold $\mathbb{S}^{n-1}$.
import autograd.numpy as anp import pymanopt anp.random.seed(42) dim = 3 manifold = pymanopt.manifolds.Sphere(dim) matrix = anp.random.normal(size=(dim, dim)) matrix = 0.5 * (matrix + matrix.T) @pymanopt.function.autograd(manifold) def cost(point): return -point @ matrix @ point problem = pymanopt.Problem(manifold, cost) optimizer = pymanopt.optimizers.SteepestDescent() result = optimizer.run(problem) # Compare with NumPy eigenvalues, eigenvectors = anp.linalg.eig(matrix) dominant_eigenvector = eigenvectors[:, eigenvalues.argmax()] print("Dominant eigenvector:", dominant_eigenvector) print("Pymanopt solution:", result.point)