Manifold Learning: t-SNE & UMAP Mathematics

Derive the mathematics of t-SNE, the Cauchy heavy-tailed resolution to the crowding problem, the analytical KL-divergence gradient, UMAP fuzzy simplicial sets, and zero-dependency implementations in Python and Rust.

02. Unsupervised Machine Learning & Dimensionality Reduction9 min read

Linear dimensionality reduction techniques like PCA maximize global variance along orthogonal projection axes. When high-dimensional data resides on non-linear topological manifolds (such as Swiss rolls, spirals, or multi-cluster embeddings), linear methods flatten distinct geodesic folds onto each other.

Non-Linear Manifold Learning preserves local neighborhood topology, allowing complex geometries to unfold into lower dimensions.

Student-t KernelTopology
1 / (1 + ||y_i - y_j||^2)

The heavy polynomial tail of the Cauchy distribution resolves the crowding problem in low-dimensional embeddings.

1. High-Dimensional Similarities & The Crowding Problem#

In t-Distributed Stochastic Neighbor Embedding (t-SNE) (van der Maaten and Hinton, 2008), pairwise Euclidean distances are converted into conditional probabilities representing neighborhood affinities.

Architecture Diagram
Rendering diagram...

In high-dimensional space RD\mathbb{R}^D, the probability that sample xix_i picks xjx_j as its neighbor under a Gaussian centered at xix_i with variance σi2\sigma_i^2 is:

pj∣i=exp⁡(−∥xi−xj∥22/2σi2)∑k≠iexp⁡(−∥xi−xk∥22/2σi2),pi∣i=0p_{j|i} = \frac{\exp\left( -\|x_i - x_j\|_2^2 / 2\sigma_i^2 \right)}{\sum_{k \ne i} \exp\left( -\|x_i - x_k\|_2^2 / 2\sigma_i^2 \right)}, \quad p_{i|i} = 0

To ensure symmetry and robust handling of outliers, affinities are symmetrized:

pij=pj∣i+pi∣j2Np_{ij} = \frac{p_{j|i} + p_{i|j}}{2N}

The Crowding Problem in Low Dimensions#

In high-dimensional space RD\mathbb{R}^D, the volume of a hypersphere of radius rr scales as rDr^D. In low-dimensional projection space R2\mathbb{R}^2, volume scales only as r2r^2.

In D=100D = 100, an enormous amount of volume is available at moderate Euclidean distances. When mapped to 2D, the available area is drastically smaller. If we use a Gaussian distribution in low dimensions, points that are moderately distant in RD\mathbb{R}^D must be pushed far apart in R2\mathbb{R}^2 to match small probabilities, crowding the center and causing distinct clusters to overlap.

2. Derivation of the Student-t Heavy-Tailed Kernel#

To solve the crowding problem, t-SNE models low-dimensional affinities using a Student-t distribution with 1 degree of freedom (Cauchy distribution):

qij=(1+∥yi−yj∥22)−1∑k∑l≠k(1+∥yk−yl∥22)−1,qii=0q_{ij} = \frac{\Big( 1 + \|y_i - y_j\|_2^2 \Big)^{-1}}{\sum_{k} \sum_{l \ne k} \Big( 1 + \|y_k - y_l\|_2^2 \Big)^{-1}}, \quad q_{ii} = 0

Because the Cauchy distribution features heavy polynomial tails (1/r21/r^2) rather than exponential Gaussian tails (e−r2e^{-r^2}), low-dimensional points must separate by significantly larger distances to reproduce small probabilities, naturally segregating clusters into visually distinct islands.

3. Analytical Gradient Derivation of t-SNE#

t-SNE minimizes the Kullback-Leibler (KL) Divergence between high-dimensional distribution PP and low-dimensional distribution QQ:

L=KL(P∥Q)=∑i=1N∑j≠ipijln⁡(pijqij)=∑i=1N∑j≠ipijln⁡(pij)−∑i=1N∑j≠ipijln⁡(qij)\mathcal{L} = KL(P \| Q) = \sum_{i=1}^N \sum_{j \ne i} p_{ij} \ln\left( \frac{p_{ij}}{q_{ij}} \right) = \sum_{i=1}^N \sum_{j \ne i} p_{ij} \ln(p_{ij}) - \sum_{i=1}^N \sum_{j \ne i} p_{ij} \ln(q_{ij})

Let dij=∥yi−yj∥2d_{ij} = \|y_i - y_j\|_2 and define the unnormalized Cauchy kernel wij=(1+∥yi−yj∥22)−1w_{ij} = (1 + \|y_i - y_j\|_2^2)^{-1} with partition function Z=∑k≠lwklZ = \sum_{k \ne l} w_{kl}, so that qij=wijZq_{ij} = \frac{w_{ij}}{Z}.

1

Step 1: Differentiate the Objective with Respect to $y_i$

Observe that pijln⁡(pij)p_{ij} \ln(p_{ij}) is constant with respect to low-dimensional coordinates yiy_i:

∂L∂yi=−∑j≠ipij∂ln⁡(qij)∂yi−∑j≠ipji∂ln⁡(qji)∂yi=−2∑j≠ipij∂ln⁡(qij)∂yi\frac{\partial \mathcal{L}}{\partial y_i} = -\sum_{j \ne i} p_{ij} \frac{\partial \ln(q_{ij})}{\partial y_i} - \sum_{j \ne i} p_{ji} \frac{\partial \ln(q_{ji})}{\partial y_i} = -2 \sum_{j \ne i} p_{ij} \frac{\partial \ln(q_{ij})}{\partial y_i}
2

Step 2: Differentiate the Log-Quotient $\ln(q_{ij})$

Expanding ln⁡(qij)=ln⁡(wij)−ln⁡(Z)\ln(q_{ij}) = \ln(w_{ij}) - \ln(Z):

∂ln⁡(qij)∂yi=1wij∂wij∂yi−1Z∂Z∂yi\frac{\partial \ln(q_{ij})}{\partial y_i} = \frac{1}{w_{ij}} \frac{\partial w_{ij}}{\partial y_i} - \frac{1}{Z} \frac{\partial Z}{\partial y_i}

Evaluate ∂wij∂yi\frac{\partial w_{ij}}{\partial y_i}:

∂wij∂yi=∂∂yi(1+∥yi−yj∥22)−1=−1⋅(1+∥yi−yj∥22)−2⋅2(yi−yj)=−2wij2(yi−yj)\frac{\partial w_{ij}}{\partial y_i} = \frac{\partial}{\partial y_i} (1 + \|y_i - y_j\|_2^2)^{-1} = -1 \cdot (1 + \|y_i - y_j\|_2^2)^{-2} \cdot 2(y_i - y_j) = -2 w_{ij}^2 (y_i - y_j)

Therefore:

1wij∂wij∂yi=−2wij(yi−yj)\frac{1}{w_{ij}} \frac{\partial w_{ij}}{\partial y_i} = -2 w_{ij} (y_i - y_j)
3

Step 3: Differentiate the Partition Function $Z$

∂Z∂yi=∂∂yi∑k≠lwkl=2∑j≠i∂wij∂yi=−4∑j≠iwij2(yi−yj)\frac{\partial Z}{\partial y_i} = \frac{\partial}{\partial y_i} \sum_{k \ne l} w_{kl} = 2 \sum_{j \ne i} \frac{\partial w_{ij}}{\partial y_i} = -4 \sum_{j \ne i} w_{ij}^2 (y_i - y_j)

Dividing by ZZ:

1Z∂Z∂yi=−4∑j≠iwij2Z(yi−yj)=−4∑j≠iqijwij(yi−yj)\frac{1}{Z} \frac{\partial Z}{\partial y_i} = -4 \sum_{j \ne i} \frac{w_{ij}^2}{Z} (y_i - y_j) = -4 \sum_{j \ne i} q_{ij} w_{ij} (y_i - y_j)
4

Step 4: Combine Terms and Factor

Substitute these derivatives back:

∂L∂yi=−2∑j≠ipij[−2wij(yi−yj)+2∑k≠iqikwik(yi−yk)]\frac{\partial \mathcal{L}}{\partial y_i} = -2 \sum_{j \ne i} p_{ij} \left[ -2 w_{ij} (y_i - y_j) + 2 \sum_{k \ne i} q_{ik} w_{ik} (y_i - y_k) \right]
=4∑j≠ipijwij(yi−yj)−4(∑j≠ipij)∑k≠iqikwik(yi−yk)= 4 \sum_{j \ne i} p_{ij} w_{ij} (y_i - y_j) - 4 \left( \sum_{j \ne i} p_{ij} \right) \sum_{k \ne i} q_{ik} w_{ik} (y_i - y_k)

Because ∑j≠ipij=∑jpij=1\sum_{j \ne i} p_{ij} = \sum_{j} p_{ij} = 1, the expression simplifies to the celebrated t-SNE Gradient:

∂L∂yi=4∑j=1N(pij−qij)(yi−yj)(1+∥yi−yj∥22)−1\frac{\partial \mathcal{L}}{\partial y_i} = 4 \sum_{j=1}^N (p_{ij} - q_{ij}) (y_i - y_j) \Big( 1 + \|y_i - y_j\|_2^2 \Big)^{-1}
Physical Analogy: Spring System
The gradient can be viewed as an electrostatic and spring equilibrium. If two points are similar in high dimensions (pij>qijp_{ij} > q_{ij}), an attractive spring force pulls them together. If two points are dissimilar (pij<qijp_{ij} < q_{ij}), an electrostatic repulsive force pushes them apart.

4. UMAP: Topological Foundations#

Uniform Manifold Approximation and Projection (UMAP) (McInnes et al., 2018) provides faster computation and superior preservation of global structure compared to t-SNE.

Rather than minimizing KL-divergence, UMAP models data as a Riemannian manifold using fuzzy simplicial sets and minimizes fuzzy set cross-entropy:

LUMAP=∑i≠j[pijln⁡(pijqij)+(1−pij)ln⁡(1−pij1−qij)]\mathcal{L}_{\text{UMAP}} = \sum_{i \ne j} \left[ p_{ij} \ln\left( \frac{p_{ij}}{q_{ij}} \right) + (1 - p_{ij}) \ln\left( \frac{1 - p_{ij}}{1 - q_{ij}} \right) \right]

The second term (1−pij)ln⁡(1−pij1−qij)(1 - p_{ij}) \ln\left( \frac{1 - p_{ij}}{1 - q_{ij}} \right) explicitly penalizes placing dissimilar points close together in low dimensions, maintaining large-scale global cluster relationships that t-SNE frequently shuffles.

5. From-Scratch Implementation: Zero Dependencies#

The following implementations compute a minimal t-SNE embedding with gradient descent from scratch in Python and Rust.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
import math import random from typing import List class TSNEFromScratch: def __init__(self, n_components: int = 2, learning_rate: float = 10.0, n_iter: int = 300): self.n_components = n_components self.learning_rate = learning_rate self.n_iter = n_iter @staticmethod def _dist_sq(a: List[float], b: List[float]) -> float: return sum((x - y) ** 2 for x, y in zip(a, b)) def fit_transform(self, X: List[List[float]]) -> List[List[float]]: num_samples = len(X) random.seed(42) # 1. Compute high-dimensional affinities P with fixed bandwidth sigma=1.0 P = [[0.0] * num_samples for _ in range(num_samples)] for i in range(num_samples): for j in range(num_samples): if i != j: P[i][j] = math.exp(-self._dist_sq(X[i], X[j]) / 2.0) # Symmetrize and normalize P total_p = sum(sum(row) for row in P) for i in range(num_samples): for j in range(num_samples): P[i][j] = max(P[i][j] / total_p, 1e-12) # 2. Initialize low-D coordinates Y with small Gaussian noise Y = [[random.gauss(0.0, 1e-4) for _ in range(self.n_components)] for _ in range(num_samples)] # 3. Gradient Descent Loop for _ in range(self.n_iter): # Compute low-D Cauchy kernel affinities Q Q = [[0.0] * num_samples for _ in range(num_samples)] inv_dist = [[0.0] * num_samples for _ in range(num_samples)] total_q = 0.0 for i in range(num_samples): for j in range(i + 1, num_samples): val = 1.0 / (1.0 + self._dist_sq(Y[i], Y[j])) inv_dist[i][j] = val inv_dist[j][i] = val total_q += 2.0 * val for i in range(num_samples): for j in range(num_samples): if i != j: Q[i][j] = max(inv_dist[i][j] / total_q, 1e-12) # Compute gradients: dL/dy_i = 4 * sum (p_ij - q_ij) * (y_i - y_j) * inv_dist dY = [[0.0] * self.n_components for _ in range(num_samples)] for i in range(num_samples): for j in range(num_samples): if i != j: mult = 4.0 * (P[i][j] - Q[i][j]) * inv_dist[i][j] for d in range(self.n_components): dY[i][d] += mult * (Y[i][d] - Y[j][d]) # Update coordinates for i in range(num_samples): for d in range(self.n_components): Y[i][d] -= self.learning_rate * dY[i][d] return Y if __name__ == "__main__": X = [ [1.0, 1.2], [1.1, 1.0], [0.9, 1.1], [8.0, 8.2], [8.1, 8.0], [7.9, 8.1] ] tsne = TSNEFromScratch(n_components=2, learning_rate=50.0, n_iter=150) embedding = tsne.fit_transform(X) print("t-SNE 2D Coordinates:") for pt in embedding: print(f" [{pt[0]:.4f}, {pt[1]:.4f}]")

6. Active Knowledge Checks#

Concept check

Why does t-SNE substitute a Student-t Cauchy distribution (1 / (1 + ||y_i - y_j||^2)) in place of a Gaussian kernel in low dimensions?

Concept check

What fundamental difference in objective functions allows UMAP to preserve global topological cluster arrangements better than t-SNE?