USAAIO Lesson 10, from Week 4 on linear algebra, fully worked. It reads the eigen-equation Av=λv geometrically, expands the characteristic polynomial det(A−λI)=0 term by term, and solves every eigenvector from (A−λI)v=0 by hand. It states the spectral theorem and verifies A=VΛVᵀ by reconstruction, derives power iteration from the eigenbasis and traces it iteration by iteration, and uses deflation to find the second eigenpair. It then builds PCA from scratch - center, covariance, eigh, sort - and matches it to sklearn. One 2×2 matrix and one 10-point dataset run through the whole deck, and every eigenvalue, trace row, and coefficient was produced by real execution. The lesson runs to 60 slides.
Subject: Machine Learning · 112 slides · code lesson
Open the interactive version of this deck · Homework for this lesson
Title
USAAIO · Lesson 10 · Week 4 (Linear Algebra)
The directions a matrix only stretches. We derive Av = λv from the geometry, expand det(A − λI) = 0 with no skipped algebra, solve every eigenvector by hand, prove the spectral theorem A = VΛVᵀ by reconstruction, and build power iteration and PCA from scratch — matched to NumPy and sklearn.
Objectives
Av = λv geometrically — eigenvectors are the axes of pure stretch, λ the stretch factordet(A − λI) = 0, expanded term by term, and each eigenvector from (A − λI)v = 0A = VΛVᵀ, and verify it by reconstructioneigh → sort) — eigenvalues are variances — and prove it matches sklearn.PCAWarm-up
Discussion prompt
Before we open Lesson 10: Eigenvalues & Eigenvectors: without looking back, what was the main idea of The Chain Rule & Backpropagation, and what could you do by the end of it that you could not do before?
Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.
Answer:
the single- and multi-variable chain rule, Jacobians, backprop as reverse-mode autodiff through a 2-layer network, the vanishing-gradient problem for sigmoid vs ReLU, and numerical gradient checking. Build a manual MLP backward pass and verify it against autograd.
Section
Part 1 of 6 — what Av = λv means
Concept
One 2×2 matrix carries the first half of this lesson. It is symmetric (A = Aᵀ), which will matter enormously.
\[ A = \begin{bmatrix} 2 & 1 \\ 1 & 2 \end{bmatrix} \]
Read A as a transformation: hand it a vector, it hands back a new one. Most inputs come back rotated and rescaled. We are hunting the rare inputs that come back only rescaled.
Counterexample
Discussion prompt
One 2×2 matrix carries the first half of this lesson. It is symmetric (A = Aᵀ), which will matter enormously.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Answer:
Read A as a transformation: hand it a vector, it hands back a new one. Most inputs come back rotated and rescaled. We are hunting the rare inputs that come back only rescaled.
Concept
An eigenvector v of A is a nonzero vector that A only scales — it keeps its direction. The scale factor λ is the eigenvalue for that vector.
\[ A v = \lambda v, \qquad v \neq 0 \]
eigenvector / eigenvalue — A direction v that A leaves pointing the same way, and the number λ telling how much A stretches it. 'Eigen' is German for 'own' — these are the matrix's OWN natural directions.
Analogy
Discussion prompt
Explain The eigen-equation Av = λv by analogy to something with no Machine Learning in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
An eigenvector v of A is a nonzero vector that A only scales — it keeps its direction. The scale factor λ is the eigenvalue for that vector.
Intuition
Picture A deforming a rubber sheet pinned at the origin. Almost every arrow drawn on the sheet tilts as the sheet stretches. Along an eigenvector the arrow keeps its heading and just grows or shrinks by λ.
λ > 1 stretches, 0 < λ < 1 compresses, λ < 0 flips the arrow around, and λ = 0 collapses that direction to a point — the mark of a singular matrix.
The eigenvectors are the transformation's skeleton: knowing them tells you everything A does, because any vector is a blend of eigen-directions, each stretched independently.
Explain it
Discussion prompt
Explain Directions of pure stretch to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
λ > 1 stretches, 0 < λ < 1 compresses, λ < 0 flips the arrow around, and λ = 0 collapses that direction to a point — the mark of a singular matrix.
Concept
Rewrite Av = λv by moving everything to one side. Since λv = λI v, we can factor:
\[ A v - \lambda v = 0 \;\Longrightarrow\; (A - \lambda I)\,v = 0 \]
We need a nonzero v solving this. A matrix that sends some nonzero vector to 0 cannot be invertible — its determinant must vanish. That single requirement is how we find λ.
Concept
So the eigenvalues are exactly the numbers λ that make A − λI singular:
\[ \det(A - \lambda I) = 0 \]
Expanding that determinant produces a polynomial in λ — the characteristic polynomial. Its roots are the eigenvalues; then we solve (A − λI)v = 0 for the eigenvector of each root.
Estimation
Predict first
Subtract λ from each diagonal entry of A (that is what −λI does) and write the matrix out:
Commit before you compute: what does Form A − λI, then its determinant come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: Determinant of a 2×2 is (top-left)(bottom-right) − (top-right)(bottom-left)
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. The standard det[[a,b],[c,d]] = ad − bc rule.
Worked example
Subtract λ from each diagonal entry of A (that is what −λI does) and write the matrix out:
Build A − λI by subtracting λ down the diagonal
Why: λI has λ on the diagonal and 0 elsewhere, so only the diagonal entries change.
\[ A - \lambda I = \begin{bmatrix} 2 - \lambda & 1 \\ 1 & 2 - \lambda \end{bmatrix} \]
Determinant of a 2×2 is (top-left)(bottom-right) − (top-right)(bottom-left)
Why: The standard det[[a,b],[c,d]] = ad − bc rule.
\[ \det(A - \lambda I) = (2-\lambda)(2-\lambda) - (1)(1) \]
Notation
Annotate
From Form A − λI, then its determinant — read this one piece at a time. What is each part doing?
On: \( A - \lambda I = \begin{bmatrix} 2 - \lambda & 1 \\ 1 & 2 - \lambda \end{bmatrix} \)
Ranking
Put in order
Put the moves of Expand to the characteristic polynomial into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. (2 − λ)² = 4 − 4λ + λ². FOIL, no shortcut.
Worked example
Multiply out (2 − λ)²
Why: (2 − λ)² = 4 − 4λ + λ². FOIL, no shortcut.
\[ (2-\lambda)^2 - 1 = \lambda^2 - 4\lambda + 4 - 1 \]
Collect constants: 4 − 1 = 3
Why: Combine the number terms into one.
\[ \lambda^2 - 4\lambda + 3 = 0 \]
Factor: two numbers multiplying to 3, adding to 4
Why: 3 = 1·3 and 1 + 3 = 4, so it factors cleanly.
\[ (\lambda - 1)(\lambda - 3) = 0 \;\Longrightarrow\; \lambda = 1 \;\text{or}\; \lambda = 3 \]
Blank canvas
Draw it
Draw what Expand to the characteristic polynomial just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.
Concept
Before finding eigenvectors, sanity-check the eigenvalues. For any square matrix the eigenvalues sum to the trace and multiply to the determinant.
\[ \sum_i \lambda_i = \operatorname{tr}(A), \qquad \prod_i \lambda_i = \det(A) \]
Here 1 + 3 = 4 = tr(A) (the diagonal 2 + 2) and 1 · 3 = 3 = det(A) (2·2 − 1·1). Both agree, so λ = 1, 3 is right. Use this on every exam problem.
Step zero
Discussion prompt
Eigenvector for λ = 3 — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: Substitute λ = 3 into A − λI
Answer:
Worked example
Plug λ = 3 into (A − λI)v = 0 and solve for the direction v = [v₁, v₂]:
Substitute λ = 3 into A − λI
Why: 2 − 3 = −1 on the diagonal.
\[ (A - 3I)v = \begin{bmatrix} -1 & 1 \\ 1 & -1 \end{bmatrix}\begin{bmatrix} v_1 \\ v_2 \end{bmatrix} = \begin{bmatrix} 0 \\ 0 \end{bmatrix} \]
Row 1 says −v₁ + v₂ = 0, so v₁ = v₂
Why: Both rows give the same equation (they must — the matrix is singular). One free parameter remains, which is why an eigenvector is a whole direction, not a single point.
Pick v = [1, 1], then normalize to unit length
Why: Divide by ‖[1,1]‖ = √2 so the eigenvector has length 1, the convention NumPy uses.
\[ v_{(\lambda=3)} = \tfrac{1}{\sqrt{2}}\begin{bmatrix} 1 \\ 1 \end{bmatrix} \approx \begin{bmatrix} 0.707 \\ 0.707 \end{bmatrix} \]
Reverse engineer
Discussion prompt
Work backwards. The example finished here:
Pick v = [1, 1], then normalize to unit length
What was it asked to do, and what must it have been given? Reconstruct the problem from its answer.
Hint: Every quantity in the result had to enter somewhere. Account for each one.
Answer:
Plug λ = 3 into (A − λI)v = 0 and solve for the direction v = [v₁, v₂]:
Ranking
Put in order
Put the moves of Eigenvector for λ = 1 into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. The two components are opposite.
Worked example
Substitute λ = 1: diagonal becomes 2 − 1 = 1
Why: Same procedure, other root.
\[ (A - 1I)v = \begin{bmatrix} 1 & 1 \\ 1 & 1 \end{bmatrix}\begin{bmatrix} v_1 \\ v_2 \end{bmatrix} = \begin{bmatrix} 0 \\ 0 \end{bmatrix} \]
Row 1 says v₁ + v₂ = 0, so v₁ = −v₂
Why: The two components are opposite. Again one free parameter.
Pick v = [1, −1], normalize by √2
Why: Unit-length convention.
\[ v_{(\lambda=1)} = \tfrac{1}{\sqrt{2}}\begin{bmatrix} 1 \\ -1 \end{bmatrix} \approx \begin{bmatrix} 0.707 \\ -0.707 \end{bmatrix} \]
Translation
\( (A - 1I)v = \begin{bmatrix} 1 & 1 \\ 1 & 1 \end{bmatrix}\begin{bmatrix} v_1 \\ v_2 \end{bmatrix} = \begin{bmatrix} 0 \\ 0 \end{bmatrix} \)
Draw it
Translate both ways. First write the expression above as a sentence with no symbols in it at all. Then cover it, and write your sentence back as notation. If the two versions disagree, the disagreement is the thing to fix.
Missing information
Discussion prompt
Never trust an eigenpair you haven't plugged back in. Multiply A by each v and confirm you get λv. This snippet is complete and runnable:
What do you need to know — or decide — before the first line can be written? List everything the problem has to hand you.
Hint: Anything you would have to invent to get started is a thing the problem must supply.
Answer:
Exactly 3v3 — the pair checks out. Likewise A v1 = [0.707, −0.707] = 1·v1.
Worked example
Never trust an eigenpair you haven't plugged back in. Multiply A by each v and confirm you get λv. This snippet is complete and runnable:
import numpy as np
A = np.array([[2., 1.], [1., 2.]])
v3 = np.array([1., 1.]) / np.sqrt(2) # claimed eigvec for lambda=3
v1 = np.array([1., -1.]) / np.sqrt(2) # claimed eigvec for lambda=1
print(A @ v3, 3 * v3) # should match
print(A @ v1, 1 * v1) # should match
vals, vecs = np.linalg.eigh(A) # symmetric solver
print(vals)
print(vecs)A v3 = [2.121, 2.121] = 3 · [0.707, 0.707]
Why: Exactly 3v3 — the pair checks out. Likewise A v1 = [0.707, −0.707] = 1·v1.
| quantity | value (verified) |
|---|---|
| A @ v3 | [2.121, 2.121] |
| 3 * v3 | [2.121, 2.121] |
| np.linalg.eigh vals | [1.0, 3.0] |
| eigh vecs (columns) | [[−0.707, 0.707], [0.707, 0.707]] |
Discrimination
Sort into buckets
Sort these by value (verified), from memory, without looking back at Verify Av = λv, and check against NumPy. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.
Concept
NumPy returned the λ = 3 vector as [0.707, 0.707] and the λ = 1 vector as [−0.707, 0.707] — the second flipped from our [0.707, −0.707].
That's fine: if v is an eigenvector, so is −v (same line, opposite arrow), because A(−v) = −Av = −λv = λ(−v). An eigenvector names a direction, and its sign is arbitrary.
So when you compare your eigenvectors to a library's, compare directions (up to sign), never raw components. This bites everyone in the PCA section — remember it now.
Anomaly
Predict first
A student writes this, and it looks reasonable:
The eigenvalues of [[3,1],[0,2]] turn out to be 3 and 2 — its diagonal. So just read eigenvalues off the diagonal of any matrix.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Generalizes a rule that only holds for TRIANGULAR matrices.
Diagonal = eigenvalues only for triangular (or diagonal) matrices, where the determinant is the product of diagonal entries. Otherwise solve det(A − λI) = 0.
Why: Generalizes a rule that only holds for TRIANGULAR matrices. A is not triangular, and we just proved its eigenvalues are 1 and 3 — nowhere near 2 and 2.
Trap
The eigenvalues of [[3,1],[0,2]] turn out to be 3 and 2 — its diagonal. So just read eigenvalues off the diagonal of any matrix.
Read eigenvalues of A = [[2,1],[1,2]] off the diagonal → 2 and 2
Why: Generalizes a rule that only holds for TRIANGULAR matrices. A is not triangular, and we just proved its eigenvalues are 1 and 3 — nowhere near 2 and 2.
Diagonal = eigenvalues only for triangular (or diagonal) matrices, where the determinant is the product of diagonal entries. Otherwise solve det(A − λI) = 0.
det([[2−λ,1],[1,2−λ]]) = (2−λ)² − 1 = 0 → λ = 1, 3
Why: The off-diagonal 1s shift the eigenvalues off the diagonal. Verified: np.linalg.eigh returns 1 and 3, not 2 and 2.
Section
Part 2 of 6 — the symmetric case
Intuition
Almost every matrix you meet in ML is symmetric: covariance matrices, Gram matrices XᵀX, Hessians of a loss, kernel matrices. So the symmetric case isn't a special corner — it's the main event.
General matrices can misbehave: complex eigenvalues, eigenvectors that aren't perpendicular, sometimes too few eigenvectors to span the space. Symmetric matrices never do any of that.
Concept
Spectral theorem. If A = Aᵀ (real symmetric), then A has real eigenvalues and a full set of orthonormal eigenvectors — perpendicular and unit length.
Stack those eigenvectors as the columns of V and the eigenvalues on the diagonal of Λ. Then A factors as:
\[ A = V \Lambda V^\top, \qquad V^\top V = I \]
VᵀV = I says the eigenvectors are orthonormal, so V⁻¹ = Vᵀ — the inverse is free. This eigen-decomposition is the engine under PCA, SVD, and spectral clustering.
Worked example
For A = [[2,1],[1,2]], put the two orthonormal eigenvectors in V and the eigenvalues in Λ:
\[ V = \tfrac{1}{\sqrt2}\begin{bmatrix} 1 & 1 \\ -1 & 1 \end{bmatrix}, \qquad \Lambda = \begin{bmatrix} 1 & 0 \\ 0 & 3 \end{bmatrix} \]
Column order of V matches diagonal order of Λ
Why: Column 1 of V is the λ=1 eigenvector, column 2 is the λ=3 eigenvector — the pairing must line up or the reconstruction fails.
Check orthonormality: v₁·v₃ = 0
Why: (1·1 + (−1)·1)/2 = 0. Perpendicular, as the spectral theorem promised for a symmetric matrix.
Pattern
Predict first
The table runs: V @ diag(vals) @ V.T | [[2., 1.], [1., 2.]] · np.allclose(recon, A) | True
In Reconstruct A from its eigen-decomposition, given the rows so far: what is the next one — the row where quantity is V.T @ V?
Correct: V.T @ V | [[1., 0.], [0., 1.]] (identity)
| quantity | value (verified) |
|---|---|
| V @ diag(vals) @ V.T | [[2., 1.], [1., 2.]] |
| np.allclose(recon, A) | True |
| V.T @ V | [[1., 0.], [0., 1.]] (identity) |
Why: The relationship between the columns, not the individual numbers, is what generates the next row. np.allclose prints True. The decomposition is faithful: A really is 'rotate into eigen-axes (Vᵀ), stretch by Λ, rotate back (V)'.
Worked example
If the theorem is real, multiplying V Λ Vᵀ back together must return A exactly. Verify it — runnable as written:
import numpy as np
A = np.array([[2., 1.], [1., 2.]])
vals, V = np.linalg.eigh(A) # vals=[1,3], V orthonormal
Lam = np.diag(vals)
recon = V @ Lam @ V.T # V Lambda V^T
print(recon)
print(np.allclose(recon, A)) # exact match?
print(V.T @ V) # should be the identityV Λ Vᵀ returns [[2,1],[1,2]] — exactly A
Why: np.allclose prints True. The decomposition is faithful: A really is 'rotate into eigen-axes (Vᵀ), stretch by Λ, rotate back (V)'.
| quantity | value (verified) |
|---|---|
| V @ diag(vals) @ V.T | [[2., 1.], [1., 2.]] |
| np.allclose(recon, A) | True |
| V.T @ V | [[1., 0.], [0., 1.]] (identity) |
Comparison
Comparison matrix
From Reconstruct A from its eigen-decomposition: refill the value (verified) column from what you know. The rest of the table is as it appeared.
| quantity | value (verified) |
|---|---|
| V @ diag(vals) @ V.T | [[2., 1.], [1., 2.]] |
| np.allclose(recon, A) | True |
| V.T @ V | [[1., 0.], [0., 1.]] (identity) |
Intuition
The decomposition reads right-to-left as three moves applied to any input vector: Vᵀ rotates it into the eigen-axes, Λ stretches each axis by its eigenvalue, then V rotates back to the original frame.
So a symmetric matrix is never a complicated tangle — it's just stretch along perpendicular axes. The eigenvectors name those axes; the eigenvalues are the stretch amounts. That's the whole picture.
It also explains det(A) = ∏λᵢ: the rotations preserve area, so the only area-scaling is the product of the diagonal stretches in Λ.
Concept
The signs of a symmetric matrix's eigenvalues tell you its type — which is exactly how we test whether a loss surface is a bowl (a minimum), a dome, or a saddle.
| all eigenvalues | matrix is | quadratic zᵀAz |
|---|---|---|
| > 0 | positive definite | > 0 for every z ≠ 0 (a bowl) |
| ≥ 0 | positive semidefinite | ≥ 0 (a bowl with a flat floor) |
| mixed signs | indefinite | positive some ways, negative others (a saddle) |
Our A = [[2,1],[1,2]] has eigenvalues 1 and 3 — both positive, so it's positive definite. Covariance matrices are always at least PSD, which is why PCA's variances (the eigenvalues) can never be negative.
Trade off
Comparison matrix
From Eigenvalues classify a symmetric matrix: every row here is a choice with a cost. Fill the quadratic zᵀAz column, then say which row you would actually pick and what you give up for it.
| all eigenvalues | matrix is | quadratic zᵀAz |
|---|---|---|
| > 0 | positive definite | > 0 for every z ≠ 0 (a bowl) |
| ≥ 0 | positive semidefinite | ≥ 0 (a bowl with a flat floor) |
| mixed signs | indefinite | positive some ways, negative others (a saddle) |
Concept
NumPy has two solvers. np.linalg.eig works on any square matrix but can return complex numbers and non-orthogonal, unsorted eigenvectors. np.linalg.eigh assumes the matrix is symmetric (Hermitian).
For symmetric A, always use eigh: it's faster, guarantees real eigenvalues returned in ascending order, and gives genuinely orthonormal eigenvectors. Using eig on a covariance matrix invites tiny imaginary parts and messy vectors.
Fill the middle
Fill in the blanks
From A non-symmetric matrix behaves differently — one line has had its right-hand side removed. Put it back.
import numpy as np
B = np.array([[3., 1.], [0., 2.]])
vals, vecs = np.linalg.eig(B) # general solver
print(vals) # [3., 2.]
print(vecs)
v_a = vecs[:, 0]; v_b = vecs[:, 1]
print(v_a @ v_b) # dot product != 0
Why: v_a is what everything below it consumes, so the wrong expression here fails later and somewhere else. Non-symmetric ⇒ the spectral theorem does NOT apply ⇒ no orthogonality guarantee.
Worked example
Contrast with the triangular B = [[3,1],[0,2]]. Its eigenvalues sit on the diagonal (it's triangular), but its eigenvectors are not perpendicular:
import numpy as np
B = np.array([[3., 1.], [0., 2.]])
vals, vecs = np.linalg.eig(B) # general solver
print(vals) # [3., 2.]
print(vecs)
v_a = vecs[:, 0]; v_b = vecs[:, 1]
print(v_a @ v_b) # dot product != 0Eigenvectors [1,0] and [−0.707, 0.707] have dot product 0.707, not 0
Why: Non-symmetric ⇒ the spectral theorem does NOT apply ⇒ no orthogonality guarantee. The columns still span the plane, just not at right angles.
| quantity | value (verified) |
|---|---|
| eig vals | [3., 2.] |
| eigvec for λ=3 | [1., 0.] |
| eigvec for λ=2 | [−0.707, 0.707] |
| dot of the two eigvecs | 0.707 (NOT orthogonal) |
Anomaly
Predict first
A student writes this, and it looks reasonable:
Eigenvectors form the natural axes of a transform, so they must be perpendicular — orthogonalize or dot them freely.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: We just measured it fail: B = [[3,1],[0,2]] has eigenvectors [1,0] and [−0.707,0.707] with dot product 0.707.
Orthonormal eigenvectors are guaranteed only by the spectral theorem — i.e. for symmetric A.
Why: We just measured it fail: B = [[3,1],[0,2]] has eigenvectors [1,0] and [−0.707,0.707] with dot product 0.707. Orthogonality is not automatic.
Trap
Eigenvectors form the natural axes of a transform, so they must be perpendicular — orthogonalize or dot them freely.
Assume ANY matrix's eigenvectors are orthogonal
Why: We just measured it fail: B = [[3,1],[0,2]] has eigenvectors [1,0] and [−0.707,0.707] with dot product 0.707. Orthogonality is not automatic.
Orthonormal eigenvectors are guaranteed only by the spectral theorem — i.e. for symmetric A.
Symmetric ⇒ orthonormal eigenvectors; general matrix ⇒ not necessarily
Why: This is exactly why PCA (covariance is symmetric) yields perpendicular components, while a generic linear map need not. Check A = Aᵀ before you assume perpendicular axes.
Break the constraint
Discussion prompt
The rule this trap just fixed:
Orthonormal eigenvectors are guaranteed only by the spectral theorem — i.e. for symmetric A.
Now break it on purpose. Build a case that violates it and follow the consequences until something visibly fails. Where does the failure first show up — and would you have noticed it if you had not been looking?
Hint: The dangerous rules are the ones whose violation still produces an answer. If yours fails loudly, try to find one that fails quietly.
Answer:
We just measured it fail: B = [[3,1],[0,2]] has eigenvectors [1,0] and [−0.707,0.707] with dot product 0.707. Orthogonality is not automatic.
Section
Part 3 of 6 — the dominant eigenpair
Intuition
Full eigen-decomposition costs a lot on a big matrix, and often you only need the dominant eigenvector — the direction of largest stretch (largest |λ|). PageRank, PCA's first component, and spectral embeddings all want exactly that top vector.
Power iteration finds it with nothing but repeated matrix–vector multiplies: start anywhere, keep applying A, renormalize, and the dominant direction takes over.
Concept
Pick any starting vector b₀. Repeat: multiply by A, then divide by the length to keep it unit-sized.
\[ b_{k+1} = \frac{A\,b_k}{\lVert A\,b_k \rVert} \]
The renormalize step is not optional — without it, entries either blow up to infinity or shrink to zero. We care about the direction, so we rescale to length 1 every step.
Once b settles, recover the eigenvalue from the Rayleigh quotient bᵀAb (with ‖b‖ = 1).
Estimation
Predict first
Write the start b₀ as a blend of eigenvectors: b₀ = c₃v₃ + c₁v₁ (dominant v₃ with λ=3, other v₁ with λ=1). Applying A scales each piece by its own eigenvalue:
Commit before you compute: what does Why it converges: work in the eigenbasis come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: For b₀ = [1,0]: ratio small/big = 1/3, 1/9, 1/27, …
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. Verified: 0.333, 0.111, 0.037, 0.0041 at k = 1,2,3,5.
Worked example
Write the start b₀ as a blend of eigenvectors: b₀ = c₃v₃ + c₁v₁ (dominant v₃ with λ=3, other v₁ with λ=1). Applying A scales each piece by its own eigenvalue:
\[ A^k b_0 = c_3\,3^k\, v_3 \;+\; c_1\,1^k\, v_1 \]
The dominant term grows like 3ᵏ; the other stays flat at 1ᵏ
Why: The ratio of the small piece to the big piece is (1/3)ᵏ — it shrinks fast. After k steps the vector is almost pure v₃.
For b₀ = [1,0]: ratio small/big = 1/3, 1/9, 1/27, …
Why: Verified: 0.333, 0.111, 0.037, 0.0041 at k = 1,2,3,5. Convergence speed is set by |λ₂/λ₁| — the closer that ratio to 1, the slower.
| k | 3ᵏ (dominant) | 1ᵏ (other) | ratio other/dominant |
|---|---|---|---|
| 1 | 3 | 1 | 0.3333 |
| 2 | 9 | 1 | 0.1111 |
| 3 | 27 | 1 | 0.0370 |
| 5 | 243 | 1 | 0.0041 |
Invariant
Step through it
Step through Why it converges: work in the eigenbasis one row at a time. One of these columns never changes — find it, and say why it cannot.
Fill the middle
Fill in the blanks
From Trace power iteration on A, step by step — one line has had its right-hand side removed. Put it back.
import numpy as np
A = np.array([[2., 1.], [1., 2.]])
b = np.array([1., 0.])
for i in range(6):
Ab = A @ b
b = Ab / np.linalg.norm(Ab)
print(i + 1, b.round(4), round(b @ A @ b, 5))
Why: Ab is what everything below it consumes, so the wrong expression here fails later and somewhere else. Divide [2,1] by 2.236. Rayleigh bᵀAb = 2.8, already climbing toward 3.
Worked example
Start from b₀ = [1, 0]. Each row: apply A, renormalize, and read the Rayleigh quotient. Complete, runnable snippet:
import numpy as np
A = np.array([[2., 1.], [1., 2.]])
b = np.array([1., 0.])
for i in range(6):
Ab = A @ b
b = Ab / np.linalg.norm(Ab)
print(i + 1, b.round(4), round(b @ A @ b, 5))Step 1: A[1,0] = [2,1], length √5 = 2.236 → b = [0.8944, 0.4472]
Why: Divide [2,1] by 2.236. Rayleigh bᵀAb = 2.8, already climbing toward 3.
| iter | b (normalized) | Rayleigh bᵀAb |
|---|---|---|
| 1 | [0.8944, 0.4472] | 2.80000 |
| 2 | [0.7809, 0.6247] | 2.97561 |
| 3 | [0.7328, 0.6805] | 2.99726 |
| 4 | [0.7158, 0.6983] | 2.99970 |
| 5 | [0.7100, 0.7042] | 2.99997 |
| 6 | [0.7081, 0.7061] | 3.00000 |
Six steps reach [0.707, 0.707] and λ = 3.0
Why: Exactly the dominant eigenpair we solved by hand. The Rayleigh quotient converges even faster than the vector — it's accurate to 5 decimals by step 6.
Error analysis
Annotate
Walk the callouts on Trace power iteration on A, step by step. Each one is a place this is easy to get subtly wrong.
Concept
Power iteration only finds the top eigenvector. To get the second, deflate: subtract the dominant piece from A so its eigenvalue drops to 0, leaving the next one on top.
\[ A' = A - \lambda_1\, v_1 v_1^\top \]
For symmetric A, A' has the same eigenvectors but with λ₁ replaced by 0. Power-iterate A' and you land on the second eigenvector. Repeat to peel off eigenpairs one at a time.
Fill the middle
Fill in the blanks
From Deflate A and recover the second eigenpair — one line has had its right-hand side removed. Put it back.
import numpy as np
A = np.array([[2., 1.], [1., 2.]])
v3 = np.array([1., 1.]) / np.sqrt(2)
A_def = A - 3.0 * np.outer(v3, v3) # remove the lambda=3 piece
b = np.array([1., 0.])
for _ in range(30):
b = A_def @ b
b = b / np.linalg.norm(b)
print(A_def)
print(b.round(4), round(b @ A_def @ b, 4))
Why: b is what everything below it consumes, so the wrong expression here fails later and somewhere else. The λ=3 direction is now dead (eigenvalue 0), so the λ=1 direction dominates A'.
Worked example
Subtract 3·v₃v₃ᵀ from A, then power-iterate the result. It should converge to the λ = 1 eigenvector. Runnable:
import numpy as np
A = np.array([[2., 1.], [1., 2.]])
v3 = np.array([1., 1.]) / np.sqrt(2)
A_def = A - 3.0 * np.outer(v3, v3) # remove the lambda=3 piece
b = np.array([1., 0.])
for _ in range(30):
b = A_def @ b
b = b / np.linalg.norm(b)
print(A_def)
print(b.round(4), round(b @ A_def @ b, 4))A' = [[0.5, −0.5], [−0.5, 0.5]] and power iteration → [0.707, −0.707], λ = 1.0
Why: The λ=3 direction is now dead (eigenvalue 0), so the λ=1 direction dominates A'. We recovered the exact second eigenpair without eig.
| quantity | value (verified) |
|---|---|
| A − 3·v₃v₃ᵀ | [[0.5, −0.5], [−0.5, 0.5]] |
| converged b | [0.707, −0.707] |
| eigenvalue bᵀA'b | 1.0 |
Anomaly
Predict first
A student writes this, and it looks reasonable:
Power iteration is just 'multiply by A a lot', so loop b = A @ b and read off the direction at the end — skip the divide.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Each multiply scales the dominant part by ~3, so after 30 steps entries are ~3³⁰ ≈ 2e14 and keep growing — eventually overflow to inf, and the eigenvalue read-out bᵀAb explodes instead of settling.
Divide by the length every iteration so the vector stays unit-sized — we only care about its direction.
Why: Each multiply scales the dominant part by ~3, so after 30 steps entries are ~3³⁰ ≈ 2e14 and keep growing — eventually overflow to inf, and the eigenvalue read-out bᵀAb explodes instead of settling.
Trap
Power iteration is just 'multiply by A a lot', so loop b = A @ b and read off the direction at the end — skip the divide.
b = A @ b (no renormalize), 30 times
Why: Each multiply scales the dominant part by ~3, so after 30 steps entries are ~3³⁰ ≈ 2e14 and keep growing — eventually overflow to inf, and the eigenvalue read-out bᵀAb explodes instead of settling.
Divide by the length every iteration so the vector stays unit-sized — we only care about its direction.
b = A @ b; b = b / np.linalg.norm(b)
Why: Renormalizing keeps magnitudes bounded while the DIRECTION still converges to the dominant eigenvector. The eigenvalue then comes cleanly from the Rayleigh quotient bᵀAb.
Section
Part 4 of 6 — the payoff
Intuition
Given a cloud of data points, PCA finds the directions of greatest spread. The first principal component is the single line along which the data varies most; the next is the most-varying direction perpendicular to it, and so on.
Those directions turn out to be eigenvectors of the covariance matrix, and the variance captured along each is its eigenvalue. All the machinery we just built — symmetric matrices, eigh, sorting by eigenvalue — is exactly what PCA runs.
Concept
Ten 2-D points, strongly correlated (as x grows, so does y). This one dataset carries the rest of the deck.
| point | x | y |
|---|---|---|
| 1 | 2.5 | 2.4 |
| 2 | 0.5 | 0.7 |
| 3 | 2.2 | 2.9 |
| … | … | … |
| 10 | 1.1 | 0.9 |
Mean of the columns is [1.81, 1.91]. Because the two features rise together, the cloud is a tilted cigar — PCA should find that tilt as PC1.
Pattern
Step through it
Step through Our running dataset one row at a time. What is driving the change, and what would the row after the last one be?
Concept
First center the data (subtract the mean), then form the covariance. With centered Xᶜ and n rows:
\[ C = \frac{X_c^\top X_c}{n - 1} \]
C is 2×2, symmetric (Xᶜᵀ Xᶜ always is), with variances on the diagonal and the covariance off it. Symmetric ⇒ the spectral theorem applies ⇒ real eigenvalues, orthonormal eigenvectors. That's why eigh is the right solver.
Explain it
Discussion prompt
Explain The covariance matrix to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
First center the data (subtract the mean), then form the covariance. With centered Xᶜ and n rows:
Picture it
Figure (svg): A tilted elliptical cloud of dots with a long arrow along its major axis labeled PC1 and a short perpendicular arrow labeled PC2.
Discussion prompt
Read the picture before the words. What is this showing, and what is the one thing it is built to make obvious? Commit to an answer, then read on.
Hint: Name the parts, then say what changes between them — and if nothing changes, say what is being held still.
Answer:
The covariance matrix encodes the shape of the data cloud: the diagonal says how spread the cloud is along each raw axis, the off-diagonal says how tilted it is.
Intuition
The covariance matrix encodes the shape of the data cloud: the diagonal says how spread the cloud is along each raw axis, the off-diagonal says how tilted it is.
Its top eigenvector points along the cloud's longest axis — the direction the cigar-shaped cloud stretches. The second eigenvector is perpendicular (spectral theorem: covariance is symmetric) and points across its narrow width.
Figure (svg): A tilted elliptical cloud of dots with a long arrow along its major axis labeled PC1 and a short perpendicular arrow labeled PC2.
Analogy
Discussion prompt
Explain What the covariance eigenvectors point at by analogy to something with no Machine Learning in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
The covariance matrix encodes the shape of the data cloud: the diagonal says how spread the cloud is along each raw axis, the off-diagonal says how tilted it is.
Step zero
Discussion prompt
Build the covariance entry by entry — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: Top-left = Σ(xᶜ)² / 9 = 5.549 / 9 = 0.6166
Answer:
Worked example
Center each column, then each covariance entry is a sum of products over the 10 points divided by n − 1 = 9. The three distinct entries (it's symmetric):
Top-left = Σ(xᶜ)² / 9 = 5.549 / 9 = 0.6166
Why: Variance of the centered x column.
Bottom-right = Σ(yᶜ)² / 9 = 6.449 / 9 = 0.7166
Why: Variance of the centered y column.
Off-diagonal = Σ(xᶜ·yᶜ) / 9 = 5.539 / 9 = 0.6154
Why: Covariance of x and y — large and positive, confirming the features move together.
\[ C = \begin{bmatrix} 0.6166 & 0.6154 \\ 0.6154 & 0.7166 \end{bmatrix} \]
Blank canvas
Draw it
Draw what Build the covariance entry by entry just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.
Ranking
Put in order
Put the moves of Eigenvalues of C by the characteristic polynomial into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. For a 2×2, det(C − λI) = λ² − tr(C)λ + det(C).
Worked example
Use trace and determinant to write the quadratic, then the quadratic formula. tr(C) = 0.6166 + 0.7166 = 1.3331, det(C) = 0.6166·0.7166 − 0.6154² = 0.06302:
Characteristic polynomial: λ² − tr·λ + det = 0
Why: For a 2×2, det(C − λI) = λ² − tr(C)λ + det(C). We derived this exact shape in Part 1.
\[ \lambda^2 - 1.3331\,\lambda + 0.06302 = 0 \]
Quadratic formula, discriminant = 1.3331² − 4·0.06302 = 1.5251
Why: √1.5251 = 1.2349.
\[ \lambda = \frac{1.3331 \pm 1.2349}{2} = 1.2840 \;\text{or}\; 0.0491 \]
λ₁ = 1.2840 (PC1 variance), λ₂ = 0.0491 (PC2 variance)
Why: Matches np.linalg.eigh exactly. PC1 holds 1.2840/(1.2840+0.0491) = 96.3% of the total variance.
Notation
Annotate
From Eigenvalues of C by the characteristic polynomial — read this one piece at a time. What is each part doing?
On: \( \lambda = \frac{1.3331 \pm 1.2349}{2} = 1.2840 \;\text{or}\; 0.0491 \)
Pattern
Predict first
The table runs: cov | [[0.6166, 0.6154], [0.6154, 0.7166]] · eigenvalues (desc) | [1.2840, 0.0491] · PC1 direction | [0.6779, 0.7352]
In PCA from scratch, top to bottom, given the rows so far: what is the next one — the row where quantity is PC1 share of variance?
Correct: PC1 share of variance | 1.2840 / 1.3331 = 96.3%
| quantity | value (verified) |
|---|---|
| cov | [[0.6166, 0.6154], [0.6154, 0.7166]] |
| eigenvalues (desc) | [1.2840, 0.0491] |
| PC1 direction | [0.6779, 0.7352] |
| PC1 share of variance | 1.2840 / 1.3331 = 96.3% |
Why: The relationship between the columns, not the individual numbers, is what generates the next row. eigh returns ascending, so we reverse the order to put the biggest-variance direction first.
Worked example
Now the whole pipeline in code: center, covariance, eigh, sort descending. X is the 10×2 array from before. Runnable:
import numpy as np
X = np.array([[2.5,2.4],[0.5,0.7],[2.2,2.9],[1.9,2.2],[3.1,3.0],
[2.3,2.7],[2.0,1.6],[1.0,1.1],[1.5,1.6],[1.1,0.9]])
Xc = X - X.mean(axis=0) # 1. CENTER
cov = (Xc.T @ Xc) / (len(Xc) - 1) # 2. covariance
evals, evecs = np.linalg.eigh(cov) # 3. eigen-decompose (ascending)
order = np.argsort(evals)[::-1] # 4. sort DESCENDING
evals, evecs = evals[order], evecs[:, order]
print(evals.round(4))
print(evecs[:, 0].round(4)) # PC1 directionSorted eigenvalues [1.2840, 0.0491]; PC1 = [0.6779, 0.7352]
Why: eigh returns ascending, so we reverse the order to put the biggest-variance direction first. PC1 points up-and-to-the-right — the cigar's long axis.
| quantity | value (verified) |
|---|---|
| cov | [[0.6166, 0.6154], [0.6154, 0.7166]] |
| eigenvalues (desc) | [1.2840, 0.0491] |
| PC1 direction | [0.6779, 0.7352] |
| PC1 share of variance | 1.2840 / 1.3331 = 96.3% |
Missing information
Discussion prompt
sklearn's PCA centers internally and reports explained_variance_ (the eigenvalues) and components_ (the eigenvectors as rows). Confirm they agree with our from-scratch numbers:
What do you need to know — or decide — before the first line can be written? List everything the problem has to hand you.
Hint: Anything you would have to invent to get started is a thing the problem must supply.
Answer:
Identical to our hand-built PCA. sklearn uses an SVD internally rather than eigh on the covariance, but the answer is the same — that's the eigen-decomposition of the covariance either way.
Worked example
sklearn's PCA centers internally and reports explained_variance_ (the eigenvalues) and components_ (the eigenvectors as rows). Confirm they agree with our from-scratch numbers:
import numpy as np
from sklearn.decomposition import PCA
X = np.array([[2.5,2.4],[0.5,0.7],[2.2,2.9],[1.9,2.2],[3.1,3.0],
[2.3,2.7],[2.0,1.6],[1.0,1.1],[1.5,1.6],[1.1,0.9]])
p = PCA(n_components=2).fit(X)
print(p.explained_variance_.round(4)) # eigenvalues
print(p.components_[0].round(4)) # PC1 directionsklearn variances [1.2840, 0.0491]; PC1 [0.6779, 0.7352]
Why: Identical to our hand-built PCA. sklearn uses an SVD internally rather than eigh on the covariance, but the answer is the same — that's the eigen-decomposition of the covariance either way.
| quantity | from scratch | sklearn PCA |
|---|---|---|
| explained variance | [1.2840, 0.0491] | [1.2840, 0.0491] |
| PC1 direction | [0.6779, 0.7352] | [0.6779, 0.7352] |
| PC1 sign | up-right | up-right (may flip) |
Comparison
Comparison matrix
From Match it against sklearn: refill the sklearn PCA column from what you know. The rest of the table is as it appeared.
| quantity | from scratch | sklearn PCA |
|---|---|---|
| explained variance | [1.2840, 0.0491] | [1.2840, 0.0491] |
| PC1 direction | [0.6779, 0.7352] | [0.6779, 0.7352] |
| PC1 sign | up-right | up-right (may flip) |
Estimation
Predict first
The eigenvalue is a promise: project the centered data onto PC1 and the projected scores should have variance equal to λ₁ = 1.2840. Check it:
Commit before you compute: what does Projecting onto PC1 recovers the variance come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: Variance of the PC1 scores = 1.2840 = λ₁
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. The scores spread out with exactly the eigenvalue's worth of variance — the concrete meaning of 'eigenvalue = variance along that component.'
Worked example
The eigenvalue is a promise: project the centered data onto PC1 and the projected scores should have variance equal to λ₁ = 1.2840. Check it:
import numpy as np
X = np.array([[2.5,2.4],[0.5,0.7],[2.2,2.9],[1.9,2.2],[3.1,3.0],
[2.3,2.7],[2.0,1.6],[1.0,1.1],[1.5,1.6],[1.1,0.9]])
Xc = X - X.mean(axis=0)
cov = (Xc.T @ Xc) / (len(Xc) - 1)
evals, evecs = np.linalg.eigh(cov)
pc1 = evecs[:, np.argmax(evals)] # dominant eigenvector
scores = Xc @ pc1 # project each point onto PC1
print(scores.round(4))
print(round(scores.var(ddof=1), 4)) # variance of the scoresVariance of the PC1 scores = 1.2840 = λ₁
Why: The scores spread out with exactly the eigenvalue's worth of variance — the concrete meaning of 'eigenvalue = variance along that component.'
| quantity | value (verified) |
|---|---|
| first 3 PC1 scores | [0.828, −1.7776, 0.9922] |
| var(scores, ddof=1) | 1.2840 |
| λ₁ from eigh | 1.2840 |
Reverse engineer
Discussion prompt
Work backwards. The example finished here:
Variance of the PC1 scores = 1.2840 = λ₁
What was it asked to do, and what must it have been given? Reconstruct the problem from its answer.
Hint: Every quantity in the result had to enter somewhere. Account for each one.
Answer:
The eigenvalue is a promise: project the centered data onto PC1 and the projected scores should have variance equal to λ₁ = 1.2840. Check it:
Anomaly
Predict first
A student writes this, and it looks reasonable:
Just eigen-decompose XᵀX/(n−1) on the raw data — skip the mean subtraction, it's an extra step.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: That top eigenvector comes out [0.686, 0.727] — which is essentially the data's MEAN direction [0.688, 0.726], not its direction of spread.
Variance is defined around the mean, so center first — always.
Why: That top eigenvector comes out [0.686, 0.727] — which is essentially the data's MEAN direction [0.688, 0.726], not its direction of spread. The 'variance' 8.977 is inflated garbage; the real PC1 variance is 1.284.
Trap
Just eigen-decompose XᵀX/(n−1) on the raw data — skip the mean subtraction, it's an extra step.
cov = XᵀX / (n−1) on un-centered data → top eigenvalue 8.977
Why: That top eigenvector comes out [0.686, 0.727] — which is essentially the data's MEAN direction [0.688, 0.726], not its direction of spread. The 'variance' 8.977 is inflated garbage; the real PC1 variance is 1.284.
Variance is defined around the mean, so center first — always.
Xc = X − X.mean(0), then cov = XcᵀXc/(n−1) → eigenvalue 1.284
Why: Only the centered covariance has eigenvectors that capture spread. sklearn centers internally, so your from-scratch version must do it explicitly or you'll silently measure the offset from the origin instead of the shape of the cloud.
Two truths and a lie
Sort into buckets
Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.
2×2 matrix carries the first half of this lesson. It is symmetric (A = Aᵀ), which will matter enormously.; An eigenvector v of A is a nonzero vector that A only scales — it keeps its direction. The scale factor λ is the eigenvalue for that vector.; λ > 1 stretches, 0 < λ < 1 compresses, λ < 0 flips the arrow around, and λ = 0 collapses that direction to a point — the mark of a singular matrix.[[3,1],[0,2]] turn out to be 3 and 2 — its diagonal. So just read eigenvalues off the diagonal of any matrix.; Eigenvectors form the natural axes of a transform, so they must be perpendicular — orthogonalize or dot them freely.Section
Part 5 of 6 — pattern & checks
Constraint
Discussion prompt
Run The eigen-toolkit with this step confiscated:
Symmetric A: use np.linalg.eigh → real eigenvalues, orthonormal vectors, A = VΛVᵀ (verify by reconstruction)
Is it still possible? If it is, say what takes its place and what it costs you. If it is not, say exactly what that step was providing that nothing else does.
Hint: A step you can drop for free was never load-bearing. If you cannot drop it, name the thing that goes wrong the moment it is gone.
Answer:
det(A − λI) = 0 — sanity-check with sum = trace, product = detλ, solve (A − λI)v = 0; normalize to unit length (sign is arbitrary)np.linalg.eigh → real eigenvalues, orthonormal vectors, A = VΛVᵀ (verify by reconstruction)b ← Ab/‖Ab‖; eigenvalue from the Rayleigh quotient bᵀAb; deflate A − λ₁v₁v₁ᵀ for the next oneXᶜᵀXᶜ/(n−1) → eigh → sort by eigenvalue (= variance) → match sklearnPattern
det(A − λI) = 0 — sanity-check with sum = trace, product = detλ, solve (A − λI)v = 0; normalize to unit length (sign is arbitrary)np.linalg.eigh → real eigenvalues, orthonormal vectors, A = VΛVᵀ (verify by reconstruction)b ← Ab/‖Ab‖; eigenvalue from the Rayleigh quotient bᵀAb; deflate A − λ₁v₁v₁ᵀ for the next oneXᶜᵀXᶜ/(n−1) → eigh → sort by eigenvalue (= variance) → match sklearnEdge cases
Discussion prompt
The eigen-toolkit works on the cases you have just seen. Push it to the edge: what is the most degenerate input it still handles — empty, zero, one item, everything equal — and what is the first case where it stops being true? Name the case, not just "it breaks".
Hint: Try the smallest legal input, then the largest, then the one where two things collide. Methods are specified at their edges; the middle takes care of itself.
Answer:
det(A − λI) = 0 — sanity-check with sum = trace, product = detλ, solve (A − λI)v = 0; normalize to unit length (sign is arbitrary)np.linalg.eigh → real eigenvalues, orthonormal vectors, A = VΛVᵀ (verify by reconstruction)b ← Ab/‖Ab‖; eigenvalue from the Rayleigh quotient bᵀAb; deflate A − λ₁v₁v₁ᵀ for the next oneXᶜᵀXᶜ/(n−1) → eigh → sort by eigenvalue (= variance) → match sklearnElimination
Eliminate the wrong options
The eigenvalues of A = [[4,2],[1,3]] are the roots of which equation?
3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.
Survives elimination: A
Why: det(A − λI) = (4−λ)(3−λ) − (2)(1) = λ² − 7λ + 10 = 0, giving λ = 2 and 5. The off-diagonal product 2·1 must be subtracted, and the trace 7 / det 10 checks confirm it.
Check
Set up the characteristic equation before you answer.
Check your understanding
The eigenvalues of A = [[4,2],[1,3]] are the roots of which equation?
Answer: A
Why: det(A − λI) = (4−λ)(3−λ) − (2)(1) = λ² − 7λ + 10 = 0, giving λ = 2 and 5. The off-diagonal product 2·1 must be subtracted, and the trace 7 / det 10 checks confirm it.
Prediction
Predict first
Which property is guaranteed for a real SYMMETRIC matrix but NOT for a general square matrix?
Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.
Correct: Real eigenvalues and orthonormal eigenvectors
Why: The spectral theorem guarantees real eigenvalues and a full orthonormal eigenbasis for symmetric matrices. A general matrix can have complex eigenvalues and non-orthogonal (or too few) eigenvectors.
Check
Which guarantee actually needs symmetry?
Check your understanding
Which property is guaranteed for a real SYMMETRIC matrix but NOT for a general square matrix?
Answer: A
Why: The spectral theorem guarantees real eigenvalues and a full orthonormal eigenbasis for symmetric matrices. A general matrix can have complex eigenvalues and non-orthogonal (or too few) eigenvectors.
Prediction
Predict first
Power iteration bₖ₊₁ = Abₖ/‖Abₖ‖ converges to the eigenvector with…
Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.
Correct: the largest eigenvalue in absolute value (the dominant one)
Why: Writing b₀ in the eigenbasis, each multiply by A scales the component along λᵢ by λᵢ. The largest-|λ| component grows fastest and dominates, so b converges to that dominant eigenvector; the eigenvalue follows from bᵀAb.
Check
Think about which component grows fastest.
Check your understanding
Power iteration bₖ₊₁ = Abₖ/‖Abₖ‖ converges to the eigenvector with…
Answer: A
Why: Writing b₀ in the eigenbasis, each multiply by A scales the component along λᵢ by λᵢ. The largest-|λ| component grows fastest and dominates, so b converges to that dominant eigenvector; the eigenvalue follows from bᵀAb.
Elimination
Eliminate the wrong options
In PCA, the eigenvalues of the (centered) covariance matrix represent…
3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.
Survives elimination: A
Why: Each eigenvalue is the variance of the data projected onto its eigenvector (principal component) — we verified it: projecting onto PC1 gave scores with variance 1.2840, exactly λ₁. The largest eigenvalue marks the direction of maximum spread.
Check
What does each eigenvalue of the covariance mean?
Check your understanding
In PCA, the eigenvalues of the (centered) covariance matrix represent…
Answer: A
Why: Each eigenvalue is the variance of the data projected onto its eigenvector (principal component) — we verified it: projecting onto PC1 gave scores with variance 1.2840, exactly λ₁. The largest eigenvalue marks the direction of maximum spread.
Section
Part 6 of 6 — the project
Concept
Build the dominant-eigenvector finder, then PCA from scratch, and prove both against NumPy/sklearn. You've derived every piece — now assemble them yourself.
| # | requirement | tool |
|---|---|---|
| 1 | Confirm eigen-pairs of the 2×2 by hand + NumPy | np.linalg.eigh |
| 2 | Power iteration → dominant eigenvector + eigenvalue | loop: b ← Ab/‖Ab‖ |
| 3 | PCA: center → covariance → eigh → sort, vs sklearn | PCA(n_components=2) |
Build rules: type every line yourself, run after each, renormalize inside the loop (or values explode), and compare eigenvector directions, not raw components — the sign is arbitrary.
Counterexample
Discussion prompt
Build the dominant-eigenvector finder, then PCA from scratch, and prove both against NumPy/sklearn. You've derived every piece — now assemble them yourself.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Answer:
Build rules: type every line yourself, run after each, renormalize inside the loop (or values explode), and compare eigenvector directions, not raw components — the sign is arbitrary.
Worked example
Your turn: get the eigenvalues of [[2,1],[1,2]]. Predict them from det(A − λI) = 0 first, then run eigh to confirm.
Hint: use np.linalg.eigh because the matrix is symmetric; it returns eigenvalues in ascending order, so the dominant one is last.
import numpy as np
A = np.array([[2., 1.], [1., 2.]])
vals, vecs = np.linalg.eigh(A)
print(vals)
print(vecs)| output | value |
|---|---|
| eigenvalues (ascending) | [1.0, 3.0] |
| dominant eigenvector (λ=3) | [0.707, 0.707] |
| other eigenvector (λ=1) | [−0.707, 0.707] |
Worked example
Your turn: loop b ← Ab, renormalize, repeat ~6 times from b = [1, 0]. Predict which eigenvector it converges to before you print.
Hint: inside the loop do b = A @ b then b = b/np.linalg.norm(b); read the eigenvalue from b @ A @ b.
import numpy as np
A = np.array([[2., 1.], [1., 2.]])
b = np.array([1., 0.])
for i in range(6):
b = A @ b
b = b / np.linalg.norm(b)
print(b.round(4), round(b @ A @ b, 4))| result | value |
|---|---|
| converged b | [0.7081, 0.7061] |
| Rayleigh bᵀAb | 3.0 |
| true dominant eigvec | [0.7071, 0.7071] |
Worked example
Your turn: center the 10×2 X, build the covariance, eigen-decompose, sort descending, and compare the variances to sklearn. Will the PC1 sign match?
Hint: Xc = X - X.mean(0); cov = Xc.T @ Xc / (len(X)-1); eigh; reverse with [::-1]. Compare evals to PCA().fit(X).explained_variance_.
import numpy as np
from sklearn.decomposition import PCA
X = np.array([[2.5,2.4],[0.5,0.7],[2.2,2.9],[1.9,2.2],[3.1,3.0],
[2.3,2.7],[2.0,1.6],[1.0,1.1],[1.5,1.6],[1.1,0.9]])
Xc = X - X.mean(0)
cov = Xc.T @ Xc / (len(X) - 1)
evals, evecs = np.linalg.eigh(cov)
evals = evals[::-1] # descending
print(evals.round(4))
print(PCA(n_components=2).fit(X).explained_variance_.round(4))| source | explained variance |
|---|---|
| from scratch | [1.2840, 0.0491] |
| sklearn | [1.2840, 0.0491] |
Worked example
Your turn: peel off the dominant direction of the covariance and power-iterate the remainder to recover the second principal component. Predict which eigenvalue you'll land on.
Hint: Cdef = cov - λ₁ · outer(pc1, pc1), then power-iterate Cdef. The Rayleigh quotient should converge to λ₂ = 0.0491.
import numpy as np
X = np.array([[2.5,2.4],[0.5,0.7],[2.2,2.9],[1.9,2.2],[3.1,3.0],
[2.3,2.7],[2.0,1.6],[1.0,1.1],[1.5,1.6],[1.1,0.9]])
Xc = X - X.mean(0)
cov = Xc.T @ Xc / (len(X) - 1)
evals2, evecs2 = np.linalg.eigh(cov)
lam1 = evals2[-1]; pc1 = evecs2[:, -1] # dominant pair
Cdef = cov - lam1 * np.outer(pc1, pc1) # deflate it away
b = np.array([1., 0.])
for _ in range(50):
b = Cdef @ b
b = b / np.linalg.norm(b)
print(b.round(4), round(b @ cov @ b, 4))| result | value |
|---|---|
| PC2 direction | [−0.7352, 0.6779] (⟂ to PC1) |
| variance along PC2 | 0.0491 (= λ₂) |
| PC1 · PC2 | ≈ 0 (orthogonal) |
Trade off
Comparison matrix
From Milestone 4 — deflate for PC2: every row here is a choice with a cost. Fill the value column, then say which row you would actually pick and what you give up for it.
| result | value |
|---|---|
| PC2 direction | [−0.7352, 0.6779] (⟂ to PC1) |
| variance along PC2 | 0.0491 (= λ₂) |
| PC1 · PC2 | ≈ 0 (orthogonal) |
Concept
import numpy as np
from sklearn.decomposition import PCA
def power_iteration(M, iters=50):
b = np.ones(M.shape[0])
for _ in range(iters):
b = M @ b
b = b / np.linalg.norm(b) # renormalize EVERY step
return b, b @ M @ b # eigenvector, eigenvalue
def pca(X, k):
Xc = X - X.mean(0) # center first, always
cov = Xc.T @ Xc / (len(X) - 1)
vals, vecs = np.linalg.eigh(cov)
idx = np.argsort(vals)[::-1][:k]
return vals[idx], vecs[:, idx]
A = np.array([[2., 1.], [1., 2.]])
v, lam = power_iteration(A)
print('power iter:', np.abs(v).round(3), round(lam, 3))| output | value (verified) |
|---|---|
| power_iteration(A) vector | [0.707, 0.707] |
| power_iteration(A) eigenvalue | 3.0 |
| pca(X, 2) variances | [1.2840, 0.0491] |
If power_iteration returns [0.707, 0.707] with eigenvalue 3.0, and your pca matches sklearn's variances [1.2840, 0.0491] — you built the eigen-toolkit from the math up.
Comparison
Comparison matrix
From The full program: refill the value (verified) column from what you know. The rest of the table is as it appeared.
| output | value (verified) |
|---|---|
| power_iteration(A) vector | [0.707, 0.707] |
| power_iteration(A) eigenvalue | 3.0 |
| pca(X, 2) variances | [1.2840, 0.0491] |
Concept
Slides closed, out loud: explain (1) why det(A − λI) = 0 finds eigenvalues, (2) why power iteration converges to the dominant one and what sets its speed, and (3) why PCA needs centering and uses eigh not eig.
Stretch (homework): prove that eigenvectors for distinct eigenvalues of a symmetric matrix are orthogonal. This eigen-thread runs straight into SVD (Lessons 16–18), kernels (Week 10), and the VAE covariance (Week 44).
Connect it up
Draw it
One page, no notation unless you need it: draw how these connect — The eigen-equation · The spectral theorem · Power iteration · Eigenvectors are PCA · The eigen-toolkit · Your turn: build it. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.
Recap
Av = λv as pure stretch, and find eigenvalues via det(A − λI) = 0 — checked against trace and determinant(A − λI)v = 0 by hand and verify Av = λv, remembering the sign is arbitraryA = VΛVᵀ (confirmed by reconstruction)bᵀAb, and deflate for the next eigenpaireigh → sort by variance — and match sklearn.PCA| idea | the one thing to remember |
|---|---|
| eigenvalues | roots of det(A − λI) = 0; sum = trace, product = det |
| diagonal = eigenvalues | only for triangular matrices |
| orthonormal eigenvectors | only guaranteed if A is symmetric |
| power iteration | renormalize every step; converges to largest |λ| |
| PCA | center first; eigenvalue = variance; use eigh |
Want this taught 1-on-1? Alexander tutors Machine Learning — $55/session, free consultation.