Greedy method for estimating mixed-curvature product manifold signatures.
This module implements the greedy signature selection approach described in Tabaghi, Pan, Chien, Peng & Milenkovic.
"Linear Classifiers in Product Space Forms" (2021).
greedy_signature_selection(candidate_components=((-1.0, 2), (0.0, 2), (1.0, 2)), max_components=3, pipeline=distortion_pipeline, verbose=False, **kwargs)
Greedily estimates an optimal product manifold signature.
This implements the greedy signature selection algorithm that incrementally builds a product manifold
by selecting components that best preserve distances. At each step, it chooses the manifold component
that maximizes distortion reduction.
| Parameters: |
-
candidate_components
(Iterable[tuple[float, int]], default:
((-1.0, 2), (0.0, 2), (1.0, 2))
)
–
Candidate (curvature, dimension) pairs to consider.
-
max_components
(int, default:
3
)
–
Maximum number of components to include.
-
pipeline
(Callable[..., float], default:
distortion_pipeline
)
–
Function that takes a ProductManifold, plus additional arguments, and returns a loss value.
-
verbose
(bool, default:
False
)
–
If True, prints progress information.
-
**kwargs
(dict[str, Any], default:
{}
)
–
Additional keyword arguments to pass to the pipeline function.
|
| Returns: |
-
optimal_pm( tuple[ProductManifold, list[float]]
) –
Optimized product manifold with the selected signature.
|
Source code in manify/curvature_estimation/greedy_method.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 | def greedy_signature_selection(
candidate_components: Iterable[tuple[float, int]] = ((-1.0, 2), (0.0, 2), (1.0, 2)),
max_components: int = 3,
pipeline: Callable[..., float] = distortion_pipeline,
verbose: bool = False,
**kwargs: dict[str, Any],
) -> tuple[ProductManifold, list[float]]:
r"""Greedily estimates an optimal product manifold signature.
This implements the greedy signature selection algorithm that incrementally builds a product manifold
by selecting components that best preserve distances. At each step, it chooses the manifold component
that maximizes distortion reduction.
Args:
candidate_components: Candidate (curvature, dimension) pairs to consider.
max_components: Maximum number of components to include.
pipeline: Function that takes a ProductManifold, plus additional arguments, and returns a loss value.
verbose: If True, prints progress information.
**kwargs: Additional keyword arguments to pass to the pipeline function.
Returns:
optimal_pm: Optimized product manifold with the selected signature.
"""
# Initialize variables
signature: list[tuple[float, int]] = []
loss_history: list[float] = []
current_loss = float("inf")
candidate_components_list = list(candidate_components) # For type safe iteration
# Greedy loop
for i in range(max_components):
if verbose:
print(f"Iteration {i + 1}/{max_components}")
best_loss, best_idx = current_loss, -1
# Try each candidate
for idx, comp in enumerate(candidate_components_list):
if verbose:
print(f" Trying component {comp} (index {idx})")
pm = ProductManifold(signature=signature.copy() + [comp])
loss = pipeline(pm, **kwargs)
if loss < best_loss:
best_loss, best_idx = loss, idx
# If no improvement, stop
if best_idx < 0:
if verbose:
print("No improvement found, stopping.")
break
# Otherwise accept that component
signature.append(candidate_components_list[best_idx])
current_loss = best_loss
loss_history.append(current_loss)
if verbose:
print(f" Accepted component {candidate_components_list[best_idx]} with loss {current_loss:.4f}")
print(f" Current signature: {signature}")
print()
# Return final manifold
optimal_pm = ProductManifold(signature=signature.copy())
return optimal_pm, loss_history
|