Math for AI course · Lesson 44 of 180

Angles and Cosine Similarity

Compute angles and cosine similarity from vector coordinates, normalize embeddings responsibly, and handle zero vectors in retrieval code.

Share this article

Two documents can be represented by vectors x=(1,2)x=(1,2) and y=(10,20)y=(10,20). Their raw dot product is 5050, so a dot-product search sees the second vector as much larger. Their directions are identical, though: after dividing by both vector lengths, their cosine similarity is 11. The scoring rule changes what “similar” means.

By the end of this lesson, you will be able to compute an angle and cosine similarity from coordinates, explain why positive rescaling leaves cosine unchanged, and handle a zero vector without assigning it a direction that it does not have.

Learning outcome: Compute an angle and cosine similarity and handle zero-vector limits.

This lesson combines two earlier ideas: Lesson 039: Norms measure vector size gives us Euclidean lengths, and Lesson 041: Inner products measure alignment gives us dot products. Lesson 043: Distance depends on the chosen geometry then showed why a distance keeps magnitude in the comparison. Section 3.4 of the official Mathematics for Machine Learning book develops the angle relationship. The examples, code, visual, and assessments here are original.

An angle separates direction from length

For two nonzero vectors x,y∈Rnx,y\in\mathbb{R}^n, write θ\theta for the angle between them. The usual dot product and Euclidean norm are

The first expression combines matching coordinates into one scalar. The second measures each vector’s Euclidean length. The third divides the alignment score by the two lengths, leaving a directional comparison. The Cauchy–Schwarz inequality guarantees that the final ratio lies between −1-1 and 11 for nonzero vectors, so the angle is recovered unambiguously in the interval [0,π][0,\pi]:

θ=arccos⁡ ⁣(xTy∥x∥2 ∥y∥2).\theta = \arccos\!\left(\frac{x^\mathsf{T}y}{\lVert x\rVert_2\,\lVert y\rVert_2}\right).

This normalized value is cosine similarity. A value near 11 means the vectors point in nearly the same direction; 00 means a right angle under the usual dot-product geometry; and −1-1 means opposite directions. Those interpretations assume both vectors are nonzero and that we are using the standard Euclidean inner product.

A worked angle from coordinates

Take x=(3,1)x=(3,1) and y=(1,2)y=(1,2). These are two-dimensional vectors, so each has two coordinates and no physical unit is implied. We can calculate every part before applying an inverse cosine.

The intermediate values matter. A dot product of 55 alone does not tell us the angle, because another pair of vectors could have the same dot product but different lengths. Dividing by both norms makes the result comparable across vector magnitudes.

Positive scaling leaves cosine unchanged

Suppose a nonzero vector is stretched by a positive number cc. Its direction does not change, and the algebra shows why the cosine score stays fixed:

cos⁡(cx,y)=(cx)Ty∥cx∥2∥y∥2=c xTyc∥x∥2∥y∥2=cos⁡(x,y),c>0.\cos(cx,y)=\frac{(cx)^\mathsf{T}y}{\lVert cx\rVert_2\lVert y\rVert_2} =\frac{c\,x^\mathsf{T}y}{c\lVert x\rVert_2\lVert y\rVert_2} =\cos(x,y),\qquad c>0.

The same cancellation works when yy is positively scaled. This is the useful feature for directional embedding comparisons: a document vector with the same orientation but a larger norm receives the same cosine score. It is also a modeling choice. If magnitude carries signal about confidence, frequency, or another property of the representation, removing it can discard information.

Explore the angle, score, and lengths together

The explorer starts with two nonzero vectors. Move any coordinate with a mouse, touch input, or arrow keys. The plot shows the same vectors from the origin, while the table repeats the dot product, both norms, cosine similarity, and recovered angle in text. Set either vector to (0,0)(0,0) to see the zero-vector boundary.

Cosine similarity changes when vector direction changes
Choose the two vector coordinates

Each range control changes one coordinate by 0.5. The table is the complete text alternative to the plotted geometry.

Vector x

Vector y

x = (1, 0.5); y = (0.5, 1.5); dot product = 1.25; norm of x = 1.118; norm of y = 1.581; cosine similarity = 0.707; angle = 0.785 radians (45 degrees). Interpretation: the score reflects partial directional alignment.

Two vectors and their angle from a shared originThe plot shows vector x = (1, 0.5) and vector y = (0.5, 1.5) from the origin. x = (1, 0.5); y = (0.5, 1.5); dot product = 1.25; norm of x = 1.118; norm of y = 1.581; cosine similarity = 0.707; angle = 0.785 radians (45 degrees). Interpretation: the score reflects partial directional alignment.-2-2-1-101122x = (1, 0.5)y = (0.5, 1.5)first coordinatesecond coordinate
Text values for the selected vectors
QuantityValue
Dot product x · y1.25
Norm of x1.118
Norm of y1.581
Cosine similarity0.707
Angle0.785 radians / 45 degrees

The vector lines use distinct dash patterns and labels, not color alone. The numeric table remains meaningful when the plot is unavailable or motion is reduced.

The plotted angle is an aid to reasoning, not a separate definition. When the controls change, the numerical table recomputes the same formula shown above. A pair that becomes longer without changing direction keeps the cosine value, while rotating either vector changes both the angle and the score.

Use the score carefully in embedding retrieval

An embedding is a vector representation of an item such as a document, query, image, or user. If the intended match is orientation, cosine similarity is a natural score: it compares normalized vectors rather than their raw lengths. The score still depends on the embedding model, preprocessing, corpus, and task; it does not turn “similar” into a universal property of the underlying documents.

The current scikit-learn pairwise-metrics guide describes cosine similarity as an L2-normalized dot product and notes its use for document vectors. Its cosine_similarity API treats rows as samples and returns a matrix of pairwise scores. That shape is easy to lose when moving between one vector and a batch, so make the row convention explicit:

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

query = np.array([[3.0, 1.0]])       # shape (1 sample, 2 features)
documents = np.array([
    [3.0, 1.0],
    [1.0, 2.0],
    [30.0, 10.0],
])                                    # shape (3 samples, 2 features)

scores = cosine_similarity(query, documents)
print(scores.round(4))
# [[1.     0.7071 1.    ]]

For a transparent NumPy implementation, the one-dimensional dot product and vector norms are enough. NumPy’s dot documentation defines the 1-D case as the inner product of two vectors, while numpy.linalg.norm accepts an axis argument when an array contains multiple vectors. The guard is part of the implementation rather than a cosmetic check:

import numpy as np

def cosine_similarity(x, y):
    x = np.asarray(x, dtype=float)
    y = np.asarray(y, dtype=float)
    x_norm = np.linalg.norm(x)
    y_norm = np.linalg.norm(y)
    if x_norm == 0 or y_norm == 0:
        raise ValueError('cosine similarity needs two nonzero vectors')

    raw = np.dot(x, y) / (x_norm * y_norm)
    return np.clip(raw, -1.0, 1.0)

print(cosine_similarity(np.array([3.0, 1.0]), np.array([1.0, 2.0])))
# 0.7071067811865475

The clip keeps a tiny floating-point overshoot from sending arccos outside its domain. It should not be used to hide a larger numerical or data-shape error. For a batch with shape (number_of_samples, number_of_features), compute a norm across axis=1 and keep the resulting one score per row; the current NumPy documentation demonstrates this axis convention for vector norms.

Boundaries that change the interpretation

A zero vector has no angle

If x=0x=0, then ∥x∥2=0\lVert x\rVert_2=0 and the cosine formula divides by zero. The dot product 0Ty=00^\mathsf{T}y=0 is still defined, but it does not mean that the zero vector points at a right angle to yy. A zero vector has no direction, so the angle and cosine similarity are undefined mathematically.

Libraries may choose a policy for this input. The current cosine_similarity example includes a zero row and displays a score of 0.0; that is an API convention to check for the version and data path you use, not a newly defined direction. A production retrieval pipeline should decide whether to reject, filter, or separately route zero embeddings and test that policy explicitly.

Opposite vectors are not “unrelated”

For nonzero xx, the pair (x,−x)(x,-x) has

cos⁡(x,−x)=−∥x∥22∥x∥22=−1,θ=π.\cos(x,-x)=\frac{-\lVert x\rVert_2^2}{\lVert x\rVert_2^2}=-1, \qquad \theta=\pi.

The score is maximally negative because the vectors point in opposite directions. A score near 00 means orthogonality under this geometry; it does not automatically mean that two items are semantically unrelated. The interpretation belongs to the representation and task.

Cosine is not the same as distance or a raw dot product

Cosine similarity ignores positive scale. Euclidean distance does not. For x=(1,1)x=(1,1) and y=(10,10)y=(10,10), cosine similarity is 11, but

∥x−y∥2=(−9)2+(−9)2=92≈12.728.\lVert x-y\rVert_2=\sqrt{(-9)^2+(-9)^2}=9\sqrt{2}\approx12.728.

The pair is directionally identical and still far apart as points. Conversely, a raw dot product mixes direction with both lengths. Choose cosine when directional agreement is the intended signal; choose a norm-induced distance or a raw score when magnitude should remain part of the decision. The scikit-learn cosine_distances API defines its value as 11 minus cosine similarity, so it preserves the same directional choice rather than recovering Euclidean separation.

The next lesson, Lesson 045: Orthogonality means no linear overlap, focuses on the special case where the inner product is zero and explains what “no linear overlap” means. The Math for AI course hub keeps the dependency sequence visible as each lesson becomes available.

Check your understanding

Question 1

For x = (3, 1) and y = (1, 2), compute the dot product, both L2 norms, cosine similarity, and angle.

Show the step-by-step solution

The dot product is 3⋅1+1⋅2=53\cdot1+1\cdot2=5. The norms are ∥x∥2=3⋅3+1⋅1=10\lVert x\rVert_2=\sqrt{3\cdot3+1\cdot1}=\sqrt{10} and ∥y∥2=1⋅1+2⋅2=5\lVert y\rVert_2=\sqrt{1\cdot1+2\cdot2}=\sqrt{5}. Therefore cos⁡(θ)=5/(105)=1/2≈0.7071\cos(\theta)=5/(\sqrt{10}\sqrt{5})=1/\sqrt{2}\approx0.7071. Applying inverse cosine gives θ=π/4\theta=\pi/4 radians, or 45∘45^\circ. Each step uses the two-coordinate vectors and the nonzero-norm assumption.

Question 2

Why do x = (1, 2) and y = (10, 20) have different dot products with their own lengths but the same cosine similarity?

Show the step-by-step solution

The vectors satisfy y=10xy=10x, so xTy=1⋅10+2⋅20=50x^\mathsf{T}y=1\cdot10+2\cdot20=50. Their norms are ∥x∥2=5\lVert x\rVert_2=\sqrt{5} and ∥y∥2=105\lVert y\rVert_2=10\sqrt{5}. The normalized score is 50/(5 105)=150/(\sqrt{5}\,10\sqrt{5})=1. The dot product grows with the scale of yy, while the same scale also appears in its norm and cancels in cosine.

Question 3

Let x = (1, 0) and y = (0, 2). What are their cosine similarity and angle, and what geometric relationship does that show?

Show the step-by-step solution

The dot product is 1⋅0+0⋅2=01\cdot0+0\cdot2=0. The norms are 11 and 22, so the cosine similarity is 0/(1⋅2)=00/(1\cdot2)=0. Since arccos⁡(0)=π/2\arccos(0)=\pi/2, the angle is 90∘90^\circ. The vectors are therefore orthogonal in the standard dot-product geometry, even though neither vector is zero.

Question 4

For x = (1, 0) and y = (−4, 0), compute cosine similarity and angle. Why is the score not zero?

Show the step-by-step solution

The dot product is 1⋅(−4)+0⋅0=−41\cdot(-4)+0\cdot0=-4, and the norms are 11 and 44. Thus cos⁡(θ)=−4/(1⋅4)=−1\cos(\theta)=-4/(1\cdot4)=-1. Inverse cosine gives θ=π=180∘\theta=\pi=180^\circ. The vectors lie on the same line but point in opposite directions, so they are maximally negatively aligned rather than perpendicular.

Question 5

What should a cosine-similarity implementation do for x = (0, 0) and y = (2, 3), and why is returning 0 not a mathematical angle?

Show the step-by-step solution

The norm of xx is 00, so the denominator ∥x∥2∥y∥2\lVert x\rVert_2\lVert y\rVert_2 is zero. The formula is undefined and xx has no direction. An implementation can reject the pair, filter the zero vector, or adopt a documented API convention such as returning 00; that convention must be tested and must not be described as a right angle.

Question 6

If x = (1, 2), y = (2, 1), and x is replaced by −3x, how does cosine similarity change? Show the sign calculation.

Show the step-by-step solution

First, xTy=1⋅2+2⋅1=4x^\mathsf{T}y=1\cdot2+2\cdot1=4 and ∥x∥2∥y∥2=55=5\lVert x\rVert_2\lVert y\rVert_2=\sqrt5\sqrt5=5, so the original cosine is 4/5=0.84/5=0.8. With c=−3c=-3, the numerator is multiplied by −3-3, while the norm of cxcx is ∣c∣∥x∥2=3∥x∥2|c|\lVert x\rVert_2=3\lVert x\rVert_2. Therefore the new score is (−3)/3⋅0.8=−0.8(-3)/3\cdot0.8=-0.8: negative scaling reverses the direction.

Question 7

Why does the NumPy implementation clip the normalized dot product before calling arccos, and what should a large overshoot indicate?

Show the step-by-step solution

Mathematically, Cauchy–Schwarz puts cosine in [−1,1][-1,1], but floating-point arithmetic can produce a value such as 1.00000000000000021.0000000000000002 after a long or nearly parallel calculation. Since arccos⁡\arccos is only real-valued on that interval, np.clip(raw, -1.0, 1.0) removes a tiny round-off overshoot. A large overshoot suggests a data, shape, or numerical-conditioning problem; clipping should not be used to conceal it.

Question 8

A query is q = (3, 1) and three document vectors are a = (3, 1), b = (1, 2), and c = (30, 10). Compare raw dot-product ranking with cosine-similarity ranking.

Show the step-by-step solution

The raw dot products are qTa=10q^\mathsf{T}a=10, qTb=5q^\mathsf{T}b=5, and qTc=100q^\mathsf{T}c=100, so raw-dot ranking is cc, then aa, then bb. Vector c=10ac=10a, so cos⁡(q,c)=cos⁡(q,a)=1\cos(q,c)=\cos(q,a)=1. For bb, cos⁡(q,b)=5/(105)≈0.7071\cos(q,b)=5/(\sqrt{10}\sqrt {5})\approx0.7071. Cosine ranking therefore ties aa and cc at the top, followed by bb. The difference exposes the modeling decision: dot products reward magnitude as well as direction, while cosine removes positive scale.

Sources

  1. Mathematics for Machine Learning book PDF, Section 3.4
  2. NumPy documentation: numpy.dot
  3. NumPy documentation: numpy.linalg.norm
  4. NumPy documentation: numpy.arccos
  5. scikit-learn documentation: Pairwise metrics, affinities and kernels
  6. scikit-learn documentation: cosine_similarity
  7. scikit-learn documentation: cosine_distances