Visual walkthrough — UMAP for dimensionality reduction
Before anything else, let us agree on the words.
We use the ordinary straight-line distance (Euclidean) as our default, the same you would get with a ruler. When we say "high-D" we mean the original -dimensional space; "low-D" means the flat 2-D map we are trying to draw.
Prerequisites worth a glance: Nearest Neighbors Algorithms (Step 1), Cross-Entropy Loss (Step 5), and cousins PCA, Isomap, Spectral Clustering, Autoencoders, Random Projection, Topological Data Analysis.
Step 1 — Find each point's neighbours
WHAT. Pick one point . Look outward and grab its closest points. That number is the hyperparameter n_neighbors. Do this for every point.
WHY. UMAP never trusts global straight-line distances (a straight line through a crumpled sheet cuts through empty air). It only trusts who is next to whom locally. So the raw material is not "all distances" but "each point's little circle of friends." This is exactly the nearest-neighbours query.
PICTURE. Below, the blue point is . The orange points are its nearest neighbours; the gray points are ignored for now. The nearest of all — its very closest friend — is circled in green; its distance is called (Greek letter "rho", just a name for "distance to closest friend").

- ::: the point we are currently focused on (blue).
- ::: how many neighbours we keep — small = very local view, large = broader view.
- ::: the distance from to its single closest neighbour (green circle).
- ::: the set of those nearest neighbours of — the only points we will ever attach a weight to.
Step 2 — Turn distances into "friendship strengths"
WHAT. A raw distance is awkward: it grows without limit. We want instead a weight between and — think "how strongly are and connected?" — where means glued together and means strangers. For every neighbour we compute:
Let us read every piece right where it sits:
- ::: raw distance from to neighbour — the ruler number.
- ::: distance beyond the closest friend. We measure from the nearest neighbour, not from itself.
- ::: a floor at zero — anything closer than can't give a negative gap, so it stays at .
- ::: the "softness" or bandwidth (Greek "sigma") — big makes distant friends still count, small makes the friendship die off fast.
- ::: the exponential decay — turns "gap" into a number that starts at and slides smoothly toward .
For any not in we simply set — those pairs never enter any sum.
WHY the and not, say, ? We want three things at once: (1) the value must sit in , (2) it must be exactly at zero gap, (3) it must fade smoothly with no sudden cliff. The falling exponential is the unique simple function that does all three: and it decays gently. That is why this tool and not a reciprocal (which blows up at ) or a hard cutoff (which has a cliff).
WHY (the subtraction)? Without it, a lonely point in a sparse region would have all weights tiny and float off alone. Subtracting guarantees the nearest friend gets — everyone is fully connected to at least one neighbour. No islands.
PICTURE. The curve below is as the gap grows. It starts at (the green dot, the nearest friend) and decays. The vertical dashed line marks where the friendship has faded to about a third — that spot is controlled by .

Step 3 — Tune so every point has the same "amount of neighbourhood"
WHAT. We don't pick by hand. We choose it so the weights of point add up to a fixed budget, where the sum runs over exactly the nearest neighbours from Step 1 (the only with nonzero ):
- ::: sum over the nearest neighbours of — not over all points, only over 's own circle of friends.
- ::: the friendship weight from Step 2.
- ::: the target budget — the "effective number of neighbours" measured in information-theory units. For this is .
WHY a fixed budget? Some points sit in crowded regions, some in empty ones. If we used one global , crowded points would be over-connected and lonely points under-connected. Forcing every point's weights to sum to the same number makes UMAP treat dense and sparse regions fairly — this is the "uniform" in Uniform Manifold Approximation. It is the same idea as perplexity in t-SNE.
HOW. The sum shrinks when is small (weights decay fast) and grows when is big. Since it moves monotonically, a binary search nails the right in about 20 halving steps.
PICTURE. Left: a dense point needs a small (tight decay) to keep its sum at the budget. Right: a sparse point needs a large . Same budget, different softness — that's the equalisation.

Step 4 — Make the graph symmetric (fuzzy union)
WHAT. So far friendship can be one-sided: might list as a neighbour but not vice-versa, so . We merge the two views into one undirected weight:
- ::: how strongly reaches out to .
- ::: how strongly reaches out to .
- ::: the fuzzy OR — "at least one of them thinks they're friends."
WHY this exact formula? Read it as probability. If is the chance -sees- and the chance -sees-, then the chance at least one is true is . That is precisely . It keeps the result in and is generous: one strong direction is enough to bind the edge.
EDGE CASES.
- If both directions are : (stays capped, never overflows past ).
- If both are : (true strangers stay strangers).
- If one is , other : (one-sided strong friendship is honoured).
- Self-edges (): we never form them. Nearest-neighbour search excludes a point from its own neighbour list, so , is never defined, and the loss in Step 6 sums only over pairs with .
PICTURE. Left: two directed arrows with unequal strengths. Right: one fused undirected edge whose thickness is the fuzzy-OR result.

The result is a single fuzzy graph — this is the fuzzy topological structure of your data, the object Topological Data Analysis also studies.
Step 5 — Give the flat map its own connection kernel
WHAT. Now flip to the 2-D world. Scatter the points anywhere as (2-D positions we get to move). Define their connection strength with a different formula:
- ::: the flat-map distance between the two 2-D dots.
- ::: the exponent controlling how sharply nearness turns into strong connection.
- ::: a scale knob for how quickly falls off.
- ::: gives at zero distance and a heavy tail — it never crashes to too abruptly.
Where do and come from? Enter min_dist. First the definition, then the picture, then the solve.
PICTURE (target shape). We first draw the shape we WANT the low-D kernel to have. It should equal for any distance up to , then fall off exponentially:
- ::: the ideal connection-vs-distance curve dictated by min_dist.
- flat top out to ::: "points within are treated as fully together."
- exponential drop after ::: gentle fade, same spirit as Step 2's high-D kernel.

THE SOLVE (fit to ). The smooth rational form is easy to differentiate (good for gradient descent) but has no flat top. So we least-squares fit so that hugs the target as closely as possible over a grid of distances:
- ::: "the that make the mismatch smallest."
- inner bracket ::: pointwise gap between the smooth kernel and the ideal target, at each sample distance .
For the library default this fit returns — the numbers quoted in the parent note are not magic constants but the output of this one-time curve fit. Change and you re-run the fit, getting new .
WHY a heavy-tailed rational curve at all (instead of another exponential)? In 2-D there isn't enough room; if far points were pulled with full force everything would pile into a blob (the "crowding problem"). The rational form has a heavy tail, so far points stay comfortably far while near points snap together — and it is differentiable everywhere, which the piecewise is not.
Step 6 — Match the two graphs with cross-entropy
WHAT. We now have two fuzzy graphs on the same points: the fixed high-D one () and the movable low-D one (). Slide the 2-D dots until the two graphs agree. We build the disagreement score from scratch.
Step 6a — one edge is a coin flip. Treat "is there an edge between and ?" as a yes/no random event (a Bernoulli variable). High-D says the true probability of "yes" is ; low-D currently predicts . The standard cross-entropy between a true probability and a predicted probability is:
- ::: penalty when the true "yes" chance is high but predicted is low.
- ::: penalty when the true "no" chance is high but predicted "no" chance is low.
Step 6b — subtract the part we can't change, showing the algebra. Only (through the dot positions) is adjustable; is frozen. The entropy of alone, , is a constant that does not depend on the dots, so minimising is identical to minimising . Watch the two constant terms cancel into the variable terms via :
- line 1 ::: write both cross-entropies out in full.
- line 2 ::: group the -terms and the -terms; signs flip so each pair subtracts.
- line 3 ::: fold each subtracted pair into a single ratio with .
This is the tidy relative form. (Writing for from here on.)
Step 6c — sum over pairs, but only the ones that matter. Add this up over pairs:
- ::: the set of actual graph edges (, i.e. neighbour pairs, ) — these supply attraction. There are only of them, not .
- ::: a small set of randomly sampled non-edges (, ) drawn fresh each step — negative sampling. These supply repulsion without ever touching all pairs.
How many negatives? For each edge we touch during a step, we draw a fixed number of random strangers — the negative_sample_rate, default 5 non-edges per positive edge. Each stranger is picked by sampling an index uniformly at random from all points (rejecting ), so is roughly pairs per epoch, still overall. This keeps the two terms balanced: one green pull per edge, five red pushes.
So the innocent-looking in the parent note is, in practice, "sum over the few real edges plus a handful of sampled strangers, all with " — never a full sweep, and never a self-edge.
WHY cross-entropy and not t-SNE's KL? t-SNE keeps only the attraction half (a single KL term over normalised probabilities), so it forgets global distances. UMAP's Bernoulli cross-entropy keeps both halves — an explicit attraction on true edges and an explicit repulsion on sampled strangers. That second, repulsive term is exactly what pushes unrelated clusters apart and so preserves the global layout that t-SNE throws away.
- ::: attraction — when high-D says "connected" ( big) but low-D has them far ( small), this blows up, so the optimiser pulls them together.
- ::: repulsion — when high-D says "strangers" ( small) but low-D placed them close ( big), this blows up, so the optimiser pushes them apart.
HOW we actually minimise it. The optimiser is plain stochastic gradient descent (no momentum, no Adam), run for a fixed n_epochs (default ~200 for large data, ~500 for small). The recipe:
- Initialise the dot positions with a spectral embedding (eigenvectors of the graph Laplacian) — a smart, deterministic starting layout that is far cheaper than random restarts.
- Per epoch, walk over every edge in . For each edge, apply the attractive gradient to pull together, then draw 5 strangers () and apply the repulsive gradient to push them apart. Gradients are clamped to to stop blow-ups.
- Learning rate (, the step size) starts at and decays linearly to across the epochs, so early epochs move boldly and late epochs fine-tune.
PICTURE. Two forces on the same dot: a green spring pulling toward a true neighbour (attraction) and a red spring pushing away a sampled non-neighbour (repulsion). Their balance is the final position.

Step 7 — Watch the map settle
WHAT. Repeat the pull/push over the ~200–500 epochs from Step 6. Each epoch walks over the edge set (one attraction per edge) and, for each edge, samples 5 strangers from (repulsion), while the learning rate decays from toward . The tangled initial scatter relaxes into separated, correctly-arranged clusters.
WHY it converges. Every edge in contributes a pull; every sampled non-edge in contributes a push. At the layout where pulls balance pushes and the learning rate has shrunk to near zero, the dots stop moving — that's the embedding.
PICTURE. Three snapshots: tangled start → mid-optimisation → settled clusters that keep both local groups and their global arrangement.

The one-picture summary
Every gadget on one canvas: neighbours → weighted graph → fuzzy union → cross-entropy pull/push → flat map.

Recall Feynman retelling — the whole thing in plain words
Imagine everybody in a huge crumpled crowd. First, each person looks around and notes their few closest friends — that little set is , and it never lists yourself (no self-edges) (Step 1). "Closeness" is turned into a friendship strength between and using a gentle falloff, and everyone is guaranteed at least one full friend so nobody floats away (Step 2). We tune each person's "reach" so everyone spends the same friendship budget summed over just their own friends — crowded folks reach short, lonely folks reach far (Step 3). Friendship is made mutual with a fuzzy-OR: if either says "friend," it counts (Step 4). Now we drop everyone onto a flat floor; we first decide the smallest allowed gap min_dist, draw the ideal closeness curve, and fit the floor's rule to it (Step 5). Finally we score the mismatch with Bernoulli cross-entropy (true-probability vs predicted , minus the constant part of ), and let plain SGD — starting at learning-rate , decaying to over ~200 epochs, one green pull per true edge and five red pushes per sampled stranger — shuffle people until pulls and pushes balance (Steps 6–7). The flat arrangement that results is the UMAP map: neighbours stayed neighbours, and the big-picture layout survived too.
Recall
Why does subtracting prevent isolated points? ::: The nearest friend gets gap , so — every point is fully connected to at least one neighbour. Over which index does run? ::: Only over the nearest neighbours of point — the sole pairs with nonzero . Where do and come from? ::: A one-time least-squares fit of the smooth kernel to the ideal min_dist target curve . Which half of the cross-entropy creates repulsion, and on which pairs? ::: The second term , evaluated on randomly sampled non-edges (negative sampling, ~5 per edge). How are self-edges () handled? ::: They never exist — nearest-neighbour search excludes each point from its own neighbour list, so every sum runs over only. What optimiser and schedule does UMAP use? ::: Plain SGD from a spectral initial layout, learning rate starting at and decaying linearly to over ~200–500 epochs, gradients clamped to . Why is UMAP's loss cross-entropy rather than t-SNE's KL? ::: Cross-entropy keeps both an attraction term (true edges) and a repulsion term (strangers); KL keeps only attraction, so it loses global structure.