Linear Algebra Intuition
Every AI model is just matrix math wearing a fancy hat.
Lesson 1 of 22Learn~60 minutesPython, Julia
Before this: Phase 0
On this page
Learning Objectives
- Implement vector and matrix operations (addition, dot product, matrix multiply) from scratch in Python
- Explain geometrically what the dot product, projection, and Gram-Schmidt process do
- Determine linear independence, rank, and basis of a set of vectors using row reduction
- Connect linear algebra concepts to their AI applications: embeddings, attention scores, and LoRA
The Problem
Open any ML paper. Within the first page, you'll see vectors, matrices, dot products, and transformations. Without linear algebra intuition, these are just symbols. With it, you can see what a neural network is actually doing -- moving points around in space.
You don't need to be a mathematician. You need to see what these operations mean geometrically, then code them yourself.
The Concept
Vectors Are Points (and Directions)
A vector is just a list of numbers. But those numbers mean something -- they're coordinates in space.
2D vector [3, 2]:
| x | y | Point |
|---|---|---|
| 3 | 2 | The vector points from origin (0,0) to (3, 2) on the plane |
The vector has magnitude $\sqrt{3^2 + 2^2} = \sqrt{13}$ and points up and to the right.
In AI, vectors represent everything:
- A word → a vector of 768 numbers (its "meaning" in embedding space)
- An image → a vector of millions of pixel values
- A user → a vector of preferences
Matrices Are Transformations
A matrix transforms one vector into another. It can rotate, scale, stretch, or project.
In AI, matrices ARE the model:
- Neural network weights → matrices that transform input into output
- Attention scores → matrices that decide what to focus on
- Embeddings → matrices that map words to vectors
The Dot Product Measures Similarity
The dot product of two vectors tells you how similar they are.
\begin{aligned}
a \cdot b &= a_1 \times b_1 + a_2 \times b_2 + \cdots + a_n \times b_n \\[4pt]
\text{Same direction:} \quad a \cdot b &> 0 \quad \text{(similar)} \\
\text{Perpendicular:} \quad a \cdot b &= 0 \quad \text{(unrelated)} \\
\text{Opposite direction:} \quad a \cdot b &< 0 \quad \text{(dissimilar)}
\end{aligned}
This is literally how search engines, recommendation systems, and RAG work -- find vectors with high dot products.
Linear Independence
Vectors are linearly independent if no vector in the set can be written as a combination of the others. If $v_1, v_2, v_3$ are independent, they span a 3D space. If one is a combination of the others, they only span a plane.
Why it matters for AI: your feature matrix should have linearly independent columns. If two features are perfectly correlated (linearly dependent), the model cannot distinguish their effects. This causes multicollinearity in regression -- the weight matrix becomes unstable, and small input changes produce wild output swings.
Concrete example:
v1 = [1, 0, 0]
v2 = [0, 1, 0]
v3 = [2, 1, 0] # v3 = 2*v1 + v2
$v_1$ and $v_2$ are independent -- neither is a scalar multiple or combination of the other. But $v_3 = 2 v_1 + v_2$, so $\lbrace v_1, v_2, v_3 \rbrace$ is a dependent set. These three vectors all lie in the xy-plane. No matter how you combine them, you cannot reach [0, 0, 1]. You have three vectors but only two dimensions of freedom.
In a dataset: if feature_3 = 2*feature_1 + feature_2, adding feature_3 gives the model zero new information. Worse, it makes the normal equations singular -- there is no unique solution for the weights.
Basis and Rank
A basis is a minimal set of linearly independent vectors that span the entire space. The number of basis vectors is the dimension of the space.
The standard basis for 3D space is {[1,0,0], [0,1,0], [0,0,1]}. But any three independent vectors in 3D form a valid basis. The choice of basis is a choice of coordinate system.
Rank of a matrix = number of linearly independent columns = number of linearly independent rows. If $\text{rank} < \min(\text{rows}, \text{cols})$, the matrix is rank-deficient. This means:
- The system has infinitely many solutions (or none)
- Information is lost in the transformation
- The matrix cannot be inverted
| Situation | Rank | What it means for ML |
|---|---|---|
| Full rank $(\text{rank} = \min(m, n))$ | Maximum possible | With at least as many rows as columns (full column rank), the least-squares solution is unique. With fewer rows than columns, infinitely many solutions fit exactly. |
| Rank deficient $(\text{rank} < \min(m, n))$ | Below maximum | Features are redundant. Infinitely many weight solutions. Regularization needed. |
| Rank 1 | 1 | Every column is a scaled copy of one vector. All data lies on a line. |
| Near rank-deficient (small singular values) | Numerically low | Matrix is ill-conditioned. Tiny input noise causes large output changes. Use SVD truncation or ridge regression. |
Projection
Projecting vector a onto vector b gives the component of a in the direction of b:
\mathop{\mathrm{proj}}_b(a) = \frac{a \cdot b}{b \cdot b} \, b
The residual $(a - \mathop{\mathrm{proj}}_b(a))$ is perpendicular to $b$. This orthogonal decomposition is the foundation of least-squares fitting.
Projection is everywhere in ML:
- Linear regression minimizes the distance from observations to the column space -- the solution IS a projection
- PCA projects data onto the directions of maximum variance
- Attention in transformers computes projections of queries onto keys
Example: $a = [3, 4]$, $b = [1, 0]$
$\mathop{\mathrm{proj}}_b(a) = (3 \cdot 1 + 4 \cdot 0) / (1 \cdot 1 + 0 \cdot 0) \cdot [1, 0] = 3 \cdot [1, 0] = [3, 0]$
The projection drops the y-component. This is dimensionality reduction in its simplest form -- throw away the directions you don't care about.
Gram-Schmidt Process
Converting any set of independent vectors into an orthonormal basis. Orthonormal means every vector has length 1 and every pair is perpendicular.
The algorithm:
- Take the first vector, normalize it
- Take the second vector, subtract its projection onto the first, normalize
- Take the third vector, subtract its projections onto all previous vectors, normalize
- Repeat for remaining vectors
Input: v1, v2, v3, ... (linearly independent)
u1 = v1 / |v1|
w2 = v2 - (v2 dot u1) * u1
u2 = w2 / |w2|
w3 = v3 - (v3 dot u1) * u1 - (v3 dot u2) * u2
u3 = w3 / |w3|
Output: u1, u2, u3, ... (orthonormal basis)
This is how QR decomposition works internally. Q is the orthonormal basis, R captures the projection coefficients. QR decomposition is used in:
- Solving linear systems (more stable than Gaussian elimination)
- Computing eigenvalues (QR algorithm)
- Least-squares regression (the standard numerical method)
eigen-directions
Build It
Step 1: Vectors from scratch (Python)
class Vector:
def __init__(self, components):
self.components = list(components)
self.dim = len(self.components)
def __add__(self, other):
return Vector([a + b for a, b in zip(self.components, other.components)])
def __sub__(self, other):
return Vector([a - b for a, b in zip(self.components, other.components)])
def dot(self, other):
return sum(a * b for a, b in zip(self.components, other.components))
def magnitude(self):
return sum(x**2 for x in self.components) ** 0.5
def normalize(self):
mag = self.magnitude()
return Vector([x / mag for x in self.components])
def cosine_similarity(self, other):
return self.dot(other) / (self.magnitude() * other.magnitude())
def __repr__(self):
return f"Vector({self.components})"
a = Vector([1, 2, 3])
b = Vector([4, 5, 6])
print(f"a + b = {a + b}")
print(f"a · b = {a.dot(b)}")
print(f"|a| = {a.magnitude():.4f}")
print(f"cosine similarity = {a.cosine_similarity(b):.4f}")
Step 2: Matrices from scratch (Python)
class Matrix:
def __init__(self, rows):
self.rows = [list(row) for row in rows]
self.shape = (len(self.rows), len(self.rows[0]))
def __matmul__(self, other):
if isinstance(other, Vector):
return Vector([
sum(self.rows[i][j] * other.components[j] for j in range(self.shape[1]))
for i in range(self.shape[0])
])
rows = []
for i in range(self.shape[0]):
row = []
for j in range(other.shape[1]):
row.append(sum(
self.rows[i][k] * other.rows[k][j]
for k in range(self.shape[1])
))
rows.append(row)
return Matrix(rows)
def transpose(self):
return Matrix([
[self.rows[j][i] for j in range(self.shape[0])]
for i in range(self.shape[1])
])
def __repr__(self):
return f"Matrix({self.rows})"
rotation_90 = Matrix([[0, -1], [1, 0]])
point = Vector([3, 1])
rotated = rotation_90 @ point
print(f"Original: {point}")
print(f"Rotated 90°: {rotated}")
Step 3: Why this matters for AI
import random
random.seed(42)
weights = Matrix([[random.gauss(0, 0.1) for _ in range(3)] for _ in range(2)])
input_vector = Vector([1.0, 0.5, -0.3])
output = weights @ input_vector
print(f"Input (3D): {input_vector}")
print(f"Output (2D): {output}")
print("This is what a neural network layer does -- matrix multiplication.")
Step 4: Julia version
a = [1.0, 2.0, 3.0]
b = [4.0, 5.0, 6.0]
println("a + b = ", a + b)
println("a · b = ", a ⋅ b) # Julia supports unicode operators
println("|a| = ", √(a ⋅ a))
println("cosine = ", (a ⋅ b) / (√(a ⋅ a) * √(b ⋅ b)))
# Matrix-vector multiplication
W = [0.1 -0.2 0.3; 0.4 0.5 -0.1]
x = [1.0, 0.5, -0.3]
println("Wx = ", W * x)
println("This is a neural network layer.")
Step 5: Linear independence and projection from scratch (Python)
def is_linearly_independent(vectors):
n = len(vectors)
dim = len(vectors[0].components)
mat = Matrix([v.components[:] for v in vectors])
rows = [row[:] for row in mat.rows]
rank = 0
for col in range(dim):
pivot = None
for row in range(rank, len(rows)):
if abs(rows[row][col]) > 1e-10:
pivot = row
break
if pivot is None:
continue
rows[rank], rows[pivot] = rows[pivot], rows[rank]
scale = rows[rank][col]
rows[rank] = [x / scale for x in rows[rank]]
for row in range(len(rows)):
if row != rank and abs(rows[row][col]) > 1e-10:
factor = rows[row][col]
rows[row] = [rows[row][j] - factor * rows[rank][j] for j in range(dim)]
rank += 1
return rank == n
def project(a, b):
scalar = a.dot(b) / b.dot(b)
return Vector([scalar * x for x in b.components])
def gram_schmidt(vectors):
orthonormal = []
for v in vectors:
w = v
for u in orthonormal:
proj = project(w, u)
w = w - proj
if w.magnitude() < 1e-10:
continue
orthonormal.append(w.normalize())
return orthonormal
v1 = Vector([1, 0, 0])
v2 = Vector([1, 1, 0])
v3 = Vector([1, 1, 1])
basis = gram_schmidt([v1, v2, v3])
for i, u in enumerate(basis):
print(f"u{i+1} = {u}")
print(f" |u{i+1}| = {u.magnitude():.6f}")
print(f"u1 · u2 = {basis[0].dot(basis[1]):.6f}")
print(f"u1 · u3 = {basis[0].dot(basis[2]):.6f}")
print(f"u2 · u3 = {basis[1].dot(basis[2]):.6f}")
Use It
Now the same thing with NumPy -- what you'll actually use in practice:
import numpy as np
a = np.array([1, 2, 3], dtype=float)
b = np.array([4, 5, 6], dtype=float)
print(f"a + b = {a + b}")
print(f"a · b = {np.dot(a, b)}")
print(f"|a| = {np.linalg.norm(a):.4f}")
print(f"cosine = {np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)):.4f}")
W = np.random.randn(2, 3) * 0.1
x = np.array([1.0, 0.5, -0.3])
print(f"Wx = {W @ x}")
Rank, Projection, and QR with NumPy
import numpy as np
A = np.array([[1, 2], [2, 4]])
print(f"Rank: {np.linalg.matrix_rank(A)}")
a = np.array([3, 4])
b = np.array([1, 0])
proj = (np.dot(a, b) / np.dot(b, b)) * b
print(f"Projection of {a} onto {b}: {proj}")
Q, R = np.linalg.qr(np.random.randn(3, 3))
print(f"Q is orthogonal: {np.allclose(Q @ Q.T, np.eye(3))}")
print(f"R is upper triangular: {np.allclose(R, np.triu(R))}")
PyTorch -- Tensors Are Vectors with Autodiff
import torch
x = torch.randn(3, requires_grad=True)
y = torch.tensor([1.0, 0.0, 0.0])
similarity = torch.dot(x, y)
similarity.backward()
print(f"x = {x.data}")
print(f"y = {y.data}")
print(f"dot product = {similarity.item():.4f}")
print(f"d(dot)/dx = {x.grad}")
The gradient of the dot product with respect to x is just y. PyTorch computed this automatically. Every operation in a neural network is built from operations like this -- matrix multiplies, dot products, projections -- and autodiff tracks gradients through all of them.
You just built from scratch what NumPy does in one line. Now you know what's happening under the hood.
Ship It
This lesson produces:
outputs/prompt-linear-algebra-tutor.md-- a prompt for AI assistants to teach linear algebra through geometric intuition
Connections
Everything in this lesson connects to specific parts of modern AI:
| Concept | Where it shows up |
|---|---|
| Dot product | Attention scores in transformers, cosine similarity in RAG |
| Matrix multiply | Every neural network layer, every linear transformation |
| Linear independence | Feature selection, avoiding multicollinearity |
| Rank | Determining if a system is solvable, LoRA (low-rank adaptation) |
| Projection | Linear regression (projecting onto column space), PCA |
| Gram-Schmidt / QR | Numerical solvers, eigenvalue computation |
| Orthonormal basis | Stable numerical computation, whitening transforms |
LoRA deserves special mention. It fine-tunes large language models by decomposing weight updates into low-rank matrices. Instead of updating a 4096x4096 weight matrix (16M parameters), LoRA updates two matrices of size 4096x16 and 16x4096 (131K parameters). The rank-16 constraint means LoRA assumes the weight update lives in a 16-dimensional subspace of the full 4096-dimensional space. That is linear algebra doing real work.
Exercises
- Implement
Vector.angle_between(other)that returns the angle in degrees between two vectors - Create a 2D scaling matrix that doubles the x-coordinate and triples the y-coordinate, then apply it to the vector [1, 1]
- Given 5 random word-like vectors (dimension 50), find the two most similar using cosine similarity
- Verify that the Gram-Schmidt output is truly orthonormal: check that every pair has dot product 0 and every vector has magnitude 1
- Create a 3x3 matrix with rank 2. Verify using the
rank()method. Then explain what geometric object the columns span. - Project the vector [1, 2, 3] onto [1, 1, 1]. What does the result represent geometrically?
Hints
Exercise 1. Start from cosine_similarity, keep the value between -1 and 1, then take the arc cosine and convert radians to degrees. Check that Vector([1, 0]) and Vector([0, 1]) give 90 and that a vector with itself gives 0.
Exercise 2. Put the two scale factors on the diagonal of a 2x2 matrix and put 0 in the other two cells. The result for [1, 1] must be [2, 3].
Exercise 3. Compute the cosine similarity for every pair of the 5 vectors (10 pairs) and keep the largest value. Check your loop first with a vector against itself, which must give 1.
Exercise 4. Use gram_schmidt on three independent vectors, then compute dot for each pair and magnitude for each vector. Compare with a tolerance such as 1e-9, because floating point does not give exact zeros.
Exercise 5. Make the third row or column the sum of the first two, so one direction repeats. rank() must return 2, and the columns then span a plane through the origin.
Exercise 6. Compute the scale $\frac{a \cdot b}{b \cdot b}$ and multiply $b$ by it, or call project_onto. Check that the remainder $a$ minus the projection has a dot product of 0 with [1, 1, 1].
Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| Vector | "An arrow" | A list of numbers representing a point or direction in n-dimensional space |
| Matrix | "A table of numbers" | A transformation that maps vectors from one space to another |
| Dot product | "Multiply and sum" | A measure of how aligned two vectors are -- the core of similarity search |
| Embedding | "Some AI magic" | A vector that represents the meaning of something (word, image, user) |
| Linear independence | "They don't overlap" | No vector in the set can be written as a combination of the others |
| Rank | "How many dimensions" | The number of linearly independent columns (or rows) in a matrix |
| Projection | "The shadow" | The component of one vector in the direction of another |
| Basis | "The coordinate axes" | A minimal set of independent vectors that span the space |
| Orthonormal | "Perpendicular unit vectors" | Vectors that are mutually perpendicular and each have length 1 |
Summary
| Formula | Meaning |
|---|---|
| $a \cdot b = \sum_i a_i b_i$ | Dot product. It measures alignment and is the core of attention scores and cosine similarity |
| $\cos\theta = \frac{a \cdot b}{\lvert a\rvert \lvert b\rvert}$ | Angle between vectors. 1 is the same direction, 0 is orthogonal |
| $\lvert a\rvert = \sqrt{a \cdot a}$ | Vector magnitude (length) |
| $y = Ax, y_i = \sum_j A_{ij} x_j$ | Matrix-vector product. A neural-network layer applies it to every input |
| $\text{proj}_b a = \frac{a \cdot b}{b \cdot b} b$ | Projection of $a$ onto $b$, the part of $a$ along $b$ |
| $b^\perp = a - \text{proj}_b a$ | Component of $a$ perpendicular to $b$, used in Gram-Schmidt |
| $\text{rank}(A) \leq \min(m,n)$ | Rank, the number of independent columns. It decides if $Ax = b$ has a solution |
| $\Delta W = BA$, $B$ is 4096×16, $A$ is 16×4096 | Low-rank update. LoRA replaces a 4096×4096 update (16M parameters) with two rank-16 factors (131K parameters) |
| Orthonormal basis: $\lvert v_i\rvert = 1, v_i \cdot v_j = 0$ | Stable, reversible transformations such as whitening and QR decomposition |
Check yourself
Quick quiz
What does the dot product of two vectors measure?
- The number of dimensions the vectors share
- How similar or aligned the two vectors are
- The distance between the two vectors
- The angle between the vectors in degrees
Show the answer
B. How similar or aligned the two vectors are The dot product measures alignment: positive means same direction (similar), zero means perpendicular (unrelated), negative means opposite direction (dissimilar). This is the basis of similarity search in AI.
In AI, what does 'embedding' refer to?
- The process of converting code to machine instructions
- A technique for compressing model weights
- Inserting one model inside another
- A vector representation that captures the meaning of something (word, image, user)
Show the answer
D. A vector representation that captures the meaning of something (word, image, user) An embedding maps discrete objects (words, images, users) to continuous vectors in a high-dimensional space where similar items are close together. It is the bridge between real-world concepts and mathematical operations.
Three vectors v1=[1,0,0], v2=[0,1,0], v3=[2,1,0] are given. Are they linearly independent?
- Yes, because there are three vectors in 3D space
- No, because v3 = 2*v1 + v2, so v3 is a linear combination of the others
- Yes, because none of the vectors are identical
- No, because they all have a zero component
Show the answer
B. No, because v3 = 2*v1 + v2, so v3 is a linear combination of the others v3 = 2*v1 + v2, making the set linearly dependent. All three vectors lie in the xy-plane and cannot reach [0,0,1]. Despite having 3 vectors in 3D, they only span a 2D subspace.
What does the rank of a matrix tell you in the context of machine learning?
- The number of linearly independent columns, indicating how many dimensions of useful information it contains
- The maximum value in the matrix
- How fast the matrix can be multiplied
- The number of non-zero entries in the matrix
Show the answer
A. The number of linearly independent columns, indicating how many dimensions of useful information it contains Rank equals the number of linearly independent columns. In ML, a rank-deficient feature matrix means redundant features, infinitely many weight solutions, and the need for regularization.
How does LoRA (Low-Rank Adaptation) use linear algebra to efficiently fine-tune large language models?
- It decomposes weight updates into two small low-rank matrices instead of updating the full weight matrix
- It removes unused rows from weight matrices to shrink the model
- It freezes the embedding layer and only trains the output head
- It converts all weights from float32 to int8
Show the answer
A. It decomposes weight updates into two small low-rank matrices instead of updating the full weight matrix LoRA decomposes a 4096x4096 weight update into two matrices of size 4096x16 and 16x4096 (rank-16), reducing trainable parameters from 16M to 131K by assuming updates live in a low-dimensional subspace.
Questions people ask
Why is linear algebra important for machine learning?
Models represent data as vectors and their learned knowledge as matrices of weights. Making a prediction is mostly multiplying and adding these. Training adjusts the matrices. Without linear algebra you cannot read how models store data or why GPUs speed them up.
Do I need to be good at math to work with AI?
To use AI tools and APIs, no. To build or tune models, a working feel for vectors, matrices, derivatives and probability helps a lot. Intuition and the ability to code the ideas matter more than proving theorems.
Lessons from AI Engineering from Scratch by Rohit Ghumare and contributors, MIT license. Notes for owners by Lozo. License