Meaning as coordinates: embeddings, similarity and the search for neighbours

After this lesson you will explain how an embedding turns a sentence or an image into a point, compute and compare cosine similarity, Euclidean distance and inner product, and say how K-D trees, R-trees, LSH, HNSW and IVFPQ find the nearest neighbours of a query — and what each of them gives up.

Page 656 of the lecture compares one new sentence, "Rainy days make me sad", with ten stored ones. First, with a score of 0.764: "Rainy days can be gloomy." No surprise, two words in common. Second, with 0.419: "Colds are unpleasant." It shares not a single word with the query. Last, with 0.034: "Success is rewarding."

An inverted index, the structure behind keyword search (lesson 24), could never put "Colds are unpleasant" second: it finds documents by the words they contain. Something on that page reads meaning. And a vector database must produce such a ranking over millions of items in milliseconds, although comparing the query with every stored point "for large datasets is computationally prohibitive" (p. 658).

Two questions, then. How does a machine see that a sad rainy day and an unpleasant cold belong together? And how does it find the best match without looking at everything? You will answer both by the end of the lesson — and learn the price of the second answer.

From things to points

"Vector databases are gaining popularity due to the rise of AI and ML applications", page 640 begins: "In particular, applications that use LLM + RAG (Retrieval Augmented Generation)". In retrieval-augmented generation a language model, before it answers, fetches the stored passages closest in meaning to the question and reads them. Page 641 widens the picture: vector databases allow "more efficient and semantically rich data retrieval", "enhancing search and recommendation systems". Fetching the closest items fast is the subject of this lesson.

Vector database — "A vector database stores data as high-dimensional vector representations. It enables contextual search and discovery of data, going beyond keyword matching" (p. 645). Vector DBs "organize the vectors by their semantic similarity, allowing for efficient and meaningful data retrieval based on context" (p. 646).

Vector search is also a feature of databases you already know. The chart of page 644 separates "Dedicated vector databases" from "Databases that support vector search", and the second group includes Neo4j, Cassandra, Elasticsearch, MongoDB and Redis — the graph, column-family, search, document and key-value databases you practise in this course.

A vector is a list of numbers — and an arrow

"In mathematics and physics, a vector is a quantity with both magnitude and direction. In vector databases, vectors represent various attributes of an object" (p. 647). Page 648 turns a house into four of them: 3 bathrooms, 1 bedroom, an area of 130 and a price of 340000, the vector \((3, 1, 130, 340000)\). The exercise session adds two words: a vector "is a list of numbers that represent magnitude and direction", and "When most values are non-zero, it is referred to as a dense vector" (p. 1685). A vector space is "a mathematical set of vectors that can be added together and scaled", continuous "when values can take on any real number" (p. 1686).

Can you compare two houses with these raw numbers?

Not yet. Raise the price of the house by 5000 and the new vector is 5000 away from it. Keep the price and change everything else — 1 bathroom, 4 bedrooms, an area of 250 — and the new vector is only 120.05 away. The price, the largest number, swamps every other coordinate. Hand-made attribute vectors must be standardised first (z-scores, as before a PCA in statistics). The vectors of an embedding model, below, do not have this problem: every coordinate is on the model's own scale, none is a price or an area.

Embedding: similar things, nearby points

Embedding — "Vectorization / embedding is the process of converting data (such as text or images) into dense numerical vectors that capture their semantics, using an embedding model" (p. 1684). In the lecture's words, "Embedding is a technique to transform text, images, or any other data into vectors" (p. 649).

What makes embeddings useful is a property of space: they "represent data (like text, images, audio, or graphs) as vectors of numbers so that similar items are close together in this vector space" (p. 1687). The drawing on that page turns three portraits into three points in one corner of a plane and two fruits into two points in another.

What does one coordinate do? The session borrows Google's food example (p. 1688; Fig. 1). On a single axis, "sandwichness", five dishes line up from borscht to shawarma; by sandwichness alone an apple strudel would fall between the hot dog and the shawarma. Add "dessertness" and it rises away from them. A third axis, "liquidness", pulls the borscht away from the salad and lifts tangyuan, sweet dumplings served in soup, high on two axes at once. Every new dimension separates things the previous ones confused.

strudel or sandwich? borscht salad pizza hot dog apple strudel shawarma less sandwich-y more sandwich-y

Before: one dimension

dessertness sandwichness apple strudel lifted borscht pizza shawarma salad hot dog

After: two dimensions

Fig. 1. The food example of p. 1688 (taken by the session from Google's Machine Learning Crash Course), redrawn. Before: on the sandwichness axis alone, the apple strudel lands between the hot dog and the shawarma. After: a dessertness axis lifts it away from the sandwiches. The slide's one-axis panel shows only the five other dishes; the strudel is placed there at its sandwichness in the slide's second panel.

Real embedding dimensions have no such names: the embedding model "often is black box", says the drawing on page 1684. You cannot read the meaning of the 17th coordinate; you can only measure which points are near which. That is enough: embedding lets vector databases "find relevant information through vector similarity, rather than exact text matches" (p. 653).

An embedding turns meaning into position: similar items get nearby coordinates, so "similar" becomes "near".

Where the coordinates come from

The session names two decisions before any model is built (p. 1689). First, "decide what ‘similar’ means for the data at hand (words in similar contexts, images of the same object, etc.)". The slide adds that "In Deep Learning, this is automatically inferred": you do not hand-craft the features, but the notion of similarity is still fixed — by the pairs the model is trained on. Second, "choose the number of dimensions": "more dimensions capture more detail but are slower and heavier; fewer dimensions are faster but less precise".

Then "build an encoder model that converts the data into vectors (e.g., BERT for text, CNN/ViT for images)" and "Train the encoder using metric learning so that similar items are clustered together, and dissimilar ones are separated" (p. 1690). The machinery comes from deep learning: a contrastive or triplet loss pulls an anchor towards its positive example and pushes it away from the negatives, and the vector is the network's last hidden representation, without the classification head.

The pipeline that uses them has three steps (p. 1691, Fig. 2): convert the data into embeddings, "Store embeddings in a vector database for fast retrieval", then apply them to "semantic search, retrieval-augmented generation (RAG), classification, clustering, and recommendations". A query must be embedded by the same model as the stored data; otherwise the two sets of coordinates mean different things.

1 · embed 2 · store 3 · search often a black box text image audio embedding model BERT, CNN, ViT or CLIP vectors vector database query 3 nearest
Fig. 2. The three steps of p. 1691, with the several media of p. 650. An embedding model turns each item into a vector (all-MiniLM-L6-v2 gives 384 numbers); the database stores the vectors; a query, embedded by the same model, gets back its nearest stored vectors — here the 3 circled ones.

Looking at the space

Page 651 embeds sentences such as 'Sunsets are breathtaking' "using models like paraphrase-MiniLM" and gives the process: "Take a sentence", "Transform it into (numerical) vector representation", "Possibly reduce dimensionality if needed / wanted". Page 652 does it to ten sentences — "sentences to 32 dimensions vectors, and then PCA to top 2 principal components" — and the plot (Fig. 3) puts "Colds are unpleasant.", "Mosquitoes are annoying." and "Traffic jams are frustrating." on the left, "Laughter brings joy.", "Music is uplifting." and "Success is rewarding." on the right.

PC 2 ↑ PC 1 → sunsets rainy days colds mosquitoes traffic jams kindness failure laughter music success negative positive
Fig. 3. The PCA plot of p. 652, redrawn: positions read off the slide's chart, labels shortened. Negative sentences lie to the left on PC 1, positive ones to the right; "Kindness is contagious." sits in the middle. "Colds" and "mosquitoes" almost coincide: the text below explains why that proves less than it seems.

This is PCA, from statistics: the two directions of largest variance. It is also only a projection. "Colds are unpleasant." and "Mosquitoes are annoying." almost overlap in the plot, yet against "Rainy days make me sad" they score 0.419 and 0.296, because the scores are computed in all the dimensions. The session's Visualization notebook draws the same kind of plot for three different models.

How close is close: three ways to compare vectors

"Metrics like Cosine and Euclidean distances are used to measure the similarity between two vectors" (p. 654). The two squares on that page show the difference. The Euclidean distance is the length of the segment between two points; the cosine looks only at the angle between the two arrows drawn from the origin.

\[ \text{similarity}(A,B) = \cos\theta = \frac{A\cdot B}{\norm{A}\,\norm{B}} \] \[ = \frac{\sum_{i=1}^{n} A_i B_i}{\sqrt{\sum_{i=1}^{n} A_i^2 \, \sum_{i=1}^{n} B_i^2}} \] \[ d(A,B) = \norm{A - B} \] \[ = \sqrt{\textstyle\sum_{i=1}^{n} (A_i - B_i)^2} \]
\(A, B\)
two non-zero vectors with \(n\) components \(A_i\), \(B_i\)
\(A\cdot B\)
the inner (dot) product, \(\sum_i A_i B_i\)
\(\norm{A}\)
the length of \(A\), \(\sqrt{\sum_i A_i^2}\)
\(\theta\)
the angle between the two vectors
\(d(A,B)\)
the Euclidean distance between their tips

The cosine depends on the angle only (p. 655, p. 1696; Fig. 4): close to 1 when the two arrows point the same way, 0 when they are orthogonal, −1 when they are opposite. The inner product is the numerator alone, so it grows with the lengths as well.

A B θ A B θ A B θ cos θ ≈ 1 cos θ = 0 cos θ = −1 similar orthogonal opposite
Fig. 4. The three cases of p. 655 and p. 1696. Only the angle counts: stretching A or B changes neither \(\theta\) nor its cosine.

The session's Comparison notebook works it out on two arrows, \(A = (2, 1)\) and \(B = (0, 2)\):

\[ A\cdot B = 2\cdot 0 + 1\cdot 2 = 2 \] \[ \norm{A} = \sqrt{2^2 + 1^2} = \sqrt{5} \approx 2.236 \] \[ \norm{B} = \sqrt{0^2 + 2^2} = 2 \] \[ \cos\theta = \frac{2}{2.236 \cdot 2} \approx 0.447 \] \[ \theta = \arccos 0.447 \approx 63.4^\circ \]

Positive and well below 1: the arrows are 63° apart, nearer to orthogonal than to identical. The other two measures of the same pair are the inner product, 2, and the Euclidean distance between the tips:

\[ \norm{A - B} = \sqrt{(2-0)^2 + (1-2)^2} \] \[ = \sqrt{5} \approx 2.236 \]

Three numbers, three different scales. The notebook's code, without its plotting cell:

import numpy as np

vec1 = np.array([2, 1])
vec2 = np.array([0, 2])

def cosine_similarity(vec1, vec2):
    dot_product = np.dot(vec1, vec2)
    norm_vec1 = np.linalg.norm(vec1)
    norm_vec2 = np.linalg.norm(vec2)
    similarity = dot_product / (norm_vec1 * norm_vec2)
    return similarity

similarity_score = cosine_similarity(vec1, vec2)
print(f"Cosine Similarity between vec1 and vec2: {similarity_score:.4f}")
# prints: Cosine Similarity between vec1 and vec2: 0.4472

Now read the table of page 656 with these tools. Its column holds the cosine similarity of "Rainy days make me sad" with each stored sentence, already sorted: 0.764286 for "Rainy days can be gloomy.", an angle of 40°; 0.418937 for "Colds are unpleasant.", 65°; down to 0.033636 for "Success is rewarding.", 88° — almost orthogonal. The stored vectors are not of length 1 (one coordinate printed in the table is 1.2116411), and the cosine does not care: it divides the lengths away.

Why cosine — and when the metrics disagree

The slide's reason: "Cosine similarity is commonly used due to its efficiency" (p. 654). On vectors of length 1 it is indeed a single inner product. Two further reasons are given outside the slides: the cosine ignores length, so a long text and a short one on the same topic still point the same way; and the metric should match the one the embedding model was trained with.

Ignoring length matters, because the metrics can disagree. Take \(a = (1, 1)\), \(b = (2, 2)\), \(c = (1, 0)\). By cosine, the nearest neighbour of \(a\) is \(b\): same direction, cosine 1. By Euclidean distance it is \(c\), 1 away, while \(b\) is 1.414 away. Normalise the three vectors to length 1 and the disagreement disappears, for a reason worth deriving once:

  1. Write the squared distance as the inner product of the difference with itself:

    \[ \norm{A - B}^2 = (A - B)\cdot(A - B) \]
  2. Expand it like \((a - b)^2\); the inner product distributes and \(A\cdot B = B\cdot A\):

    \[ \norm{A - B}^2 = \norm{A}^2 + \norm{B}^2 - 2\,A\cdot B \]
  3. Normalise: divide every vector by its length, so that \(\norm{A} = \norm{B} = 1\):

    \[ \norm{A - B}^2 = 2 - 2\,A\cdot B \]
  4. With lengths 1 the denominator of the cosine is 1, so the inner product is the cosine:

    \[ A\cdot B = \cos\theta \] \[ \norm{A - B} = \sqrt{2 - 2\cos\theta} \]
  5. Read it: the distance shrinks exactly when the cosine grows. Sorting by smallest Euclidean distance, by largest inner product or by largest cosine gives the same list. For the best match of page 656 the unit-length distance would be \(\sqrt{2 - 2 \cdot 0.764} \approx 0.687\), still first.

That is why FAISS can run a cosine search with an inner-product index on normalised vectors, and why an inner-product search — the "metric_type": "IP" printed in the Zilliz call of the exam of 11 Feb 2025 (lesson 28) — ranks exactly like a cosine search once the vectors have length 1. Try the three metrics on one query:

Three metrics, one query: who are the nearest neighbours?

Each dot is a review of one restaurant as a toy 2-D vector: its direction says what the review is about (food, service, prices, the room), and its length varies, as the lengths of raw embeddings do. The positions are made up for this widget, not computed by a model. Drag the query, choose the metric and k; the shaded region holds exactly the k nearest, and hovering a dot shows its text. The one thing to see: the metrics disagree while the lengths differ, and agree once every vector has length 1.

  1. Start with cosine similarity and k = 3. Before switching to Euclidean distance, predict: will the same three reviews win?
    What you should see

    Only "The pasta was perfect." stays. Cosine picked the three closest in direction, "Fresh fish, great sauce." (2.5°), "The pasta was perfect." (3.0°) and "Best tiramisu in Milan." (5.0°), whatever their length. Euclidean distance wants tips near the query's tip: "The pasta was perfect." (0.16), "Risotto a bit salty." (0.25), "Gnocchi melt in the mouth." (0.38). The cone has become a disc.

  2. Switch to inner product. Which kind of vector do you expect at the top?
    What you should see

    Long vectors pointing roughly the right way: "Pizza crust was burnt." (2.38), "Best tiramisu in Milan." (2.19) — and "We waited an hour." (2.00), a review about the service, 38° away, which gets in because it is the longest vector of all. The inner product rewards length; its region is a half-plane.

  3. Turn on "normalise every vector to length 1". What happens to the three rankings?
    What you should see

    They coincide: "Fresh fish, great sauce.", "The pasta was perfect.", "Best tiramisu in Milan." under all three metrics, and the readout says yes. Every dot now sits on the unit circle, where the disc, the cone and the half-plane cut the same arc: \(\norm{A-B}^2 = 2 - 2\cos\theta\) and \(A\cdot B = \cos\theta\).

  4. Normalisation off. Drag the query outward along its own direction until it is about twice as long. Which metric changes its neighbours?
    What you should see

    Only Euclidean distance, which now prefers the long vectors: at exactly twice the start, (2.24, 0.56), it lists "Best tiramisu in Milan.", "Pizza crust was burnt." and "Fresh fish, great sauce.". The cosine looks at the angle only, and the inner product multiplies every score by the same factor, so its order stays.

Exact search, and why it stops working

Finding the k stored vectors most similar to a query is k-nearest-neighbour search. Without an index it means "computing the distance from the query to each point in the database" (p. 658): \(n\) vectors of \(d\) numbers cost \(n \cdot d\) multiplications per query. A million 384-dimensional vectors make \(3.84 \times 10^8\) of them, for every single query. Hence: "Let’s create indexes with tree-based exact vector search techniques" (p. 658).

K-D trees: cut the space in halves

"k-d trees are a special case of binary space partitioning trees": "a binary tree in which every node is a k-dimensional point. Every non-leaf node implicitly generates splitting hyperplane that divides the space into two parts. The split is on the median value" (p. 659). Each cut is perpendicular to one axis, and the axis changes with the depth: the cube of page 659 is cut first by a red plane on the first coordinate, then by green planes on the second, then by blue ones on the third. Cutting at the median puts half of the points on each side, so the tree stays balanced.

Page 660 grows such a tree over records with three attributes — Name, Age, Salary — and cycles through them level by level. Its inner nodes keep only the split value and the records sit in the leaves, a common variant of the definition. Follow a query record down it, Ivan, 61, $55k (Fig. 5): four comparisons reach one leaf out of twenty.

query: Ivan, 61, $55k Name: Kevin Age: 60 Salary: $80k Name: Charlie David · 68 · $40k Age: 47 Salary: $50k Name: Alice Age: 73 +9 records +5 records +3 records +2 records Ivan < Kevin 61 ≥ 60 55k < 80k Ivan > Charlie
Fig. 5. The K-D tree of p. 660, searched for Ivan, 61, $55k (the query is ours). Name at the root, then Age, then Salary, then Name again: each level cuts on the next attribute. Four comparisons reach David's leaf; the dashed branches, 19 of the 20 records, are never opened.

For a nearest-neighbour query the leaf is only a first candidate. The search then climbs back and looks into a sibling subtree whenever its splitting plane is closer to the query than the best candidate so far: the true neighbour may lie just across a cut.

Page 661 gives the cost together with its condition: "Complexity of search (and other operations): O(log n)", under the hypothesis \(n \gg 2^k\), where \(n\) is the number of vectors and \(k\) their dimensionality. Careful: on this slide \(k\) is the number of dimensions (the \(d\) above), not the \(k\) of k-nearest neighbours. The O(log n) is the cost to expect when the hypothesis holds; in the worst case the search visits every leaf. The tree needs many points in every cell for its cuts to rule anything out. Table 1 shows what the condition asks for.

Table 1. The hypothesis of p. 661, \(n \gg 2^k\), for a few dimensionalities \(k\). 384 is the size of the all-MiniLM-L6-v2 vectors of the session's notebook.
dimensions \(k\)\(2^k\)enough vectors?
24easily
101 024easily
20\(1.05 \times 10^{6}\)needs many millions
32\(4.3 \times 10^{9}\)hardly ever
128\(3.4 \times 10^{38}\)never
384\(3.9 \times 10^{115}\)never

No collection will ever hold \(10^{115}\) vectors. For embeddings the last line of the slide applies: "With high-dimensional data, most of the points in the tree will be evaluated and the efficiency is no better than exhaustive search" (p. 661).

R-trees: boxes inside boxes

The R stands for rectangle. The idea of page 662 is "to group nearby objects and represent them with their minimum bounding rectangle in the next higher level of the tree", which gives the pruning rule: "a query that does not intersect the bounding rectangle also cannot intersect any of the contained objects". At the leaves each rectangle is one object; higher up, each covers more of them — "an increasingly coarse approximation of the data set" (Fig. 6).

R1 R2 R3 R4 R5 R6 Q R1 R2 R3 R4 R5 R6 skipped
Fig. 6. A two-level R-tree drawn for this lesson (p. 663 shows a larger one, with up to three entries per page). The query Q intersects R1 and, inside it, only R3: R2 and R4 are skipped with everything they contain — the rule of p. 662.

An R-tree is balanced and "Organizes data in pages"; each page has a maximum and a minimum filling, and the slide reports the "Best performance with min filling around 40%" (p. 663). Building a good one is the hard part: it must stay balanced, its rectangles must not "cover too much empty space", and they must "not overlap too much (so that during search, fewer subtrees need to be processed)". The M-tree variant uses "circles / spheres" instead of rectangles.

Both trees hit the same wall: "For high-dimensional data, also tree-based exact vector search techniques such as the k-d tree and R-tree do not perform well enough because of the curse of dimensionality". For the R-tree the wall looks like this: in hundreds of dimensions a box around even a few points spans most of the range of most coordinates, so the boxes overlap and almost every query intersects almost every box. So "Vector databases use Approximate Nearest Neighbor algorithms" (p. 665).

Exact trees prune by geometry; in hundreds of dimensions almost nothing can be pruned, and the search falls back to a scan.

Approximate search: hashes and graphs

An approximate nearest neighbour (ANN) search returns vectors that are very probably the nearest. HNSW, page 670 says, "sacrifices a small amount of accuracy to achieve much faster search times". The distances HNSW computes are exact; what it may do is miss a vector it never looked at (IVFPQ, at the end of the lesson, adds a second error: distances estimated from compressed codes). The usual measure is recall, the share of the true nearest neighbours that the search returns. Page 666 lists four such algorithms: LSH, HNSW, IVFADC and IVFPQ — the last two are in fact one method (section 6).

LSH: a hash that wants collisions

"locality-sensitive hashing (LSH) is a fuzzy hashing technique that hashes similar input items into the same ‘buckets’ with high probability"; "The number of buckets is much smaller than the universe of possible input items"; and the key sentence: "It differs from conventional hashing techniques in that hash collisions are maximized, not minimized" (p. 667). Compare the CRC16 hash of Redis Cluster (lesson 19), built to spread keys evenly over the slots. A query is hashed, and only the items of its bucket are compared exactly. Since a long vector becomes a short bucket code, "LSH can be seen as a way to reduce the dimensionality of high-dimensional data".

One classic family for the cosine makes "with high probability" precise (background, not on the slides): draw a random hyperplane through the origin and record on which side a vector falls — one bit. A few such cuts give every vector a short code, and the code is its bucket (Fig. 7).

q a b c 100 110 111 011 001 000 bucket → vectors 100 110 111 q, a b c q meets a, not b
Fig. 7. Random-hyperplane LSH with three cuts (background, not on the slides). Each dashed cut through the origin gives one bit — on which side of it a vector lies — so the plane falls into sectors with 3-bit codes: the buckets. The query q and its neighbour a share bucket 100 and are compared; b, only 34° from q, lies across a cut in bucket 110 and is never compared. That is why an LSH index keeps several tables with different cuts.

Two vectors at angle \(\theta\) get the same bit with probability \(1 - \theta / 180^\circ\). For the rainy-day pair at 40° that is 0.78; with 8 such bits as the bucket code, they share a bucket with probability \(0.78^8 \approx 0.13\), two orthogonal sentences with \(0.5^8 \approx 0.004\). One code alone would miss the good pair most of the time, so an LSH index keeps several tables with different random hyperplanes and looks into the query's bucket in each: with 20 tables the rainy-day pair meets at least once with probability 0.94, the orthogonal pair with 0.08.

HNSW: a skip list made of graphs

Hierarchical Navigable Small World is "an efficient algorithm for approximate nearest neighbor (ANN) search in high-dimensional spaces" (p. 668). Two ideas meet in its name. "Navigable Small-World Graphs" are "graphs where most nodes can be reached from any other in a small number of hops": every vector is a node, linked to vectors near it. The "Hierarchical Structure" organises the graph "into multiple layers, each containing fewer nodes than the layer below", and the lowest layer holds all the data.

Page 669 explains the search with a skip list, "looking for 11" (Fig. 8). The top layer has few nodes and long links, so one hop jumps over most of the data; when the next hop would overshoot, the search drops a layer and continues with shorter hops.

layer 3 layer 2 layer 1 layer 0 start 3 5 7 11 14 19 21 end looking for 11
Fig. 8. The skip list of p. 669, redrawn. Layer 3 (the entry layer): start → 5, then 5 → end overshoots, so drop at 5. Layer 2: 5 → 19 overshoots, drop again. Layer 1: 5 → 11, found. Highlighted boxes are the ones the search looks at; it never walks the bottom row, which holds everything.

In HNSW the layers hold vectors, and "overshoot" becomes "no linked node is closer to the query". The search starts at an entry point in the top layer and moves to whichever linked node is closest to the query; when none is closer it descends a layer from where it stands, "refining the result by looking at more nodes" (p. 669), down to layer 0, where it may keep several candidates at once (the parameter ef of the HNSW paper; background). New vectors arrive "in a probabilistic manner": "the algorithm randomly selects a level in the hierarchy where the point should be placed. It then connects the new point to its closest neighbors at that level" (p. 670) — and, the slide leaves this out, at every level below it too, down to layer 0.

An HNSW walk against brute force

N points of a 2-D cloud, indexed by HNSW as in the paper: up to 4 links per node in the upper layers and 8 in layer 0, the level of each point drawn at random. The plot shows the graph of the layer the search is in; the rest of the data are small grey dots. Press Search to watch the walk hop by hop, from the entry point down to layer 0; a ring marks every vector whose distance to the query has been computed. Pick a preset query or click the plot to place your own. The one thing to see: the walk computes a few dozen distances where brute force computes N — and once in a while it stops at a near neighbour that is not the nearest.

  1. Query A, N = 400, ef = 1. Before pressing Search: how many of the 400 distances will the walk need? Then watch where it starts and how long its hops are.
    What you should see

    32. It starts at the only node of the top layer (layer 5, the entry point), makes long hops while the layers are sparse and short ones in layer 0, and ends on the true nearest neighbour. Brute force would compute 400 distances.

  2. Watch the line "200 test queries" while you move N from 100 to 1000. Brute force grows tenfold; how much does the walk grow?
    What you should see

    From 20.1 to 33.1 distances per query on average (ef = 1), finding the true nearest 95.5 % and 96.5 % of the time. Ten times the data costs about 13 more distances, not ten times as many: the slow, roughly logarithmic growth the layers are there for (the averages wobble a little from one N to the next, because each N builds a new random graph).

  3. Query B with N = 400 and ef = 1: does the walk end at the true nearest neighbour? Then set ef = 2 and search again.
    What you should see

    No. After 27 distances it stops at the 4th nearest vector, because no node linked to it is closer to the query: a local minimum of a greedy walk. With ef = 2, layer 0 keeps two candidates at a time and the walk finds the true nearest with 38 distances. Over the 200 test queries, ef = 1, 2, 4 find it 96.5 %, 98.5 % and 100 % of the time, at 30.3, 31.9 and 35.2 distances.

Page 671 lists four advantages: "Scalability", up to "millions or even billions of vectors"; "Speed", because the hierarchy "significantly reduces the number of comparisons needed to find a similar vector"; "Adaptability", "dynamic insertion and deletion of data points"; and "Memory Efficiency" — the one that does not hold.

Yu. A. Malkov and D. A. Yashunin posted HNSW in March 2016 from the Institute of Applied Physics of the Russian Academy of Sciences, in Nizhny Novgorod. Its "small world" goes back to Stanley Milgram's experiment of the 1960s, in which people in Nebraska tried to pass a letter to a stranger in Massachusetts through acquaintances, and to Jon Kleinberg's note of 2000 in Nature, which showed when such greedy forwarding with only local knowledge finds the short chains. That is what the walk does, among vectors.

Cells and codes: IVFPQ

The Inverted File with Product Quantization "Combines coarse quantization (to partition the vector space) and product quantization (to compress vectors)" (p. 672). Two independent ideas: the first reduces how many vectors a query meets, the second how much each of them costs to store and to compare.

Coarse quantization: cells and an inverted file

"The entire dataset is divided into clusters using a clustering algorithm like k-means. Each vector is assigned to its nearest cluster center (called a coarse centroid)", and the centroids become the "bins" of the space (p. 673). It is k-means clustering. Around each centroid lies the region of points nearer to it than to any other centroid, a Voronoi cell — the polygons drawn on page 673. For each centroid the index keeps the list of its vectors: an inverted file, like the term-to-documents lists of an inverted index (Fig. 9). "During a search, instead of searching the entire dataset, the algorithm only searches within the relevant clusters".

4 9 12 1 6 15 2 7 21 c₁ c₂ c₃ inverted file c₁ c₂ c₃ 4 · 9 · 12 1 · 6 · 15 2 · 7 · 21 one list is read
Fig. 9. The inverted file of p. 673 on a toy plane. k-means gives three cells, each with its coarse centroid c₁, c₂, c₃ (diamonds); the index keeps, for every centroid, the list of the vectors in its cell — centroid → vectors, as an inverted index keeps term → documents. The query (cross) is compared with the three centroids and reads only the list of the nearest one, c₂. Its true nearest neighbour, 21, lies just across the border, in the list of c₃, and is missed.

The lower drawing of page 673 shades the "Filtered Cell" and puts the "Query Vector" right on its border. That is the risk: the query's true nearest neighbour may lie in the next cell, which is never opened. Searching more cells — nprobe of them, in FAISS's vocabulary — buys back the recall, at the price of more distances.

IVF: open nprobe cells out of sixteen

The same 400 points as the HNSW widget at N = 400, clustered by k-means into 16 cells; the diamonds are the coarse centroids. A search compares the query with the 16 centroids, opens the nprobe cells whose centroids are nearest (shaded), and compares the query with every vector inside them; the vectors of the other cells stay grey. Pick a preset query or click the plot to place your own. The one thing to see: more cells probed means more recall and more distances — and probing every cell is brute force with extra work.

  1. Query A with nprobe = 1: how many distances, and is the result the true nearest neighbour?
    What you should see

    52 = 16 centroids + the 36 vectors of the query's cell, and yes: the true nearest neighbour lies inside that cell.

  2. Query B lies near the border of its cell. Predict the result with nprobe = 1, then raise nprobe to 2.
    What you should see

    With one cell it returns the 2nd nearest vector after 32 distances (16 + 16): the true nearest sits across the border, in a cell that was never opened — the "Query Vector" on the edge of the "Filtered Cell" of p. 673. With nprobe = 2 the neighbouring cell is opened too, and the true nearest is found with 49 distances.

  3. Set nprobe = 16 and compare the count with brute force.
    What you should see

    416: all 400 vectors plus the 16 centroids. The recall is perfect and the index costs more than no index. Over the 200 test queries, nprobe = 1, 2, 3 find the true nearest 92.5 %, 100 % and 100 % of the time, at 43.0, 68.5 and 90.7 distances: in this 2-D toy, one extra cell is enough.

Product quantization: four bytes instead of 512

"Product quantization works by dividing each vector into smaller sub-vectors and then quantizing each sub-vector separately" (p. 674). Each sub-space gets its own codebook, a small set of centroids learned by k-means, and each sub-vector is replaced by the number of its nearest centroid. With 256 centroids per codebook that number fits in one byte (the usual choice; the slide says only "with fewer bits"). Fig. 10 does it to a 128-dimensional vector of 4-byte numbers.

one vector: 128 numbers × 4 bytes = 512 bytes 32 numbers 32 numbers 32 numbers 32 numbers u₁ u₂ u₃ u₄ 17 203 5 88 c₁ c₂ c₃ c₄ code: 4 bytes, 128 times smaller decoded: close to the vector, not equal
Fig. 10. Product quantization as drawn on p. 674, with numbers. A 128-dimensional vector of 4-byte numbers (512 bytes) is cut into 4 sub-vectors of 32 numbers, u₁ … u₄; each becomes the number of its nearest centroid in its own codebook of 256, c₁ … c₄ (the four numbers are made up for the drawing).

Four bytes of 256 values each give \(256^4 \approx 4.3 \times 10^9\) different codes from only \(4 \times 256 = 1024\) stored sub-centroids: that Cartesian product is the "product" in the name. The price is the slide's NOTE: "the reconstructed vector is not identical to the original vector" (p. 674), so distances computed on codes are estimates.

The search runs in two steps (p. 675). "Coarse Search": the algorithm "first identifies the relevant clusters (coarse centroids) where similar vectors are likely to be found". "Refined Search": inside them, "product quantization is used to compare the query vector with the compressed representations of the vectors in those clusters". Two sources of error follow — a neighbour in an unopened cell, a distance misjudged through its code — so IVFPQ "may not find the exact nearest neighbors". Its advantages (p. 676) mirror the two ideas: "Memory Efficiency" from the codes, "Fast Search" from the cells, "Scalability", and "Customizable Accuracy" through parameters "like the number of clusters or the precision of quantization".

Table 2. The search methods of this lesson side by side (pp. 658–676; the memory remarks are background).
methodexact?how it organises the vectorswhat it pays
brute forceyesnot at all: the query meets every vector\(n \cdot d\) operations per query
K-D treeyesbinary tree of axis-aligned median cutsfast only while \(n \gg 2^k\)
R-treeyesnested minimum bounding rectangles, balanced pagesoverlap and empty space; fails in high dimension
LSHnobuckets of a hash that makes similar vectors collidemisses neighbours hashed elsewhere
HNSWnolayers of navigable small-world graphsmemory for the links; may stop short of the nearest
IVFPQnok-means cells plus product-quantized codesmisses in unopened cells; estimated distances

The rainy day, solved

Back to page 656. "Colds are unpleasant." came second because an encoder trained, as page 1690 asks, to cluster similar items placed it 65° from "Rainy days make me sad", while an unrelated sentence such as "Success is rewarding." sits at 88°. The inverted index never had a chance: the two sentences share no word. And a real vector database would not compare the query with every stored sentence: an HNSW walk or a few IVF cells meet a few dozen candidates — 32 instead of 400 in the widget. The price is the word "approximate": now and then the true best match lies just outside what the search looked at.

Exam-style questions

Theory questions in the forms the readme of the past papers names — multiple choice, true or false, open answers; wrong answers may carry a penalty, announced just before the exam — and one practical item in the style of the past papers' "Which DB technology" table.

Which of the following statements are true? Select all that apply.

  • Cosine similarity always takes values between 0 and 1. False: it ranges from −1 (opposite vectors) through 0 (orthogonal) to 1 (same direction) (p. 655). Only vectors without negative components stay within [0, 1].
  • Locality-sensitive hashing differs from conventional hashing in that hash collisions are maximized, not minimized. True, almost verbatim from p. 667: similar items should land in the same bucket.
  • A K-D tree answers nearest-neighbour queries in O(log n) whatever the dimensionality of the vectors. False: only under the hypothesis \(n \gg 2^k\) (p. 661); with high-dimensional data "the efficiency is no better than exhaustive search".
  • If all vectors are normalised to unit length, ranking by Euclidean distance gives the same order as ranking by cosine similarity. True: on unit vectors \(\norm{A-B}^2 = 2 - 2\cos\theta\), a decreasing function of the cosine.
  • HNSW guarantees to return the exact nearest neighbour. False: "HNSW does not guarantee an exact nearest neighbor search" (p. 670); a greedy walk can stop at a local minimum.
  • In IVFPQ, the vector reconstructed from its product-quantization code is identical to the original vector. False: "the reconstructed vector is not identical to the original vector" (p. 674); quantization is lossy.
Show model answer

True: the second and the fourth. Cosine similarity ranges from −1 to 1; the O(log n) of a K-D tree holds only when n ≫ 2^k; HNSW and IVFPQ are approximate methods, and product quantization loses information.

With a possible penalty for wrong answers, tick only what a slide sentence supports. Each false statement here bends one: a wrong range, a dropped condition ("whatever the dimensionality"), or "approximate" turned into "exact".

Compute the cosine similarity between the vectors \(A = (2, 1)\) and \(B = (0, 2)\), to three decimals. Are the two vectors similar, orthogonal or opposite?

Show model answer

\(A\cdot B = 2\cdot 0 + 1\cdot 2 = 2\); \(\norm{A} = \sqrt{5} \approx 2.236\); \(\norm{B} = 2\).

\[ \cos\theta = \frac{A\cdot B}{\norm{A}\,\norm{B}} = \frac{2}{2.236 \cdot 2} \approx 0.447 \]

The angle is about 63°: the vectors are moderately similar. The cosine is positive, so they are not opposite (cosine −1), and it is clearly above 0, so they are not orthogonal, but it is well below 1, the value of vectors pointing the same way.

The vectors of the session's Comparison notebook, which prints 0.4472. The usual slips: forgetting a square root in a norm, or dividing by the sum of the lengths instead of their product.

Explain what an embedding is and why a vector database can retrieve documents that a keyword search based on an inverted index would miss. Use the query "Rainy days make me sad" as an example.

Show model answer

Vectorization or embedding is the process of converting data, such as text or images, into dense numerical vectors that capture their semantics, using an embedding model: an encoder (BERT for text, CNNs or ViTs for images, CLIP for both) trained with metric learning so that similar items are close together and dissimilar ones far apart. A vector database stores these high-dimensional vectors; a query is embedded with the same model, and the database returns the stored vectors most similar to it, typically by cosine similarity, which compares the directions of the vectors. An inverted index only finds documents that contain the query's terms. "Colds are unpleasant." shares no word with "Rainy days make me sad", yet in the lecture's example its cosine similarity with the query is 0.419, the second best after "Rainy days can be gloomy." (0.764), because both sentences express discomfort. Vector search is contextual search that goes beyond keyword matching.

A complete answer has four parts: the definition, the role of the model (it creates the meaning; the database stores and compares), the similarity measure, and the contrast with keyword matching — with an example that has no shared word.

Why do vector databases not rely on K-D trees or R-trees to find the nearest neighbours of embedding vectors? What do they use instead, and at what price?

Show model answer

A K-D tree is a binary tree whose nodes split the space with axis-aligned hyperplanes at the median, cycling through the dimensions. Its search costs O(log n) only under the hypothesis n ≫ 2^k, where k is the dimensionality; embeddings have hundreds of dimensions (2^384 ≈ 3.9 × 10^115), so the hypothesis never holds, and a nearest-neighbour search must examine most of the tree: "the efficiency is no better than exhaustive search". An R-tree groups nearby objects into minimum bounding rectangles and skips every subtree whose rectangle the query does not intersect; in high dimension the rectangles overlap and cover much empty space, so little can be skipped. This is the curse of dimensionality. Vector databases therefore use Approximate Nearest Neighbor algorithms — LSH, HNSW, IVFPQ — which are much faster but may miss the true nearest neighbour: they sacrifice a small amount of accuracy for speed.

A complete answer names the hypothesis \(n \gg 2^k\) with its meaning, the phrase "curse of dimensionality", and the trade-off of ANN. Naming the R-tree's pruning rule shows you know what stops working.

Compare HNSW and IVFPQ: how each organizes the vectors, how a query is answered, and what each trades for speed.

Show model answer

HNSW builds a navigable small-world graph in which every vector is a node linked to its near neighbours, organized in layers, each with fewer nodes than the layer below; layer 0 contains all the vectors. The top layer of a new vector is chosen at random, and the vector is linked to its closest neighbours in that layer and in the layers below. A query starts at the entry point in the top layer, moves greedily to the linked node closest to the query, and descends a layer whenever no neighbour is closer, until layer 0. It needs few comparisons but stores the full vectors plus the links, and the result is approximate. IVFPQ first partitions the space with k-means (coarse quantization): every vector is filed in the inverted list of its nearest coarse centroid. Product quantization then compresses each vector by cutting it into sub-vectors and replacing each with the id of its nearest centroid in a small codebook. A query first finds the nearest centroids and searches only those clusters (coarse search), then compares itself with the compressed codes inside them (refined search). It is memory-efficient and fast but approximate twice, since the true neighbour may sit in a cluster that was not searched and distances computed on codes are estimates; its accuracy is tuned through the number of clusters searched and the precision of the quantization.

A good answer keeps the two indexes in parallel — structure, search, trade-off. It also avoids two slide slips: the upper HNSW layers are random, not "representative points", and HNSW is fast, not small.

An exercise in the style of the past papers' "Which DB technology" table. Consider the ER model of a system tracking Formula 1 races (the scenario of the Exam of 11 Feb 2025): Person (Personal_ID, Name, Surname, Birth_Date, Gender) with the subtypes Spectator (Favourite_Team), Team Member and Team Owner (Total_Assets, Biography (Textual)), an ISA hierarchy (T, E); Team Member has the subtypes Engineer and Mechanic (Role_Description (Textual)) and Pilot (Championships_Won, Career (Textual)), an ISA hierarchy (P, E); Team, Race, Track and Ticket complete the model.

The organizers want a "find similar pilots" feature: given a free-text description such as "a young driver who wins on wet tracks", return the pilots whose career is most similar in meaning, even when it uses different words. Which DB technology would be the best to store the entities and attributes involved? Fill in one row of the table ENTITIES / REL. · DB TYPE · MOTIVATION, and say what is stored for each pilot.

Show model answer
ENTITIES / REL.DB TYPEMOTIVATION
Pilot (Career), and in the same way Team Owner (Biography), Engineer and Mechanic (Role_Description)Vector database (e.g. Milvus / Zilliz)The textual attributes are converted by an embedding model into dense vectors that capture their meaning; the database indexes them (e.g. with HNSW) and returns the k vectors most similar to the embedded description, by cosine similarity: semantic search beyond keyword matching.

For each pilot: Personal_ID, Name and Championships_Won as scalar fields (to identify and filter the results), the Career text or a reference to it, and the embedding of the Career (for example 384 numbers with all-MiniLM-L6-v2). The description in the query is embedded with the same model.

The exercise is ours; the table itself is worth 1 point on the papers of 6 Jun 2024, 24 Jun 2024, 6 Sep 2024, 21 Jan 2025 and 13 Jun 2025 (the last one prints "Jan. 21, 2025" in its header). A good row names one DB family and gives a motivation tied to the data or to the query. For text the real choice is keyword against meaning: a condition such as "the career includes the words Champion and Car", the Elasticsearch exercise of the same 11 Feb 2025 paper, belongs to a search engine; "similar in meaning" belongs to a vector database.

Check yourself

A query \(q = (1, 0)\) and two stored vectors: \(a = (2, 0)\), twice as long as \(q\) in the same direction, and \(b = (0.87, 0.5)\), of length 1 and 30° away. Which metric ranks \(b\) above \(a\)?

  • Cosine similarity The cosine of \(a\) is 1 (same direction), that of \(b\) is 0.87: \(a\) wins.
  • Euclidean distance \(\norm{q - a} = 1\) but \(\norm{q - b} \approx 0.52\): the tip of \(b\) is nearer.
  • Inner product \(q\cdot a = 2\) and \(q\cdot b = 0.87\): the longer vector wins.
  • All three Cosine and inner product prefer \(a\); only the Euclidean distance looks at where the tips are.

All the vectors of a collection are normalised to length 1. Which statements are now true? Select all that apply.

  • The inner product of two vectors equals their cosine similarity The denominator \(\norm{A}\,\norm{B}\) is 1.
  • The Euclidean distance equals \(1 - \cos\theta\) It is \(\sqrt{2 - 2\cos\theta}\); \(1 - \cos\theta\) is the cosine distance, a different number with the same order.
  • Sorting by Euclidean distance gives the same order as sorting by cosine similarity The distance is a decreasing function of the cosine.
  • Every pair of vectors now has cosine similarity 1 Normalising changes lengths, not directions: the angles, and their cosines, stay as they were.

This is why a cosine search can run on an inner-product index: normalise first.

A K-D tree is built over \(10^9\) embedding vectors of 384 dimensions. What does the hypothesis of page 661 tell you?

  • A search takes about \(\log_2 10^9 \approx 30\) steps That is the cost under the hypothesis, which does not hold here.
  • Choosing the median more carefully would make the tree efficient The median already balances the tree; the problem is the number of dimensions, not the cuts.
  • K-D trees work only up to 3 dimensions They work for any \(k\) as long as \(n \gg 2^k\): for \(k = 10\), a few hundred thousand points suffice.
  • \(n \gg 2^k\) fails by far, so the search degrades towards evaluating most points \(2^{384} \approx 3.9 \times 10^{115}\), against \(10^9\) vectors: "no better than exhaustive search".

Which nodes make up the top layer of an HNSW index?

  • The most representative points, chosen as hubs That is the wording of p. 668, corrected in the red pen: nobody chooses them.
  • The centroids of a k-means clustering Centroids belong to IVF; HNSW layers contain actual vectors.
  • The vectors whose randomly drawn level reached that layer "the algorithm randomly selects a level in the hierarchy" (p. 670): the upper layers are a thinning random sample.
  • The first vectors inserted The entry point changes whenever a new vector draws a higher level than the current top.

An IVF index is searched with nprobe = 1, and the query lies close to the border of its cell. What can happen?

  • The true nearest neighbour lies in the next cell, is never compared, and a farther vector is returned Query B of the widget: the 2nd nearest after 32 distances; nprobe = 2 finds the true one.
  • The search fails with an error It always returns something: the nearest among the vectors it compared.
  • The index opens all the cells automatically It opens exactly nprobe cells; raising nprobe is your decision.
  • Nothing can go wrong: a k-means cell always contains its query's nearest neighbour Cells are built from the stored vectors, not around queries; near a border the nearest neighbour may be on the other side.

A 128-dimensional vector of 4-byte numbers is product-quantized into 4 sub-vectors, each encoded with a codebook of 256 centroids. How much does its code take, and what is lost?

  • 4 bytes; nothing is lost The size is right, but "the reconstructed vector is not identical to the original vector" (p. 674).
  • 4 bytes; the decoded vector only approximates the original One byte per sub-vector (256 values), against 512 bytes: 128 times smaller, and lossy.
  • 128 bytes; nothing is lost 128 is the number of dimensions; each sub-vector shrinks to one byte.
  • 32 bytes; the order of the sub-vectors 32 is the length of a sub-vector; the order of the codes is kept.

The CRC16 of Redis Cluster spreads keys over 16384 slots. A locality-sensitive hash, by contrast, …

  • spreads similar items over different buckets, to balance the load That is what a conventional hash does; LSH does the opposite on purpose.
  • maps every item into its own bucket "The number of buckets is much smaller than the universe of possible input items" (p. 667).
  • is a cryptographic hash, so that two items never share a bucket Cryptographic hashes avoid collisions; LSH wants them for similar items.
  • puts similar items into the same bucket with high probability: its collisions are wanted "hash collisions are maximized, not minimized" (p. 667); a query then compares only the items of its bucket.

Key take-aways

What to watch and read