pymanopt

repository·master·Indexed 21 days ago

https://github.com/pymanopt/pymanopt

A 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.

Tokens
8.8K
Snippets
23
Records
37
Agent score
75%

What's inside pymanopt

  1. What is Pymanopt

    master
    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.
  2. Available Optimizers in Pymanopt

    master

    Pymanopt 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 Gradients
    • Steepest Descent
    • Riemannian Trust Regions Algorithm
    • Line-Search Methods

    Derivative-free methods:

    • Nelder-Mead Algorithm
    • Particle Swarms
  3. Define cost functions and derivatives using Automatic Differentiation

    master

    In 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.Function instance. This provides a backend-agnostic API to the pymanopt.core.problem.Problem class, 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.FixedRankEmbedded manifold, points are represented via singular value decomposition rather than a single matrix. Therefore, your cost function must accept three arguments (u, s, and vt) 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 value
  4. How to use Pymanopt for optimization

    master

    Pymanopt follows a modular four-step workflow to solve optimization problems on manifolds:

    1. Instantiate a manifold: Select a manifold from the pymanopt.manifolds package (e.g., Sphere).
    2. 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.
    3. Create a Problem: Instantiate a pymanopt.Problem by tying the manifold and the cost function together.
    4. Run an optimizer: Instantiate an optimizer from pymanopt.optimizers and 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)
  5. How to implement a custom manifold in Pymanopt

    master

    If 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:

    1. pymanopt.manifolds.manifold.Manifold: Use this for general manifold implementations.
    2. pymanopt.manifolds.manifold.RiemannianSubmanifold: Use this if your manifold is a smooth subset of a Euclidean space.
  6. Install Pymanopt with automatic differentiation backends

    master

    Pymanopt 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, or torch.

    If you are unsure which to use, autograd is recommended as it wraps NumPy and is very simple to use.

    $ pip install "pymanopt[autograd]" # or [jax], [tensorflow], [torch]
  7. Create a custom Automatic Differentiation backend

    master

    To implement a new autodiff backend, you must:

    1. Inherit from the pymanopt.autodiff.backends._backend.Backend class.
    2. Create a backend decorator using the pymanopt.autodiff.backend_decorator_factory function.
    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)
  8. Implement Riemannian submanifolds using RiemannianSubmanifold

    master

    If 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:

    1. projection(point, vector): To project vectors onto the tangent space.
    2. 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): Uses projection to convert the gradient.
    • euclidean_to_riemannian_hessian(point, euclidean_gradient, euclidean_hessian, tangent_vector): Uses both projection and weingarten to 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_vector
  9. Define a custom manifold by subclassing Manifold

    master

    To implement a new Riemannian manifold in Pymanopt, you must create a subclass of the Manifold base 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, use 1. For manifolds representing points as a tuple/list of $n$ arrays, use n.

    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): Require euclidean_to_riemannian_gradient.
    • Second-order optimizers (e.g., TrustRegions): Require euclidean_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])
  10. Use RetrAsExpMixin to fallback to retraction

    master

    If your manifold does not have an efficient or available exponential map (exp), you can use the RetrAsExpMixin to automatically fallback to the retraction method when exp is called. This will trigger a RuntimeWarning notifying 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_vector
  11. Handle singular covariance matrices with custom Line Search

    master

    In 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.LinAlgError during the retraction step and resets the problematic component to a random point on the manifold.

    To implement this, create a class that implements a search method (similar to BackTrackingLineSearcher) and a helper method (e.g., _newxnewf) that wraps the manifold.retraction call in a try-except block.

    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, False
  12. Solve for the dominant eigenvector of a symmetric matrix

    master

    This 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)