Math · Week 1 · Note 4 of 14
Norms, distance and cosine similarity
A norm measures how big a vector is, a distance measures how far apart two vectors are, and cosine similarity measures only whether they point the same way. The three answer different questions, and choosing the wrong one is the most common mistake in “find me similar things” systems. This note gives you the pictures, the numbers and the rule for choosing.
Why this note matters
The vectors note ended with word vectors: lists of numbers where similar words get similar lists. The obvious next question is “how do I measure how similar?”, and there are three honest answers: how big the dot product is, how far apart the two points are, and whether the two arrows point the same way. They rank things differently. Every system that searches by meaning, from “find documents like this question” to recommendations, ends with one line of code that picks one of the three, and picking without knowing why is how people ship search that returns long documents instead of relevant ones. Tomorrow’s feature scaling note needs the distance half of this, and “how big is this vector?” comes back in week 3 as a way to keep a model’s weights small.
One word before the questions. In AI, a list of numbers that stands for a word, a sentence or a whole document, built so that similar meanings get similar lists, is called an embedding . The word vectors from the last note are embeddings. You will learn how models make them later; in this note an embedding is simply a vector, and everything here works on any vector.
Interview questions that are really this note:
- “Cosine similarity or dot product for embeddings? When would they rank differently?”
- “Why do we normalize embeddings before storing them for search?”
- “Is a cosine similarity of 0.3 high or low?”
- “What is the difference between L1 and L2 regularization , and why does L1 produce zeros?” (Week 3; the picture is here.)
- “What does gradient clipping do, exactly?” (Week 6; the arithmetic is here.)
In plain words
| Everyday idea | Technical term | Symbol or code |
|---|---|---|
| How long the arrow is | norm, specifically the L2 norm | or ; np.linalg.norm(a) |
| Total of the absolute values | L1 norm (Manhattan, taxicab) | ; norm(a, ord=1) |
| The single biggest entry | L∞ norm (max norm) | ; norm(a, ord=np.inf) |
| Straight-line gap between two tips | Euclidean distance | |
| Gap walking along the grid | Manhattan distance | |
| Same direction, length ignored | cosine similarity | |
| Shrink or stretch to length 1 | L2-normalize | ; a / np.linalg.norm(a) |
| Cap how long a vector may be | clipping by norm | a * min(1, c / norm(a)) |
The norm: how big a vector is
The L2 norm (also Euclidean norm, length, or magnitude) of a vector is
When the subscript is missing, means the L2 norm. Three worked values: ; ; . Four properties, each with a one-line reason:
- Never negative, and zero only for the zero vector. Squares cannot be negative, and the sum is zero only when every entry is.
- Scaling. . Doubling the arrow doubles its length; flipping it leaves the length alone.
- Triangle inequality. . Walking two arrows tip to tail is never shorter than the straight line.
- The squared norm is a dot product. , a sum of squares with no square root. Mean squared error is one of these: the squared length of the vector of errors, divided by the count. Sums of squares have very simple slopes (week 2), which is why so many losses and penalties are built from them.
The norm of a model’s weight vector is “how big the weights are”. The norm of the step that training is about to take is “how big a step”. Both get controlled directly later on: week 3 adds a penalty on the first to fight overfitting ; week 6 caps the second so that one bad batch cannot throw training off.
L1, L2 and L∞
The three norms you will meet, all for the example :
| Norm | Formula | For (3, −4) | Picture of “size 1” | Where it appears |
|---|---|---|---|---|
| L1 | 3 + 4 = 7 | diamond | mean absolute error (week 2); the L1 penalty that zeroes out weights (week 3) | |
| L2 | √25 = 5 | circle | distances, cosine, mean squared error; the L2 penalty on weights (week 3); capping training steps (week 6) | |
| L∞ | 4 | square | “the largest change in any single weight”; rarely needed, know the name |
These are all members of one family, the Lp norms , with , and . Only those three matter here. The important difference between L1 and L2 is visible in Fig. 1 and is the answer to a classic interview question. The L1 diamond has corners on the axes, where one coordinate is exactly zero. In week 3 you will add a penalty to the loss that says “keep the weights small”. If “small” is measured with L1, the best weights tend to land on a corner of the diamond, which means some weights become exactly zero and the model becomes sparse: it ignores some features entirely. If “small” is measured with L2, the circle has no corners, so all weights shrink towards zero smoothly but rarely become exactly zero. The regularization note draws the full picture; the shape of the ball is the half you need now.
Read Wikipedia: Norm (mathematics) Only the sections “Euclidean norm”, “Taxicab norm” and “Maximum norm”, and the picture of unit circles for different p. Ignore the general definitions.Distance between two vectors
The Euclidean distance between and is the L2 norm of their difference:
and the Manhattan distance is the L1 norm of the difference, . Notice that distance depends on where the tips are, not on where the arrows point. Two vectors in exactly the same direction can be far apart if one is longer, and that is the whole difference between distance and cosine, coming next.
One identity ties distance to the dot product and is worth being able to derive on a whiteboard. Expand the squared distance with the dot product rules from the vectors note:
So for a fixed pair of lengths, a bigger dot product means a smaller distance. When both vectors have length 1 this becomes , which says that for unit vectors, ranking by Euclidean distance and ranking by cosine similarity give the same order. Every system that stores vectors for search relies on that fact.
Euclidean distance is what the “copy the answers of the most similar examples” methods of week 4 (k-nearest neighbours and k-means ) use by default, and the mean squared error you already know is the squared Euclidean distance between the vector of predictions and the vector of true values, divided by the count.
Read Khan Academy: The distance formula Only if the 2-D case is rusty. Five minutes, then do the exercise set under it.Cosine similarity: direction only
Cosine similarity is the dot product with both lengths divided out, which by the geometry of the previous note is the cosine of the angle between the vectors:
It is always between −1 and 1. It is 1 for the same direction, 0 for orthogonal, −1 for opposite. And it is scale-invariant: multiply either vector by any positive number and the cosine does not move.
Work the numbers in Fig. 3 once by hand; they are the kind an interviewer asks for.
- , , , so .
- , , so , an angle of about 37°.
- and .
You will also meet cosine distance, defined as , which runs from 0 (same direction) to 2 (opposite) and lets “smaller is closer” libraries treat cosine like a distance. It is the same information with the sign flipped.
Watch StatQuest: Cosine Similarity, Clearly Explained About 10 minutes. Uses word counts in short and long documents, which is exactly the film-review story, and works the arithmetic on screen.Dot product, cosine or distance?
Query . Document points exactly like the query but is four times longer, like a long article on the right topic. is a short document very near the query. is long and mostly about something else. The dot product rewards length, so it ranks the off-topic above the on-topic . Cosine ranks purely by angle. Euclidean distance rewards being nearby as a point, so it demotes the long on-topic .
The rule for choosing:
| Measure | Sensitive to length? | Use it when | Typical place |
|---|---|---|---|
| Dot product | Yes, rewards long vectors | Length carries meaning you want, such as popularity or confidence, or vectors are already normalized | Recommendation scores; attention (week 8); models trained to be compared this way |
| Cosine | No | Only direction matters; lengths vary for irrelevant reasons (document length, word count) | Searching text by meaning, duplicate detection |
| Euclidean | Yes, as position | Vectors are points in a space where absolute position matters, usually after feature scaling | k-nearest neighbours, k-means, tables of numbers |
And the fact that dissolves most of the debate: if you L2-normalize every vector first, all three measures give the same ranking. For unit vectors the dot product is the cosine, and by the identity in the distance section, Euclidean distance is a decreasing function of it. This is why models that produce embeddings usually ship normalized vectors, and why the databases built for storing them offer “cosine” as an option: internally they normalize once and then use the cheaper dot product. The honest answer to “cosine or dot?” is therefore often “normalize, then it does not matter; if you cannot normalize, decide whether length means something”.
Read Pinecone: Vector similarity explained A vendor article, but a clear one: Euclidean, cosine and dot product side by side with pictures, and the advice to match the metric to how the embedding model was trained. Read Sentence-Transformers: Semantic search Scroll to the paragraph on cosine similarity versus dot product and the note that normalized embeddings make them equivalent. This is the library most search-by-meaning projects start with.Normalizing: three things with one name
| Name | What is rescaled | How | Why |
|---|---|---|---|
| L2 normalization (unit vectors) | One vector | ; a / np.linalg.norm(a) |
So that dot product = cosine and length cannot bias similarity |
| Standardization (z-scoring, feature scaling) | Each feature column of a data table | per column; StandardScaler |
So that a feature in dollars and one in years count equally in distances and in training |
| LayerNorm / RMSNorm (week 6) | Each vector of numbers flowing through a big model, per example | LayerNorm: subtract the mean, divide by the std of the entries. RMSNorm: divide by | So that the size of those numbers stays steady from step to step during training |
Two of these are norms in disguise. L2 normalization divides by the L2 norm. RMSNorm divides by the root mean square of the entries, which is ; so RMSNorm, used inside most recent language models, is L2 normalization scaled by , followed by a learned per-entry gain. Standardization is the odd one out: it is per column, not per vector, and it belongs to the feature scaling note, which is next in the reading order.
The habit to build: whenever someone says “normalize”, ask “which axis?”. Per vector (a row) or per feature (a column)? The answer changes the code and the meaning.
What changes in 768 dimensions
For two random vectors with independent entries, the cosine similarity has mean 0 and a spread (standard deviation) of about . In 768 dimensions that is 0.036. Two unrelated embeddings would therefore sit within a few hundredths of zero if the embedding space were random, so a cosine of 0.3 is already far outside chance. Two practical cautions follow:
- Thresholds are model-specific. Real embedding models are not random: many produce vectors that all share a common direction, so even unrelated texts can score 0.5 or more. A threshold like “0.8 means duplicate” has to be calibrated on your own data for your own model; it does not transfer.
- Distances concentrate. In high dimensions the nearest and farthest neighbours of a point are often not very different in distance, which makes brute-force nearest-neighbour search both slow and fragile. This is one face of the curse of dimensionality, and it is why week 11’s vector databases use approximate search structures.
The fact has one more consequence you will meet in week 8: dot products between random -dimensional vectors have a spread of about , which is why attention divides its scores by . Problem 14 in the vectors note measured exactly this.
Read Wikipedia: Curse of dimensionality Read only the “Distance functions” section. Two paragraphs, and it is the formal statement of “distances concentrate”.Code
Type these out. Predict every printed number before running.
Norms, distances and cosine by hand and by library
import numpy as np
v = np.array([3.0, -4.0])
print(np.linalg.norm(v)) # 5.0 L2, the default: sqrt(9 + 16)
print(np.linalg.norm(v, ord=1)) # 7.0 L1: |3| + |-4|
# L-infinity: the largest |entry|
print(np.linalg.norm(v, ord=np.inf))
# prints 4.0
# the L2 norm is the square root of v·v
print(np.sqrt(v @ v))
# prints 5.0
a = np.array([1.0, 2.0])
b = np.array([4.0, 6.0])
# Euclidean distance: the norm of the difference
print(np.linalg.norm(a - b))
# prints 5.0
# Manhattan distance: add up the absolute differences
print(np.abs(a - b).sum())
# prints 7.0
def cosine(a, b):
return (a @ b) / (np.linalg.norm(a) * np.linalg.norm(b))
q = np.array([1.0, 2.0])
# same direction, twice as long
print(round(cosine(q, np.array([2.0, 4.0])), 3))
# prints 1.0
print(round(cosine(q, np.array([2.0, 1.0])), 3)) # 0.8 a bit apart
# at a right angle
print(round(cosine(q, np.array([-2.0, 1.0])), 3))
# prints 0.0
# opposite direction
print(round(cosine(q, np.array([-1.0, -2.0])), 3))
# prints -1.0
Three measures, three rankings
import numpy as np
q = np.array([1.0, 1.0]) # the query
# document 1: same direction as q, four times as long
D = np.array([[4.0, 4.0],
[0.8, 1.2], # document 2: very close to q as a point
[3.0, 0.3]]) # document 3: long, pointing elsewhere
# one dot product per row
dot = D @ q
# axis=1: the length of each row
cos = dot / (np.linalg.norm(D, axis=1) * np.linalg.norm(q))
# distance from each row to q
euc = np.linalg.norm(D - q, axis=1)
# dot product: biggest is 'best'
print(np.round(dot, 2))
# prints [8. 2. 3.3 ]
print(np.round(cos, 2)) # [1. 0.98 0.77] cosine: biggest is 'best'
# distance: smallest is 'best'
print(np.round(euc, 2))
# prints [4.24 0.28 2.12]
# Rankings, best first. argsort gives the positions that would sort the
# array, smallest first;
# sorting -dot puts the largest dot product first. +1 turns positions 0, 1,
# 2 into document numbers.
print("dot ranks:", np.argsort(-dot) + 1) # [1 3 2]
print("cosine ranks:", np.argsort(-cos) + 1) # [1 2 3]
# smallest distance first
print("distance ranks:", np.argsort(euc) + 1)
# prints [2 3 1]
The three measures disagree because they answer different questions. Make every vector length 1 first, and the disagreement disappears:
import numpy as np
q = np.array([1.0, 1.0])
D = np.array([[4.0, 4.0], [0.8, 1.2], [3.0, 0.3]])
# Normalize every row and the query to length 1, then rank again.
# shape (3, 1): one length per row
lengths = np.linalg.norm(D, axis=1, keepdims=True)
# each row divided by its own length
D_unit = D / lengths
q_unit = q / np.linalg.norm(q)
dot_unit = D_unit @ q_unit
euc_unit = np.linalg.norm(D_unit - q_unit, axis=1)
print("dot ranks:", np.argsort(-dot_unit) + 1) # [1 2 3]
# now all three agree
print("distance ranks:", np.argsort(euc_unit) + 1)
# prints [1 2 3]
All pairwise cosines at once
import numpy as np
# a random-number generator with a fixed seed
rng = np.random.default_rng(0)
# five vectors of 8 random numbers, one per row
E = rng.normal(size=(5, 8))
# Every pairwise cosine similarity: make each row length 1, then one matrix
# product.
# shape (5, 1): one length per row
lengths = np.linalg.norm(E, axis=1, keepdims=True)
# each row now has length 1
E_unit = E / lengths
# (5, 5): S[i, j] = cosine of rows i and j
S = E_unit @ E_unit.T
print(np.round(S, 2))
# [[ 1. -0.25 0.1 -0.47 0.78]
# [-0.25 1. 0.18 0.35 -0.2 ]
# [ 0.1 0.18 1. -0.22 0.07]
# [-0.47 0.35 -0.22 1. -0.39]
# [ 0.78 -0.2 0.07 -0.39 1. ]]
# each row with itself: 1
print(np.allclose(np.diag(S), 1.0))
# prints True
Capping a vector’s length
import numpy as np
# Capping a vector's length: if it is longer than the cap,
# shrink it to exactly the cap and keep its direction.
v = np.array([3.0, 4.0, 12.0]) # length 13
cap = 1.0
length = np.linalg.norm(v)
# 1.0 means "already short enough, leave it alone"
scale = min(1.0, cap / length)
v_capped = v * scale
# same direction as v
print(np.round(v_capped, 3))
# prints [0.231 0.308 0.923]
print(np.linalg.norm(v_capped)) # 1.0 length now equals the cap
Cosine between random vectors as the dimension grows
import numpy as np
rng = np.random.default_rng(0)
for d in [3, 30, 300, 768]:
# 5000 random vectors with d entries each
A = rng.normal(size=(5000, d))
B = rng.normal(size=(5000, d)) # 5000 more
# row-by-row dot products: 5000 numbers
dots = np.sum(A * B, axis=1)
cos = dots / (np.linalg.norm(A, axis=1) * np.linalg.norm(B, axis=1))
average = round(cos.mean(), 3) # the average cosine
# how far the cosines typically sit from their average
spread = round(cos.std(), 3)
# the 1 / sqrt(d) rule from the section above
rule = round(1 / np.sqrt(d), 3)
print("d =", d, " average cosine =", average, " spread =", spread, " 1/sqrt(d) =", rule)
# d = 3 average cosine = -0.012 spread = 0.579 1/sqrt(d) = 0.577
# d = 30 average cosine = -0.0 spread = 0.182 1/sqrt(d) = 0.183
# d = 300 average cosine = -0.0 spread = 0.058 1/sqrt(d) = 0.058
# d = 768 average cosine = 0.0 spread = 0.036 1/sqrt(d) = 0.036
The spread of the cosines matches closely. Two unrelated random directions in 768 dimensions have a cosine within a few hundredths of zero, so a cosine of 0.3 there is a strong signal, not a weak one.
Read NumPy documentation: numpy.linalg.norm Check the table of ord values. Note the default is L2 for vectors, and that axis= lets you take the norm of every row of a matrix at once. Read scikit-learn documentation: cosine_similarity The library version of the two-line pairwise cosine above: pass one matrix and get every pair. Two minutes.Where you will meet this again
- Tomorrow, feature scaling. Standardising columns so that Euclidean distance treats every feature fairly is the whole point of the next note.
- Week 3, keeping weights small. Regularization adds the norm of the weight vector to the loss: L2 shrinks every weight; L1 zeroes some out, for the diamond-versus-circle reason in Fig. 1.
- Week 4, nearest neighbours and clustering. kNN and k-means are Euclidean distance plus bookkeeping, and both break on unscaled features.
- Week 6, training tricks. Gradient clipping is the “cap the length” code above applied to the training step: almost every modern model is trained with the step’s total L2 norm capped at 1. Weight decay is the L2 penalty applied a little at every step. LayerNorm and RMSNorm rescale the vector of numbers at each position inside the model; RMSNorm divides by an L2 norm in disguise.
- Week 8, attention. The in attention exists because dot products of -dimensional vectors have spread , the fact from the high-dimensions section.
- Weeks 11 and 12, retrieval. A RAG system turns document chunks and questions into vectors, usually L2-normalizes them, and ranks by cosine as a dot product of unit vectors. The database’s “metric” setting is the choice in the which-measure section.
The full interview answer
The most common form is “cosine similarity versus dot product versus Euclidean distance, and which one would you use for embeddings?”. Here is a complete answer in about seventy seconds.
If they push further, be ready for:
- “Why does an L1 penalty give sparse weights and L2 not?” The L1 ball has corners on the axes; the best point tends to land on a corner where coordinates are exactly zero. The L2 ball is round.
- “What does clipping by norm do?” Computes the L2 norm of the whole vector; if it exceeds the threshold, scales every entry by threshold/norm so the norm equals the threshold. Direction unchanged, size capped. (Week 6 applies this to training steps.)
- “Is a cosine of 0.3 high?” For random 768-D vectors the spread is about 0.036, so 0.3 is far from chance; but real models have a shared component, so calibrate per model.
- “Cosine distance?” , in [0, 2]. Not a true metric (fails the triangle inequality), which some libraries care about.
Practice questions
Answer each one out loud before opening it.
Define the L1, L2 and L∞ norms and give all three for (3, −4).
Sum of absolute values: 7. Square root of the sum of squares: 5. Largest absolute entry: 4. Always L∞ ≤ L2 ≤ L1.
What is the relationship between the L2 norm and the dot product?
, so the squared norm is the self dot product: a plain sum of squares.
How do you compute the Euclidean distance between two vectors, and how does it relate to the dot product?
The L2 norm of the difference. : larger dot product, smaller distance, for fixed lengths.
Two vectors have cosine similarity 1 but Euclidean distance 10. How?
They point in the same direction but have different lengths, for example (1, 0) and (11, 0). Cosine ignores length; distance does not.
Why do vector databases often normalize embeddings and then use the dot product?
For unit vectors the dot product equals the cosine similarity and Euclidean distance is a decreasing function of it, so all three rankings agree, and the dot product is the cheapest to compute.
When would you deliberately keep the raw dot product instead of cosine?
When vector length carries meaning you want to rank by (popularity, confidence) or when the embedding model was trained with a dot-product objective and its documentation says to use it.
Why does an L1 penalty on the weights lead to sparse models? (Week 3 asks this; the picture is here.)
The L1 unit ball is a diamond with corners on the coordinate axes. Keeping the loss small while keeping the L1 norm small tends to land on a corner, where some coordinates are exactly zero. The L2 ball is a sphere with no corners, so L2 shrinks weights smoothly without zeroing them.
What exactly does “clip this vector to norm c” do?
It computes the vector’s L2 norm. If that norm exceeds , every entry is multiplied by , so the norm becomes and the direction is unchanged. Training code does this to its update step (week 6).
Name three operations people call “normalization” and the axis each acts on.
L2 normalization: one vector, scaled to unit length (per row). Standardization: each feature column, to zero mean and unit variance (per column). LayerNorm/RMSNorm: each vector of numbers inside a big model, per example (per row).
What is RMSNorm in terms of the L2 norm?
It divides a -dimensional vector by , then multiplies by a learned gain. It is L2 normalization up to the factor .
What happens to the cosine similarity of random vectors as the dimension grows?
It concentrates around zero with spread about ; in 768 dimensions that is about 0.036. Random high-dimensional vectors are nearly orthogonal.
Is cosine distance a true distance metric?
No. is non-negative and symmetric but can violate the triangle inequality. Libraries that need a metric use the angle itself or Euclidean distance on normalized vectors instead.
Practice problems
Paper first, then check. Solutions are hidden under each problem.
By hand
1. Compute L1, L2 and L∞ norms of (1, −2, 2), (0, 0, 5), (−1, −1, −1, −1) and (0.6, 0.8).
(1, −2, 2): 5, 3, 2. (0, 0, 5): 5, 5, 5. (−1, −1, −1, −1): 4, 2, 1. (0.6, 0.8): 1.4, 1, 0.8.
2. Normalize (2, −2, 1) and (3, 0, 4) to unit length. Then compute their cosine similarity in two ways: from the originals and as a dot product of the unit vectors.
Norms 3 and 5, so and . Dot product of originals , over , gives 0.667. Unit vectors: . Same.
3. Euclidean and Manhattan distances between (1, 2, 3) and (4, 6, 3), and between (0, 0) and (−3, −4).
Differences (3, 4, 0): Euclidean 5, Manhattan 7. Differences (−3, −4): Euclidean 5, Manhattan 7. Signs do not matter to either.
4. Verify ‖a − b‖² = ‖a‖² + ‖b‖² − 2 a·b for a = (1, 2) and b = (3, 1).
Left: . Right: . ✓
5. Two unit vectors have cosine similarity 0.5. What is the Euclidean distance between them? And if the cosine is −1?
, so distance 1. For cosine −1: , distance 2, the diameter of the unit sphere.
6. Query (2, 1), documents A = (4, 2), B = (1, 1), C = (0, 3). Rank by dot product, cosine and Euclidean distance.
Dot: A 10, B 3, C 3. Cosine: A 1.0, B , C . Distance: A , B 1, C . Rankings: dot A, then B and C tied; cosine A, B, C; distance B, A, C.
7. A training step wants to move the weights by (3, 4, 12), but the step’s length is capped at 1.0. What step is actually taken?
Norm . Scale by : , which has norm 1. Same direction, capped size.
8. Which points have L1 norm exactly 1 in 2-D? Sketch them. Where on that shape is the point with the most zeros?
The diamond with corners (1, 0), (0, 1), (−1, 0), (0, −1). The corners have one coordinate equal to zero; every other point on the diamond has both coordinates non-zero. That is the geometric reason for L1 sparsity.
9. Data column: heights in centimetres (160, 170, 180) and a column of incomes (20,000, 50,000, 80,000). Standardize both columns. Why would k-nearest neighbours be useless without this step?
Heights: mean 170, std 8.16 → (−1.22, 0, 1.22). Incomes: mean 50,000, std 24,495 → (−1.22, 0, 1.22). Without scaling, a 10 cm height difference is 10 units while a 30,000 income difference is 30,000 units, so Euclidean distance would depend almost entirely on income. (Population standard deviation used.)
10. RMSNorm with no learned gain is applied to x = (1, 2, 3, 6). What comes out? Check that the result has root mean square 1.
Mean of squares , root 3.536. Output . Squares sum to 4, mean 1. ✓
11. In 100 dimensions, roughly what cosine similarity would you expect between two unrelated random vectors, and what range covers most pairs?
Mean 0, spread about . Most pairs fall within about ±0.2. A cosine of 0.5 would be five spreads away from chance.
12. Show that cosine similarity is unchanged when a is replaced by 3a, and flips sign when a is replaced by −a.
, the 3s cancel. With −1 the numerator flips sign but the norm uses , so the cosine flips.
Code
13. Implement norms for p = 1, 2, ∞, Euclidean and Manhattan distance, and cosine similarity, with plain Python loops. Test against NumPy on random vectors, including the property L∞ ≤ L2 ≤ L1.
import numpy as np
def norm(a, p=2):
if p == 1:
total = 0.0
for x in a:
total += abs(x) # add up the sizes of the entries
return total
if p == 2:
total = 0.0
for x in a:
total += x * x # add up the squares
return total ** 0.5 # then take the square root
# otherwise p is infinity: the largest size of any entry
largest = 0.0
for x in a:
largest = max(largest, abs(x))
return largest
def distance(a, b, p=2):
difference = []
for i in range(len(a)):
difference.append(a[i] - b[i])
# distance = norm of the difference
return norm(difference, p)
def cosine(a, b):
dot = 0.0
for i in range(len(a)):
dot += a[i] * b[i]
return dot / (norm(a) * norm(b))
rng = np.random.default_rng(0)
for trial in range(500): # 500 random tests
a = rng.normal(size=20)
b = rng.normal(size=20)
assert np.isclose(norm(a, 1), np.linalg.norm(a, ord=1))
assert np.isclose(norm(a, 2), np.linalg.norm(a))
assert np.isclose(norm(a, np.inf), np.linalg.norm(a, ord=np.inf))
assert norm(a, np.inf) <= norm(a, 2) <= norm(a, 1)
assert np.isclose(distance(a, b), np.linalg.norm(a - b))
assert np.isclose(distance(a, b, 1), np.abs(a - b).sum())
assert np.isclose(cosine(a, b), (a @ b) / (np.linalg.norm(a) * np.linalg.norm(b)))
print("all 500 tests passed") # all 500 tests passed
14. Load the iris dataset, pick one flower as the query, and find its 5 nearest neighbours by Euclidean distance (a) on raw features and (b) after standardizing each column. Compare the two neighbour lists.
import numpy as np
from sklearn.datasets import load_iris
# X: (150, 4), four measurements in centimetres
X, y = load_iris(return_X_y=True)
query = 10 # the flower in row 10 is our query
def five_nearest(M):
# distance from every row to the query
distances = np.linalg.norm(M - M[query], axis=1)
# row numbers, closest first
order = np.argsort(distances)
# skip position 0: that is the query itself
return order[1:6]
def overlap(first, second):
count = 0
for row in first:
# is this row in the other list too?
if row in second:
count += 1
return count
raw = five_nearest(X)
# standardize each COLUMN (axis=0)
X_std = (X - X.mean(axis=0)) / X.std(axis=0)
std = five_nearest(X_std)
print("raw :", raw) # [48 27 36 19 46]
print("standardized:", std) # [48 46 21 19 18]
print("shared :", overlap(raw, std), "of 5") # 3 of 5
# Now pretend column 0 was recorded in different units (times 100) and look
# again.
X_big = X.copy()
X_big[:, 0] = X_big[:, 0] * 100
big = five_nearest(X_big)
print("column 0 x100:", big, " shared with raw:", overlap(raw, big), "of 5")
# column 0 x100: [ 5 16 20 31 84] shared with raw: 0 of 5 one column now decides everything
X_big_std = (X_big - X_big.mean(axis=0)) / X_big.std(axis=0)
# units no longer matter
print("standardized again:", five_nearest(X_big_std))
# prints [48 46 21 19 18]
15. Sentences as word-count vectors: represent 6 short sentences by how many times each word appears in them, compute pairwise dot products and cosine similarities, and show one pair where the two measures disagree about which sentence is most similar to the first.
import numpy as np
sentences = ["the cat sat on the mat",
# three times
"the cat sat on the mat the cat sat on the mat the cat sat on the mat",
"a dog sat on a rug",
"the cat",
"stocks fell sharply on the news",
"the mat"]
# The vocabulary: every different word, in alphabetical order.
vocab = []
for s in sentences:
# split() cuts a sentence into words at the spaces
for word in s.split():
if word not in vocab:
vocab.append(word)
vocab = sorted(vocab)
# One row per sentence, one column per vocabulary word: how many times the
# word appears.
V = np.zeros((len(sentences), len(vocab)))
for i in range(len(sentences)):
words = sentences[i].split()
for j in range(len(vocab)):
# list.count: how many times this word is in the list
V[i, j] = words.count(vocab[j])
# dot product of every sentence with sentence 0
dot = V @ V[0]
cos = dot / (np.linalg.norm(V, axis=1) * np.linalg.norm(V[0]))
for i in range(len(sentences)):
print(i, " dot =", dot[i], " cos =", round(cos[i], 2), " ", sentences[i])
# 0 dot = 8.0 cos = 1.0 the cat sat on the mat
# 1 dot = 24.0 cos = 1.0 the cat sat on the mat the cat sat on ...
# 2 dot = 2.0 cos = 0.25 a dog sat on a rug
# 3 dot = 3.0 cos = 0.75 the cat
# 4 dot = 3.0 cos = 0.43 stocks fell sharply on the news
# 5 dot = 3.0 cos = 0.75 the mat
# The dot product scores sentence 1 three times higher than sentence 0
# itself,
# only because sentence 1 is longer. Cosine gives both 1.0: same words, same
# direction.
# "the cat" and "stocks fell ..." tie on dot product (3.0), but cosine rightly ranks
# "the cat" far higher. Cosine is the sensible measure here.
16. Several arrays together form one long vector (think: all the weights of a model, in pieces). Write clip_by_global_norm(arrays, max_norm) that treats them as one vector, caps its L2 norm, and leaves the direction unchanged. Test that the joint norm afterwards is exactly max_norm, that nothing happens when it was already small, and that every array was scaled by the same factor.
import numpy as np
def joint_norm(arrays):
total = 0.0
for a in arrays:
# squares of every entry of every piece
total += (a ** 2).sum()
# the length of all pieces taken as one vector
return np.sqrt(total)
def clip_by_global_norm(arrays, max_norm):
length = joint_norm(arrays)
# only shrink, never grow
scale = min(1.0, max_norm / (length + 1e-12))
# (1e-12 is a tiny number that avoids dividing by zero)
clipped = []
for a in arrays:
# the SAME factor for every piece keeps the direction
clipped.append(a * scale)
return clipped
rng = np.random.default_rng(0)
pieces = [rng.normal(size=(3, 3)) * 5, rng.normal(size=4) * 5]
clipped = clip_by_global_norm(pieces, 1.0)
print(round(joint_norm(pieces), 2), round(joint_norm(clipped), 6))
# prints 16.85 1.0
assert np.isclose(joint_norm(clipped), 1.0)
# how much each entry of piece 0 was scaled
factor_0 = clipped[0] / pieces[0]
factor_1 = clipped[1] / pieces[1] # and piece 1
# one shared factor everywhere
assert np.allclose(factor_0, factor_1[0])
small = [np.array([0.1, 0.2])]
# already short: untouched
assert np.allclose(clip_by_global_norm(small, 1.0)[0], small[0])
print("all checks passed")
# Week 6 applies this exact function to each training step and calls it
# gradient clipping.
Common mistakes
All sources
Everything linked above, in the order to use it, plus one reference.
- StatQuest, “Cosine Similarity, Clearly Explained” (video, 10 min). Start here.
- Wikipedia, Norm (mathematics), the three named norms and the unit-circle picture, and Cosine similarity.
- Khan Academy, The distance formula (video and exercises), if needed.
- Pinecone, “Vector similarity explained” and Sentence-Transformers, Semantic search (articles), for choosing a measure in practice.
- NumPy, linalg.norm and scikit-learn, cosine_similarity (documentation), and the site’s NumPy reference.
- Wikipedia, Curse of dimensionality, the “Distance functions” section.
- Deisenroth, Faisal and Ong, Mathematics for Machine Learning, sections 3.1–3.4 (free PDF): norms, inner products, lengths and distances, angles.