Careeroza
Cosine Similarity
The Workhorse of Semantic Search
Cosine similarity measures the angle between two vectors: 1 = same direction (same meaning), 0 = unrelated, -1 = opposite. In practice text embeddings mostly give values between 0 and 1, and what matters is the ranking: which documents are closest to the query.
Cosine and Normalization
Example
Normalize once when you store vectors; then similarity is a cheap dot product at query time:
import math
def normalize(v):
n = math.sqrt(sum(x * x for x in v))
return [x / n for x in v]
def cosine(a, b):
return sum(x * y for x, y in zip(a, b)) / (math.sqrt(sum(x*x for x in a)) * math.sqrt(sum(y*y for y in b)))
docs = {"refund policy": [3, 1, 0, 0], "delivery times": [0, 4, 1, 0], "size chart": [0, 0, 1, 5]}
query = [2, 1, 0, 0]
stored = {k: normalize(v) for k, v in docs.items()}
q = normalize(query)
for name in docs:
fast = sum(x * y for x, y in zip(q, stored[name]))
print(f"{name:15} cosine={cosine(query, docs[name]):.3f} dot-of-normalized={fast:.3f}")Output
refund policy cosine=0.990 dot-of-normalized=0.990 delivery times cosine=0.434 dot-of-normalized=0.434 size chart cosine=0.000 dot-of-normalized=0.000
Exercise
Exercise: Write top_k(query_vec, vectors, k) that returns the k names with the highest cosine similarity, best first.
Show answer
def top_k(query_vec, vectors, k=2):
scored = [(cosine(query_vec, v), name) for name, v in vectors.items()]
scored.sort(reverse=True)
return [name for _, name in scored[:k]]
print(top_k([2, 1, 0, 0], docs, k=2)) # ['refund policy', 'delivery times']