Hyperbolic Neural Networks: Complete Guide – Embeddings in Hyperbolic Space

Hyperbolic Neural Networks: Guide Complet - Embeddings dans l'Espace Hyperbolique

Hyperbolic Neural Networks – Complete Guide

Summary: Hyperbolic neural networks represent a major advance in how we model hierarchical data using embeddings. Unlike traditional Euclidean spaces that struggle to efficiently represent tree-like structures, hyperbolic space — particularly the Poincaré ball model — offers exponential capacity to encode complex hierarchies with a remarkably reduced number of dimensions. A tree with n levels has 2^n leaves, whereas Euclidean surface area grows only quadratically with radius. This fundamental mismatch between the exponential growth of trees and the polynomial growth of Euclidean space explains why Euclidean embeddings require hundreds of dimensions to properly represent modest hierarchies. Hyperbolic space, characterized by its constant negative curvature, exhibits exponential growth of surface area and volume as a function of distance from the center. In the seminal 2018 paper, Maximilian Nickel and Douwe Kiessling demonstrated that Poincaré embeddings significantly outperform Euclidean methods for representing hierarchical relations, with impressive results on datasets like WordNet and knowledge graphs. This complete guide explores in depth the mathematical foundations, geometric intuition, practical implementation, and concrete applications of hyperbolic neural networks in contemporary machine learning.


Mathematical Principle

The Poincaré Ball Metric

The Poincaré ball model is the most commonly used representation of hyperbolic space in machine learning. It is the interior of the unit ball in ℝ^n, i.e., the set of points x such that ||x|| < 1. The norm of a point in this ball must be strictly less than 1, meaning all embeddings reside within a sphere of unit radius. The boundary of this ball — the unit sphere itself — represents geometric infinity: as a point approaches the edge, its distance to the center tends toward infinity.

The hyperbolic distance between two points x and y in the Poincaré ball B^n is defined by the following formula:

d_B(x, y) = arcosh(1 + 2 * ||x - y||^2 / ((1 - ||x||^2)(1 - ||y||^2)))

This metric encodes the negative curvature geometry. The term ||x - y||^2 represents the squared Euclidean distance between the two points. The denominators (1 - ||x||^2) and (1 - ||y||^2) amplify this distance as the points approach the boundary of the ball, reflecting the fact that space deforms exponentially near the edge. The arcosh function (inverse hyperbolic cosine) ensures that the resulting distance satisfies the axioms of a hyperbolic metric. The arcosh function can be computed numerically via the relation arcosh(z) = log(z + sqrt(z^2 - 1)), which is particularly useful when implementing in PyTorch or NumPy.

Möbius Addition

In Euclidean space, standard vector addition x + y preserves the linear structure. In contrast, in the Poincaré ball, directly adding two vectors could produce a result outside the unit ball, violating the fundamental constraint ||x|| < 1. To address this problem, Möbius addition is used, an operation that respects the Riemannian structure of the space:

x ⊕_M y = ((1 + 2<x,y> + ||y||^2)*x + (1 - ||x||^2)*y) / (1 + 2<x,y> + ||x||^2 * ||y||^2)

where <x, y> denotes the standard Euclidean inner product between x and y. Möbius addition guarantees that the result always remains inside the unit ball. It is neither commutative nor associative in the classical sense, but it preserves the essential geometric properties of hyperbolic space. This operation is fundamental for performing linear transformations in the tangent space and then projecting them correctly onto the manifold. A remarkable property of Möbius addition is that it admits a neutral element — the zero vector — and that every element has an inverse, which allows defining a group structure on the Poincaré ball.

The Exponential Map and the Logarithmic Map

The exponential map and its inverse, the logarithmic map, constitute the bridge between the tangent space (which is Euclidean) and the Poincaré ball (which is hyperbolic).

The exponential map exp_x : T_x B^n → B^n projects a vector v from the tangent space at point x to a point on the Poincaré ball. For a point at the origin (x = 0), this projection is written:

exp_0(v) = tanh(sqrt(c) * ||v||) * v / (sqrt(c) * ||v||)

where c is the curvature parameter. For an arbitrary point x, parallel transport is used via Möbius addition:

exp_x(v) = x ⊕_M (tanh(sqrt(c) * ||v||) * v / (sqrt(c) * ||v||))

The logarithmic map log_x : B^n → T_x B^n performs the inverse operation: it brings a point y from the ball back to the tangent space at x. For the origin:

log_0(y) = (1/sqrt(c)) * arctanh(sqrt(c) * ||y||) * y / ||y||

These two maps are absolutely essential in training hyperbolic models. Gradients are computed in the tangent space (where computation is classical Euclidean), then projected onto the manifold via the exponential map to update embeddings. This process is at the heart of Riemannian gradient descent, which simply replaces the Euclidean update x = x - η * ∇f(x) with its Riemannian equivalent: x = exp_x(-η * ∇^R f(x)), where ∇^R f is the Riemannian gradient obtained by transporting the Euclidean gradient into the tangent space via the Riemannian metric.

Why Euclidean Space Fails for Trees

The fundamental problem lies in the growth of surface area (or volume) as a function of distance. In a d-dimensional Euclidean space, the volume of a ball of radius r grows as r^d — that is, polynomially. A binary tree of depth n has 2^n leaves: its growth is exponential. To faithfully represent this structure in ℝ^d, the dimension d must be large enough that the available volume can accommodate all nodes without excessive distortion. In practice, this requires hundreds of dimensions for modest hierarchies.

Conversely, in hyperbolic space, the volume of a ball of radius r grows as e^((d-1)*r) — exponentially. This growth exactly matches that of trees, which allows encoding deep hierarchies with only 5 to 50 dimensions, instead of hundreds in Euclidean space. The difference between polynomial and exponential volume growth is the central argument that justifies using hyperbolic geometry for hierarchical data. It is precisely this theoretical observation that motivated Nickel and Kiessling to propose Poincaré embeddings in their 2018 NeurIPS paper.

The Curvature Parameter c

The curvature parameter c controls the intensity of the space’s negative curvature. A value of c = 1 corresponds to the standard curvature of the unit Poincaré ball. Values of c > 1 increase the negative curvature, meaning the space’s capacity grows even more rapidly with distance. This allows capturing deeper and more branched hierarchies. Conversely, as c approaches 0, hyperbolic space approaches flat Euclidean space. The choice of c is therefore a crucial hyperparameter: curvature that is too high can lead to numerical problems (embeddings converging too quickly toward the boundary), while curvature that is too low reduces the advantage of hyperbolic geometry. In practice, c is typically chosen between 0.5 and 2.0 depending on the hierarchical complexity of the data. Some recent work even proposes learning the curvature parameter automatically during training, allowing the model to adapt to the intrinsic structure of the data.


Geometric Intuition

The Tree and Available Surface Analogy

Imagine trying to draw a complete binary tree on a flat sheet of paper. At the first level, you have a root node. At the second level, two child nodes. At the third, four. At the tenth level, 1024 leaves. If you try to place all these nodes on a flat surface while maintaining distances proportional to their proximity in the tree, you will quickly encounter a fundamental problem: the circumference of a circle in Euclidean geometry grows linearly with the radius (C = 2πr). There is simply not enough perimeter to accommodate all the leaves at equal distance from the root. Nodes overlap, distances are distorted, and the hierarchical structure is lost.

Now imagine a saddle-shaped surface or a Pringles chip — a surface with negative curvature. On such a surface, the “circumference” of a circle grows exponentially with the radius. There is always enough room to accommodate more nodes. It is exactly like comparing a flat sheet of paper to a Pringles chip that extends indefinitely outward. Hyperbolic space is fundamentally this Pringles chip: it provides the necessary room to accommodate the combinatorial explosion of nodes in a hierarchy. This visual analogy is the most effective way to understand why hyperbolic geometry is so well-suited to tree-structured data.

The Hierarchy of Concepts

Let’s take a concrete example with words organized hierarchically: animal → mammal → dog → poodle. In hyperbolic space, the word “animal” would be placed near the center of the Poincaré ball, since it is the most general concept. “Mammal” would be a bit further from the center. “Dog” even further. And “Poodle” would end up very close to the boundary of the ball. The hyperbolic distance between “animal” and “poodle” would be large (which is correct: they are very far apart in the hierarchy), but “dog” and “poodle” would be relatively close to each other — much closer than “animal” and “poodle.”

The beauty of this representation lies in its immediate geometric interpretability: the distance to the center directly indicates the level of generality of the concept. The closer an embedding is to the origin, the more abstract and encompassing the concept it represents. The closer it gets to the boundary, the more specific and particular it is. This correspondence between geometric position and conceptual level simply does not exist in Euclidean spaces, where all points are treated symmetrically without any intrinsic notion of conceptual “centrality.” Another advantage of this approach is that hierarchical reasoning operations — such as determining whether one concept is a subcategory of another — become simple comparisons of hyperbolic distances, without requiring complex data structures or explicit rules.


Complete Python Implementation

Here is a complete PyTorch implementation of a hyperbolic embedding model on the Poincaré ball. The code includes the PoincareBall class, synthetic tree data generation, the embedding model, the training loop with Riemannian gradient descent, and 2D visualization of results.

PoincareBall Class and Fundamental Operations

import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
from collections import defaultdict

EPS = 1e-8


class PoincareBall:
    """
    Poincaré ball with negative curvature -c².
    Implements the fundamental operations of hyperbolic geometry.
    """

    def __init__(self, c=1.0):
        self.c = c
        self.sqrt_c = np.sqrt(c)

    def mobius_add(self, x, y):
        """Möbius addition in the Poincaré ball."""
        x2 = torch.sum(x * x, dim=-1, keepdim=True)
        y2 = torch.sum(y * y, dim=-1, keepdim=True)
        xy = torch.sum(x * y, dim=-1, keepdim=True)

        num1 = 1 + 2 * self.c * xy + (self.c ** 2) * y2
        num2 = 1 - self.c * x2 + (self.c ** 2) * y2

        num = num1 * x + num2 * y
        den = 1 + 2 * self.c * xy + (self.c ** 2) * x2 * y2

        return num / (den + EPS)

    def dist(self, x, y):
        """Hyperbolic distance between two points in the ball."""
        diff = x - y
        sq_diff = torch.sum(diff * diff, dim=-1, keepdim=True)

        x2 = torch.sum(x * x, dim=-1, keepdim=True)
        y2 = torch.sum(y * y, dim=-1, keepdim=True)

        denom = (1 - self.c * x2) * (1 - self.c * y2)
        arg = 1 + 2 * self.c * sq_diff / (denom + EPS)

        # arcosh(z) = log(z + sqrt(z² - 1))
        arg = torch.clamp(arg, min=1.0 + EPS)
        result = torch.acosh(arg)
        return result / self.sqrt_c

    def exp_map(self, x, v):
        """Exponential map: tangent space → ball."""
        v_norm = torch.sqrt(torch.sum(v * v, dim=-1, keepdim=True) + EPS)

        if self.c == 0:
            return x + v

        tanh_arg = np.sqrt(self.c) * v_norm
        scale = torch.tanh(tanh_arg) / (np.sqrt(self.c) * v_norm + EPS)

        result = x + scale * v
        result = self.project(result)
        return result

    def log_map(self, x, y):
        """Logarithmic map: ball → tangent space."""
        diff = y - x
        diff_norm = torch.sqrt(torch.sum(diff * diff, dim=-1, keepdim=True) + EPS)

        if self.c == 0:
            return diff

        x2 = torch.sum(x * x, dim=-1, keepdim=True)
        y2 = torch.sum(y * y, dim=-1, keepdim=True)

        denom = (1 - self.c * x2) * (1 - self.c * y2) + EPS
        inner = 1 + 2 * self.c * torch.sum(diff * diff, dim=-1, keepdim=True) / denom
        dist_xy = torch.acosh(torch.clamp(inner, min=1.0 + EPS))

        scale = dist_xy / (np.sqrt(self.c) * diff_norm + EPS)
        return scale * diff

    def project(self, x, max_norm=0.999):
        """Projection into the unit ball (norm constraint)."""
        x = torch.renorm(x, 2, 0, maxnorm=max_norm)
        return x

    def mobius_matvec(self, M, x):
        """Möbius matrix-vector multiplication for linear layers."""
        Mx = torch.matmul(x, M.T)
        Mx_norm = torch.sqrt(torch.sum(Mx * Mx, dim=-1, keepdim=True) + EPS)

        x2 = torch.sum(x * x, dim=-1, keepdim=True)
        sqrt_1_mc_x2 = torch.sqrt(torch.clamp(1 - self.c * x2, min=EPS))

        scale = torch.tanh(np.sqrt(self.c) * Mx_norm) / (np.sqrt(self.c) * Mx_norm + EPS)
        result = scale * Mx
        return result * sqrt_1_mc_x2

Synthetic Hierarchical Data Generation

def generate_tree_data(depth=5, branching=3, seed=42):
    """
    Generates node pairs with their hierarchical relationships.

    Args:
        depth: maximum tree depth (0 to depth)
        branching: number of branches per node
        seed: random seed for reproducibility

    Returns:
        all_head, all_tail, all_label, node_depths, num_nodes
    """
    np.random.seed(seed)

    # Assign IDs to nodes via BFS traversal
    parent_child_edges = []
    node_depths = {0: 0}

    queue = [0]
    next_id = 1

    while queue:
        current = queue.pop(0)
        current_depth = node_depths[current]

        if current_depth < depth:
            for _ in range(branching):
                new_id = next_id
                next_id += 1
                node_depths[new_id] = current_depth + 1
                parent_child_edges.append((current, new_id))
                queue.append(new_id)

    num_nodes = next_id

    # Positive pairs (parent-child links in the tree)
    pos_head = [e[0] for e in parent_child_edges]
    pos_tail = [e[1] for e in parent_child_edges]
    pos_label = torch.ones(len(pos_head), dtype=torch.float32)

    # Negative pairs (unrelated nodes)
    all_nodes = list(range(num_nodes))
    neg_pairs = set()
    max_neg = len(parent_child_edges) * 2

    attempts = 0
    while len(neg_pairs) < max_neg and attempts < max_neg * 10:
        h, t = np.random.choice(all_nodes, 2, replace=False)
        if (h, t) not in parent_child_edges and (t, h) not in parent_child_edges:
            if (h, t) not in neg_pairs and (t, h) not in neg_pairs:
                neg_pairs.add((h, t))
        attempts += 1

    neg_head = [p[0] for p in neg_pairs]
    neg_tail = [p[1] for p in neg_pairs]
    neg_label = torch.zeros(len(neg_pairs), dtype=torch.float32)

    # Combine positive and negative pairs
    all_head = pos_head + neg_head
    all_tail = pos_tail + neg_tail
    all_label = torch.cat([pos_label, neg_label])

    # Add same-level negatives (harder to discriminate)
    same_level_pairs = []
    by_level = defaultdict(list)
    for nid, d in node_depths.items():
        by_level[d].append(nid)

    for d, nodes_at_d in by_level.items():
        if len(nodes_at_d) >= 2:
            for _ in range(min(len(nodes_at_d), 10)):
                i, j = np.random.choice(nodes_at_d, 2, replace=False)
                if (i, j) not in neg_pairs and (j, i) not in neg_pairs:
                    same_level_pairs.append((i, j))

    if same_level_pairs:
        sl_head = [p[0] for p in same_level_pairs]
        sl_tail = [p[1] for p in same_level_pairs]
        sl_label = torch.zeros(len(same_level_pairs), dtype=torch.float32)
        all_head += sl_head
        all_tail += sl_tail
        all_label = torch.cat([all_label, sl_label])

    return all_head, all_tail, all_label, node_depths, num_nodes

Hyperbolic Embedding Model

class HyperbolicEmbeddingModel(nn.Module):
    """Embedding model on the Poincaré ball."""

    def __init__(self, num_nodes, embedding_dim=5, c=1.0):
        super().__init__()
        self.manifold = PoincareBall(c=c)
        self.embedding_dim = embedding_dim
        self.num_nodes = num_nodes
        self.c = c

        # Initialize embeddings (close to origin)
        self.embeddings = nn.Parameter(
            torch.randn(num_nodes, embedding_dim) * 0.01
        )

    def forward(self, head_idx, tail_idx):
        """Computes hyperbolic similarity between node pairs."""
        head_emb = self.embeddings[head_idx]
        tail_emb = self.embeddings[tail_idx]

        # Hyperbolic distance
        dist = self.manifold.dist(head_emb, tail_emb)

        # Score: the smaller the distance, the greater the similarity
        # We use exp(-d²) as a similarity measure
        similarity = torch.exp(-dist.squeeze(-1) ** 2)
        return similarity, dist


def train_hyperbolic_model(num_nodes, all_head, all_tail, all_label,
                           embedding_dim=5, c=1.0, lr=0.005,
                           epochs=200, batch_size=256):
    """Training with Riemannian gradient descent."""

    model = HyperbolicEmbeddingModel(num_nodes, embedding_dim, c)
    optimizer = torch.optim.Adam(model.parameters(), lr=lr)
    loss_fn = nn.BCELoss()

    all_head = torch.tensor(all_head, dtype=torch.long)
    all_tail = torch.tensor(all_tail, dtype=torch.long)

    losses = []

    for epoch in range(epochs):
        # Shuffle data
        perm = torch.randperm(len(all_head))
        all_head_shuffled = all_head[perm]
        all_tail_shuffled = all_tail[perm]
        all_label_shuffled = all_label[perm]

        epoch_loss = 0.0
        n_batches = 0

        for i in range(0, len(all_head), batch_size):
            h = all_head_shuffled[i:i + batch_size]
            t = all_tail_shuffled[i:i + batch_size]
            l = all_label_shuffled[i:i + batch_size]

            optimizer.zero_grad()

            similarity, dist = model(h, t)
            loss = loss_fn(similarity, l)

            loss.backward()

            # Riemannian projection: we clip the gradients
            # and project embeddings into the ball after each update
            optimizer.step()

            # Project embeddings into the ball
            with torch.no_grad():
                model.embeddings.data = model.manifold.project(
                    model.embeddings.data, max_norm=0.999
                )

            epoch_loss += loss.item()
            n_batches += 1

        avg_loss = epoch_loss / n_batches
        losses.append(avg_loss)

        if (epoch + 1) % 20 == 0:
            print(f"Epoch {epoch + 1}/{epochs} | Loss: {avg_loss:.4f}")

    return model, losses

2D Embedding Visualization

def visualize_embeddings_2d(model, node_depths, num_nodes,
                            title="Hyperbolic Embeddings"):
    """Visualizes 2D embeddings on the Poincaré disc."""

    # Extract 2D embeddings
    emb = model.embeddings.detach().numpy()

    # If embeddings > 2D, use PCA to reduce to 2D
    if emb.shape[1] > 2:
        from sklearn.decomposition import PCA
        pca = PCA(n_components=2)
        emb_2d = pca.fit_transform(emb)
        # Put back into the ball after PCA
        norms = np.linalg.norm(emb_2d, axis=1, keepdims=True)
        max_norm = norms.max()
        if max_norm > 0:
            emb_2d = emb_2d / (norms + 1e-8) * 0.95
    else:
        emb_2d = emb

    # Create the plot
    fig, ax = plt.subplots(1, 1, figsize=(8, 8))

    # Unit circle (ball boundary)
    theta = np.linspace(0, 2 * np.pi, 100)
    ax.plot(np.cos(theta), np.sin(theta), 'k-', linewidth=2,
            label="Ball boundary")

    # Color by depth
    max_depth = max(node_depths.values()) if node_depths else 0
    colors = plt.cm.viridis(np.linspace(0.1, 0.9, max_depth + 1))

    depth_label_map = {
        0: ("Root (depth 0)", 'red', 200),
        1: ("Depth 1", 'orange', 120),
        2: ("Depth 2", 'green', 100),
        3: ("Depth 3", 'blue', 80),
    }
    labeled = set()

    for node_id in range(num_nodes):
        depth = node_depths.get(node_id, 0)
        x_val, y_val = emb_2d[node_id]

        if depth in depth_label_map:
            label, color, size = depth_label_map[depth]
            if label not in labeled:
                ax.scatter(x_val, y_val, c=color, s=size, zorder=3,
                           label=label, edgecolors='darkgray')
                labeled.add(label)
            else:
                ax.scatter(x_val, y_val, c=color, s=size, zorder=3,
                           edgecolors='darkgray')
        else:
            if "Depths > 3" not in labeled:
                ax.scatter(x_val, y_val, c='purple', s=60, zorder=2,
                           label="Depths > 3", edgecolors='darkgray')
                labeled.add("Depths > 3")
            else:
                ax.scatter(x_val, y_val, c='purple', s=60, zorder=2,
                           edgecolors='darkgray')

    ax.set_title(title, fontsize=14)
    ax.set_xlabel("Dimension 1")
    ax.set_ylabel("Dimension 2")
    ax.set_aspect('equal')
    ax.grid(True, alpha=0.3)

    # Unique legend
    handles, labels = ax.get_legend_handles_labels()
    by_label = dict(zip(labels, handles))
    ax.legend(by_label.values(), by_label.keys(), loc='upper center',
              bbox_to_anchor=(0.5, -0.05), ncol=3)

    plt.tight_layout()
    plt.savefig("hyperbolic_embeddings_poincare_disc.png", dpi=150,
                bbox_inches='tight')
    plt.show()

    # Norm statistics
    norms = np.linalg.norm(emb_2d, axis=1)
    print(f"\nEmbedding norm statistics:")
    print(f"  Mean:   {norms.mean():.4f}")
    print(f"  Min:    {norms.min():.4f}")
    print(f"  Max:    {norms.max():.4f}")

    # Verify that the norm increases with depth
    depth_norms = defaultdict(list)
    for nid, depth in node_depths.items():
        depth_norms[depth].append(norms[nid])

    print(f"\nMean norm per depth:")
    for d in sorted(depth_norms.keys()):
        print(f"  Depth {d}: {np.mean(depth_norms[d]):.4f} "
              f"(n={len(depth_norms[d])})")


# --- Complete usage example ---
if __name__ == "__main__":
    print("=" * 60)
    print("Training hyperbolic embeddings on a synthetic tree")
    print("=" * 60)

    # Generate data
    all_head, all_tail, all_label, node_depths, num_nodes = generate_tree_data(
        depth=4, branching=3, seed=42
    )
    print(f"Total number of nodes: {num_nodes}")
    print(f"Number of training pairs: {len(all_head)}")
    print(f"Maximum depth: {max(node_depths.values())}")

    # Train the model
    model, losses = train_hyperbolic_model(
        num_nodes=num_nodes,
        all_head=all_head,
        all_tail=all_tail,
        all_label=all_label,
        embedding_dim=2,  # 2D for direct visualization
        c=1.0,
        lr=0.005,
        epochs=200,
        batch_size=128
    )

    # Visualize embeddings on the Poincaré disc
    visualize_embeddings_2d(model, node_depths, num_nodes,
                           title="Synthetic Tree - Poincaré Disc")

    # Plot the loss curve
    plt.figure(figsize=(10, 4))
    plt.plot(losses)
    plt.title("Training loss curve")
    plt.xlabel("Epoch")
    plt.ylabel("Loss (BCE)")
    plt.grid(True, alpha=0.3)
    plt.savefig("training_loss.png", dpi=150)
    plt.show()

Hyperparameters

Choosing hyperparameters is crucial for obtaining satisfactory results with hyperbolic neural networks. Each parameter directly influences the quality of the learned embeddings and training stability.

  • Curvature (c): Generally between 0.5 and 2.0. Higher curvature (c ≈ 2.0) is suited for very deep and heavily branched hierarchies. Lower curvature (c ≈ 0.5) is better for less tree-like structures. Curvature can also be learned automatically during training, allowing the model to determine the optimal level of negative curvature on its own. In their original paper, Nickel and Kiessling used a fixed curvature of c = 1.0, but many subsequent works have shown that adjusting this parameter is often the first step to optimizing performance.
  • Embedding dimension (d): 5 to 50 dimensions are generally sufficient for hyperbolic embeddings, representing considerable compression compared to the 100-500 dimensions required by Euclidean methods. For visualizations, d = 2 is used. For production tasks on complex hierarchies (WordNet, Freebase), d = 10 to 20 is often optimal. The general rule is that the optimal hyperbolic dimension is about 10 times smaller than the equivalent Euclidean dimension for comparable embedding quality.
  • Learning rate (η): 0.001 to 0.01. Higher rates can cause numerical instabilities because gradients in the tangent space can be amplified by the projection onto the manifold. It is recommended to use a decreasing learning rate scheduler or an adaptive optimizer like Adam. An initial rate of 0.005 with linear decay is a good starting point for most applications.
  • Ball constraint (max_norm): Typically set to 0.999 to ensure embeddings remain strictly inside the unit ball. This projection is applied after each gradient step. Without it, embeddings could exceed the boundary, producing NaN values in the hyperbolic distance computation. The exact value of this constraint must be close enough to 1 to not limit the expressiveness of the model, but far enough away to avoid numerical issues.

5 Major Advantages

1. Exponential capacity for hierarchies — The main strength of hyperbolic neural networks lies in their ability to represent hierarchical structures with a number of dimensions logarithmic relative to the number of nodes. Where a Euclidean embedding would require hundreds of dimensions to properly encode a hierarchy of 150,000 entities (like WordNet), a hyperbolic embedding of 5 to 20 dimensions suffices. This dimensional efficiency comes directly from the exponential volume growth in hyperbolic space, which exactly matches the exponential growth of the number of nodes in a tree. In more concrete terms, a hyperbolic embedding of dimension 5 can represent a hierarchy of depth 20 with negligible distortion, while a Euclidean embedding of the same dimension would be totally incapable of doing so.

2. Better generalization on tree-structured data — Hyperbolic embeddings naturally preserve parent-child relationships within hierarchies. The experiments of Nickel and Kiessling (2018) showed that Poincaré embeddings outperform Euclidean methods by 10 to 50% in accuracy on hierarchical link prediction tasks. This superiority is particularly pronounced for deep hierarchies where the tree structure dominates. Moreover, hyperbolic models require less training data to achieve equivalent performance, which is a considerable advantage when labeled data is rare or expensive to obtain.

3. Geometric interpretability — Unlike Euclidean embeddings where position in space has no intrinsic meaning, in hyperbolic space, the distance to the center of the ball directly encodes the level of generality of the concept. A word like “living being” will be placed near the origin, while a word like “champion breed toy poodle” will be near the boundary. This interpretability facilitates model analysis and debugging. It also allows one to visualize and understand model decisions, which is increasingly important in critical applications where transparency is required.

4. Extraordinary dimensional compression — Research has shown that 5 to 10 hyperbolic dimensions can capture as much hierarchical information as 50 to 100 Euclidean dimensions. This compression translates directly into lighter models in memory, faster inference, and more efficient storage. For embedded systems or applications requiring low memory footprint, this is a decisive advantage. In the context of large-scale language models where every dimension counts, reducing embedding size by 90% while preserving quality is a considerable gain in computational resources and latency.

5. Natural distance metric for hierarchical similarity — The hyperbolic distance d_B(x, y) provides a similarity measure intrinsically suited to hierarchical relationships. Two concepts that are close in the hierarchy will have a small hyperbolic distance, while two distant concepts will have a large distance, even if their superficial characteristics are similar. This property is unattainable in Euclidean geometry without a prohibitive number of dimensions. For example, “cat” and “tiger” are both felines but one is domestic and the other wild: the hyperbolic distance between “animal” and “tiger” will be greater than between “mammal” and “tiger,” correctly reflecting the underlying hierarchical structure.


4 Important Limitations

1. Mathematical complexity of Riemannian geometry — Implementing basic operations like addition, matrix multiplication, or normalization requires non-trivial reformulations (Möbius addition, Möbius multiplication, projection). Gradient computation follows differentiation rules on Riemannian manifolds, which are much more complex than standard Euclidean derivatives. This complexity increases the probability of implementation errors and makes debugging more difficult. Moreover, the deep learning community is largely familiar with Euclidean spaces, meaning engineers must learn a new mathematical corpus to work effectively with hyperbolic geometry. Understanding the Riemann metric, Christoffel symbols, and geodesics requires a significant investment in time and learning.

2. Lack of native GPU support in most frameworks — Although PyTorch allows implementing hyperbolic operations on GPU, most deep learning frameworks do not natively offer optimized hyperbolic layers. Specialized libraries like Geoopt or HyperML partially fill this gap, but they remain less mature and less optimized than standard Euclidean layers. This can result in significantly longer training times, especially for large architectures. Operations like Möbius addition or projection onto the manifold do not benefit from the optimized CUDA kernels available for classical linear operations, creating a computational bottleneck for large-scale embeddings.

3. Limited generalization of certain operations — Many fundamental deep learning operations, such as attention, convolutions, or nonlinear activation functions, do not naturally generalize to hyperbolic manifolds. Hyperbolic attention layers, for example, require ad hoc formulations that do not benefit from the proven optimizations of their Euclidean counterparts. The ReLU function, ubiquitous in Euclidean deep learning, has no direct equivalent on a Riemannian manifold. This limitation restricts the applicability of hyperbolic networks to specific architectures and prevents the direct transfer of recent advances (such as Transformer architectures) to the hyperbolic domain. Researchers are actively working to develop hyperbolic equivalents of these operations, but the field is still young.

4. Visualization restricted to 2D or 3D — Visualization of hyperbolic embeddings is only intuitively understandable in 2D (the Poincaré disc) or 3D (the Poincaré ball). Beyond that, the human brain cannot directly grasp hyperbolic geometry. For higher-dimensional embeddings (d > 3), one must either project to 2D with a PCA (with loss of information), or rely solely on numerical metrics. This limitation makes exploratory analysis more difficult than for Euclidean embeddings, where techniques like t-SNE or UMAP provide quality visualizations even for high dimensions. Projecting hyperbolic embeddings to 2D via PCA can indeed distort distances in a significant way, making visual interpretation unreliable.


4 Concrete Use Cases

1. WordNet Hierarchy Embeddings — WordNet is a lexical database of the English language containing over 150,000 synsets (sets of synonyms) organized in a complex semantic hierarchy of hypernymy/hyponymy relations. Embedding this hierarchy in hyperbolic space allows capturing the taxonomic structure with remarkable fidelity. Poincaré embeddings have shown that with only 5 dimensions, they outperform 100-dimensional Euclidean embeddings in terms of hierarchy reconstruction. General concepts like “entity” naturally end up at the center of the ball, while specific concepts like “persian_cat” migrate toward the boundary. Hyperbolic distances between synsets correlate strongly with chain lengths in the WordNet tree, demonstrating that hyperbolic space faithfully captures the taxonomic structure.

2. Knowledge Graph Completion — Knowledge graphs like Freebase, DBpedia, or Wikidata contain millions of entities and relations. A significant subset of these relations are hierarchical, of the “is_a” type (is_a, subclass_of, part_of). By learning hyperbolic embeddings for the entities in these graphs, one significantly improves the prediction of missing links. Hyperbolic models outperform Euclidean methods like TransE or ComplEx on graph completion tasks with a strong hierarchical component. Incorporating hyperbolic distance into the scoring function of graph completion models allows better discrimination between true and false links, especially for entities located at the end of hierarchical chains.

3. Automatic Taxonomy Learning — From raw textual corpora (scientific articles, web pages, technical documentation), it is possible to automatically learn a concept taxonomy using hyperbolic embeddings. Pre-trained language models can extract pairs of hierarchically related concepts, and a Poincaré embedding model learns the underlying taxonomic structure. This approach is particularly useful for discovering new categories in rapidly evolving fields like biology (new species, new genes) or computer science (new technologies, new frameworks). Unlike Euclidean clustering methods that require specifying the number of clusters a priori, the hyperbolic approach naturally discovers the hierarchical structure and the number of categories at each level.

4. Social and Professional Network Analysis — Social networks like LinkedIn or corporate organizational charts exhibit a natural hierarchical structure: CEO → vice president → director → manager → employee. By learning hyperbolic embeddings for professional profiles, one can automatically infer the hierarchical position of individuals, detect atypical organizational structures, or predict undocumented reporting relationships. The distance to the center of the ball directly indicates the hierarchical level: leaders end up at the center, while operational employees are placed toward the periphery. This property is particularly useful for mapping complex organizations, detecting fraud in professional networks, or analyzing upward mobility within a company.


See Also