The geometry of meaning
Once words are vectors, "how similar are these two words?" becomes "how close are these two points?". But close can mean several things, and the choice matters a great deal. Attention in a Transformer uses one notion, vector databases usually another, and a surprising amount of confusion (and many bugs) comes from mixing them up.
This section builds the three tools you'll use for the rest of the book (dot product, cosine similarity, and Euclidean distance), shows exactly how they relate, and then takes a trip into high-dimensional space, where your 3-D intuition quietly breaks. We finish with the trick that makes similarity search over millions of vectors possible, random hyperplanes.
The dot product measures alignment times magnitude
For the dot product has two equivalent definitions, one algebraic and one geometric,
where is the angle between the vectors (in this section only, is an angle, not the model's parameters) and is the length.
In plain words, multiply matching coordinates and add them up. Geometrically, that equals the product of the two lengths times the cosine of the angle between them.
So the dot product mixes two different things. It's large when the vectors point the same way (large ) or when they're long (large norms). It's zero when they're perpendicular and negative when they point in opposing directions.
There's a second, very useful reading. , where , is the length of the shadow that casts onto the direction of . That shadow is called the scalar projection, and it answers "how much of points along ?" In §1.9 we'll use exactly this to measure how much gender a word's vector carries.
A worked example. Take and . The dot product is . Both lengths are 5, so and . The shadow of on has length . Now double to . The dot product doubles to 48, the shadow doubles to 9.6, and the angle and cosine do not change at all.
Cosine similarity measures direction only
If we only care about direction, we divide out the lengths,
In plain words, cosine similarity is the dot product of the two vectors after scaling each to length 1. (The range is the Cauchy–Schwarz inequality, .)
It's invariant to rescaling, since . That's usually what you want when comparing word vectors, because the length of a word vector tends to carry other information. In word2vec, vector norms are empirically correlated with word frequency and with how consistently a word is used (Schakel & Wilson, 2015). If you used raw dot products for "most similar word", long, frequent words would win just for being long.
Euclidean distance, and the identity that ties everything together
The ordinary straight-line distance is . Expand its square,
(You used this in Exercise 1.1d.) Now suppose both vectors are unit length. Then , and
In plain words, for unit vectors, squared distance is a decreasing function of cosine. For example, two unit vectors with cosine 0.8 are apart, and two with cosine 0 are apart, like the one-hot vectors of §1.1.
This is one of the most practically important facts in the chapter. Once vectors are normalized, ranking by Euclidean distance, by cosine, and by dot product all give the same order. That's why vector databases usually say "normalize your embeddings, then use inner product". Maximum inner-product search on unit vectors is cosine search, which is nearest-neighbor search.
Tick "Normalize" in the playground above to check the identity numerically.
Which one should you use?
| measure | sensitive to length? | range | where you'll meet it |
|---|---|---|---|
| dot product | yes | inside models, such as attention scores, softmax logits, word2vec's objective (§1.5), recommender scores | |
| cosine | no | comparing embeddings, such as "most similar word", semantic search, and joint image and text spaces | |
| Euclidean | yes | k-means, t-SNE (§1.6), metric-space indexes, and the same ranking as cosine on unit vectors |
Let's put this into code. Here are a few hand-made 4-D "word vectors", with one deliberately long.
Read the table row by row. The dot product ranks "the" first (4.07, nearly three times kitten's 1.46), purely because it is long. Cosine ranks kitten first (0.987) and puts "the" third (0.738). Euclidean distance also ranks kitten first, but it is fooled in a different way, because "the" is far away for being long.
Cosine-similarity matrix, safely
Write cosine_sim_matrix(A, B) that returns the matrix for A of shape (n, d) and B of shape (m, d). It must be vectorized (one matrix multiply), and zero vectors must not produce NaN. Define their similarity as 0.
Top-k nearest neighbors in O(V) time
Given a query vector q of shape (d,) and an embedding matrix E of shape (V, d), return the indices of the k rows with the highest cosine similarity to q, sorted from most to least similar, optionally excluding some indices (like the query word itself).
Sorting all scores costs . You can do better. np.argpartition finds the top in on average (it uses introselect), and then you sort only those , for in total.
When lengths differ, inner-product search is a different problem
Recommenders often score items by a raw dot product , because the item vector's length carries meaning (popularity, quality). Finding the best item is then maximum inner-product search (MIPS), and it is not nearest-neighbor search. A long item can beat a closer short one, and a vector need not even be its own best match ().
There is a neat reduction (Bachrach et al., 2014; Neyshabur & Srebro, 2015). Let . Append one coordinate to every item, , so every augmented item has length exactly , and append a 0 to the query, . Then
In plain words, the first two terms are the same for every item, so the nearest augmented item is the one with the largest inner product. Any nearest-neighbor index can now answer MIPS.
A trip into high dimensions
Embeddings live in hundreds or thousands of dimensions. Geometry there is not a scaled-up version of 3-D, and three facts will shape everything we do.
Fact 1. Random vectors are almost orthogonal
Draw with independent entries. The dot product is a sum of independent terms, each with mean 0 and variance 1. So it has mean 0 and variance , a typical size of . Meanwhile each norm is about (next fact). Therefore
In fact the variance is exactly , and the derivation is short enough to carry around.
∂ Why the cosine of two random directions has variance exactly 1/d
A Gaussian vector with independent standard entries has a rotation-invariant distribution, so its direction is uniform on the unit sphere and independent of its length. The cosine is . Condition on and rotate the coordinates so that . Then the cosine is just , the first coordinate of a uniform unit vector.
By symmetry, (flipping the sign of a coordinate doesn't change the distribution) and every coordinate has the same second moment. The coordinates of a unit vector satisfy , so taking expectations gives , that is,
For large the distribution is close to by the central limit theorem applied to .
In plain words, in dimensions, the cosine between two random directions is typically about . At that's , and at it's .
Two consequences follow.
- "Small" cosines can be big signals. A cosine of 0.3 between two 768-dimensional embeddings is about 8 standard deviations above chance ().
- There's room for a huge number of almost-orthogonal directions. You can fit exponentially many (in ) vectors whose pairwise cosines are all below any fixed . That's what lets a model with represent far more than 4096 features by giving each a nearly orthogonal direction. This phenomenon, called superposition, is central to understanding what is inside large models, and it is one reason why the features a model learns rarely line up with single coordinates.
Fact 2. Norms and distances concentrate
The squared norm is also a sum of independent terms, each with mean 1 and variance 2. So and , which means . A first-order expansion of the square root gives . At that is . Random Gaussian vectors don't fill the ball. They live on a thin shell, whose absolute thickness stays constant while its radius grows like .
Volume behaves the same way. The fraction of a -dimensional ball's volume that lies within radius of its center is . For and , that is . More than 99.9% of the volume sits in the outer 1% of the radius.
The same thing happens to distances. For random data, the ratio between the farthest and the nearest neighbor of a query tends to 1 as grows (Beyer et al., 1999). The widget's "distance contrast" shows this. It's the precise sense in which nearest-neighbor search gets harder in high dimensions. A related effect, hubness, is that a few points become the nearest neighbor of a disproportionate number of others (Radovanović et al., 2010), and it plagues retrieval with high-dimensional embeddings. Real embeddings are useful only because they are not random. Their meaningful structure lives near much lower-dimensional patterns.
Fact 3. Random projections preserve distances (Johnson–Lindenstrauss)
Here's the good news. You can squash high-dimensional points into far fewer dimensions with a random matrix and barely distort their distances.
Johnson–Lindenstrauss lemma. For any and any points , if the target dimension satisfies
then there exists a linear map (a random Gaussian matrix scaled by works with probability at least by this proof, and in practice a single draw almost always works, as the cell below shows) such that for all pairs,
In plain words, points can be squeezed into about dimensions while every pairwise distance stays within a factor . This is the form proved by Dasgupta and Gupta (2003). Notice what's missing, namely the original dimension . The target dimension depends only on the number of points, and only logarithmically.
Worked numbers make the bound concrete. For points and , the bound asks for . For a million points and , it asks for 11,842 dimensions, and going to a billion points only raises that to about 17,800. The bound is conservative (you'll measure how conservative in the exercise), but its shape is the lesson. The logarithm is the deep reason why modest dimensions can serve enormous vocabularies, and in practice the dimensions needed are smaller still, because real data is far from the worst case and an embedding only has to keep the distances that matter for its task. It's also the theory behind random-projection tricks in sketching, hashing, and approximate nearest-neighbor search.
How many dimensions does JL say you need?
Write jl_min_dim(n, eps) that returns the smallest integer satisfying the Dasgupta–Gupta bound above. Then use the max_distortion helper from the cell above (it's provided in this exercise's setup) to check empirically whether the bound is tight or loose for , . Print what you find. The tests only check jl_min_dim, and the investigation is for you. (Ask the AI to review your conclusion.)
Random hyperplanes, and search that skips most of the data
Johnson–Lindenstrauss projects to fewer real numbers. Push the idea to its extreme and keep only the sign of each random projection, one bit per hyperplane. This is SimHash (Charikar, 2002), the simplest locality-sensitive hashing (LSH) scheme for cosine similarity, and it comes with an exact formula.
Draw a random direction with independent Gaussian entries and record the bit . The hyperplane orthogonal to splits space in two, and two vectors get different bits exactly when the hyperplane passes between them. Then
∂ Why the separation probability is θ/π
Only the component of in the plane spanned by and matters for the signs of and . Because is rotation invariant, the direction of that 2-D component is uniform on the circle. The two signs differ exactly when that direction makes an angle of more than 90° with one vector and less than 90° with the other. On the circle, those directions form two arcs, each of angle (one near each side of the wedge between the vectors). So the probability is . The same lemma is the heart of the Goemans–Williamson rounding algorithm for MAX-CUT.
In plain words, each random hyperplane is a coin that comes up "different" with probability equal to the angle between the vectors, as a fraction of 180°. For cosine 0.9 () the coin says "different" 14.4% of the time. For cosine 0 () it says "different" half the time.
Now build an index. Concatenate bits into a bucket key, so two vectors share a bucket only if all hyperplanes agree, with probability where . One table with large is precise but misses true neighbors, so use independent tables and take every point that shares a bucket with the query in any table as a candidate. The candidate probability is
In plain words, makes buckets picky and gives each neighbor several chances, and together they turn a gentle function of the angle into a steep S-curve. With and , a neighbor at cosine 0.9 becomes a candidate with probability 0.83, one at cosine 0.5 with probability 0.03, and an unrelated vector at cosine 0 with probability 0.0003. Then we rank only the candidates by exact cosine. Search cost drops from all points to a small candidate set, at the price of occasionally missing a neighbor.
The LSH candidate probability
Implement collision_prob(cos_sim, n_bits, n_tables) returning from the formula above. It must work elementwise on NumPy arrays of cosines, and clip the cosine into first (rounding can produce 1.0000000002, and arccos of that is NaN).
A random-hyperplane LSH index versus brute force
Build a small cosine-similarity index. Implement a class LSHIndex(E, n_bits, n_tables, seed=0) that draws n_tables independent sets of n_bits Gaussian hyperplanes, hashes every row of E into one bucket per table, and a method query(q, k) that returns (ids, n_candidates). The ids are the top k candidates ranked by exact cosine with q (most similar first), and n_candidates is the number of distinct rows that shared a bucket with q in at least one table.
The tests plant 50 tight clusters of 40 points in 32 dimensions and require that your index finds at least 90% of the true top 10 while scoring fewer than a quarter of the points. This is the design question behind every vector database, in miniature.
Real systems mostly use graph indexes (HNSW) and product quantization rather than plain LSH, because those adapt to the data instead of using random hyperplanes. But LSH is the one whose behavior you can derive exactly, and the trade-off it exposes (more candidates means higher recall and more work) is the same knob every vector index turns. §7.4 builds the rest.
In PyTorch
Read more
- Blum, Hopcroft & Kannan. Foundations of Data Science, Chapter 2. High-dimensional geometry, Gaussian annuli, and random projection.
- Vershynin. High-Dimensional Probability. Concentration of norms (§3.1) and Johnson–Lindenstrauss (§5.3), rigorously.
- Dasgupta & Gupta (2003). An Elementary Proof of the Johnson–Lindenstrauss Theorem. The bound used in this section.
- Charikar (2002). Similarity Estimation Techniques from Rounding Algorithms. SimHash and the θ/π collision probability.
- Beyer et al. (1999), When Is Nearest Neighbor Meaningful?, and Radovanović, Nanopoulos & Ivanović (2010), Hubs in Space. Distance concentration and hubness.
- Neyshabur & Srebro (2015). On Symmetric and Asymmetric LSHs for Inner Product Search. The norm-augmentation reduction from inner-product search to nearest-neighbor search.
- Prince (2023). Understanding Deep Learning. Notebook 8.4 explores high-dimensional spaces interactively.