Recall the big idea: an embedding turns text into a vector of numbers, where similar meanings land close together (fundamentals Module 4, "king − man + woman ≈ queen"). Half 1 used embeddings inside a model. Now we use them directly: embed a bunch of documents, embed a query, and find the documents whose vectors are closest to the query's.
Closeness in vector space = closeness in meaning. So "find similar text" becomes "find nearby vectors" — a geometry problem a computer can solve instantly, even across millions of documents.
Old search matched exact words. If you searched "car" it missed "automobile." Semantic (embedding) search matches meaning — it knows those are the same idea.
Type a query. Keyword search only lights up docs sharing your exact words; semantic search finds docs with the same meaning. See the difference.
Real embeddings have hundreds of dimensions, but we can squish them to 2D to see the geography of meaning. Related words cluster. Click any point — its nearest neighbors light up. That "nearest neighbor" lookup is semantic search.
How do we measure "how close" two vectors are? The standard tool is cosine similarity: the cosine of the angle between them. Same direction → 1.0 (identical meaning). Perpendicular → 0 (unrelated). Opposite → −1. We care about direction, not length, which is why it's the angle.
import torch, torch.nn.functional as F
a = embed("how to fix my bike")
b = embed("repairing a bicycle")
sim = F.cosine_similarity(a, b, dim=0)
print(sim) # tensor(0.89) → very similar!Drag the angle between two vectors and watch cosine similarity change. When they point the same way, sim = 1. As they spread apart, it drops. This is exactly how "how similar?" is scored.
The real thing: embed the query, compute cosine similarity to every document, sort by score, return the top matches. Pick a query and watch the ranked results.
from sentence_transformers import SentenceTransformer, util
model = SentenceTransformer("all-MiniLM-L6-v2") # a small embedding model
doc_embs = model.encode(documents) # embed all docs once
q_emb = model.encode("how to fix my bike") # embed the query
scores = util.cos_sim(q_emb, doc_embs)[0] # similarity to each
top = scores.argsort(descending=True)[:3] # best 3Build your intuition for semantic closeness. For each query, pick which candidate an embedding model would rank as most similar in meaning.
Order the steps of a semantic search system, from documents to results. This exact pipeline is what a vector database (next module!) automates for you.
sentence-transformers do the embedding.Next up → Module 10: Vector Databases (ChromaDB) — computing cosine similarity against millions of docs one-by-one is too slow. A vector database stores embeddings and finds nearest neighbors instantly. You'll use ChromaDB to build a real searchable store.