Skip to main content
Milestone #05Not StartedPhase 3Advanced16–22 hours · 2 weeks14 required tasks

Neural Network From Scratch

Implement and train a vectorized multilayer neural network in NumPy

PythonNumPyPyTorch
Build Progress0 / 14 tasks (0%)
0%(0/14 steps)Not Started
Project Specification & Architecture

What You Will Build

Scalar autodiff (Project 4) teaches how computational graphs work on individual numbers, but real deep learning operates on batched tensors using vectorized matrix calculus. In this project, you will implement a modular, multi-layer neural network in pure NumPy. You will design `Linear` layers (`Z = XW + b`), nonlinear activations (`ReLU`, `Sigmoid`, `Softmax`), numerically stable Cross-Entropy loss, He/Xavier weight initialization, explicit backward pass Jacobians (`dX = dZ @ W.T`, `dW = X.T @ dZ`, `db = sum(dZ, axis=0)`), and mini-batch training—verifying your analytical tensor gradients against both numerical finite differences and PyTorch.

Why This Project Matters in the AI/LLM Stack

Deriving and coding the matrix backward pass (`dW = Xᵀ dZ`, `dX = dZ Wᵀ`) makes tensor shape duality second nature: you will immediately see why weight gradients match the shape of `W` and input gradients match the shape of `X`.

Learning Objectives

  • Implement modular Dense (`Linear`) and activation layers with forward activation caching and explicit `.backward(d_out)` methods.
  • Derive and code numerically stable Softmax Cross-Entropy loss and its combined logit gradient `(probs - one_hot) / B`.
  • Validate every layer's backward pass using central finite differences and compare learned trajectories against an identical PyTorch model.
  • Train a multi-layer neural network with mini-batch gradient descent and weight initialization that prevents vanishing/exploding activations.

Prerequisites

  • Project 1 (Matrix Operations) and Project 4 (Autodiff Engine)
  • Phase 3 Module 3.1 & 3.2 (Forward pass, matrix Jacobians, He/Xavier initialization)

Required Mathematical Concepts

  • Batched affine transformation: Z = XW + b with X ∈ ℝ^{B×D_in}, W ∈ ℝ^{D_in×D_out}, b ∈ ℝ^{1×D_out}
  • Linear backward pass: dW = Xᵀ dZ, db = ∑_{i=1}^B dZ_i, dX = dZ Wᵀ
  • Elementwise activation backward pass: dZ = dA ⊙ f'(Z)
  • Log-sum-exp stabilized Softmax and Cross-Entropy gradient: dZ_logits = (P - Y_one_hot) / B
  • Variance-preserving initialization: He normal N(0, √(2/D_in)) for ReLU
Interactive Engineering Checklist

Step-by-Step Implementation Tasks (14 Steps)

Check off each engineering task as your code passes its acceptance criteria. Progress is saved automatically in your browser.

Stage 1 of 40/5 tasks complete

Architecture Design, Weight Initialization & Forward Pass

Implement `Linear` dense layers with He/Xavier initialization, `ReLU`/`Sigmoid` activations, and forward caching.

Core

A clean layer interface (`forward(x)` and `backward(dout)`) lets you stack arbitrary sequences of dense and activation layers inside a `Sequential` container.

Implementation: Design a base `Layer` abstraction in `nn_numpy/layers.py` and a `Sequential` model container that chains `.forward(X)` in order and `.backward(dloss)` in reverse order.
Expected Outcome: `Sequential([Linear(4, 16), ReLU(), Linear(16, 3)])` routes tensors cleanly forward and backward.
Acceptance Check: Each stateful layer exposes its parameters `(W, b)` and gradients `(dW, db)` cleanly.
Core

Initializing weights to all zeros causes all hidden neurons to compute identical gradients (symmetry breaking failure); overly large weights saturate activations.

Implementation: Implement He initialization `W ~ N(0, sqrt(2 / fan_in))` for ReLU layers and Xavier/Glorot initialization `W ~ N(0, sqrt(1 / fan_in))` for Sigmoid/Tanh layers, with biases initialized to `0.0`.
Expected Outcome: Hidden activations maintain unit-scale variance across multiple layers at initialization.
Acceptance Check: Supports passing a reproducible `np.random.Generator` seed.
Core

A dense (`Linear`) layer computes `Z = X @ W + b` and caches `X` during the forward pass so `dW = X.T @ dZ` can be computed during the backward pass.

Implementation: Implement `Linear(in_features, out_features)` with `.forward(X)` caching `self.cache_x = X` and returning `X @ self.W + self.b`.
Expected Outcome: Input `(B, D_in)` produces output `(B, D_out)` and stores `cache_x`.
Acceptance Check: Bias `b` has shape `(1, D_out)` or `(D_out,)` and broadcasts cleanly across batch size `B`.
Core

Elementwise nonlinearities allow multi-layer networks to approximate non-linear decision boundaries.

Implementation: Implement `ReLU`, `Sigmoid`, and `Tanh` layers in `nn_numpy/activations.py`, caching the necessary forward mask or output tensor for backpropagation.
Expected Outcome: Forward and backward passes preserve input shape `(B, D)`.
Acceptance Check: `ReLU.backward(dout)` zeroes out gradients wherever forward input `x <= 0`.
Core

Chaining layers transforms raw input features `(B, D_in)` into class logits `(B, C)`.

Implementation: Implement `Sequential.forward(X)` passing activations through every layer and returning the final logits.
Expected Outcome: Full network forward pass executes in vectorized NumPy without sample-level loops.
Acceptance Check: Output shape equals `(batch_size, num_classes)`.
Stage 2 of 40/3 tasks complete

Loss Functions & Matrix Backpropagation

Implement stabilized Cross-Entropy / MSE losses, derive layer Jacobians, and code backward propagation.

Intermediate

Fusing Softmax and Cross-Entropy avoids computing `exp(z)` without max-subtraction and yields the remarkably clean gradient `(probs - one_hot) / B`.

Implementation: Implement `SoftmaxCrossEntropyLoss` (using `logits - np.max(logits, axis=1, keepdims=True)`) and `MSELoss`, returning both the scalar loss and the gradient `d_logits` with respect to the network's output.
Expected Outcome: Passing large logits like `[1000.0, 1001.0, 999.0]` computes finite loss and gradients without `NaN` overflow.
Acceptance Check: Loss is averaged over batch size `B`, and `d_logits` is divided by `B` consistently.
Intermediate

Dimensional analysis confirms the matrix chain rule: for `Z = XW + b`, since `W` is `(D_in, D_out)`, `dW` must be `X.T @ dZ` (`(D_in, B) @ (B, D_out)`).

Implementation: Write out the exact matrix derivatives for `Linear`, `ReLU`, `Sigmoid`, and `SoftmaxCrossEntropyLoss`, verifying that every matrix product's inner dimensions match.
Expected Outcome: Clear mathematical specification of `dW`, `db`, and `dX`.
Acceptance Check: `dW.shape == W.shape`, `db.shape == b.shape`, and `dX.shape == X.shape`.
Intermediate

Propagating `d_logits` backward through `reversed(layers)` populates `layer.dW` and `layer.db` on every `Linear` layer.

Implementation: Implement `Linear.backward(dZ)` setting `self.dW = self.cache_x.T @ dZ`, `self.db = np.sum(dZ, axis=0, keepdims=True)`, and returning `dX = dZ @ self.W.T`. Wire `Sequential.backward(d_logits)`.
Expected Outcome: Calling `model.backward(d_logits)` computes exact parameter gradients for all layers.
Acceptance Check: Works for networks of any depth (2, 3, or 5 layers).
Stage 3 of 40/3 tasks complete

Mini-Batch Optimization & Training Loop

Implement parameter updates, mini-batch shuffling, and loss/accuracy tracking on a classification dataset.

Core

An optimizer iterates through all trainable layers and updates `W -= lr * dW` and `b -= lr * db`.

Implementation: Implement an `SGD` optimizer (with optional momentum and L2 weight decay) and a mini-batch data iterator that shuffles `(X, y)` each epoch.
Expected Outcome: Clean separation between model architecture (`Sequential`) and parameter update rule (`SGD.step()`).
Acceptance Check: Supports configurable `learning_rate`, `batch_size`, and `weight_decay`.
Core

Non-linearly separable datasets (like concentric circles, spirals, two-moons, or multi-class digits) prove that your hidden ReLU layers learn non-linear representations.

Implementation: Train a 3-layer MLP on a non-linear classification dataset (e.g., 3-class spiral / moons or digits dataset) where a linear classifier fails (`~50%` accuracy) and achieve `> 95%` accuracy.
Expected Outcome: Multi-layer NumPy network learns a curved decision boundary with `> 95%` validation accuracy.
Acceptance Check: Demonstrates significantly higher accuracy than a 1-layer linear model on the same dataset.
Core

Recording epoch-by-epoch training loss, validation loss, and classification accuracy verifies healthy learning dynamics.

Implementation: Log and plot training/validation loss and accuracy curves across epochs.
Expected Outcome: Saved diagnostic training curves and console progress table.
Acceptance Check: Loss decreases smoothly and validation accuracy reaches target threshold.
Stage 4 of 40/3 tasks complete

Numerical Gradient Checking, PyTorch Comparison & Docs

Verify gradients numerically, compare step-for-step against PyTorch, and document results.

Advanced

Perturbing each entry `W[i, j]` by `±1e-5` and comparing `(L_plus - L_minus) / (2e-5)` against `layer.dW[i, j]` catches any transpose or scaling bug.

Implementation: Implement a matrix finite-difference gradient checker in `tests/test_nn_numpy.py` that checks `dW`, `db`, and `dX` for a 3-layer network.
Expected Outcome: Maximum relative gradient error across all parameters is `< 1e-6`.
Acceptance Check: Automated pytest test runs the numerical gradient check and passes.
Advanced

Initializing a `torch.nn.Sequential` model with the exact same initial weight matrices `W` and biases `b` allows verifying that forward logits, loss, and backward `.grad` tensors match PyTorch to floating-point precision.

Implementation: Write a parity test that copies your initial NumPy weights into `torch.nn.Linear` layers (remembering PyTorch stores `weight` transposed as `(out_features, in_features)`!), runs one forward + backward pass in both frameworks, and asserts `np.allclose` on logits, loss, `dW`, and `db`.
Expected Outcome: NumPy network gradients match PyTorch `.grad` within `1e-6`.
Acceptance Check: Parity test included in `tests/test_nn_numpy.py`.
Core

Clear documentation of the backward pass equations, gradient checks, and training curves completes the project.

Implementation: Document your matrix Jacobian derivations, PyTorch parity test results, and training curves in `README.md`.
Expected Outcome: Complete engineering write-up and reproducible repository.
Acceptance Check: Includes explicit shape table for forward and backward tensors.
Reference Architecture & Verification Code

Starter & Verification Code Examples

Dense Layer Forward & Backward Pass with Stabilized Softmax Cross-Entropy

Shows the exact NumPy matrix operations for a Linear layer and fused Softmax Cross-Entropy loss.

import numpy as np

class Linear:
    def __init__(self, in_features: int, out_features: int, rng: np.random.Generator) -> None:
        # He normal initialization
        self.W = rng.normal(0.0, np.sqrt(2.0 / in_features), size=(in_features, out_features))
        self.b = np.zeros((1, out_features), dtype=np.float64)
        self.dW = np.zeros_like(self.W)
        self.db = np.zeros_like(self.b)
        self._cache_x: np.ndarray | None = None

    def forward(self, x: np.ndarray) -> np.ndarray:
        self._cache_x = x
        return x @ self.W + self.b

    def backward(self, dout: np.ndarray) -> np.ndarray:
        assert self._cache_x is not None
        self.dW = self._cache_x.T @ dout
        self.db = np.sum(dout, axis=0, keepdims=True)
        return dout @ self.W.T

def softmax_cross_entropy(logits: np.ndarray, y_indices: np.ndarray) -> tuple[float, np.ndarray]:
    batch_size = logits.shape[0]
    shifted = logits - np.max(logits, axis=1, keepdims=True)
    exp_scores = np.exp(shifted)
    probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
    loss = float(-np.mean(np.log(probs[np.arange(batch_size), y_indices] + 1e-12)))

    dlogits = probs.copy()
    dlogits[np.arange(batch_size), y_indices] -= 1.0
    dlogits /= batch_size
    return loss, dlogits
Verification & Acceptance Suite

How to Test Your Implementation

Run finite-difference gradient checks on every layer and verify exact forward/backward numerical parity against PyTorch.

$ pytest tests/test_nn_numpy.py -vTest Runner

Matrix Finite-Difference Gradient Check

Verify that `Linear.backward` and `SoftmaxCrossEntropyLoss.backward` compute exact analytical gradients.

Steps: For a random batch X (5, 4) and labels y (5,) through Linear(4, 6) -> ReLU -> Linear(6, 3), compare analytical dW1, db1, dW2, db2 with central finite differences (h=1e-5).

Pass Criteria: Relative error < 1e-6 across all weight and bias tensors.

Single-Step PyTorch Parity Test

Prove your NumPy forward logits, Cross-Entropy loss, and backward gradients match `torch.nn`.

Steps: Copy W and b into `torch.nn.Linear` (`linear_torch.weight.data = torch.from_numpy(W.T)`), run `F.cross_entropy` + `.backward()`, and compare gradients.

Pass Criteria: `np.allclose(dW_numpy, linear_torch.weight.grad.T, atol=1e-6)` is True.

Overall Project Acceptance Criteria

  • Dense layers, activations, and Softmax Cross-Entropy loss implemented in pure NumPy.
  • Analytical matrix gradients `dW`, `db`, and `dX` verified against numerical finite differences (`< 1e-6` relative error).
  • Forward and backward passes verified for exact parity against PyTorch given identical initial weights.
  • Network trained with mini-batch gradient descent to `> 95%` accuracy on a non-linear classification task.
Progressive Spoiler-Free Guidance

Expandable Hints & Debugging Tips

Try implementing each stage on your own first. Expand a hint below if you want to verify tensor shapes, mathematical identities, or common edge cases.

Optional Bonus Extensions

Optional Stretch Challenges

Optional · Does Not Block Completion

Implement Adam & Momentum Optimizers in NumPy

Implement SGD with Momentum and the Adam optimizer (tracking first moment `m`, second moment `v`, and bias corrections `1 - β₁ᵗ`, `1 - β₂ᵗ`) and compare convergence speed against vanilla SGD.

What You Learn: How adaptive per-parameter learning rates accelerate training across ill-conditioned loss surfaces.

Inverted Dropout & Layer Normalization From Scratch

Implement `Dropout(p=0.2)` (scaling active neurons by `1/(1-p)` during training and identity during evaluation) and `LayerNorm(d_model)` with its backward pass.

What You Learn: How regularization and normalization layers behave differently in `train` vs `eval` modes.

Expected Deliverable

What Your Completed Repository Should Contain

A fully working NumPy neural network implementation.

Deliverable Artifacts

  • Modular NumPy neural network library (`Linear`, `ReLU`, `Sigmoid`, `SoftmaxCrossEntropyLoss`, `Sequential`, `SGD`)
  • Numerical gradient checker and PyTorch parity test suite
  • Training script and loss/decision-boundary visualizations

After Completing This Project, You Should Be Able To...

  • Derive and implement vectorized forward and backward passes for dense layers, activations, and Softmax Cross-Entropy loss in NumPy.
  • Initialize neural network weights using variance-preserving He and Xavier schemes.
  • Verify analytical tensor gradients using finite differences and step-by-step PyTorch parity tests.
  • Train a multi-layer neural network using mini-batch gradient descent to solve non-linear classification tasks.
Portfolio & Repository Tracking

GitHub Repository Submission

Link your public or private GitHub repository URL to keep a structured portfolio of your from-scratch AI builds. Stored locally in your browser (self-reported; no GitHub tokens or remote code execution).

Status: Not Linked

Suggested Repository: neural-network-from-scratch-numpy

Recommended Directory Layout:

nn_numpy/layers.py
nn_numpy/losses.py
nn_numpy/optim.py
tests/test_nn_numpy.py
README.md

Recommended README Documentation Checklist

  • Forward and backward matrix equations with tensor shapes
  • Finite-difference and PyTorch parity verification output
  • Training loss curve and accuracy results
Local Engineering Notebook

Personal Implementation Notes