DAV Lab Code Explanation

August 19, 2026

1. Setup & Dataset Generation

np.random.seed(0)
N_pos, N_neg = 200, 200
H1, H2 = 4, 2   # neurons in hidden layers
def inside_diamond(x, y):
    return np.abs(x) + np.abs(y) <= 1
pos_points, neg_points = [], []
while len(pos_points) < N_pos or len(neg_points) < N_neg:
    p = np.random.uniform(-2, 2, size=(1, 2))
    if inside_diamond(p[0,0], p[0,1]):
        if len(pos_points) < N_pos:
            pos_points.append(p)
    else:
        if len(neg_points) < N_neg:
            neg_points.append(p)
pos_points = np.vstack(pos_points)
neg_points = np.vstack(neg_points)

data = np.vstack((pos_points, neg_points))
labels = np.array([1]*N_pos + [0]*N_neg)

idx = np.random.permutation(len(data))
data = data[idx]
labels = labels[idx]
# Add bias to input layer
data = np.hstack((np.ones((data.shape[0], 1)), data)) 

2. Activation Functions

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

def sigmoid_deriv(a):
    return a * (1 - a)

3. Forward Pass Function

def forward(x, W1, W2, w3, b3):
    # Layer 1
    z1 = W1 @ x
    h1 = sigmoid(z1)
    
    # Layer 2
    z2 = W2 @ h1
    h2 = sigmoid(z2)
    
    # Output layer
    out_raw = w3 @ h2 + b3
    y_hat = sigmoid(out_raw)
    
    return h1, h2, y_hat, z1, z2, out_raw

Pushes a single data point vector xx (shape 3×13 \times 1) through the network:

  1. Layer 1: Multiplies W1W_1 (4×34 \times 3) by xx (3×13 \times 1) to get z1z_1 (4×14 \times 1), then squashes it via sigmoid to get activation h1h_1.
  2. Layer 2: Multiplies W2W_2 (2×42 \times 4) by h1h_1 (4×14 \times 1) to get z2z_2 (2×12 \times 1), then squashes it to get h2h_2.
  3. Output Layer: Multiplies weight vector w3w_3 (2×12 \times 1) by h2h_2 and adds scalar bias b3b_3 to get out_raw. Then squashes it to get y^\hat{y} (the predicted probability that this point is inside the diamond).
  4. Returns all intermediate values because backpropagation needs them to calculate gradients.

4. Backpropagation Function (grad_params)

def grad_params(x, y, W1, W2, w3, b3):
    # Forward pass to get current values
    h1, h2, y_hat, z1, z2, out_raw = forward(x, W1, W2, w3, b3)
    
    # Output layer gradient
    dL_dyhat = y_hat - y
    dL_dout = dL_dyhat
    grad_w3 = dL_dout * h2
    grad_b3 = dL_dout
    # Second hidden layer gradient
    dL_dh2 = w3 * dL_dout
    dL_dz2 = dL_dh2 * sigmoid_deriv(h2)
    grad_W2 = np.outer(dL_dz2, h1)
    # First hidden layer gradient
    dL_dh1 = W2.T @ dL_dz2
    dL_dz1 = dL_dh1 * sigmoid_deriv(h1)
    grad_W1 = np.outer(dL_dz1, x)
    
    return grad_W1, grad_W2, grad_w3, grad_b3

5. Training Loop (train_network)

def train_network(X, y, lr=0.1, epochs=50, lam=0.0):
    # Initialize weights randomly from a normal distribution
    W1 = np.random.randn(H1, X.shape[1])
    W2 = np.random.randn(H2, H1)
    w3 = np.random.randn(H2)
    b3 = 0.0
    
    for epoch in range(epochs):
        for xi, yi in zip(X, y):
            # 1. Compute gradients for this point
            grad_W1, grad_W2, grad_w3, grad_b3 = grad_params(xi, yi, W1, W2, w3, b3)
            
            # 2. Update weights (Stochastic Gradient Descent)
            W1 -= lr * (grad_W1 + lam * W1)
            W2 -= lr * (grad_W2 + lam * W2)
            w3 -= lr * (grad_w3 + lam * w3)
            b3 -= lr * grad_b3
            
    return W1, W2, w3, b3

6. Visualization Functions

def plot_decision(ax, W1, W2, w3, b3, title):
    xx, yy = np.meshgrid(np.linspace(-2,2,300), np.linspace(-2,2,300))
    grid = np.c_[np.ones(xx.size), xx.ravel(), yy.ravel()]
    Z = []
    for xi in grid:
        _, _, y_hat, _, _, _ = forward(xi, W1, W2, w3, b3)
        Z.append(y_hat)
    Z = np.array(Z).reshape(xx.shape)
    ax.contourf(xx, yy, Z, levels=[0, 0.5, 1], colors=['blue', 'red'], alpha=0.5)
    ax.contour(xx, yy, Z, levels=[0.5], colors='k')
    ...
# Second hidden layer activations plot
H2_out = []
for xi in data:
    _, h2, _, _, _, _ = forward(xi, W1, W2, w3, b3)
    H2_out.append(h2)
H2_out = np.array(H2_out)

ax.scatter(H2_out[labels==0,0], H2_out[labels==0,1], c='blue', alpha=0.5, label='y=0')
ax.scatter(H2_out[labels==1,0], H2_out[labels==1,1], c='red', alpha=0.5, label='y=1')