Notebook 12: matrix factorizations, and what each one costs - #59
Conversation
Fills in the teaching body of the first take-home deep dive, and adds the handbook appendix that mirrors it. The notebook answers the two questions the workshop never got to: which factorization to reach for, and what it costs. Six of them — LU, Cholesky, QR, eigendecomposition, SVD, NMF — framed as constrained optimizations, then a cost table derived from flop counts and a second one the reader measures. Four exercises, five widgets, and every number in the prose executed and checked against a real run: - Degree-10 polynomial on the real airline series, fit by the normal equations and by QR. kappa(X) = 2.16e7, kappa(XtX) = 4.65e14, and coefficient errors of 1.95e-03 against 2.04e-14 — while the residuals agree to six decimals, so the check most people run sees nothing. - The 1797x1797 RBF kernel system of the digits, solved 200 ways: factoring once is ~350x a per-column refactor and ~4x an explicit inverse, which is also less accurate. - A size sweep whose fitted log-log slopes land near 2.2-2.5 rather than 3, with the honest reason (parallelism and cache reuse grow with n) and the prediction that does survive — the SVD/Cholesky ratio, ~32x against 39x. - Eckart-Young checked to 1e-13 on the astronaut image, a rank explorer that separates numbers stored from bytes stored, and a dB-threshold exercise. - Digits truncated to rank k feeding a least-squares classifier, where the best held-out accuracy sits at k = 41, above full rank: truncation as regularization, not just as compression. - NMF against the SVD at rank 16: worse by four points of error, with over 80% of its component entries at zero against the SVD's 21%. No new data — the images, digits and airline series are all already in the workshop. Ran top to bottom under nbconvert with no errors. Handbook: adds Appendix E with the cost table and TODO blocks mirroring the notebook's exercises. Appendix D keeps its Monte Carlo take-home, which Appendix E does not cover, but loses the framing paragraph that now duplicates it and points across instead. Facilitator Notes moves to Appendix G, leaving F for the tensor-factorizations deep dive (#55), and slides/README.md's pointer at it is corrected — it named Appendix D, which was already wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DP1TzSePjs3YLNLKJF2Zrw
The header generated from `_variables.yml` claims the reader will be able to explain "what each factorization assumes about its input, and what it does when that assumption fails", and to "compare cost against numerical stability". Neither was in the body. Adds §12.1's assumption-failure cell, on real data: Cholesky raises on the covariance of 20 digit pixels (one is constant, so it is singular), NMF raises on data shifted negative — and `eigh` on a non-symmetric pixel block returns eigenvalues with no error at all, because it mirrors the lower triangle and factorizes a matrix you never built. That asymmetry is the point: the ones that refuse are doing you a favour. Exercise 1 gains the cost half of its comparison. The normal equations are not merely the naive choice, they are the cheaper one — about `mn² + n³/3` against QR's `2mn² − 2n³/3`, roughly half the work for a tall matrix. Saying only that they are unstable makes the trap look easy to avoid. Also qualifies the handbook's new "§1.4" as "Part I §1.4". Everywhere else in that document `§` means a section of the Deep Learning chapter, so a bare §1.4 reads as a book reference that does not exist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DP1TzSePjs3YLNLKJF2Zrw
m_img/n_img were assigned and never read — the print uses image.shape and the exercise-4 solution takes its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DP1TzSePjs3YLNLKJF2Zrw
- Appendix F was a hole in the lettering. The handbook shipped A, B, C, D, E, G, with F reserved for #55, so a reader saw a skipped letter and two pointers at "Appendix G" past a gap. F is now a short reserved stub naming what lands there, which makes the sequence deliberate. - budget() quoted square-only formulas for rectangular input. Cholesky, LU and the eigendecomposition are n^3/3, 2n^3/3 and 9n^3, but the widget evaluated all six over whatever the sliders held — so at its own default of 10,000 x 500 it printed a flop count, a factor size and a compression ratio for three factorizations that do not exist for that shape. They now print "n/a — needs a square matrix" when m != n. This is the one cell inviting a reader to trust it on a matrix the workshop has never seen. - The eig/eigh cell took `.real` off eig's output and called it "correct", which is the very habit the cell exists to warn against — silently dropping imaginary parts on a non-symmetric matrix. It now prints the largest imaginary part so the reader sees the discard is justified here, notes that a rotation matrix genuinely has complex eigenvalues, and sorts both rows so they actually line up (eig returns unordered, eigh ascending). - The sweep and the calibration drew from the shared rng, so re-running any earlier cell changed the matrices they were timed on. Both now take private generators, matching what the two data splits already did. - Appendix E's TODO 4 snippet divided by an exact zero without the errstate guard the notebook version has. - "SVD/Cholesky ~31x" is a machine property — two runs here gave 31.3x and 31.8x — so it now reads as landing well under the predicted 39x. Re-executed under nbconvert: 46 cells, zero errors. All ten checks pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DP1TzSePjs3YLNLKJF2Zrw
project-delphi
left a comment
There was a problem hiding this comment.
Automated review — /code-review (medium effort)
Reviewed all three commits at the time of review — 4cfa900, f654c7a, 781408a — against main.
The reviewer re-executed the numeric claims independently rather than trusting the PR body. Every quoted figure reproduced: κ(X)=2.16e7 / κ(XᵀX)=4.65e14, coefficient errors 1.95e-03 vs 2.04e-14 with residuals identical to six decimals; degree-12 is indeed where the residual itself degrades (549.76 vs 503.69); Eckart–Young equality to ≤5e-14 at k=5…100; rank 16 → 21.09 dB at 12.5% int16 bytes, rank 50 → 27.35 dB at 39.1%, rank 128 → 100.1%; needed_rank → [13, 35, 72, 124] with the k=35 verification at 25.13 dB against 24.96 one rank lower; digits best held-out accuracy 0.950 at k=41, above full-rank 0.937; NMF 0.2567 vs SVD 0.2180 error with 83.2% vs 21.0% near-zero entries. Cell 10 behaves as described (Cholesky raises, NMF raises, eigh silently returns the mirrored matrix's spectrum). check_links.py passes all ten checks, and docs/notebooks/12-…ipynb is byte-identical to notebooks/ at every commit.
Critical Issues
None.
Important Issues
tensors_workshop_plan_with_quizzes.md — the appendix lettering ships with a hole at F. The handbook runs A, B, C, D, E, G. Appendix F is reserved for the tensor-factorizations deep dive in #55, which is not merged, so what Pages serves today has a visibly skipped letter, and slides/README.md:34 plus the facilitator note at line 8 both point readers at "Appendix G" past a gap. Flagged rather than blocking: the deviation is intentional per the PR body, but the PR body only defends keeping Appendix D, not the gap itself.
Suggestions
budget()applies square-only formulas to non-square input with no caveat.FLOPS["Cholesky"],["LU"]and["Eigendecomposition"]aren³/3,2n³/3,9n³, butbudget()evaluates all six over whatever the sliders hold. At the defaultm = 10,000, n = 500it prints a flop count, factor size and compression ratio for a Cholesky, an LU and an eigendecomposition of a 10,000×500 matrix — none of which exist. §12.7 is explicitly framed as "the matrix you will bring tomorrow", so this is the one cell where a learner is invited to trust output on unfamiliar shapes.- Appendix E's TODO 4 snippet has an unguarded divide-by-zero.
db = 10 * np.log10(1.0 / (tail / A.size))divides bytail[-1], exactly0.0by construction. The notebook wraps the identical line innp.errstate(divide="ignore"); the handbook copy does not, so pasting it emits a RuntimeWarning and raises undernp.seterr(divide="raise"). np.linalg.eig(block)[0].realis labelled "(correct)". Real for this fixed block, so the output is right — but.realon a general non-symmetric matrix silently discards imaginary parts, which is precisely the failure mode the cell exists to warn about. Alsoeigreturns eigenvalues unordered whileeighreturns them ascending, so the two printed rows do not correspond element-wise even though the layout invites comparison.- Number drift across the three places the same measurement is quoted (handbook "~31x", PR body "~32x", prose "2.2 to 2.9" vs "near 2.4"). These are machine properties, and the facilitator note already declares Appendix E's timings non-reproducible.
- Shared
rngis consumed in stream order by cells 19, 21, 23 and 41, so re-running any one shifts the matrices every later cell receives — the §12.3 sweep and §12.7 calibration are not reproducible across a non-linear execution order. The notebook already works around this twice with private generators.
Strengths
- The
#appendix-e-…anchors resolve correctly under Quarto's id generation (em dash collapsed, not doubled), verified against the committed HTML, and check 3 covers them going forward. - Cells 16 and 21 rebind
X/yandsq/D2/Gfrom the folded solutions with an explicit comment, which is what keeps check 9 green across 162 visible cells. needed_rankcomputes the whole PSNR-vs-k curve from one SVD via a reversed cumulative sum instead of rebuilding 512 truncations — the exercise's stated point, correctly implemented, including theerrstateguard on the terminal zero.- The §12.6 sparsity headline survives the obvious objection: re-measured with a per-component threshold instead of the global one gives 79.4% vs 19.9%, so the 83%/21% claim is not an artifact of NMF's scale indeterminacy.
docs/notebooks/stayed in sync through all three commits, including two mid-review pushes — the gate added in #58 is doing its job.
What I did about it
Zero Critical Issues, so this is mergeable. I fixed the Important issue and all five Suggestions rather than deferring them — see the follow-up commit:
- Appendix F is now a short reserved stub pointing at the notebook, so the sequence reads as deliberate instead of broken.
budget()printsn/a — needs a square matrixfor Cholesky, LU and the eigendecomposition whenm != n, with a line saying why.- Appendix E's snippet gained the
errstateguard the notebook already had. - The
eig/eighcell now prints the largest imaginary part (so dropping it is justified rather than assumed, with a note that a rotation matrix genuinely has complex eigenvalues) and sorts both rows so they line up. - The sweep and calibration cells take private generators, matching the pattern the notebook already used for the two splits.
- The handbook's "~31x" is now "well under the predicted 39x" — the ratio is a machine property and two runs here gave 31.3x and 31.8x.
Closes #54.
Fills in the teaching body of
notebooks/12-matrix-factorizations.ipynb— the stub from #53 — and adds the handbook appendix that mirrors it.The notebook
44 cells: four exercises with folded solutions, five widgets, and a bilingual body throughout. Six factorizations framed as constrained optimizations (§12.1), a cost table derived from Trefethen & Bau's flop counts (§12.2), and a second cost table the reader measures for themselves (§12.3).
Every number quoted in the prose was executed and checked against a full
nbconvert --executerun, which completes with zero errors and zero warnings:κ(X) = 2.16e7,κ(XᵀX) = 4.65e14, coefficient errors1.95e-03(normal equations) against2.04e-14(QR) — while the residuals agree to six decimals, so the check most people run sees nothing wrong1e-13on the astronaut image; rank explorer separating numbers stored from bytes stored (int16vsfloat32)kfeeding a least-squares classifier: best held-out accuracy atk = 41, above full rank. Truncation as regularization, not just compressionNo new datasets. The images, digits and airline series are all already in the workshop; the airline CSV is the one notebook 08 fetches.
Handbook
Adds Appendix E — Matrix Factorizations: Which One, and What It Costs: framing paragraph, the cost table, four
# TODOblocks mirroring the notebook's exercises, and a Colab link. Facilitator Notes moves to Appendix G, leaving F for the tensor-factorizations deep dive (#55). Cross-links added from §1.4's factorization map and the wrap-up's "Where to go next".slides/README.mdpointed at "Appendix D's facilitator guidance" — already the wrong letter — now corrected to G.One deviation from the issue, flagged
The issue asked to shrink Appendix D to a pointer, on the grounds that it is "a one-exercise version of material this appendix covers properly." That is true of D's framing but not of its content: D is about Cholesky as a sampler (correlated Monte Carlo, portfolio tails, with quantified verified results), and Appendix E covers Cholesky as a solver. Nothing in E replaces it.
So D keeps its exercises and solution, and loses only the framing paragraph that E now duplicates — replaced by a cross-link naming the split explicitly. Happy to cut it entirely if the redundancy was the point rather than the reasoning; say so and I will.
Checks
All ten checks in
check_links.pypass, including the byte-identicaldocs/notebooksgate from #58 and the internal-fragment check that validates the new appendix anchor.gen_tables.pyandgen_notebooks.pyare idempotent on the result. Rendered with Quarto 1.6.40 anddocs/is committed.🤖 Generated with Claude Code
https://claude.ai/code/session_01DP1TzSePjs3YLNLKJF2Zrw