Project 40: Neural Net From Scratch Lite 🧠¶
Difficulty: 🔴 Advanced
Run cells top to bottom. Each step builds on the previous one and saves real files under outputs/neural_net_from_scratch_lite/.
Description¶
Train a tiny 2-layer neural net on a moon-shaped dataset with only NumPy — then plot the decision boundary.
Libraries & Modules¶
numpymatplotlib
Python Concepts You'll Practice¶
Forward/backprop basics, loss curves, decision boundaries
🎛️ Parameter Variations¶
HIDDENlayer sizeLR,EPOCHSSEED
In [1]:
# Install dependencies for this project (safe to re-run)
import sys
!{sys.executable} -m pip install pillow numpy matplotlib wordcloud qrcode[pil] python-barcode fpdf2 jinja2 folium plotly pandas scipy ipywidgets -q
Skipping pip install (already installed)
In [2]:
# Interactive Jupyter setup — run this cell first
%matplotlib inline
from pathlib import Path
from IPython.display import display, Image as IPImage, HTML, Markdown, Audio, IFrame
OUTPUT_DIR = Path("outputs") / "neural_net_from_scratch_lite"
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
print(f"✅ Outputs folder: {OUTPUT_DIR.resolve()}")
✅ Outputs folder: C:\Users\Hansel Yan\Projects\CodeItAll\outputs\neural_net_from_scratch_lite
Step 1 — Make moons + init network¶
In [3]:
import numpy as np
# 🎛️ TWEAK THESE
N = 200
HIDDEN = 16
LR = 0.1
EPOCHS = 400
SEED = 0
rng = np.random.default_rng(SEED)
def make_moons(n, noise=0.15):
n2 = n // 2
t = np.linspace(0, np.pi, n2)
x1 = np.c_[np.cos(t), np.sin(t)]
x2 = np.c_[1 - np.cos(t), 0.5 - np.sin(t)]
x = np.vstack([x1, x2])
x += noise * rng.normal(size=x.shape)
y = np.array([0] * n2 + [1] * n2)
idx = rng.permutation(n)
return x[idx], y[idx].reshape(-1, 1)
X, y = make_moons(N)
W1 = rng.normal(scale=0.8, size=(2, HIDDEN))
b1 = np.zeros((1, HIDDEN))
W2 = rng.normal(scale=0.8, size=(HIDDEN, 1))
b2 = np.zeros((1, 1))
print("Dataset", X.shape, "labels", y.mean())
Dataset (200, 2) labels 0.5
Step 2 — Train + plot boundary¶
In [4]:
import matplotlib.pyplot as plt
def sigmoid(z):
return 1 / (1 + np.exp(-np.clip(z, -30, 30)))
losses = []
for epoch in range(EPOCHS):
z1 = X @ W1 + b1
a1 = np.tanh(z1)
z2 = a1 @ W2 + b2
a2 = sigmoid(z2)
loss = -np.mean(y * np.log(a2 + 1e-9) + (1 - y) * np.log(1 - a2 + 1e-9))
losses.append(loss)
dz2 = (a2 - y) / len(X)
dW2 = a1.T @ dz2
db2 = dz2.sum(axis=0, keepdims=True)
dz1 = (dz2 @ W2.T) * (1 - a1 ** 2)
dW1 = X.T @ dz1
db1 = dz1.sum(axis=0, keepdims=True)
W1 -= LR * dW1; b1 -= LR * db1
W2 -= LR * dW2; b2 -= LR * db2
xx, yy = np.meshgrid(np.linspace(X[:,0].min()-0.4, X[:,0].max()+0.4, 180),
np.linspace(X[:,1].min()-0.4, X[:,1].max()+0.4, 180))
grid = np.c_[xx.ravel(), yy.ravel()]
zz = sigmoid(np.tanh(grid @ W1 + b1) @ W2 + b2).reshape(xx.shape)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
axes[0].plot(losses, color="#111")
axes[0].set_title("Loss")
axes[1].contourf(xx, yy, zz, levels=20, cmap="RdGy")
axes[1].scatter(X[:,0], X[:,1], c=y.ravel(), cmap="RdGy", edgecolor="k", s=18)
axes[1].set_title("Decision boundary")
fig.suptitle("Neural Net From Scratch Lite — CodeItAll")
fig.tight_layout()
out = OUTPUT_DIR / "tiny_net.png"
fig.savefig(out, dpi=150)
plt.show()
print(f"Final loss {losses[-1]:.4f} → {out}")
Final loss 0.2478 → outputs\neural_net_from_scratch_lite\tiny_net.png