← Back to AI Home Day 5 of 28

Cosine Similarity, Distance & Normalization Mathematics

Time Target: 20–30 Mins | Angular Geometry, Unit Normalization & Length-Invariant Retrieval

1. Core Concept: Angular Similarity & Normalization

Cosine Similarity measures the cosine of the angle $\alpha$ between two vectors, isolating their directional orientation regardless of vector length:

$$\text{cosine\_similarity}(\mathbf{a}, \mathbf{b}) = \frac{\mathbf{a} \cdot \mathbf{b}}{\|\mathbf{a}\| \|\mathbf{b}\|} = \frac{\sum_{i=1}^n a_i b_i}{\sqrt{\sum_{i=1}^n a_i^2}\sqrt{\sum_{i=1}^n b_i^2}}$$

Distance Conversion

Because vector databases like Chroma DB minimize distance values when ranking nearest neighbors, similarity is converted into Cosine Distance:

$$\text{cosine\_distance}(\mathbf{a}, \mathbf{b}) = 1 - \text{cosine\_similarity}(\mathbf{a}, \mathbf{b})$$

  • Distance Range: $\in [0, 2]$. A distance of $0.0$ means identical direction ($\alpha = 0^\circ$), $1.0$ means orthogonal ($\alpha = 90^\circ$), and $2.0$ means complete opposite direction ($\alpha = 180^\circ$).
  • Chroma DB Parameter: Set "space": "cosine" in the HNSW collection configuration.

Unit Normalization & Computational Shortcut

To normalize a vector to unit length ($L2$ norm = 1.0), divide each component by its magnitude:

$$\text{norm}(\mathbf{a}) = \frac{\mathbf{a}}{\|\mathbf{a}\|} \implies \sum_{i=1}^n \left(\text{norm}(\mathbf{a})_i\right)^2 = 1.0$$

When vectors are pre-normalized, their Cosine Similarity simplifies directly to the Dot Product:

$$\text{cosine\_similarity}(\mathbf{a}_{\text{norm}}, \mathbf{b}_{\text{norm}}) = \mathbf{a}_{\text{norm}} \cdot \mathbf{b}_{\text{norm}}$$

Vector Metric Cosine Distance Dot Product (Inner Product) Squared L2 ($L2^2$)
Chroma Key "space": "cosine" "space": "ip" "space": "l2"
Length Sensitive? No (Direction only) Yes (Direction + Magnitude) Yes (Geometric displacement)
Pre-Normalized Equivalence $1 - (\mathbf{a}_{\text{norm}} \cdot \mathbf{b}_{\text{norm}})$ $-(\mathbf{a}_{\text{norm}} \cdot \mathbf{b}_{\text{norm}})$ $2 - 2(\mathbf{a}_{\text{norm}} \cdot \mathbf{b}_{\text{norm}})$
Primary NLP Fit Variable-length texts, sentence embeddings Normalized models, recommender rankings Dense visual features, coordinate geometry

2. Daily Targeted Drills (15–20 Mins)

Drill 5.1: Manual Cosine Similarity Calculation

Given vectors $\mathbf{a} = [4.0, 8.0]$ and $\mathbf{b} = [11.5, 5.0]$ (from our Day 3 & 4 drills), where $\|\mathbf{a}\| \approx 8.9443$, $\|\mathbf{b}\| \approx 12.5399$, and $\mathbf{a} \cdot \mathbf{b} = 86.0$: Calculate the Cosine Similarity and Chroma Cosine Distance.

Solution:
$\text{cosine\_similarity}(\mathbf{a}, \mathbf{b}) = \frac{86.0}{8.9443 \times 12.5399} = \frac{86.0}{112.16} \approx 0.7668$
$\text{Chroma Cosine Distance} = 1 - 0.7668 = 0.2332$

Drill 5.2: Unit Vector Normalization Steps

Normalize vector $\mathbf{a} = [4.0, 8.0]$ to unit length ($L2$ norm = 1.0), and verify that the sum of squared elements equals 1.0.

Solution:
$\|\mathbf{a}\| = \sqrt{4.0^2 + 8.0^2} = \sqrt{16 + 64} = \sqrt{80} \approx 8.9443$
$\mathbf{a}_{\text{norm}} = \left[\frac{4.0}{8.9443}, \frac{8.0}{8.9443}\right] \approx [0.4472, 0.8944]$
Verification: $(0.4472)^2 + (0.8944)^2 \approx 0.2000 + 0.8000 = 1.0000$

Drill 5.3: Document Length Invariance in RAG

Why is Cosine Distance the default choice for sentence transformer embeddings (e.g., all-MiniLM-L6-v2) when comparing chunk sizes of varying token counts?

Answer: Embedding models that use mean or sum pooling often produce vector magnitudes that fluctuate with sentence structure or token density. Cosine distance neutralizes magnitude differences by measuring only angular orientation, ensuring short and long passages with identical semantic topics match accurately.

Drill 5.4: Chroma DB Query Output Interpretation

You query a collection with "space": "cosine". The result returns distances [0.18, 0.65, 1.25]. Rank these results from most semantically similar to least similar, and describe what $1.25$ indicates.

Answer:
Rank 1 (Most similar): 0.18 (angle $\alpha \approx 35^\circ$)
Rank 2: 0.65 (angle $\alpha \approx 69^\circ$)
Rank 3 (Least similar): 1.25
A cosine distance $> 1.0$ indicates an obtuse angle ($\alpha > 90^\circ$), meaning the document has negative semantic correlation to the query.

Drill 5.5: Division-by-Zero Edge Case

What happens if an unhandled zero-vector $\mathbf{z} = [0.0, 0.0]$ enters a Cosine Similarity computation, and how do production systems safeguard against it?

Answer: The $L2$ norm $\|\mathbf{z}\| = 0$, resulting in a division-by-zero error ($\frac{0}{0} \implies \text{NaN}$). Production pipelines add a small numerical stabilization epsilon ($\epsilon \approx 10^{-12}$) to the denominator: $\frac{\mathbf{a} \cdot \mathbf{b}}{\max(\|\mathbf{a}\| \|\mathbf{b}\|, \epsilon)}$.

3. Hands-On Verification Snippet

Save and run this script to verify Cosine Similarity vs. Cosine Distance in NumPy and observe query rankings in Chroma DB with "space": "cosine":

import numpy as np
import chromadb

# 1. NumPy verification of Cosine Similarity, Distance, and Normalization
a = np.array([4.0, 8.0], dtype=np.float32)
b = np.array([11.5, 5.0], dtype=np.float32)

# Manual Cosine Similarity
norm_a = np.linalg.norm(a)
norm_b = np.linalg.norm(b)
cos_sim = np.dot(a, b) / (norm_a * norm_b)
cos_dist = 1.0 - cos_sim

print(f"Norm ||a||:          {norm_a:.4f}")
print(f"Norm ||b||:          {norm_b:.4f}")
print(f"Cosine Similarity:   {cos_sim:.4f}")   # 0.7668
print(f"Cosine Distance:     {cos_dist:.4f}")  # 0.2332

# Unit Normalization Verification
a_unit = a / norm_a
b_unit = b / norm_b
dot_normalized = np.dot(a_unit, b_unit)
print(f"Normalized Dot Prod: {dot_normalized:.4f}")  # 0.7668 (identical to cos_sim)

# 2. Chroma DB Collection with Cosine Space
client = chromadb.Client()
collection = client.create_collection(
    name="cosine_metric_demo",
    configuration={"hnsw": {"space": "cosine"}}  # Chroma computes 1 - cos(theta)
)

collection.add(
    ids=["doc_similar", "doc_orthogonal", "doc_divergent"],
    embeddings=[
        [4.0, 8.0],   # Angle ~ 39.9° relative to b
        [-5.0, 11.5], # Nearly orthogonal
        [-11.5, -5.0] # Complete opposite direction (180°)
    ],
    documents=[
        "Document with closely aligned topic",
        "Document with unrelated context",
        "Document with opposing context"
    ]
)

# Query using reference point b = [11.5, 5.0]
results = collection.query(
    query_embeddings=[[11.5, 5.0]],
    n_results=3
)

print("\nChroma Query Output (space='cosine'):")
for doc_id, dist, doc in zip(results["ids"][0], results["distances"][0], results["documents"][0]):
    sim = 1.0 - dist
    print(f"ID: {doc_id:<16} | Distance: {dist:.4f} | Cosine Sim: {sim:>6.4f} | {doc}")