Classification with Manifold-Valued Predictors#
Suppose each predictor is a point \(x\in\mathcal M\), rather than a vector in a chosen coordinate chart. Two natural classifiers use only the intrinsic distance:
Tangent-space classifiers make a stronger local approximation. GeoJAX maps training logarithms at a Fréchet mean into a metric-orthonormal basis, then fits multinomial logistic regression or regularized discriminant analysis. The basis uses the Riemannian inner product and therefore also works for SPD and Product metrics that are not ambient Frobenius metrics. Distance-to-mean and tangent-coordinate classifiers are standard geometric learning patterns [Barachant et al., 2012, Fréchet, 1948].
We compare all four strategies on noisy circular observations.
from pathlib import Path
import jax
jax.config.update("jax_enable_x64", True)
import jax.numpy as jnp
import matplotlib.pyplot as plt
import numpy as np
from geojax.geometry import Sphere
from geojax.learning import (
knn_classifier,
nearest_centroid_classifier,
tangent_space_discriminant_analysis,
tangent_space_logistic_regression,
)
plt.rcParams.update({
"figure.dpi": 220,
"savefig.dpi": 320,
"font.size": 10,
"axes.titlesize": 11,
"axes.spines.top": False,
"axes.spines.right": False,
})
M = Sphere(size=2)
key = jax.random.key(510)
train_keys = jax.random.split(key, 2)
train_angles = jnp.concatenate([
-0.72 + 0.23 * jax.random.normal(train_keys[0], (28,)),
0.72 + 0.23 * jax.random.normal(train_keys[1], (28,)),
])
train_labels = jnp.concatenate([
jnp.zeros(28, dtype=int),
jnp.ones(28, dtype=int),
])
test_keys = jax.random.split(jax.random.key(511), 2)
test_angles = jnp.concatenate([
-0.72 + 0.25 * jax.random.normal(test_keys[0], (120,)),
0.72 + 0.25 * jax.random.normal(test_keys[1], (120,)),
])
test_labels = jnp.concatenate([
jnp.zeros(120, dtype=int),
jnp.ones(120, dtype=int),
])
def on_circle(angles):
return jnp.stack([jnp.cos(angles), jnp.sin(angles)], axis=-1)
train = on_circle(train_angles)
test = on_circle(test_angles)
models = {
"centroid": nearest_centroid_classifier(M, train, train_labels),
"3-NN": knn_classifier(M, train, train_labels, n_neighbors=3, weights="distance"),
"logistic": tangent_space_logistic_regression(M, train, train_labels, maxiter=300),
"LDA": tangent_space_discriminant_analysis(M, train, train_labels, method="lda"),
}
accuracies = {
name: float(jnp.mean(model.predict(test) == test_labels))
for name, model in models.items()
}
for name, accuracy in accuracies.items():
print(f"{name:10s}: test accuracy = {accuracy:.3f}")
centroid : test accuracy = 1.000
3-NN : test accuracy = 1.000
logistic : test accuracy = 1.000
LDA : test accuracy = 1.000
Decision rules around the full circle#
The two distance rules are global. Logistic regression and LDA depend on one logarithm chart, so their behavior far from the training arc should be read as an extrapolation of that chart rather than a new intrinsic identity.
grid_angles = jnp.linspace(-jnp.pi, jnp.pi, 720, endpoint=False)
grid = on_circle(grid_angles)
grid_predictions = np.stack([
np.asarray(model.predict(grid)) for model in models.values()
])
grid_confidence = {
name: np.asarray(jnp.max(model.predict_proba(grid), axis=1))
for name, model in models.items()
}
fig, axes = plt.subplots(1, 3, figsize=(13.4, 4.0), constrained_layout=True)
circle = np.linspace(-np.pi, np.pi, 600)
axes[0].plot(np.cos(circle), np.sin(circle), color="0.82", linewidth=1.2)
for label, color, marker in ((0, "#2563EB", "o"), (1, "#E45756", "s")):
selected = np.asarray(train_labels == label)
axes[0].scatter(
np.asarray(train[selected, 0]), np.asarray(train[selected, 1]),
color=color, marker=marker, s=34, edgecolor="white", linewidth=0.4,
label=f"class {label}",
)
centers = models["centroid"].centers
axes[0].scatter(
np.asarray(centers[:, 0]), np.asarray(centers[:, 1]),
marker="*", s=210, color="#111827", edgecolor="white", linewidth=0.6,
label="Fréchet centroids",
)
axes[0].set(aspect="equal", xlim=(-1.1, 1.1), ylim=(-1.1, 1.1), title="Training sample")
axes[0].set_xticks([])
axes[0].set_yticks([])
axes[0].legend(frameon=False, loc="center")
axes[1].imshow(
grid_predictions,
aspect="auto",
interpolation="nearest",
cmap=plt.matplotlib.colors.ListedColormap(["#93C5FD", "#FCA5A5"]),
extent=(-np.pi, np.pi, len(models) - 0.5, -0.5),
)
axes[1].scatter(
np.asarray(train_angles),
np.asarray(train_labels) * 0.0 - 0.35,
c=np.asarray(train_labels), cmap="coolwarm", s=10, clip_on=False,
)
axes[1].set(
title="Predicted class over the angular chart",
xlabel="query angle",
yticks=np.arange(len(models)),
yticklabels=list(models),
xlim=(-np.pi, np.pi),
)
axes[1].axvline(-np.pi, color="0.4", linestyle=":", linewidth=0.8)
axes[1].axvline(np.pi, color="0.4", linestyle=":", linewidth=0.8)
for name, color in zip(models, ("#111827", "#2563EB", "#009E8E", "#7C3AED")):
axes[2].plot(
np.asarray(grid_angles), grid_confidence[name],
color=color, linewidth=1.8, label=f"{name} ({accuracies[name]:.2f})",
)
axes[2].set(
title="Maximum normalized class score",
xlabel="query angle",
ylabel="normalized score",
xlim=(-np.pi, np.pi),
ylim=(0.48, 1.02),
)
axes[2].grid(alpha=0.18)
axes[2].legend(frameon=False, fontsize=8)
output = next(
path for path in (
Path("../_static/tutorials/manifold-classification.png"),
Path("docs/_static/tutorials/manifold-classification.png"),
Path("_static/tutorials/manifold-classification.png"),
)
if path.parent.exists()
)
output.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(output, bbox_inches="tight")
plt.show()
Nearest-centroid prediction is interpretable and stable when classes are
unimodal. k-NN adapts to nonlinear class regions but stores the complete
training set. Tangent logistic regression and discriminant analysis connect
to familiar supervised models, with the important cost that their coordinates
are local to one reference point. The normalized curves share the
predict_proba interface, but only express each fitted rule’s relative class
scores; in particular, nearest-centroid and k-NN scores are not calibrated
posterior probabilities and should not be compared across models as such.
References#
Alexandre Barachant, Stéphane Bonnet, Marco Congedo, and Christian Jutten. Multiclass brain–computer interface classification by Riemannian geometry. IEEE Transactions on Biomedical Engineering, 59(4):920–928, 2012. doi:10.1109/TBME.2011.2172210.
Maurice Fréchet. Les éléments aléatoires de nature quelconque dans un espace distancié. Annales de l'Institut Henri Poincaré, 10(4):215–310, 1948. URL: https://www.numdam.org/item/AIHP_1948__10_4_215_0/.