Deep Learning › Neural Network Foundations › Day 199
Hands-on lab — Day 199: Forward Propagation
- ← Back to the Day 199 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/deep-learning/day-199-forward-propagation/
Commands
Setup
pip install -r requirements/requirements.txt Run
python3 examples/forward_propagation_lib.py Test
./tests/run_tests.sh File tree
examples/forward_propagation_lib.py examples/test_forward_propagation_lib.py expected-output/examples-run.txt expected-output/FIELDS.md expected-output/measured-values.txt expected-output/starter-run.txt expected-output/test-run.txt metadata.yml README.md requirements/requirements.txt security.md starter/forward_propagation_lib.py starter/test_forward_propagation_lib.py tests/run_tests.sh tests/test_forward_propagation_lib.py troubleshooting.md
Lab README
Lab: Day 199 -- Forward Propagation
Lesson
Day number: 199 of 365. Course: Course05-SS01 (Deep Learning - Neural Networks). Topic: Multi-Layer Forward Propagation and Activation Caching.
Purpose
Build a generalized, modular L-Layer Forward Propagation engine in pure NumPy. You will formulate linear affine transformations, manage activation functions across layers, enforce strict matrix dimension contracts, construct forward activation caches, and calculate mini-batch Categorical Cross-Entropy loss.
Learning objectives
- Implement vectorized dense layer affine transformations
Z = W A_prev + b. - Structure modular L-layer forward passes with activation caching.
- Enforce strict matrix dimension contracts across mini-batches.
- Calculate numerically stable Categorical Cross-Entropy (CCE) loss.
Prerequisites
- Linear algebra (matrix multiplication, broadcasting).
- Python 3.11+ with NumPy.
Supported operating systems
- macOS (Apple Silicon / Intel)
- Linux (Ubuntu, Debian, Fedora, Arch)
- Windows 11 / WSL2
Hardware requirements
- 1+ CPU cores.
- 512 MB RAM.
- 50 MB disk space.
Required software
- Python 3.11 or newer.
- pip package manager.
- virtualenv or venv module.
Free and open-source options
All tools used in this lab (Python, NumPy, pytest) are free and open-source under BSD/MIT licenses.
Installation
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements/requirements.txt
File structure
starter/forward_propagation_lib.py: Student scaffold file.examples/forward_propagation_lib.py: Complete reference implementation.tests/test_forward_propagation_lib.py: Pytest automated validation suite.expected-output/: Verified output logs and baseline values.
How to run
Execute the reference demonstration script:
python3 examples/forward_propagation_lib.py
What the commands do
- Constructs a 3-layer neural network
[784, 128, 64, 10]. - Executes forward propagation across a mini-batch of 32 samples.
- Computes initial Categorical Cross-Entropy loss.
Expected output
Forward Demo: Output Shape = (10, 32), Initial CCE Loss = 2.3026
Validation steps
- Verify that output probabilities sum to 1.0 along the class axis for every sample.
- Confirm that forward caches store
A_prev,Z,W, andbwith correct dimensions. - Ensure all unit test assertions pass.
Tests
Run the test runner script:
./tests/run_tests.sh
Cleanup
find . -type d -name "__pycache__" -exec rm -rf {} +
find . -type d -name ".pytest_cache" -exec rm -rf {} +
Troubleshooting
- Dimension Mismatch: Ensure input matrix has shape
(features, batch_size).
Security notes
All tensor calculations execute locally in system RAM.
Extension exercises
- Implement Binary Cross-Entropy (BCE) loss evaluation.
- Build an inverted Dropout forward pass.
Navigation
- Lesson title: Forward Propagation
- Day number: 199 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-199-forward-propagation
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-199-forward-propagationwhen the site is running.
Expected output
FIELDS.md
# Expected Output Fields: Day 199
- `Output Shape`: Shape of final output probability tensor.
- `Initial CCE Loss`: Categorical cross-entropy loss evaluated on random weights.
examples-run.txt
Forward Demo: Output Shape = (10, 32), Initial CCE Loss = 2.3026
measured-values.txt
Output Shape: (10, 32)
Initial CCE Loss: 2.3026
starter-run.txt
Starter scaffold executed. Ready for student implementation.
test-run.txt
============================= test session starts ==============================
collected 3 items
tests/test_forward_propagation_lib.py::test_dense_layer_shapes_and_caching PASSED [ 33%]
tests/test_forward_propagation_lib.py::test_multilayer_network_forward_propagation PASSED [ 66%]
tests/test_forward_propagation_lib.py::test_categorical_crossentropy_loss PASSED [100%]
============================== 3 passed in 0.09s ===============================
Source files
examples/forward_propagation_lib.py (2569 bytes)
import numpy as np
from typing import List, Tuple, Dict, Any
class DenseLayer:
def __init__(self, in_features: int, out_features: int, activation: str = "relu"):
self.in_features = in_features
self.out_features = out_features
self.activation = activation
limit = np.sqrt(2.0 / in_features) if activation == "relu" else np.sqrt(1.0 / in_features)
self.W = np.random.randn(out_features, in_features) * limit
self.b = np.zeros((out_features, 1))
def forward(self, A_prev: np.ndarray) -> Tuple[np.ndarray, Dict[str, np.ndarray]]:
Z = np.dot(self.W, A_prev) + self.b
if self.activation == "relu":
A = np.maximum(0.0, Z)
elif self.activation == "sigmoid":
A = np.where(Z >= 0, 1.0 / (1.0 + np.exp(-Z)), np.exp(Z) / (1.0 + np.exp(Z)))
elif self.activation == "softmax":
Z_shift = Z - np.max(Z, axis=0, keepdims=True)
exp_Z = np.exp(Z_shift)
A = exp_Z / np.sum(exp_Z, axis=0, keepdims=True)
else:
A = Z
cache = {"A_prev": A_prev, "Z": Z, "W": self.W, "b": self.b}
return A, cache
class MultiLayerNetwork:
def __init__(self, layer_dims: List[int], activations: List[str]):
self.layer_dims = layer_dims
self.activations = activations
self.layers = []
for i in range(len(layer_dims) - 1):
self.layers.append(DenseLayer(layer_dims[i], layer_dims[i+1], activations[i]))
def forward(self, X: np.ndarray) -> Tuple[np.ndarray, List[Dict[str, np.ndarray]]]:
A = X
caches = []
for layer in self.layers:
A, cache = layer.forward(A)
caches.append(cache)
return A, caches
@staticmethod
def compute_categorical_crossentropy(A_last: np.ndarray, Y_onehot: np.ndarray) -> float:
m = Y_onehot.shape[1]
eps = 1e-15
loss = - (1.0 / m) * np.sum(Y_onehot * np.log(A_last + eps))
return float(loss)
def run_forward_demo():
np.random.seed(42)
# Architecture: 784 -> 128 -> 64 -> 10
net = MultiLayerNetwork([784, 128, 64, 10], ["relu", "relu", "softmax"])
m = 32
X = np.random.randn(784, m)
Y = np.zeros((10, m))
for i in range(m):
Y[np.random.randint(0, 10), i] = 1.0
A_out, caches = net.forward(X)
loss = net.compute_categorical_crossentropy(A_out, Y)
print(f"Forward Demo: Output Shape = {A_out.shape}, Initial CCE Loss = {loss:.4f}")
return A_out, loss
if __name__ == "__main__":
run_forward_demo()
examples/test_forward_propagation_lib.py (1306 bytes)
import pytest
import numpy as np
from examples.forward_propagation_lib import DenseLayer, MultiLayerNetwork
def test_dense_layer_shapes_and_caching():
layer = DenseLayer(in_features=20, out_features=10, activation="relu")
m = 8
A_prev = np.random.randn(20, m)
A, cache = layer.forward(A_prev)
assert A.shape == (10, m)
assert cache["A_prev"].shape == (20, m)
assert cache["Z"].shape == (10, m)
assert cache["W"].shape == (10, 20)
assert cache["b"].shape == (10, 1)
def test_multilayer_network_forward_propagation():
net = MultiLayerNetwork([10, 16, 8, 4], ["relu", "relu", "softmax"])
m = 12
X = np.random.randn(10, m)
A_out, caches = net.forward(X)
assert A_out.shape == (4, m)
assert len(caches) == 3
# Check that softmax probabilities sum to 1.0 along class dimension (axis 0)
assert np.allclose(np.sum(A_out, axis=0), np.ones(m))
def test_categorical_crossentropy_loss():
m = 4
# Perfect predictions
Y_onehot = np.array([[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0]])
A_pred = Y_onehot.copy()
loss = MultiLayerNetwork.compute_categorical_crossentropy(A_pred, Y_onehot)
assert loss < 1e-5
metadata.yml (432 bytes)
lesson_id: D199
day: 199
kind: lab
languages:
- python
setup_commands:
- 'pip install -r requirements/requirements.txt'
run_commands:
- 'python3 examples/forward_propagation_lib.py'
test_commands:
- './tests/run_tests.sh'
cleanup_commands:
- 'find . -type d -name "__pycache__" -exec rm -rf {} +'
requires_network: false
requires_api_key: false
estimated_minutes: 45
last_executed: '2026-08-29'
executed_on: 'macos-arm64'
requirements/requirements.txt (28 bytes)
numpy>=1.26.0
pytest>=8.0.0
starter/forward_propagation_lib.py (979 bytes)
import numpy as np
from typing import List, Tuple, Dict, Any
class DenseLayer:
def __init__(self, in_features: int, out_features: int, activation: str = "relu"):
self.in_features = in_features
self.out_features = out_features
self.activation = activation
self.W = None
self.b = None
def forward(self, A_prev: np.ndarray) -> Tuple[np.ndarray, Dict[str, np.ndarray]]:
# TODO: Compute Z = W.A_prev + b, apply activation, and return (A, cache)
pass
class MultiLayerNetwork:
def __init__(self, layer_dims: List[int], activations: List[str]):
# TODO: Initialize layers list
pass
def forward(self, X: np.ndarray) -> Tuple[np.ndarray, List[Dict[str, np.ndarray]]]:
# TODO: Execute layer-by-layer forward propagation
pass
@staticmethod
def compute_categorical_crossentropy(A_last: np.ndarray, Y_onehot: np.ndarray) -> float:
# TODO: Compute CCE loss
pass
starter/test_forward_propagation_lib.py (1306 bytes)
import pytest
import numpy as np
from examples.forward_propagation_lib import DenseLayer, MultiLayerNetwork
def test_dense_layer_shapes_and_caching():
layer = DenseLayer(in_features=20, out_features=10, activation="relu")
m = 8
A_prev = np.random.randn(20, m)
A, cache = layer.forward(A_prev)
assert A.shape == (10, m)
assert cache["A_prev"].shape == (20, m)
assert cache["Z"].shape == (10, m)
assert cache["W"].shape == (10, 20)
assert cache["b"].shape == (10, 1)
def test_multilayer_network_forward_propagation():
net = MultiLayerNetwork([10, 16, 8, 4], ["relu", "relu", "softmax"])
m = 12
X = np.random.randn(10, m)
A_out, caches = net.forward(X)
assert A_out.shape == (4, m)
assert len(caches) == 3
# Check that softmax probabilities sum to 1.0 along class dimension (axis 0)
assert np.allclose(np.sum(A_out, axis=0), np.ones(m))
def test_categorical_crossentropy_loss():
m = 4
# Perfect predictions
Y_onehot = np.array([[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0]])
A_pred = Y_onehot.copy()
loss = MultiLayerNetwork.compute_categorical_crossentropy(A_pred, Y_onehot)
assert loss < 1e-5
tests/run_tests.sh (227 bytes)
#!/usr/bin/env bash
set -euo pipefail
echo "========================================"
echo "Running Day 199 Lab Test Suite"
echo "========================================"
pytest tests/ -v
echo "All tests passed successfully."
tests/test_forward_propagation_lib.py (1306 bytes)
import pytest
import numpy as np
from examples.forward_propagation_lib import DenseLayer, MultiLayerNetwork
def test_dense_layer_shapes_and_caching():
layer = DenseLayer(in_features=20, out_features=10, activation="relu")
m = 8
A_prev = np.random.randn(20, m)
A, cache = layer.forward(A_prev)
assert A.shape == (10, m)
assert cache["A_prev"].shape == (20, m)
assert cache["Z"].shape == (10, m)
assert cache["W"].shape == (10, 20)
assert cache["b"].shape == (10, 1)
def test_multilayer_network_forward_propagation():
net = MultiLayerNetwork([10, 16, 8, 4], ["relu", "relu", "softmax"])
m = 12
X = np.random.randn(10, m)
A_out, caches = net.forward(X)
assert A_out.shape == (4, m)
assert len(caches) == 3
# Check that softmax probabilities sum to 1.0 along class dimension (axis 0)
assert np.allclose(np.sum(A_out, axis=0), np.ones(m))
def test_categorical_crossentropy_loss():
m = 4
# Perfect predictions
Y_onehot = np.array([[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0]])
A_pred = Y_onehot.copy()
loss = MultiLayerNetwork.compute_categorical_crossentropy(A_pred, Y_onehot)
assert loss < 1e-5
Troubleshooting
Troubleshooting: Day 199 - Forward Propagation
Common Issues
- Matrix Incompatible Shapes:
- Cause: Layer weights dimension does not match input activation dimension.
- Fix: Ensure W has shape (out_features, in_features).
Security notes
Security & Privacy: Day 199 - Forward Propagation
Security Guidance
- All forward activations execute strictly in local memory.