Labs¶
Hands-on labs accompany every volume. Each lab targets a specific skill and is graded by observable, reproducible output.
Lab Structure¶
Every lab includes:
- Objective — One sentence stating exactly what you will build
- Prerequisites — Chapters that must be completed first
- Dataset — Source, size, description
- Expected Output — What a correct solution produces
- Starter Code — Scaffold provided so you focus on the core concept
- Solution — Full reference solution (attempt the lab first!)
- Extension Challenges — 2-3 stretch goals for advanced students
Lab Index¶
| Lab | Volume | Skill | Difficulty |
|---|---|---|---|
| Lab 01: NumPy from Scratch | Vol 1 | Scientific Python | Beginner |
| Lab 02: Linear Regression from Scratch | Vol 3 | ML Fundamentals | Beginner |
| Lab 03: Logistic Regression | Vol 3 | Supervised Learning | Intermediate |
| Lab 04: Decision Tree Implementation | Vol 3 | Supervised Learning | Intermediate |
| Lab 05: k-Means Clustering | Vol 3 | Unsupervised Learning | Intermediate |
| Lab 06: MLP in NumPy | Vol 4 | Neural Networks | Intermediate |
| Lab 07: PyTorch CNN — CIFAR-10 | Vol 4 | CNNs | Intermediate |
| Lab 08: Text Classification with LSTM | Vol 4 | RNNs | Intermediate |
| Lab 09: Attention from Scratch | Vol 5 | Transformers | Advanced |
| Lab 10: Fine-Tune BERT for Classification | Vol 5 | Pre-trained Models | Advanced |
| Lab 11: Build a RAG Pipeline | Vol 7 | RAG | Advanced |
| Lab 12: Agentic Search Tool | Vol 8 | AI Agents | Advanced |
| Lab 13: Deploy an LLM with FastAPI | Vol 9 | MLOps | Advanced |
| Lab 14: Monitor LLM Quality with Langfuse | Vol 9 | Monitoring | Advanced |
Lab 01: NumPy from Scratch¶
Objective: Implement matrix multiplication, broadcasting, and a linear algebra solver using only NumPy — no SciPy, no sklearn.
Prerequisites: Vol 1 Ch 2 (Mathematics), Vol 1 Ch 3 (Python Tools)
Dataset: None (synthetic data)
Tasks:
- Implement
matmul(A: np.ndarray, B: np.ndarray) -> np.ndarraywithout using@ornp.dot - Implement
cosine_similarity(a: np.ndarray, b: np.ndarray) -> float - Implement PCA from scratch: centre, compute covariance matrix, eigendecompose, project
- Verify your PCA matches
sklearn.decomposition.PCAon the Iris dataset
# Starter code
import numpy as np
from sklearn.datasets import load_iris
def matmul(A: np.ndarray, B: np.ndarray) -> np.ndarray:
"""Matrix multiply A @ B without using np.dot or @."""
assert A.shape[1] == B.shape[0], "Incompatible shapes"
# Your implementation here
...
def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
"""Cosine similarity between two 1-D vectors."""
...
def pca(X: np.ndarray, n_components: int) -> np.ndarray:
"""Project X onto its top n_components principal components."""
...
Expected Output:
matmul matches np.dot: True
cosine_similarity([1,0,0], [1,0,0]) = 1.0
cosine_similarity([1,0,0], [0,1,0]) = 0.0
PCA variance explained: [0.926, 0.053, 0.017, 0.005]
Custom PCA matches sklearn: True
Lab 02: Linear Regression from Scratch¶
Objective: Implement linear regression using the normal equation and gradient descent; compare them on the California Housing dataset.
Prerequisites: Vol 1 Ch 2, Vol 3 Ch 1
# Starter code
import numpy as np
from sklearn.datasets import fetch_california_housing
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
class LinearRegressionNormal:
"""Linear regression via the normal equation."""
def fit(self, X: np.ndarray, y: np.ndarray) -> "LinearRegressionNormal":
...
def predict(self, X: np.ndarray) -> np.ndarray:
...
class LinearRegressionGD:
"""Linear regression via gradient descent."""
def __init__(self, lr: float = 0.01, epochs: int = 1000) -> None:
...
def fit(self, X: np.ndarray, y: np.ndarray) -> "LinearRegressionGD":
...
def predict(self, X: np.ndarray) -> np.ndarray:
...
Extension: Add Ridge regularisation (\(\lambda ||w||^2\)) to both implementations and plot the regularisation path.