Rung 1: Math From Scratch¶
Month 2 | Phase 0: Foundations | ~25–32 hours total
This is the first public artifact you produce, and its job is singular: prove that you understand the mathematics behind what you’re doing rather than just calling library functions. The bar is not “does it work?” — sklearn.decomposition.PCA() works. The bar is “can you explain every line in terms of the underlying linear algebra, and can you prove your implementation is correct?” A hiring manager who opens this repository should finish with zero doubt that you understand eigendecomposition, gradient flow, and the connection between optimization geometry and loss landscape curvature.
What to Build¶
You are building two implementations in a single GitHub repository, each with full mathematical documentation.
Implementation 1: PCA From Scratch¶
Build a PCA class using only NumPy — no sklearn, no scipy decomposition shortcuts, no torch.
The implementation must execute this exact mathematical pipeline:
Mean centering: subtract the column-wise mean from the data matrix
X(shape:n_samples × n_features)Covariance matrix: compute
C = X_centered.T @ X_centered / (n - 1). The/n-1is not cosmetic — explain why in your notes.Eigendecomposition: call
np.linalg.eigh(C)— notnp.linalg.eig. You must explain in comments: (a) whyeighis correct for symmetric matrices (Hermitian eigenvalue problem), (b) whateighguarantees about the output thateigdoes not (real eigenvalues, orthogonal eigenvectors), (c) why this matters for numerical stability.Sort descending: sort eigenvalues and corresponding eigenvectors by eigenvalue magnitude, largest first.
Select top-k: slice the first
keigenvectors to form the projection matrixW(shape:n_features × k).Project: compute
X_projected = X_centered @ WReconstruct: compute
X_reconstructed = X_projected @ W.T + mean
Visualizations required (both must be generated by running a script):
Scatter plot of 2D synthetic data with the two principal component vectors overlaid as arrows, scaled by their eigenvalues
Explained variance ratio bar chart (showing cumulative explained variance as a secondary axis)
Reconstruction error curve: plot reconstruction MSE as a function of number of components retained (from 1 to
n_features)Side-by-side MNIST digit (28×28): original vs. reconstruction at k=10, k=50, k=100, k=200 components
Implementation 2: Gradient Descent Family From Scratch¶
Implement all four optimizer variants using only NumPy. Apply them to both a 2D quadratic toy problem (for visualization) and logistic regression on a real dataset.
Optimizers to implement (each in its own class or function, not monolithic):
Optimizer |
Key parameters |
What to verify |
|---|---|---|
Vanilla GD |
|
Convergence on convex loss |
Momentum (SGD + momentum) |
|
Faster convergence than vanilla, oscillation behavior |
RMSProp |
|
Adaptive LR behavior, why epsilon matters |
Adam |
|
Bias correction in early steps, why it matters |
For Adam specifically: implement the bias-corrected moment estimates explicitly and add a comment explaining that without bias correction, the first step would have massively inflated gradient estimates due to zero initialization. This is the part most people skip. Don’t skip it.
Visualizations required:
Contour plot of 2D quadratic loss surface with optimization trajectories for all 4 optimizers overlaid (different colors), showing starting point and convergence path
Loss vs. iteration curves for all 4 optimizers on the same axes, with logarithmic y-axis
Final accuracy on the logistic regression task for all 4 optimizers in a bar chart with error bars (run 3 seeds each)
Datasets to use:
Toy 2D: generate a quadratic bowl with
f(x,y) = 0.5*(x-2)^2 + 2*(y+1)^2(asymmetric to show differential convergence behavior)Real: use
sklearn.datasets.make_classification(n_samples=1000, n_features=20)— no downloading required
Acceptance Criteria¶
This rung is complete when every criterion below is satisfied. Not most. Every.
git clone <repo> && pip install -r requirements.txt && python scripts/run_pca.pyproduces all PCA figures. No errors, no warnings, no “please run the notebook first.”python scripts/run_optimizers.pyproduces all optimizer figures. Same standard.pytest tests/passes. All tests green.tests/test_pca_correctness.pyverifies that your PCA output matchessklearn.decomposition.PCAto within absolute tolerance1e-5for eigenvalues and1e-5for projected values (accounting for sign ambiguity:abs(your_result) ≈ abs(sklearn_result))tests/test_optimizer_convergence.pyverifies that all 4 optimizers converge to loss < 0.1 on the toy quadratic within 500 iterations withlr=0.01Every non-trivial function has a docstring. Every eigendecomposition call, every gradient update, every moment update has an inline comment explaining the mathematical operation.
A
MATH_NOTES.mdfile exists (or equivalent README section) containing derivations for: (a) why covariance matrix eigendecomposition gives principal components, (b) the full Adam update rule with bias correction derived from first-moment and second-moment estimation, (c) what “explained variance ratio” means and how to compute it from eigenvaluesThe repository has ≥ 8 commits — not a single-commit dump. The commit history should tell the story of development.
No Jupyter notebooks as primary deliverable. One exploratory
notebooks/exploration.ipynbis acceptable but the.pyfiles are what counts.README opens (on GitHub, without scrolling) to: repo title, one-sentence description, two preview images, run instructions in ≤ 5 lines.
Hard stop gate question: If you cannot answer “why is np.linalg.eigh used instead of np.linalg.eig for symmetric matrices, and why does it matter numerically?” without looking it up — the rung is not complete. This is not an obscure fact. It is a direct consequence of the Spectral Theorem for real symmetric matrices.
Repository Structure¶
ml-from-scratch/
├── README.md ← primary signal to employers
├── MATH_NOTES.md ← mathematical derivations
├── requirements.txt ← numpy, matplotlib, scikit-learn (tests only), pytest
├── pca/
│ ├── __init__.py
│ ├── pca.py ← PCA class
│ └── visualization.py ← all PCA plotting functions
├── optimizers/
│ ├── __init__.py
│ ├── base.py ← base optimizer class/interface
│ ├── vanilla_gd.py
│ ├── momentum.py
│ ├── rmsprop.py
│ ├── adam.py
│ └── visualization.py ← loss curve and contour plotting
├── models/
│ └── logistic_regression.py ← pure NumPy logistic regression
├── data/
│ └── generators.py ← synthetic data generation (no large files)
├── figures/ ← pre-generated PNGs committed for README display
│ └── .gitkeep
├── scripts/
│ ├── run_pca.py
│ └── run_optimizers.py
├── tests/
│ ├── test_pca_correctness.py
│ └── test_optimizer_convergence.py
└── notebooks/
└── exploration.ipynb ← optional, not the primary deliverable
README Opening Template¶
The first visible content on your GitHub repo page must be:
# ML From Scratch: PCA and Gradient Descent
Implementing Principal Component Analysis and four gradient descent variants
(Vanilla GD, Momentum, RMSProp, Adam) using only NumPy — no sklearn, no
autograd, no shortcuts. Every line is documented against its mathematical basis.
**Purpose:** Demonstrate that I understand what the library calls are doing,
not just that I can call them.
| [PCA demo](figures/pca_components.png) | [Optimizer trajectories](figures/optimizer_contours.png) |
|---|---|
|  |  |
## Quick Start
pip install -r requirements.txt
python scripts/run_pca.py # generates figures/pca_*.png
python scripts/run_optimizers.py # generates figures/optimizer_*.png
pytest tests/ # verify correctness
## What's Implemented
...
What This Signals to Employers¶
A senior engineer or hiring manager who opens this repository for 3 minutes should conclude three things:
“This person understands the mathematics” — evidenced by derivations in MATH_NOTES.md and inline mathematical comments
“This person writes reproducible, structured code” — evidenced by the project structure, requirements.txt, and scripts that run without a prayer
“This person can communicate technical work” — evidenced by the README and the quality of the docstrings
This is the foundation signal. It does not get you hired on its own — it prevents elimination in the first 30-second scan. A portfolio without this rung has no legitimate claim to “first principles understanding.” Every subsequent rung is built on the credibility this one establishes.
Time Estimate¶
Task |
Estimated Hours |
|---|---|
PCA implementation + debugging + verification |
7–9 |
Optimizer implementations (all 4) + debugging |
8–10 |
Visualization code for both modules |
4–5 |
Tests (correctness + convergence) |
2–3 |
MATH_NOTES.md derivations |
3–4 |
README + repo structure cleanup |
2–3 |
Total |
26–34 hours |
Practical schedule: ~3 hours per session × 9–11 sessions across Month 2. Do not compress this into a weekend — the distributed practice is where understanding is built.
What Weakens This Rung¶
These are the specific failure modes that reduce this rung from “strong evidence” to “I built a thing once”:
Notebook as final deliverable: immediately signals “I explored, I didn’t engineer.” Notebooks are drafts. The
.pyfiles are the product.No tests: a correctness test against sklearn takes 25 lines and proves your implementation is mathematically right. Skipping it implies you don’t know if it is.
Thin README: if a hiring manager must open source code to understand what the project does, the README failed its primary job.
Missing bias correction in Adam: everyone who copied Adam from a tutorial skipped this. Implementing and explaining it is how you prove you didn’t.
Skipping the optimizer trajectory visualization: this is the single most visually impressive artifact in this rung, and it’s also the one that most concisely demonstrates you understand optimization geometry. Skipping it because it’s hard is exactly the wrong decision.
Magic numbers without comment:
lr=0.001, beta1=0.9, beta2=0.999with no annotation communicates that you copied a config file. Three lines explaining where these values come from communicates that you read the paper.Single massive commit: tells reviewers the repo was created the night before the interview, not developed over a month. Commit incrementally as you build.
Return to README.md · Next: 02_rung_2_classical_ml_battle.md