Spaces:
Sleeping
Sleeping
| # src/optimizer/manifolds.py | |
| """Manifold helpers: stereographic S^3 projection and Fisher-Rao simplex geometry.""" | |
| from __future__ import annotations | |
| import sys | |
| from pathlib import Path | |
| import torch | |
| import torch.nn.functional as F | |
| import geoopt | |
| def _ensure_fisher_rao_path() -> None: | |
| candidates = [ | |
| Path(__file__).resolve().parents[2] / "fisher_rao", | |
| Path.home() / "Projects" / "Fisher_Rao", | |
| ] | |
| for root in candidates: | |
| if root.exists() and str(root.parent) not in sys.path: | |
| sys.path.insert(0, str(root.parent)) | |
| return | |
| _ensure_fisher_rao_path() | |
| try: | |
| from fisher_rao.metrics import ( | |
| sqrt_embed, | |
| simplex_to_sphere, | |
| sphere_to_simplex, | |
| softmax_to_fr_sphere, | |
| fisher_rao_distance, | |
| egrad_to_rgrad_fr, | |
| ) | |
| except ImportError: | |
| def _normalize_probs(p: torch.Tensor, eps: float = 1e-8) -> torch.Tensor: | |
| p = p.clamp(min=eps) | |
| return p / p.sum(dim=-1, keepdim=True) | |
| def sqrt_embed(p: torch.Tensor, eps: float = 1e-8) -> torch.Tensor: | |
| p = _normalize_probs(p, eps=eps) | |
| s = p.sqrt() | |
| return s / s.norm(dim=-1, keepdim=True).clamp(min=eps) | |
| simplex_to_sphere = sqrt_embed | |
| def sphere_to_simplex(s: torch.Tensor, eps: float = 1e-8) -> torch.Tensor: | |
| return _normalize_probs(s.pow(2).clamp(min=0.0), eps=eps) | |
| def softmax_to_fr_sphere(logits: torch.Tensor) -> torch.Tensor: | |
| return sqrt_embed(F.softmax(logits, dim=-1)) | |
| def fisher_rao_distance(p: torch.Tensor, q: torch.Tensor, eps: float = 1e-8) -> torch.Tensor: | |
| p = _normalize_probs(p, eps=eps) | |
| q = _normalize_probs(q, eps=eps) | |
| bc = (p.sqrt() * q.sqrt()).sum(dim=-1).clamp(0.0, 1.0) | |
| return 2.0 * torch.acos(bc.clamp(-1.0 + 1e-7, 1.0 - 1e-7)) | |
| def egrad_to_rgrad_fr(s: torch.Tensor, egrad: torch.Tensor) -> torch.Tensor: | |
| inner = (s * egrad).sum(dim=-1, keepdim=True) | |
| return egrad - inner * s | |
| class FisherRaoSphere(geoopt.manifolds.Sphere): | |
| """ | |
| Unit sphere with Fisher-Rao interpretation via sqrt-embedding of simplices. | |
| Geodesics and inner products are the standard round metric on S^{n-1}, | |
| which corresponds to the Fisher-Rao metric on Δ^{n-1} under φ(p) = √p/||√p||. | |
| """ | |
| def simplex_to_manifold(self, p: torch.Tensor) -> torch.Tensor: | |
| return simplex_to_sphere(p) | |
| def manifold_to_simplex(self, s: torch.Tensor) -> torch.Tensor: | |
| return sphere_to_simplex(s) | |
| def fr_distance_on_simplex(self, p: torch.Tensor, q: torch.Tensor) -> torch.Tensor: | |
| return fisher_rao_distance(p, q) | |
| def egrad2rgrad(self, x: torch.Tensor, u: torch.Tensor) -> torch.Tensor: | |
| return egrad_to_rgrad_fr(x, u) | |
| def logits_to_fr_manifold_param( | |
| logits: torch.Tensor, | |
| manifold: FisherRaoSphere | None = None, | |
| ) -> geoopt.ManifoldParameter: | |
| """Create a ManifoldParameter on the Fisher-Rao sphere from unconstrained logits.""" | |
| if manifold is None: | |
| manifold = FisherRaoSphere() | |
| s = softmax_to_fr_sphere(logits) | |
| return geoopt.ManifoldParameter(s, manifold=manifold) |