Back to Deep Learning Notes
Topic #471

Practice: Neural Networks

Implementation exercises for core neural network mechanics — building a perceptron and a small MLP from scratch in plain NumPy, to genuinely understand forward propagation and backpropagation before relying on a framework to handle it.

🟢 Problem 1: Implement a single perceptron from scratch

Task: Implement a perceptron that takes a NumPy input vector, computes the weighted sum plus bias, and applies a step activation function. Train it on the AND logic gate (4 examples).

import numpy as np

def perceptron_train(X, y, lr=0.1, epochs=20):
    weights = np.zeros(X.shape[1])
    bias = 0.0
    for epoch in range(epochs):
        for xi, target in zip(X, y):
            pred = 1 if np.dot(xi, weights) + bias > 0 else 0
            error = target - pred
            weights += lr * error * xi
            bias += lr * error
    return weights, bias

X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([0,0,0,1])   # AND gate
w, b = perceptron_train(X, y)
print(f"weights={w}, bias={b}")
for xi in X:
    pred = 1 if np.dot(xi, w) + b > 0 else 0
    print(f"{xi} -> {pred}")

Hint if stuck: The perceptron learning rule updates weights only when a prediction is wrong — if error == 0, weights don't change for that example.

🟡 Problem 2: Implement forward propagation for a 2-layer MLP, by hand

Task: Given a 2-input, 3-hidden-unit, 1-output network with sigmoid activations, implement the forward pass manually using matrix operations. Verify your output shape at each step.

def sigmoid(z): return 1 / (1 + np.exp(-z))

def forward_pass(X, W1, b1, W2, b2):
    Z1 = X @ W1 + b1          # (batch, 2) @ (2, 3) -> (batch, 3)
    A1 = sigmoid(Z1)
    Z2 = A1 @ W2 + b2          # (batch, 3) @ (3, 1) -> (batch, 1)
    A2 = sigmoid(Z2)
    return A1, A2

np.random.seed(0)
W1 = np.random.randn(2, 3) * 0.1
b1 = np.zeros(3)
W2 = np.random.randn(3, 1) * 0.1
b2 = np.zeros(1)

X = np.array([[0.5, 0.8]])
A1, A2 = forward_pass(X, W1, b1, W2, b2)
print(f"Hidden activations: {A1}")
print(f"Output: {A2}")

Hint if stuck: Track each matrix's shape at every line — a shape mismatch almost always reveals exactly where the bug is.

🔴 Problem 3: Implement full backpropagation for the same 2-layer MLP

Task: Extend Problem 2 to compute gradients for W1, b1, W2, b2 given a target output, using mean squared error loss. Then implement one gradient descent update step.

def backward_pass(X, y, A1, A2, W2):
    m = X.shape[0]
    dZ2 = (A2 - y) * A2 * (1 - A2)          # dL/dZ2, sigmoid derivative included
    dW2 = A1.T @ dZ2 / m
    db2 = np.sum(dZ2, axis=0) / m

    dA1 = dZ2 @ W2.T
    dZ1 = dA1 * A1 * (1 - A1)                # sigmoid derivative for hidden layer
    dW1 = X.T @ dZ1 / m
    db1 = np.sum(dZ1, axis=0) / m
    return dW1, db1, dW2, db2

y = np.array([[1.0]])
dW1, db1, dW2, db2 = backward_pass(X, y, A1, A2, W2)

lr = 0.1
W1 -= lr * dW1; b1 -= lr * db1
W2 -= lr * dW2; b2 -= lr * db2
print("Weights updated via one gradient descent step")

Hint if stuck: Work backward one layer at a time — compute \(dZ2\) first (it only needs the output layer), then use it to compute \(dW2\)/\(db2\), then propagate to \(dA1\), then \(dZ1\), then \(dW1\)/\(db1\). Trying to derive all four gradients simultaneously is where most people get lost.

🟡 Problem 4: Verify your gradients numerically

Task: Implement numerical gradient checking — perturb one weight slightly, measure the resulting change in loss, and confirm it approximately matches your analytically computed gradient.

def numerical_gradient_check(X, y, W1, b1, W2, b2, epsilon=1e-4):
    def compute_loss(W1, b1, W2, b2):
        _, A2 = forward_pass(X, W1, b1, W2, b2)
        return np.mean((A2 - y) ** 2)

    i, j = 0, 0   # check one specific weight
    W1_plus = W1.copy(); W1_plus[i, j] += epsilon
    W1_minus = W1.copy(); W1_minus[i, j] -= epsilon

    numerical_grad = (compute_loss(W1_plus, b1, W2, b2) - compute_loss(W1_minus, b1, W2, b2)) / (2 * epsilon)
    print(f"Numerical gradient: {numerical_grad:.6f}")
    print(f"Analytical gradient (from backward_pass): {dW1[i, j]:.6f}")
    # These two values should be very close if your backward pass is implemented correctly

Hint if stuck: This is exactly how you'd debug a suspected bug in a real backprop implementation — if the numerical and analytical gradients diverge significantly, the bug is in your backward pass math, not somewhere else.

Want to go beyond the notes?

Join Coding Now Tech Institute's Deep Learning course — live mentorship, real projects, and 100% placement support.

Enroll Now — Free Demo Available

Practice: Neural Networks – FAQs

Quick answers about learning Practice: Neural Networks in Deep Learning.

This free note from Coding Now Tech Institute explains Practice: Neural Networks in Deep Learning — concept, syntax and worked code examples you can copy, run and revise before interviews.
Yes. Every Deep Learning topic on Coding Now Tech Institute, including Practice: Neural Networks, is 100% free with no signup required.
With focused practice, most students grasp Practice: Neural Networks in 1–3 days from these notes; pairing it with Coding Now Tech Institute's mentor-led course takes you to job-ready depth faster.
Use the code examples in this note, then ask doubts for free on the Coding Now Tech Institute Community (/community) — expert instructors answer within 24 hours.
Call NowEnroll Now