datarekha

Vectors, dot products, and norms

A vector is an arrow in d-dimensional space. Once you see that, similarity, projection, and embeddings all click into place.

6 min read Beginner Math for ML Lesson 2 of 37

What you'll learn

  • The two ways to think about a vector (list of numbers vs arrow in space)
  • Dot product as similarity, projection, and cosine
  • L1 vs L2 norms and when to reach for each
  • Why "embedding" really just means "vector"

Before you start

The last lesson left the word vector doing two jobs at once. It called a vector a row of the dataset — a plain list of numbers — and then, in the same breath, spoke of two vectors “pointing the same way.” Lists do not point anywhere. Arrows do. The trick, and it is the whole trick, is that both descriptions are of the same object.

A vector is [3.0, -1.2, 0.8] — a list of d numbers. That is the bookkeeping answer, and it is correct. The answer that makes machine learning easier is that those same d numbers are an arrow in d-dimensional space: it has a direction it points and a length it reaches, it can be added to another arrow, stretched, flipped, and compared by the angle between them. Make the flip from “list” to “arrow” and the two questions this whole chapter keeps circling — how long is this vector? and what is the angle between these two? — turn from algebra into something you can see.

Two pictures, same object

The very same pair of numbers [3, 2] can be drawn two ways: as an arrow from the origin out to a tip, or as a single point sitting where that tip lands.

as an arrow01232as a point01232
The vector [3, 2] drawn two ways — both correct.

Reach for whichever picture fits the task. “Arrow” is the better lens for operations like addition; “point” is the better lens for picturing a dataset, where each sample is one dot in space. Drag the arrow below and watch its numbers, length, and angle move together — that linked motion is the idea.

Tryvector playground

Drag the arrow tips — watch the dot product change

uv
u(3.0, 4.0)
v(4.0, 1.0)
|u|5.00
|v|4.12
u·v16.00
cos θ0.776
angle39°
Drag the colored tip of each arrow. The dot product spikes when the arrows point the same way and vanishes when they're perpendicular.

Addition and scaling

Adding two vectors follows the parallelogram rule: to form a + b, set the tail of b at the tip of a and read off where you land. Scaling by a number just stretches or shrinks the arrow without turning it (a negative number flips it around).

import numpy as np

a = np.array([3.0, 1.0])
b = np.array([1.0, 2.0])

print("a + b   =", a + b)        # tip-to-tail
print("2 * a   =", 2 * a)        # twice as long, same direction
print("-a      =", -a)           # flipped
print("a - b   =", a - b)        # a + (-b)
a + b   = [4. 3.]
2 * a   = [6. 2.]
-a      = [-3. -1.]
a - b   = [ 2. -1.]

It is not a deep idea, but you will meet it constantly. A gradient update w_new = w_old - lr * grad is exactly “stand at w_old and step in a direction (-grad) for a chosen length (lr).” Averaging two embeddings is (a + b) / 2, the midpoint of the parallelogram. The arrow arithmetic is quietly doing the work.

The dot product (similarity in disguise)

The dot product multiplies two vectors position by position and sums — but it has a second face that is where its power lives:

a · b = a[0]·b[0] + a[1]·b[1] + … + a[d-1]·b[d-1]
      = ||a|| · ||b|| · cos(θ)

That second line is the magic: the dot product equals the two lengths multiplied together, times the cosine of the angle between the arrows. So the sign and size of one number report the geometry directly.

  • Same direction (θ = 0) → cos = 1 → dot product large and positive
  • Perpendicular (θ = 90°) → cos = 0 → dot product exactly 0
  • Opposite (θ = 180°) → cos = −1 → dot product large and negative
import numpy as np

a = np.array([1.0, 0.0])
b = np.array([1.0, 0.0])    # same direction
c = np.array([0.0, 1.0])    # perpendicular
d = np.array([-1.0, 0.0])   # opposite

print("a · b:", a @ b)      # aligned
print("a · c:", a @ c)      # perpendicular
print("a · d:", a @ d)      # opposite
a · b: 1.0
a · c: 0.0
a · d: -1.0

There is a catch hiding in that first form, though: because the dot product multiplies by both lengths, a very long vector can score a big dot product just for being long, not for pointing the right way. To compare pure direction, divide the lengths back out — that is cosine similarity:

import numpy as np

def cosine(u, v):
    return (u @ v) / (np.linalg.norm(u) * np.linalg.norm(v))

# Two product embeddings (made-up)
shoes_red  = np.array([0.8, 0.1, 0.5, 0.2])
shoes_blue = np.array([0.7, 0.2, 0.6, 0.1])
shirt      = np.array([0.1, 0.9, 0.0, 0.7])

print("red vs blue shoes:", round(cosine(shoes_red, shoes_blue), 3))
print("red shoes vs shirt:", round(cosine(shoes_red, shirt), 3))
red vs blue shoes: 0.978
red shoes vs shirt: 0.279

Two shoes sit at 0.978 — almost the same direction; a shoe and a shirt at 0.279 — barely related. Cosine similarity always lands between −1 and 1 no matter how long the vectors are, which is exactly why recommendation systems and semantic search prefer it: the length of an embedding should not get a vote on what counts as similar.

Projection: shadows

The dot product answers one more question: the projection of one vector onto another — how much of a lies along the direction of b. The scalar projection onto b is a · b̂, where b̂ = b / ||b|| is the unit vector (length 1) pointing the way b points. Geometrically, it is the shadow a casts on the line through b.

baprojection of a onto b = (a · b̂)
Drop a perpendicular from a to b — the shadow on b is the projection.

That shadow is the move behind linear regression (find the closest point on a hyperplane), PCA (project the data onto its principal axes), and constrained gradient descent (project the gradient onto the surface you are allowed to move on). One quiet operation, three famous methods.

Norms: how long is a vector?

A norm — also called magnitude or length — answers “how big is this vector?” Two of them cover almost everything you will do:

  • L2 norm (Euclidean): ||v||₂ = sqrt(v[0]² + v[1]² + …). The ordinary straight-line length.
  • L1 norm (Manhattan): ||v||₁ = |v[0]| + |v[1]| + …. The sum of absolute values — distance walked on a city grid, never cutting the corner.
import numpy as np

v = np.array([3.0, -4.0])

print("L2 norm:", np.linalg.norm(v, ord=2))   # the 3-4-5 triangle
print("L1 norm:", np.linalg.norm(v, ord=1))
print("Manual L2:", np.sqrt(v @ v))            # same as the L2 norm

# Normalize to unit length: same direction, length 1
v_unit = v / np.linalg.norm(v)
print("Unit:", v_unit, "Length:", np.linalg.norm(v_unit))
L2 norm: 5.0
L1 norm: 7.0
Manual L2: 5.0
Unit: [ 0.6 -0.8] Length: 1.0

The same arrow is “5 long” by the straight-line ruler and “7 long” by the city-grid ruler — proof that length is a choice, not a fact. That choice shows up directly in regularization: L2 regularization (weight decay, ridge) adds λ · ||w||₂² to the loss and gently discourages large weights, while L1 regularization (lasso) adds λ · ||w||₁ and pushes small weights all the way to exactly zero, handing you sparsity. Same idea of “size,” two different rulers, two different behaviours.

In one breath

A vector is one object with two faces: a list of numbers and an arrow with a length and a direction. The dot product reads the angle between two arrows (and cosine similarity strips out length so only direction counts); projection is the shadow one arrow casts on another; and a norm measures length — but “length” depends on the ruler you pick, L2 (straight-line) or L1 (city-grid), a choice that quietly decides how a model penalises its weights.

Practice

Quick check

0/3
Q1Two vectors a = [1, 0] and b = [3, 0]. What's their cosine similarity?
Q2Why does L1 regularization produce sparse weights (many exact zeros) while L2 does not?
Q3An embedding model maps sentences to 768-dim vectors. To find the most similar sentence to a query, you'd usually compute…

A question to carry forward

We measured a vector’s length with the L2 norm, mentioned the L1 norm almost in passing — and then watched that “almost in passing” turn out to matter, because the same arrow was 5 units long one way and 7 the other. That was a hint with consequences. “Length” is not a single fixed quantity; it is whatever norm you choose, and the norm you choose silently sets how your model measures the distance between two points and which points it considers “close.”

So here is the thread onward: looked at squarely as distances rather than just lengths, what are the L1, L2, and other norms really doing — how do they disagree about which neighbours are nearest, and why does that disagreement quietly change what a k-nearest-neighbours search, a clustering, or a regulariser actually learns?

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Practice this in an interview

All questions
What are embeddings, and how do you measure similarity between them for vector search?

Embeddings are dense vectors that map text or other data into a geometric space where semantically similar items are close together. Vector search ranks candidates by similarity, most commonly cosine similarity or dot product and sometimes Euclidean distance, retrieving the nearest vectors to a query embedding.

What are embeddings and why are they central to modern deep learning?

An embedding is a dense, learned vector representation of a discrete or high-dimensional object — a word, image, user, product — in a continuous low-dimensional space. Proximity in embedding space reflects semantic or behavioural similarity, making embeddings a universal interface between raw data and neural networks.

What is a vector database and how does it enable semantic retrieval?

A vector database stores dense numerical embeddings alongside their source documents and uses approximate nearest-neighbor (ANN) algorithms to find the most semantically similar entries for a query vector in milliseconds. Unlike a keyword index, similarity is measured in geometric space so synonyms and paraphrases match naturally. Common choices include Pinecone, Weaviate, Qdrant, and pgvector for Postgres.

What is the difference between covariance and correlation, and when does each matter?

Covariance measures the direction of the linear relationship between two variables and is expressed in the product of their units, making it scale-dependent and hard to interpret across different variable pairs. Correlation normalises covariance by both standard deviations to produce a dimensionless measure bounded between -1 and 1, enabling comparison across pairs.

Related lessons

Explore further

Skip to content