Part 01 · Mathematics for AI
Vectors and Matrices
To a model, a word, a picture or a user is just a list of numbers. Vectors, the dot product that measures how alike two of them are, and the matrix multiply that every layer runs.
⏱ 1-2 hours · Station 1 of 92
What it is and why it matters
A streaming app wants to recommend one movie to you. It can't understand "I like action, a bit of romance, no slapstick". It can only work with numbers. So it describes every movie with a few numbers, say how much action, comedy and romance it has, and describes your taste with numbers in the same three slots:
Dhaka Attack [0.9, 0.1, 0.2]
your taste [0.8, 0.0, 0.4]
A list of numbers like this is a vector. That's the whole idea behind the first step of every AI system: a word, a photo, a song or a customer becomes a vector. Once things are vectors, "how alike are these two?" becomes arithmetic.
Two operations do almost all the work in this course:
- The dot product measures how much two vectors agree.
- Matrix multiplication runs many dot products at once. Every layer of a neural network, and every attention step in an LLM, is mostly this.
How it works
Dot product. Multiply matching slots and add everything up:
[0.9, 0.1, 0.2] · [0.8, 0.0, 0.4]
= 0.9×0.8 + 0.1×0.0 + 0.2×0.4
= 0.72 + 0 + 0.08 = 0.80
Where both vectors are big (action: 0.9 and 0.8), the product is big. Where one is zero (comedy: you gave 0.0), that slot adds nothing. So a large dot product means "they point the same way".
Length and cosine similarity. The dot product also grows when vectors are just longer. A movie with every number doubled would score twice as high without being any more "your type". To compare only the direction, divide by both lengths:
length(a) = √(a·a)
cosine(a, b) = (a·b) / (length(a) × length(b)) between -1 and 1
Cosine 1 means same direction, 0 means unrelated, -1 means opposite. Search engines and RAG systems (Part 5) rank documents by exactly this number.
Matrix. Stack the movie vectors as rows and you get a matrix with shape (3, 3): 3 rows, 3 columns. Multiplying it by your taste vector gives one dot product per row, which is one score per movie, in a single operation.
The shape rule. You can multiply (m × n) by (n × p) only when the inner numbers match, and the result is (m × p). Most real bugs with matrices are shape bugs, so learn to check shapes before anything else.
A batch of 32 tokens, each a vector of 768 numbers, goes into a layer whose weight matrix is (768, 3072). What shape comes out?
Visual
Practical example
The same recommendation in code. NumPy's @ operator does the matrix multiply; the Go version writes the loops by hand so you can see there's nothing magic inside.
import numpy as np
# Each movie as 3 numbers: [action, comedy, romance], each 0 to 1.
names = ["Dhaka Attack", "Monpura", "Hawa"]
movies = np.array([
[0.9, 0.1, 0.2],
[0.1, 0.3, 0.9],
[0.6, 0.2, 0.5],
])
# One user's taste, in the same 3 slots.
user = np.array([0.8, 0.0, 0.4])
print("shapes:", movies.shape, "@", user.shape)
# One matrix multiply = one dot product per movie (row).
scores = movies @ user
for name, s in zip(names, scores):
print(f"{name:<13} score {s:.2f}")
print("recommend:", names[int(np.argmax(scores))])
# Cosine similarity: dot product of the two directions, ignoring length.
def cosine(a, b):
return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b)))
print(f"cos(Dhaka Attack, Hawa) = {cosine(movies[0], movies[2]):.3f}")
print(f"cos(Dhaka Attack, Monpura) = {cosine(movies[0], movies[1]):.3f}")package main
import (
"fmt"
"math"
)
// dot multiplies matching slots and adds them up.
func dot(a, b []float64) float64 {
s := 0.0
for i := range a {
s += a[i] * b[i]
}
return s
}
func cosine(a, b []float64) float64 {
return dot(a, b) / (math.Sqrt(dot(a, a)) * math.Sqrt(dot(b, b)))
}
func main() {
// Each movie as 3 numbers: [action, comedy, romance], each 0 to 1.
names := []string{"Dhaka Attack", "Monpura", "Hawa"}
movies := [][]float64{
{0.9, 0.1, 0.2},
{0.1, 0.3, 0.9},
{0.6, 0.2, 0.5},
}
// One user's taste, in the same 3 slots.
user := []float64{0.8, 0.0, 0.4}
fmt.Printf("shapes: (%d, %d) @ (%d,)\n", len(movies), len(movies[0]), len(user))
// A matrix times a vector is one dot product per row.
best := 0
scores := make([]float64, len(movies))
for i, row := range movies {
scores[i] = dot(row, user)
if scores[i] > scores[best] {
best = i
}
fmt.Printf("%-13s score %.2f\n", names[i], scores[i])
}
fmt.Println("recommend:", names[best])
fmt.Printf("cos(Dhaka Attack, Hawa) = %.3f\n", cosine(movies[0], movies[2]))
fmt.Printf("cos(Dhaka Attack, Monpura) = %.3f\n", cosine(movies[0], movies[1]))
}Output (both programs print the same):
shapes: (3, 3) @ (3,)
Dhaka Attack score 0.80
Monpura score 0.44
Hawa score 0.68
recommend: Dhaka Attack
cos(Dhaka Attack, Hawa) = 0.883
cos(Dhaka Attack, Monpura) = 0.339
Reading it:
shapes: (3, 3) @ (3,)is the shape check: 3 movies with 3 numbers each, times one vector of 3. The inner 3s match.- The three scores are the three dot products from the diagram. Dhaka Attack wins with 0.80.
- Cosine says Dhaka Attack and Hawa are close (0.883, both action-heavy), while Dhaka Attack and Monpura are far apart (0.339, one is action, one is romance). That's "similar movies" without any rules written by hand.
In production
- Shapes break first. A (768,) vector where the code expects (1, 768) silently broadcasts into the wrong answer in NumPy and PyTorch. Assert shapes at the boundaries of your functions.
- Memory adds up fast. One million documents, each a 768-number vector in float32 (4 bytes), is 1,000,000 × 768 × 4 ≈ 3 GB. Vector databases (Part 5) exist because of this number.
- Normalise once. If you divide every vector by its length when you store it, cosine similarity becomes a plain dot product, which is cheaper at query time.
- Zero vectors. An empty document can produce an all-zero vector. Its length is 0, cosine divides by zero, and you get NaN that spreads through rankings. Guard against it.
- Speed. A matrix multiply is millions of independent multiply-adds, which is why GPUs (Part 10), with thousands of small cores, run it so much faster than a CPU.
Key takeaway
Things become vectors; the dot product measures how much two vectors agree; a matrix multiply is many dot products at once, and its shapes must line up as (m × n)(n × p) → (m × p).
Practice
Add a fourth movie, "Aynabaji" with [0.7, 0.1, 0.3], and a second user who loves romance, [0.1, 0.1, 0.9]. Put both users in a (2, 3) matrix and compute every user-movie score with one multiply. What shape is the result, and which movie does each user get?
Answer
Users (2 × 3) @ movies transposed (3 × 4) = a (2 × 4) table of scores: one row per user, one column per movie. In NumPy: users @ movies.T. User 1 scores Dhaka Attack 0.80, Monpura 0.44, Hawa 0.68, Aynabaji 0.68, so still Dhaka Attack. User 2 scores 0.28, 0.85, 0.53, 0.35, so Monpura.
Station stamp
Finish the chapter
Done reading? Press Complete to save your progress. XP is Pro-only — on Pro this chapter is worth +10 XP.
Go Pro to earn XP →