retrieval · Level 3
Embeddings and vector search from first principles
What an embedding is, how closeness is measured and how nearest-neighbour search works, built and measured in Python with vectors you compute yourself.

Your next step
Start this course online
Register your start, then work through the lessons and exercises at your own pace. You can download the PDF separately. Where a scored exam is available, you will find it after the lessons.
Register or resume your course
Start with the essentials
The short answer
An embedding is a list of numbers standing for a piece of text, made so that texts with similar meanings sit close together. Vector search turns the question into the same kind of list, scores it against the stored vectors with a measure such as cosine similarity, and returns the nearest. It fails in predictable ways, so measure it with labelled questions using recall@k and mean reciprocal rank.
What you will learn
- You will be able to explain what a vector and an embedding are, and build count and TF-IDF vectors from text.
- You will be able to compute a dot product, cosine similarity and Euclidean distance by hand, and explain when they disagree and why normalising makes them agree.
- You will have built and run an exact, brute-force nearest-neighbour search in Python.
- You will be able to score a search with recall@k and mean reciprocal rank on a labelled question set.
- You will be able to recognise the common failures and say which a neural embedding model would change.
- You will understand the recall and speed trade-off of approximate search, and why vectors of personal text need protecting.
Who it is for
Builders who have met retrieval augmented generation and want to understand what an embedding is and how vector search works, by building one. You need to run a Python script from a terminal. The maths is multiplication, square roots and fractions.
Before you start
- The RAG and knowledge bases workbook, or equivalent familiarity with chunks, embeddings and top-k retrieval. Enough Python to run a script from a terminal: the Python: from first script to a useful automation workbook covers this. Chapter 3 gives the two lines that install the packages into a virtual environment.
Learn in your browser · Chapter 01 of 06
Text as numbers
Search compares lists of numbers, not sentences, so every vector search starts by turning text into one.
A vector is an ordered list of numbers, such as (4, 3). Each position is a dimension. Chapter 3's vectors have 132 dimensions, but the arithmetic is the same as for two.
In the RAG workbook, an embedding was a vector made by a trained neural network, an embedding model, so that similar meanings land close together. This workbook downloads no model: it uses word counts, TF-IDF and a two-dimensional meaning space labelled by hand.
Bag of words. Put every word in the collection in a fixed order, the vocabulary, and count each one in a text. Word order is lost: the textbook notes that 'Mary is quicker than John' and 'John is quicker than Mary' get the same vector.
vocabulary: books fines late online renew
Late books, late fines. 1 1 2 0 0
Renew books online. 1 0 0 1 1Try it yourself · Activity 01
10 minCount vectors by hand
Make count vectors for 'Pets are not allowed.' and 'Assistance dogs are allowed.'
- List the vocabulary alphabetically, ignoring capitals and full stops.
- Write each sentence's counts in that order.
What does the vector miss about the word 'not'?
Worked answer
Vocabulary: allowed, are, assistance, dogs, not, pets. Vectors: (1, 1, 0, 0, 1, 1) and (1, 1, 1, 1, 0, 0). 'Not' is one more dimension: the vector records that it is there, not that it reverses 'allowed'.
Learn in your browser · Chapter 02 of 06
Closeness: dot product, cosine and distance
Search finds the stored vectors closest to the question's vector. Three measures are common, and they can disagree.
In our meaning space the first number means about money, the second about time. 'What is the charge for a late book?' is q = (4, 3). A (3, 4) is a short note on late returns. B (8, 6) points exactly the same way as q but is twice as long, like a longer text's count vector. C (0, 10) is the opening hours.
dot(a, b) = a1 x b1 + a2 x b2
length(a) = sqrt(a1 x a1 + a2 x a2)
cosine(a, b) = dot(a, b) / (length(a) x length(b))
distance(a, b) = length(a - b)Cosine similarity keeps only the angle. For Euclidean distance, smaller is closer. length(q) = sqrt(16 + 9) = 5.
A: dot = 3x4 + 4x3 = 24 cosine = 24 / (5 x 5) = 0.96 distance = sqrt(1 + 1) = 1.414
B: dot = 8x4 + 6x3 = 50 cosine = 50 / (10 x 5) = 1.00 distance = sqrt(16 + 9) = 5.000
C: dot = 0x4 + 10x3 = 30 cosine = 30 / (10 x 5) = 0.60 distance = sqrt(16 + 49) = 8.062Scroll sideways to see every column.
| Measure | Ranking | Rewards |
|---|---|---|
| Dot product | B, C, A | Direction and length: C beats A only by being long |
| Cosine | B, A, C | Direction only |
| Distance | A, B, C | Position: B is on topic, yet 5 away |
Normalising divides a vector by its length, giving a unit vector. For unit vectors the dot product is the cosine, as the textbook shows, and squared distance is 2 - 2 x cosine, so all three measures agree. For A: unit A = (0.6, 0.8), unit q = (0.8, 0.6), squared distance 0.04 + 0.04 = 0.08 = 2 - 2 x 0.96. Normalise stored vectors and queries alike.
Try it yourself · Activity 02
12 minRank by hand
Repeat for the question 'When do you open?' at q = (0, 2).
- Rank A, B and C by dot product, cosine and distance.
- Normalise all four vectors and rank by distance again.
Which measure put the wrong document first, and why?
Worked answer
Dot: A 8, B 12, C 20 (C, B, A). Cosine: A 0.8, B 0.6, C 1.0 (C, A, B). Distance: A sqrt(13) = 3.606, B sqrt(80) = 8.944, C 8.0 (A, C, B): A wins by being short. Unit distances: A 0.632, B 0.894, C 0.0, the cosine order.
Learn in your browser · Chapter 03 of 06
Build a brute-force search
Now search sixteen invented library help notes: one TF-IDF vector per note, every note scored, the best k returned.
Tested with Python 3.12.10 on Windows 11; these pins need Python 3.12 or later. In a new folder, run the lines below. They create a virtual environment (a private set of packages) and install pinned versions. On macOS and Linux, type python3 for python and .venv/bin/python for .venv\Scripts\python; those lines were not run.
python -m venv .venv
.venv\Scripts\python -m pip install numpy==2.5.3 scikit-learn==1.9.1TF-IDF multiplies term frequency (how often a word appears in the text) by inverse document frequency (idf), which is high for rare words. The textbook's idf is log(N / df), where df counts the documents containing the word. scikit-learn's default, below, is ln((1 + n) / (1 + df)) + 1, and each row is then scaled to length 1.
n = 16 notes
'bicycle' is in 1 note: idf = ln(17 / 2) + 1 = 3.1401
'the' is in 6 notes: idf = ln(17 / 7) + 1 = 1.8873Save this as library.py in that folder and run it with .venv\Scripts\python library.py.
# library.py: brute-force TF-IDF search over 16 invented help notes
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
DOCS = [
"Opening hours: open Monday to Saturday, 9am to 6pm. Closed on Sundays.",
"Summer opening hours: open Monday to Saturday, 9am to 5pm. Closed on Sundays.",
"Joining is free for anyone who lives, works or studies in the town.",
"Members can borrow up to 12 books at a time for three weeks.",
"You can renew books twice, online or by phone, unless they are reserved.",
"There are no fines for late books, but you cannot borrow more until they are back.",
"If you lose a book, you pay the cost of replacing it.",
"Public computers can be booked for one hour a day with your library card.",
"Free wifi covers the whole building. The network name is on the notice board.",
"Printing costs 10p per page in black and white and 50p per page in colour.",
"Children under 12 must be with an adult at all times.",
"Rhyme time for babies and toddlers runs every Wednesday at 10am.",
"Study room 7 seats six people and can be booked at the front desk.",
"There is no car park. Bicycle racks are by the side entrance.",
"A lift serves every floor, and large print books are on the ground floor.",
"Assistance dogs are welcome. Other pets are not allowed.",
]
vectorizer = TfidfVectorizer() # defaults: lowercase, unit-length rows
X = vectorizer.fit_transform(DOCS) # one row per document
def search(query, k=3):
q = vectorizer.transform([query])
scores = (X @ q.T).toarray().ravel() # dot product = cosine here
top = np.argsort(-scores, kind="stable")[:k]
return [(f"D{i + 1}", round(float(scores[i]), 2)) for i in top if scores[i] > 0]
if __name__ == "__main__":
print(X.shape)
for query in ["Can I renew a book online?", "Where can I leave my bike?"]:
print(query, search(query))(16, 132)
Can I renew a book online? [('D5', 0.42), ('D7', 0.16), ('D4', 0.09)]
Where can I leave my bike? [('D4', 0.24), ('D13', 0.22), ('D5', 0.22)]This is brute-force, or exact, nearest-neighbour search: score everything, sort, keep the top k. Its cost grows in step with the collection. kind="stable" makes ties repeatable, and zero scores (no shared word) are dropped.
Renewal works. Bike fails: D14 says 'Bicycle', shares no word with the question and scores 0, while the top three share only 'can'. A neural embedding model is trained so that synonyms like these can land close, but we ran none, so we show no score.
Try it yourself · Activity 03
10 minPredict, then search
Add 'How many books can I borrow?', 'Where can I park my car?' and 'Do you sell train tickets?' to the list at the bottom of library.py.
- Predict the top note for each, then run it.
- For any surprise, find the words shared with the top note.
Which result looks confident but is wrong?
Worked answer
Borrow: D4 first at 0.45, correct. Park: D14 first at 0.41, through 'park' and 'car'. Tickets: [('D7', 0.47), ('D5', 0.24), ('D6', 0.21)]. The library sells no tickets, yet D7 outscores the correct renewal answer (0.42): 'you' appears twice in D7 and in only three notes.
Learn in your browser · Chapter 04 of 06
Measure it: recall@k and mean reciprocal rank
Two questions prove nothing. Score the search on labelled questions, and keep that set fixed while you change things.
A labelled set pairs each question with its gold documents, the ones that answer it.
- Recall@k: the share of a question's gold documents in the top k, averaged over questions. The textbook defines recall as the fraction of relevant documents retrieved. With one gold document per question, it is the RAG workbook's hit rate.
- Mean reciprocal rank (MRR): 1 divided by the rank of the first correct result, or 0 if none, averaged over questions. The TREC-8 question answering track used it.
Save this as evaluate.py beside library.py and run it.
# evaluate.py: recall@k and mean reciprocal rank on labelled questions
from library import search
LABELLED = { # question: documents that answer it
"Can I renew a book online?": {"D5"},
"How many books can I borrow?": {"D4"},
"Is the library open on Sunday?": {"D1", "D2"},
"What does it cost to print in colour?": {"D10"},
"Are pets allowed?": {"D16"},
"Do you charge for printing?": {"D10"},
"Where can I leave my bike?": {"D14"},
"Do I need to pay to join?": {"D3"},
}
firsts, r1, r3 = [], 0, 0
for question, gold in LABELLED.items():
ranked = [doc for doc, score in search(question, k=16)]
r1 += len(gold & set(ranked[:1])) / len(gold)
r3 += len(gold & set(ranked[:3])) / len(gold)
hits = [n for n, doc in enumerate(ranked, 1) if doc in gold]
firsts.append(hits[0] if hits else None)
n = len(LABELLED)
mrr = sum(1 / f for f in firsts if f) / n # a miss adds 0
print("first hits:", firsts)
print(f"recall@1 {r1 / n:.3f} recall@3 {r3 / n:.3f} MRR {mrr:.3f}")first hits: [1, 1, 2, 2, 1, 3, None, None]
recall@1 0.375 recall@3 0.750 MRR 0.542MRR is (1 + 1 + 0.5 + 0.5 + 1 + 0.333 + 0 + 0) / 8 = 0.542. The Sunday question has two gold notes, at ranks 2 and 3, so it scores 1 for recall@3. The misses are the honest failure case: 'bike' against 'Bicycle', 'join' against 'Joining'. The scikit-learn documentation notes that a bag of words ignores word derivations.
Try it yourself · Activity 04
10 minScore a ranking by hand
Five questions have one gold document each, except Q4, which has two. Gold results appear at: Q1 rank 1, Q2 rank 3, Q3 none, Q4 ranks 2 and 5, Q5 rank 4.
- Work out recall@3 and recall@5.
- Work out the MRR.
Which measure noticed that Q2's answer was third, not first?
Worked answer
Recall@3: (1 + 1 + 0 + 0.5 + 0) / 5 = 0.50. Recall@5: (1 + 1 + 0 + 1 + 1) / 5 = 0.80. MRR: (1 + 0.333 + 0 + 0.5 + 0.25) / 5 = 0.417. Only MRR noticed: recall@3 treats ranks 1 and 3 alike.
Learn in your browser · Chapter 05 of 06
Why it fails, and what a neural embedding changes
Vector search fails in a few predictable ways. Reproduce them, then ask which a neural embedding model would change.
Save this as failures.py beside library.py and run it.
# failures.py: numbers, near-duplicates, negation, out of domain, chunk size
from library import vectorizer, search, DOCS
def cos(x, y):
a, b = vectorizer.transform([x, y])
return round(float((a @ b.T).toarray()[0, 0]), 2)
print(vectorizer.build_analyzer()("Study room 7 seats six people"))
print(search("When are you open on Saturday?"))
print(cos("Other pets are allowed.", "Other pets are not allowed."))
print(search("Do you sell train tickets?"))
print(cos("colour printing", DOCS[9]), cos("colour printing", " ".join([DOCS[0], DOCS[7], DOCS[9]])))['study', 'room', 'seats', 'six', 'people']
[('D1', 0.37), ('D2', 0.35), ('D6', 0.23)]
0.88
[('D7', 0.47), ('D5', 0.24), ('D6', 0.21)]
0.32 0.22Scroll sideways to see every column.
| Failure | What the run shows | With a neural embedding (not run: reasoning unless cited) |
|---|---|---|
| Different words | 'bike' and 'join' miss (chapter 4). | The main gain: synonyms and paraphrases can land close (dense retrieval paper). |
| Numbers, rare terms | Tokens need two or more characters, so '7' vanishes. | Rare, salient phrases can be missed, the dense retrieval paper found. Test them. |
| Near-duplicates | Normal and summer hours take the top two places. | Likely unchanged. Keep one current version, or filter by date. |
| Negation | 'allowed' and 'not allowed' score 0.88. | Do not assume a model separates them. Test pairs. |
| Out of domain | Train tickets scores 0.47. | Search always returns k results. Set any cut-off from labelled data. |
| Big chunks | D10 scores 0.32 alone, 0.22 merged with two notes. | Still one vector per chunk, so mixed topics likely dilute it. Measure it. |
Chunk size and overlap. In vector terms, mixing topics dilutes a chunk's score, and overlap copies text into neighbouring chunks, creating near-duplicates. Measure both.
A stale index. An edited note keeps its old vector until you re-embed it. TF-IDF's idf depends on the whole collection, so one new note changes every word's weight, and new words stay unknown until you refit and rebuild. With a neural model, re-embed changed chunks, and everything if the model changes: each model makes its own space, so vectors from different models should not be compared. Store each vector with its chunk id, a text hash, the model version and a date.
Try it yourself · Activity 05
15 minChange one line, then re-measure
Copy library.py and evaluate.py into a new subfolder, chars. In the copied library.py, change the vectoriser line to vectorizer = TfidfVectorizer(analyzer="char_wb", ngram_range=(3, 5)), which uses fragments of 3 to 5 characters instead of words.
- In
chars, run..\.venv\Scripts\python evaluate.py. - Append
a = vectorizer.build_analyzer()andprint(sorted(set(a("Where can I leave my bike?")) & set(a(DOCS[13]))))to the copiedlibrary.py, and run it the same way.
Did the search improve, or only the numbers?
Worked answer
First hits [1, 1, 1, 1, 1, 1, 1, 2]: recall@1 0.812, recall@3 1.000, MRR 0.938. But the shared fragments are ' bi', ' ca', 'ere', 'ere ', 'her', 'here', 'here ' and 're '. Only ' bi' links bike to bicycle; the rest come from 'Where' and 'There', 'can' and 'car'. D14 wins 0.21 to 0.20, largely by coincidence. Test changes on new questions.
Learn in your browser · Chapter 06 of 06
High dimensions, faster search and privacy
Brute force is exact but grows with the collection. See how high dimensions behave, what approximate indexes trade away, and why vectors need protecting.
High dimensions behave oddly. dims.py compares the farthest and nearest of 1,000 random points from a random query:
# dims.py: farthest over nearest distance for random points
import numpy as np
rng = np.random.default_rng(0)
for d in [2, 10, 100, 1000]:
dist = np.linalg.norm(rng.random((1000, d)) - rng.random(d), axis=1)
print(f"{d:>4} dimensions: {dist.max() / dist.min():.2f}") 2 dimensions: 135.62
10 dimensions: 4.48
100 dimensions: 1.39
1000 dimensions: 1.11In 2 dimensions the farthest point is over 135 times as far as the nearest; in 1,000, only 1.11 times. Beyer and colleagues showed that, under broad conditions, nearest and farthest distances converge as dimensions grow, with the effect appearing from as few as 10 to 15 dimensions. They stress that high-dimensional search is not therefore meaningless, and text vectors are not uniform random points. Distrust 2-D intuition, and compare any index with a plain linear scan.
Approximate nearest-neighbour search scores only a promising part of the collection and may miss true neighbours. The textbook's cluster pruning picks sqrt(N) random documents as leaders, attaches every document to its nearest leader, then scores only the groups of the b leaders nearest the query. pruning.py tests it on 10,000 synthetic unit vectors around 50 hidden topics, counting scores, not seconds.
# pruning.py: cluster pruning (leaders and followers) against brute force
import numpy as np
rng = np.random.default_rng(0)
centres = rng.normal(size=(50, 32)) # 50 hidden topics
def sample(n):
x = centres[rng.integers(50, size=n)] + 0.5 * rng.normal(size=(n, 32))
return x / np.linalg.norm(x, axis=1, keepdims=True)
X, queries = sample(10_000), sample(200)
leaders = rng.choice(10_000, size=100, replace=False) # sqrt(10,000)
owner = np.argmax(X @ X[leaders].T, axis=1) # nearest leader
for b in [1, 2, 5, 10]: # leaders searched
recall, work = [], []
for q in queries:
exact = set(np.argsort(-(X @ q))[:10]) # brute force
near = np.argsort(-(X[leaders] @ q))[:b]
cand = np.flatnonzero(np.isin(owner, near))
found = cand[np.argsort(-(X[cand] @ q))[:10]]
recall.append(len(exact & set(found)) / 10)
work.append(100 + len(cand))
print(f"b={b:>2}: recall@10 {np.mean(recall):.2f}, scores {np.mean(work):,.0f}")b= 1: recall@10 0.76, scores 255
b= 2: recall@10 0.94, scores 335
b= 5: recall@10 1.00, scores 594
b=10: recall@10 1.00, scores 1,063Brute force computes 10,000 scores. One group costs 255 on average but finds only 76% of the true top 10; two reach 94% for 335. These topics are tidy; yours may not be.
Graph indexes make the same trade differently. The HNSW paper links each vector to near neighbours in layered graphs; a search moves greedily from an entry point towards the query, and a candidate-list size (ef) sets how much it explores. We have not run one. For any index, measure recall@k against brute force on your data.
Privacy. Vectors are not anonymous. vectorizer.inverse_transform(X[15]) hands back D16's eight distinct words. Neural embeddings are harder to read but not safe: one study recovered 50–70% of input words from popular sentence embeddings, and another recovered 92% of 32-token texts exactly and, in a separate test, full names from clinical notes.
Under the UK GDPR, as the ICO explains, personal data relates to someone identifiable directly or indirectly, and pseudonymised data is still personal data. A working rule, not legal advice: treat vectors of personal text as personal data, with the same access control, retention and deletion. The ICO notes this guidance is under review after the Data (Use and Access) Act.
Try it yourself · Activity 06
12 minChoose a setting
Use the pruning.py output.
- Pick the smallest b giving recall@10 of at least 0.9, and its share of the brute-force work.
- Change
0.5 * rng.normalto1.0 * rng.normal, so the topics overlap more, and repeat.
Where are your own vectors stored or sent, and who can read them?
Worked answer
b = 2: recall 0.94 for 335 scores, 3.35% of 10,000. With more overlap, recall is 0.53, 0.69, 0.87 and 0.94 for b = 1, 2, 5 and 10, so you need b = 10: 1,088 scores, 10.88%. Messier data costs more.
Keep learning
The complete workbook
This workbook builds vector search from first principles, using vectors you compute yourself rather than a downloaded model. You will compare vectors by hand, run an exact search over sixteen invented documents, score it with recall@k and mean reciprocal rank, and reproduce its typical failures. It ends with high dimensions, approximate indexes and privacy.
- 01Text as numbersRead here · 1 exercise
Search compares lists of numbers, not sentences, so every vector search starts by turning text into one.
- 02Closeness: dot product, cosine and distanceRead here · 1 exercise
Search finds the stored vectors closest to the question's vector. Three measures are common, and they can disagree.
- 03Build a brute-force searchRead here · 1 exercise
Now search sixteen invented library help notes: one TF-IDF vector per note, every note scored, the best k returned.
- 04Measure it: recall@k and mean reciprocal rankRead here · 1 exercise
Two questions prove nothing. Score the search on labelled questions, and keep that set fixed while you change things.
- 05Why it fails, and what a neural embedding changesRead here · 1 exercise
Vector search fails in a few predictable ways. Reproduce them, then ask which a neural embedding model would change.
- 06High dimensions, faster search and privacyRead here · 1 exercise
Brute force is exact but grows with the collection. See how high dimensions behave, what approximate indexes trade away, and why vectors need protecting.
Also inside: a 10-point checklist, a glossary of 12 terms and 10 questions and answers to test yourself. 6 hands-on exercises, each with a worked answer at the back where the workbook gives one.
The PDF is free. We ask for your name and email before downloading. Read the privacy notice at the download form. Free to download and use for personal learning, study groups and inside your own team. Please do not resell the workbooks or republish them as your own. Link people to trust-agent.ai instead.
Get your free PDF workbook
Enter your name and email, then download the complete workbook. This form also works without JavaScript.
Test yourself
Questions and answers
What is an embedding, in plain words?
A list of numbers that stands for a piece of text. An embedding model is trained so that texts with similar meanings get similar lists, so you can find related text by comparing numbers instead of words. The list has a fixed length, and each position is a dimension.
Are TF-IDF vectors embeddings?
They are vectors of the same kind and are searched the same way, but they are not neural embeddings. Word-level TF-IDF only records which words a text contains, weighted by rarity, so 'bike' and 'bicycle' never match. A neural embedding model can place synonyms and paraphrases close together. That is why this workbook uses TF-IDF to teach the mechanics, not to show meaning.
Should I use cosine similarity, dot product or Euclidean distance?
For unit-length vectors it does not matter: all three rank in the same order. For other vectors they can disagree, because the dot product rewards length and distance punishes it. Cosine similarity ignores length. Use the measure your embedding tool documents, and normalise stored vectors and queries the same way.
What does normalising a vector do?
It divides the vector by its length, so the new length is 1 and only the direction remains. Afterwards the dot product equals the cosine, and squared distance equals 2 minus twice the cosine. scikit-learn's TfidfVectorizer normalises its rows by default.
Is brute-force search good enough?
Often. It is exact, simple and easy to test, and its cost grows in step with the number of vectors. Move to an approximate index only when brute force is measurably too slow, then check the index's recall@k against brute-force results on your own data.
What is recall@k, and how does it differ from hit rate?
Recall@k is the share of a question's gold documents that appear in the top k, averaged over questions. When each question has exactly one gold document, it equals the hit rate from the RAG workbook. When a question has several, finding some of them earns partial credit.
What is mean reciprocal rank?
For each question, take 1 divided by the rank of the first correct result, or 0 if none is found, then average across questions. A correct first result scores 1, a second-place one 0.5. It rewards putting the right answer at the top, which suits retrieval that feeds a model's prompt.
Why does vector search return results for questions it cannot answer?
Nearest-neighbour search always returns the k nearest vectors, even when none is relevant. In this workbook, a question about train tickets scored 0.47 against a note about lost books. Scores are not probabilities, so choose any cut-off from labelled data that includes unanswerable questions, and make the answer step able to say it does not know.
Do I need to rebuild the index when documents change?
Yes. Stored vectors describe the text as it was when it was embedded. With TF-IDF, one new document changes every word's weight, so refit and rebuild. With a neural model, re-embed the changed chunks, and re-embed everything if you change the model. Store a text hash and model version with each vector so you can detect stale entries.
Can someone recover my text from its embeddings?
Partly, and sometimes almost entirely. A TF-IDF vector gives its words straight back. Published research recovered 50–70% of input words from sentence embeddings, and 92% of 32-token texts exactly from others, plus full names from clinical notes in a separate test. If the text is personal data, protect its vectors in the same way.
Write as you go
My notes
Use this scratchpad for your own notes, working or answers as you read. Notes are saved in this browser on this device. Downloading makes a local copy; it does not send your notes to Mickai.
Consolidate your learning
Review your work
Work through the exercises and questions, then compare your reasoning with the worked answers. A scored exam and verifiable certificate are not yet available for this course.
Review the questionsLearn the language
Key terms
- Vector
- An ordered list of numbers. Each position is a dimension.
- Embedding
- A vector made by a trained neural network so that texts with similar meanings get nearby vectors.
- Bag of words
- A vector with one dimension per vocabulary word, counting how often each appears. Word order is lost.
- TF-IDF
- Term frequency times inverse document frequency: a word weighting that makes words common across the collection count for less.
- Dot product
- Multiply matching positions of two vectors and add the results. It grows with both direction and length.
- Cosine similarity
- The dot product divided by both lengths: the cosine of the angle between two vectors, ignoring length.
6 of the workbook's 12 terms. The complete glossary is in the workbook.
Follow the evidence
Sources and checks
Facts last checked: .
Examples in this workbook were run on: Python 3.12.10 on Windows 11, in a fresh virtual environment with numpy 2.5.3 and scikit-learn 1.9.1 (which installed scipy 1.18.1, joblib 1.6.0, threadpoolctl 3.7.0, narwhals 2.26.0 and cloudpickle 3.1.2) using pip 25.0.1 (2026-09-26).
These workbooks use AI assistance. See how the workbooks are made.
- Introduction to Information Retrieval (Manning, Raghavan and Schutze): Term frequency and weighting (the bag of words model)Cambridge University Press (online edition, Stanford NLP Group)
- Introduction to Information Retrieval: Dot productsCambridge University Press (online edition, Stanford NLP Group)
- Introduction to Information Retrieval: Queries as vectorsCambridge University Press (online edition, Stanford NLP Group)
- Introduction to Information Retrieval: Inverse document frequencyCambridge University Press (online edition, Stanford NLP Group)
- Introduction to Information Retrieval: Evaluation of unranked retrieval sets (precision and recall)Cambridge University Press (online edition, Stanford NLP Group)
- Introduction to Information Retrieval: Evaluation of ranked retrieval resultsCambridge University Press (online edition, Stanford NLP Group)
- Introduction to Information Retrieval: Cluster pruningCambridge University Press (online edition, Stanford NLP Group)
- The TREC-8 Question Answering Track Report (Voorhees)National Institute of Standards and Technology (NIST)
- sklearn.feature_extraction.text.TfidfVectorizerscikit-learn 1.9.1 documentation
- Feature extraction: text feature extraction, tf-idf and the limits of bag of wordsscikit-learn 1.9.1 documentation
- numpy.linalg.normNumPy v2.5 Manual
- numpy.argsort (including the stable sort option)NumPy v2.5 Manual
- numpy 2.5.3 (Requires: Python >=3.12)Python Package Index (PyPI)
- Dense Passage Retrieval for Open-Domain Question Answering (Karpukhin et al., 2020)arXiv, 2004.04906
- When Is "Nearest Neighbor" Meaningful? (Beyer, Goldstein, Ramakrishnan and Shaft, 1999)Springer, Database Theory: ICDT'99
- Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs (Malkov and Yashunin)arXiv, 1603.09320
- Information Leakage in Embedding Models (Song and Raghunathan, 2020)arXiv, 2004.00053
- Text Embeddings Reveal (Almost) As Much As Text (Morris et al., 2023)arXiv, 2310.06816
- What is personal data?Information Commissioner's Office (ICO)
Created by Mickarle Wagstaff-Irons - Micky Irons with the Mickai team. Published by Mickai LTD. Last updated 26 September 2026.
NextKeep going
Where to go next
Recommended for you
The loop harness: how AI agents actually run
How an agent harness runs a model in a loop: context, tools, permissions, budgets, stop conditions, logging and testing, with a minimal Python loop and a paper design exercise.
Recommended for you
Host a chatbot on your website, safely
Put a chatbot on your site without leaking keys or running up bills: server function, limits, CORS, privacy, accessibility, fallbacks, monitoring and a launch checklist.
Recommended for you
Data analysis with Python: investigate and explain a dataset
Answer a real question from a messy table with pandas: inspect, clean with a log, group, join, chart, measure uncertainty and check AI-written analysis code.