Phase 3 Projects¶
Three projects. Each one is designed to force a specific type of understanding that reading cannot provide. The acceptance criteria are binary — either the artifact exists and meets the bar, or it doesn’t. “I understand it conceptually” is not a passing criterion for any of these.
Estimated total time: 30–45 hours across Month 7–8, working 10–15 hrs/week alongside full-time work. Sequence them in order — each builds on the previous.
Project 1: nanoGPT — Character-Level Transformer from Scratch¶
What you’re building: A decoder-only transformer trained on Shakespeare’s complete works, generating character-by-character text. Based on Andrej Karpathy’s nanoGPT (github.com/karpathy/nanoGPT).
Why this project, not just the tutorial: Writing your own transformer forces you to confront exactly where your attention mechanism understanding breaks down. The 20 bugs you will fix in forward() are more educational than 20 hours of reading.
Specification¶
Dataset: Tiny Shakespeare (~1MB), available at:
wget https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt
Architecture:
Context length: 256 characters
Embedding dim: 384
Layers: 6
Heads: 6 (d_head = 64)
Feedforward dim: 4 × 384 = 1536
Dropout: 0.2
Total parameters: ~10M
Training:
Optimizer: AdamW, lr=3e-4, weight_decay=0.1
Batch size: 64
Steps: 5000 (GPU), 2000 (CPU — expect lower final performance)
Device: any — but record what you trained on
Acceptance Criteria (all must be met):
Implementation is original — not copy-pasted from nanoGPT source. Reference the architecture; write the code yourself.
Training loss curve shows clear convergence (save a plot)
Validation perplexity < 3.5 after full training on GPU (< 5.0 on CPU acceptable)
Model generates Shakespeare-style text that a reader could identify as “Shakespeare-like” even if grammatically imperfect
Post a model card on HuggingFace Hub (
RaghulR2003/nanogpt-shakespeareor similar) with: training loss curve, sample generated text (200+ characters), hardware used, training time
Implementation checklist (things that bite people):
Causal mask applied correctly — future tokens must not influence current predictions (use
torch.tril)Positional encoding added to token embeddings, not concatenated
Weight tying: input embedding and output projection share weights (reduces parameters, improves performance)
model.eval()andtorch.no_grad()during generationTemperature sampling works: temperature < 1.0 makes output more deterministic; > 1.0 makes it more random
What you’ll learn from the bugs:
The causal mask is the difference between encoder and decoder — understanding where it goes wrong makes the architecture concrete
Numerical stability of softmax with large logits (you will likely hit NaN at some point; track it down)
Why the √d_k scaling matters: remove it and watch your training destabilize
Common failure mode to diagnose: If perplexity plateaus above 4.5, check: (1) learning rate — try 1e-4 and 1e-3 to bracket, (2) causal mask correctness, (3) whether positional encoding is being added or concatenated.
Project 2: Domain-Specific Fine-Tuning (BERT or ViT)¶
What you’re building: A fine-tuned pretrained model that outperforms a classical ML baseline on a task from your actual work domain or personal interest. The requirement that it be domain-specific is intentional — it forces you to deal with real data preprocessing, class imbalance, and evaluation design rather than running on a clean benchmark.
Candidate tasks (pick one or define your own):
Text classification: Zoho CRM support ticket categorization, code comment intent classification, Stack Overflow tag prediction
Image classification: product defect detection from photos, document type classification, satellite image land-use
NER/information extraction: extracting entities from engineering documents, parsing structured information from resumes
Required baseline: Train a classical ML model (logistic regression, SVM, or gradient boosting) on the same task using hand-crafted features (TF-IDF for text, HOG for images, etc.). Record its performance. Your fine-tuned model must beat this baseline by a statistically meaningful margin (≥2% on the primary metric).
Specification¶
For text tasks — use BERT:
Model:
bert-base-uncasedor a domain-appropriate variant (bert-base-casedfor proper nouns,roberta-basefor cleaner training)Fine-tuning strategy: full fine-tuning (all layers) for datasets > 1000 samples; LoRA (via
peftlibrary) if compute is constrainedEvaluation metric: match metric to task (accuracy for balanced, F1-macro for imbalanced, MCC for severely imbalanced)
For image tasks — use ViT:
Model:
google/vit-base-patch16-224(pretrained on ImageNet-21K)Fine-tuning: last 4 transformer blocks + classification head minimum; full fine-tuning if >5K training samples
Data augmentation: RandomHorizontalFlip + ColorJitter + RandomRotation(15°) — these matter for small datasets
Acceptance Criteria (all must be met):
Classical ML baseline trained and evaluated on same train/test split (document exact split — use
random_state=42)Fine-tuned model exceeds baseline by ≥2% on primary metric
Confusion matrix generated and analyzed — identify which classes are hardest and why
Model pushed to HuggingFace Hub with a model card that includes: task description, dataset description (n_train, n_test, class distribution), baseline vs. fine-tuned comparison table, inference example
Inference works from a fresh Python environment with
from transformers import pipeline
What you’ll learn from the friction:
HuggingFace Trainer handles a lot, but custom evaluation loops reveal where the abstraction leaks
Tokenization for text has edge cases: truncation strategy, special token placement, handling long documents (BERT max 512 tokens — what do you do with 2000-token documents?)
For images: ViT’s ImageProcessor expects specific normalization — using raw PIL images without it will produce silently wrong results
Failure diagnosis: If fine-tuned model doesn’t beat the baseline, check in this order: (1) learning rate too high (try 2e-5), (2) train/test split is different between baseline and fine-tuned (data leakage in classical ML baseline), (3) insufficient epochs (BERT typically needs 3–5 epochs; run early stopping with patience=2), (4) class imbalance not handled.
Project 3: DDPM on MNIST — Generative Model from Scratch¶
What you’re building: A complete DDPM (Denoising Diffusion Probabilistic Model) implementation that generates recognizable MNIST digits. The code from 04_diffusion_models_foundations.md is your starting point; this project requires you to understand it well enough to debug, modify, and evaluate it.
Why this exact benchmark: MNIST is small enough to train on CPU overnight, large enough to produce meaningful results, and familiar enough that anyone can evaluate sample quality visually. This is the canonical “proof of concept” for diffusion.
Specification¶
Architecture:
UNet as described in
04_diffusion_models_foundations.mdTime embedding dimension: 256
Channel sequence: 1 → 64 → 128 → 256 (encoder); mirror (decoder) with skip connections
Training:
Dataset: MNIST training set (60,000 images), normalized to [-1, 1]
Noise schedule: linear, β₁=1e-4 to β_T=0.02, T=1000
Optimizer: AdamW, lr=2e-4
Batch size: 128
Epochs: 50 (GPU: ~20 minutes; CPU: ~2 hours)
Evaluation — three required artifacts:
Forward process visualization (
forward_process.png): A single MNIST digit shown at t = 0, 100, 200, 300, 500, 700, 900, 999. Demonstrates the noising schedule works as expected.Training loss curve (
training_loss_curve.png): x-axis = epoch, y-axis = MSE loss. Loss must show clear monotonic decrease from ~0.08 to below 0.015. A flat loss curve means your noise or gradient flow is broken.Generated samples grid (
generated_samples.png): 8×8 grid of 64 generated samples. A naive observer (non-ML person) should identify ≥50% of the images as “handwritten digit-like.”
Acceptance Criteria (all must be met):
All three artifacts generated and committed to a public GitHub repository
Training loss converges below 0.015 (not just decreasing — must actually converge)
Visual inspection: ≥50% of 64 generated samples identifiable as digits to a non-ML person
README.md in the repo includes: model architecture summary, training configuration, loss curve, sample grid, training hardware and time
Bonus (not required): implement DDIM sampling (50 steps) and show it produces comparable samples — add the comparison to the README
Debugging guide — the failure modes you will encounter:
Symptom |
Root Cause |
Fix |
|---|---|---|
Loss NaN after a few steps |
Gradient explosion in UNet |
Gradient clipping: |
Loss converges but samples are noise |
Sampling loop incorrect |
Check DDPM Algorithm 2 step-by-step; verify |
All samples identical |
Collapsed stochastic term |
Verify |
Blurry smears, not digits |
Loss too low (overfit to mean) |
This paradox is real — try adding EMA of model weights |
Loss plateaus at 0.025+ |
Learning rate too high |
Try lr=5e-5 |
Forward process visualization incorrect |
|
Check cumulative product: print |
What you’ll learn that the paper doesn’t state clearly: The UNet’s time embedding injection is the mechanism that makes the network a conditional denoiser rather than a general denoiser. If you remove the time embedding and train, the model will try to denoise images at all noise levels simultaneously with the same parameters — it will fail because t=1 (lightly noised) and t=999 (pure noise) require completely different operations. The time embedding is how you turn one network into 1000 specialized denoisers that share weights.
Delivery Summary¶
When all three projects are complete, you will have:
Artifact |
Location |
Demonstrates |
|---|---|---|
nanoGPT repo + model card |
HuggingFace Hub |
Transformer internals understood at implementation level |
Domain fine-tune + model card |
HuggingFace Hub |
Transfer learning pipeline in production context |
DDPM repo + visuals |
GitHub |
Generative model math → working implementation |
These three artifacts together constitute a stronger signal of Phase 3 mastery than any written exam. They are the portfolio you bring to a PhD application interview, a senior ML role conversation, or a research collaboration. They also happen to be exactly the kind of evidence that distinguishes someone who read papers from someone who shipped working implementations.
Return to [README.md] · Previous: [05_graph_neural_networks.md]