Chapter 1 · Section 1.6 · 70 min

Looking inside an embedding space

You've trained embeddings. Are they any good? What's actually in them? These questions are harder than they look, because embedding spaces are high-dimensional, and every way of summarizing them (a benchmark number, an analogy, a 2-D picture) throws information away. This section teaches the standard tools and, just as important, how each one can fool you.

Nearest neighbors, the first sanity check

The simplest probe is the one you built in §1.3. For a query word, list the words with the highest cosine similarity. If frog's neighbors are toad, frogs, lizard, salamander, training probably worked. If they're the, of, said, something is broken. Typical causes are missing subsampling, too few epochs, a forgotten scatter-add, or comparing raw dot products instead of cosines.

Neighbors are qualitative, but they catch most bugs, and they reveal what kind of similarity your setup learned, substitutable words or merely associated ones (§1.4).

Intrinsic evaluation asks whether cosines agree with people

Similarity benchmarks give word pairs rated by humans, such as (tiger, cat) = 7.35 or (king, cabbage) = 0.23 on a 0 to 10 scale. We compute the cosine for each pair and measure how well the two rankings agree, using Spearman's rank correlation ρ\rho,

ρ=corr⁡(rank⁡(human scores), rank⁡(cosines)),\rho = \operatorname{corr}\big(\operatorname{rank}(\text{human scores}),\ \operatorname{rank}(\text{cosines})\big),

where corr⁡\operatorname{corr} is the ordinary Pearson correlation. In plain words, replace every score by its position in sorted order, then ask how linearly related the two lists of positions are. Ranks, not raw values, because human scales and cosine scales aren't comparable. Only the order matters, so any monotone transformation of the cosines leaves ρ\rho unchanged.

When there are no ties, ρ\rho has a closed form in terms of the rank differences did_i over nn pairs, ρ=1−6∑idi2n(n2−1)\rho = 1 - \frac{6\sum_i d_i^2}{n(n^2-1)}. A worked example with four pairs. Humans give (9.1,7.4,3.0,0.2)(9.1, 7.4, 3.0, 0.2) and the model gives cosines (0.62,0.70,0.31,0.05)(0.62, 0.70, 0.31, 0.05). The human ranks (lowest is 1) are (4,3,2,1)(4, 3, 2, 1), the model ranks are (3,4,2,1)(3, 4, 2, 1), the differences are (1,−1,0,0)(1, -1, 0, 0), and ρ=1−6⋅24⋅15=0.8\rho = 1 - \frac{6\cdot2}{4\cdot15} = 0.8. The model swapped the top two pairs and got everything else right.

Common benchmarks are WordSim-353 (Finkelstein et al., 2002), MEN (Bruni et al., 2014), and SimLex-999 (Hill et al., 2015).

Extrinsic evaluation plugs the embeddings into a real task (sentiment classification, named-entity recognition, retrieval) and measures task performance. That's what ultimately matters, and intrinsic scores don't always predict it. Chiu, Korhonen and Pyysalo (2016) found that word-similarity scores correlated poorly, and sometimes negatively, with performance on downstream tagging tasks.

Core

Spearman correlation from scratch (with ties)

Implement spearman(x, y) without SciPy. Convert each array to ranks, giving tied values the average of their ranks (values [10, 20, 20, 30] get ranks [1, 2.5, 2.5, 4]), then return the Pearson correlation of the ranks. (The closed form with di2d_i^2 is only valid without ties, which is why we don't use it here.)

Hidden tests, hints, and AI help, from $1.99 a month.

Analogies, the famous trick and its fine print

The result that made word2vec famous is

vking−vman+vwoman  ≈  vqueen.\mathbf{v}_{\text{king}} - \mathbf{v}_{\text{man}} + \mathbf{v}_{\text{woman}} \;\approx\; \mathbf{v}_{\text{queen}} .

The analogy a is to b as c is to ? ("man is to king as woman is to ?") is answered by the word whose vector is most similar to vb−va+vc\mathbf{v}_b - \mathbf{v}_a + \mathbf{v}_c. With unit vectors, this 3CosAdd rule is

x⋆=arg⁡max⁡x ∉ {a,b,c}  cos⁡(vx, vb−va+vc)  =  arg⁡max⁡x ∉ {a,b,c}[cos⁡(x,b)−cos⁡(x,a)+cos⁡(x,c)].x^\star = \arg\max_{x \,\notin\, \{a,b,c\}} \; \cos(\mathbf{v}_x,\ \mathbf{v}_b - \mathbf{v}_a + \mathbf{v}_c) \;=\; \arg\max_{x \,\notin\, \{a,b,c\}} \big[\cos(x,b) - \cos(x,a) + \cos(x,c)\big] .

(The second form holds because, for unit vectors, the cosine with the target is the dot product with it divided by the target's length, which is the same for every candidate xx.) In plain words, find the word that is similar to king and woman, and dissimilar to man.

A worked example with five hand-made 3-D vectors, the same ones the exercise uses. They are man =(1,0,0.1)=(1, 0, 0.1), woman =(0,1,0.1)=(0, 1, 0.1), king =(1,0.1,1)=(1, 0.1, 1), queen =(0.1,1,1)=(0.1, 1, 1), apple =(0.3,0.3,−1)=(0.3, 0.3, -1). After normalizing, the target v^king−v^man+v^woman\hat{\mathbf v}_{\text{king}} - \hat{\mathbf v}_{\text{man}} + \hat{\mathbf v}_{\text{woman}} is about (−0.29,1.07,0.71)(-0.29, 1.07, 0.71). Its cosines with the five words are man −0.17-0.17, woman 0.860.86, king 0.280.28, queen 0.940.94, apple −0.33-0.33. Queen wins here even without excluding the inputs, but notice how close woman comes. In real embedding spaces the input word usually wins.

Geometrically, this works when a relation (male to female, country to capital, verb to past tense) corresponds to a roughly constant offset vector. Why would training produce such offsets? Here is a heuristic explanation via PMI (§1.5). If king and queen differ in their co-occurrence statistics in the same way that man and woman do (more he and his versus more she and her), then because v⊤u≈PMI⁡\mathbf{v}^\top\mathbf{u}\approx\operatorname{PMI}, the vector differences align. Allen and Hospedales (2019) make this argument precise under explicit assumptions.

Core

Analogies with 3CosAdd and 3CosMul

Implement analogy(E, vocab, a, b, c, method="add", exclude=True) that returns the answer word for a is to b as c is to ?. E has one (not necessarily normalized) row per word in vocab. For "mul", shift cosines to [0,1][0,1] with (cos⁡+1)/2(\cos+1)/2 and use ϵ=10−3\epsilon = 10^{-3}. When exclude=True, never return aa, bb, or cc.

The tests use a hand-built space where 3CosAdd and 3CosMul disagree.

Hidden tests, hints, and AI help, from $1.99 a month.
Interview

Analogy accuracy over a whole benchmark, with no loops

Benchmarks like the Google analogy set have about 19,500 questions, and a loop over them in Python is slow. Implement analogy_accuracy(E, quads, method="add") where quads is an (Q, 4) int array of rows (a, b, c, d) meaning "a is to b as c is to d". Predict every answer at once with one (Q, V) score matrix, exclude each question's own a, b, c, and return (accuracy, predictions).

Hidden tests, hints, and AI help, from $1.99 a month.

Looking at the space by projecting to 2-D

We can't see 300 dimensions, so we project to 2 (or 3). The methods differ in what they choose to keep, and that determines what they distort.

PCA, the best flat shadow

Principal component analysis finds the orthogonal directions of maximum variance and projects onto the top two. With centered data X\mathbf{X} (rows are words, the mean row subtracted) and SVD X=UsvdΣVsvd⊤\mathbf{X} = \mathbf{U}_{\text{svd}}\boldsymbol\Sigma\mathbf{V}_{\text{svd}}^\top, the 2-D coordinates are X(Vsvd):,1:2=(Usvd):,1:2Σ1:2\mathbf{X}(\mathbf{V}_{\text{svd}})_{:,1:2} = (\mathbf{U}_{\text{svd}})_{:,1:2}\boldsymbol\Sigma_{1:2}. That's the same Eckart–Young optimality as §1.4, the best rank-2 approximation in squared error. The fraction of variance kept is

σ12+σ22∑iσi2,\frac{\sigma_1^2 + \sigma_2^2}{\sum_i \sigma_i^2} ,

where σi\sigma_i are the singular values of the centered data. In plain words, each squared singular value is the variance along one principal direction (times the number of rows), so this ratio is the share of the total spread that the picture shows.

PCA is linear and faithful in one specific sense. Distances in the picture are never larger than the real ones, because an orthogonal projection can only shrink vectors. Large-scale structure (which clusters are far apart) is preserved as well as two dimensions allow. Its weakness is that if the top two directions only explain, say, 15% of the variance, most of the structure is invisible, and distinct groups can land on top of each other. Centering matters too. Without it, the first "component" mostly points at the mean vector, which for anisotropic embeddings (§1.9) is a large shared offset that says nothing about differences between words.

Core

PCA from the SVD

Implement pca(X, k) returning (Z, components, explained), where components is (k, d) with orthonormal rows (the top principal directions), Z = (X - mean) @ components.T is (n, k), and explained is the (k,) fraction of variance along each component. Make the signs deterministic by flipping each component so that its largest-magnitude entry is positive.

Hidden tests, hints, and AI help, from $1.99 a month.

t-SNE keeps the neighbors and sacrifices everything else

t-SNE (van der Maaten & Hinton, 2008) doesn't try to preserve distances at all. It tries to preserve who is near whom, in three steps.

  1. In the original space, turn distances into neighbor probabilities, pj∣i∝exp⁡(−βi∥xi−xj∥2)p_{j\mid i} \propto \exp(-\beta_i\lVert\mathbf{x}_i-\mathbf{x}_j\rVert^2), normalized over j≠ij \neq i. The precision βi\beta_i (written 1/2σi21/2\sigma_i^2 in the paper) is tuned per point so the distribution has a fixed perplexity 2H(Pi)2^{H(P_i)}, where H(Pi)=−∑jpj∣ilog⁡2pj∣iH(P_i) = -\sum_j p_{j\mid i}\log_2 p_{j\mid i} is the entropy in bits. Perplexity is roughly "the effective number of neighbors" (§0.5 explains why). Then symmetrize, pij=(pj∣i+pi∣j)/2np_{ij} = (p_{j\mid i}+p_{i\mid j})/2n, where nn is the number of points.
  2. In 2-D, use a heavy-tailed Student-t kernel, qij∝(1+∥yi−yj∥2)−1q_{ij} \propto (1+\lVert\mathbf{y}_i-\mathbf{y}_j\rVert^2)^{-1}, normalized over all pairs.
  3. Move the 2-D points yi\mathbf{y}_i to minimize KL⁡(P ∥ Q)=∑ijpijlog⁡pijqij\operatorname{KL}(P\,\Vert\,Q) = \sum_{ij}p_{ij}\log\frac{p_{ij}}{q_{ij}}, by gradient descent with gradient
∂ KL∂yi=4∑j(pij−qij) (yi−yj) (1+∥yi−yj∥2)−1.\frac{\partial\,\mathrm{KL}}{\partial \mathbf{y}_i} = 4\sum_j (p_{ij}-q_{ij})\,(\mathbf{y}_i-\mathbf{y}_j)\,(1+\lVert\mathbf{y}_i-\mathbf{y}_j\rVert^2)^{-1} .

In plain words, pairs that are neighbors in the original space but too far apart in the picture (pij>qijp_{ij} > q_{ij}) attract, pairs that are too close repel, and the force is damped for points that are already far apart.

The heavy tail solves the crowding problem. There isn't enough room in 2-D to place all of a point's moderately distant neighbors at faithful distances, so the t-kernel lets non-neighbors be pushed far away. That's what makes t-SNE clusters look so crisp. It also means sizes and gaps in a t-SNE plot are not to be trusted. Because each βi\beta_i adapts to local density, a tight cluster and a diffuse one come out about the same size.

Interview

t-SNE input affinities with perplexity calibration

The first step of t-SNE is a nice interview problem, because it combines numerics and a search. Implement tsne_affinities(X, perplexity) returning (P_cond, P). P_cond is the (n, n) matrix of pj∣ip_{j\mid i} (rows sum to 1, zero diagonal) with each row's precision βi\beta_i found by bisection so that its perplexity matches the target to within 0.1%. P is the symmetrized joint matrix pijp_{ij}. Do the bisection for all rows at once (vectorized), not row by row.

The tests also check that scaling the data by 10 leaves P_cond unchanged. Perplexity is defined on the shape of the distribution, so the precisions must absorb the scale.

Hidden tests, hints, and AI help, from $1.99 a month.
What does a real embedding space look like, and how do 2-D pictures lie?
60 words embedded by a real model (all-MiniLM-L6-v2, 384 dimensions) running in your browser, squashed to 2-D. Click a word to compare its true neighbors with what the picture suggests.
Loading interactive figure…

Wattenberg, Viégas and Johnson (2016) collected the lessons of reading t-SNE plots. Run the next cell to see three of them on synthetic data where we know the truth.

Python · cell 1

Here is what to notice.

  • Cluster sizes mean nothing. Blue is 10 times tighter than orange in reality, but t-SNE draws them about the same size, because it adapts its precision to local density.
  • Distances between clusters may mean nothing. The gap from blue to green (about 134 in the original space) is 10 times the gap from blue to orange (about 13), but t-SNE doesn't show that reliably.
  • Hyperparameters change the story. Perplexity changes the picture, sometimes completely.

UMAP

UMAP (McInnes, Healy & Melville, 2018) builds a weighted kk-nearest-neighbor graph, treats it as a fuzzy topological structure, and optimizes a 2-D layout whose graph matches it, using a cross-entropy loss and negative sampling (the same trick as §1.5). It's much faster than exact t-SNE, scales to millions of points, and is often said to preserve more global structure. But Kobak and Linderman (2021) showed that much of that difference comes from initialization. Both methods preserve global structure better when initialized with PCA. The same warnings apply. n_neighbors and min_dist change the picture, and sizes and gaps are unreliable.

methodpreservesdistortsuse it for
PCAglobal variance, linear structureeverything beyond the top 2 directionsfirst look, honest overview, checking scale
t-SNElocal neighborhoodscluster sizes, inter-cluster distances, densityfinding local groups, spotting outliers
UMAPlocal neighborhoods (plus some global with good init)sizes, distances, densitythe same as t-SNE, at scale
Interview

Quantify how much a 2-D picture lies

Here is a principled check for any projection. For each point, what fraction of its kk nearest neighbors in the original space are also among its kk nearest neighbors in the projection? Implement knn_preservation(X, Y, k) returning the average overlap in [0,1][0, 1], using Euclidean distance in both spaces and excluding each point itself. Vectorize the distance computations (reuse the trick from Exercise 1.1d).

Then, optionally, use it to compare PCA and t-SNE on the data in the cell above for k=10k = 10. For structureless data the overlap of a random picture is about k/nk/n, which gives you a baseline to beat.

Hidden tests, hints, and AI help, from $1.99 a month.

In PyTorch and the ecosystem

PCA is a few lines of linear algebra, so it runs in PyTorch directly, on the GPU if you like.

🔥 In PyTorchfor a machine with PyTorch

For t-SNE and UMAP, use the standard libraries rather than writing your own optimizer.

Read more

Watch next

🎬 StatQuest: Principal Component Analysis (PCA), Step-by-Step · StatQuest with Josh StarmerPCA built up one step at a time.
🎬 StatQuest: t-SNE, Clearly Explained · StatQuest with Josh StarmerThe intuition behind t-SNE's similarity scores and heavy-tailed kernel.
🎬 UMAP Dimension Reduction, Main Ideas!!! · StatQuest with Josh StarmerHow UMAP builds and lays out its neighbor graph.
🎬 Visualizing Data Using t-SNE · Google TechTalksLaurens van der Maaten explains t-SNE and the crowding problem himself.