Cosine Similarity
The most common way to score how close two embedding vectors are.
Syntax
cosine(a, b) = dot(a, b) / (norm(a) * norm(b))Cosine similarity measures the angle between two vectors. It ignores length and focuses on direction, which works well for comparing meaning.
Reading the score
- 1.0 — same direction (very similar).
- 0 — unrelated.
- -1 — opposite.
In practice, embedding vectors of related text score close to 1, and you rank results by their cosine score. You do not usually compute this by hand — vector databases do it for you — but it helps to know what the number means.
Example
function cosine(a, b) {
let dot = 0, na = 0, nb = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
na += a[i] * a[i];
nb += b[i] * b[i];
}
return dot / (Math.sqrt(na) * Math.sqrt(nb));
}
console.log(cosine([1, 0, 1], [1, 0, 1])); // 1 (identical)
console.log(cosine([1, 0, 0], [0, 1, 0])); // 0 (unrelated)When to use it
- A vector search index uses cosine similarity to rank thousands of product embeddings against a query embedding in milliseconds.
- A duplicate-detection pipeline flags pairs of support tickets whose embeddings score above 0.95 cosine similarity for human review.
- A news aggregator clusters articles by computing cosine similarity between their embeddings and grouping those above a threshold.
More examples
Cosine similarity from scratch
Implements cosine similarity from first principles to show the dot-product-over-magnitudes formula.
import math
def cosine_similarity(a, b):
dot = sum(x * y for x, y in zip(a, b))
mag_a = math.sqrt(sum(x**2 for x in a))
mag_b = math.sqrt(sum(x**2 for x in b))
return dot / (mag_a * mag_b)
v1 = [1.0, 0.5, 0.2]
v2 = [0.9, 0.6, 0.1]
v3 = [-1.0, -0.5, -0.2] # opposite direction
print(f'v1 vs v2: {cosine_similarity(v1, v2):.4f}') # close to 1
print(f'v1 vs v3: {cosine_similarity(v1, v3):.4f}') # close to -1Cosine similarity with numpy
Uses numpy for efficient cosine similarity on real embeddings, confirming related words score higher.
import numpy as np
from openai import OpenAI
client = OpenAI()
def embed(text):
return np.array(
client.embeddings.create(model='text-embedding-3-small', input=text).data[0].embedding
)
def cosine(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
pairs = [
('cat', 'kitten'),
('cat', 'automobile'),
]
for w1, w2 in pairs:
score = cosine(embed(w1), embed(w2))
print(f'{w1} vs {w2}: {score:.4f}')Threshold-based similarity filter
Filters candidates to only those whose cosine similarity exceeds a minimum threshold score.
import numpy as np
from openai import OpenAI
client = OpenAI()
def embed(text):
return np.array(
client.embeddings.create(model='text-embedding-3-small', input=text).data[0].embedding
)
cosine = lambda a, b: np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
query = 'password reset'
candidates = ['reset my login', 'forgot credentials', 'best pizza in town', 'account recovery help']
THRESHOLD = 0.5
q_vec = embed(query)
matches = [c for c in candidates if cosine(q_vec, embed(c)) >= THRESHOLD]
print('Matches above threshold:', matches)
Discussion