python
# Forward and backward for 2-layer FC net (vectorized, batch)
def two_layer_forward_backward(X, Y, W1, b1, W2, b2):
N = X.shape[0] # 1
Z1 = X @ W1 + b1 # 2
A1 = relu(Z1) # 3
Z2 = A1 @ W2 + b2 # 4
logits = Z2 # 5
probs = softmax(logits) # 6
loss = -np.mean(np.sum(Y * np.log(probs), 1)) # 7
dlogits = (probs - Y) / N # 8
dW2 = A1.T @ dlogits # 9
db2 = np.sum(dlogits, axis=0, keepdims=True) # 10
dA1 = dlogits @ W2.T # 11
dZ1 = dA1 * (Z1 > 0).astype(float) # 12
dW1 = X.T @ dZ1 # 13
db1 = np.sum(dZ1, axis=0, keepdims=True) # 14
grads = {'W1': dW1, 'b1': db1, 'W2': dW2, 'b2': db2} # 15
return loss, grads # 16
Line-by-line plain-language explanation for a product manager:1. N = X.shape[0]: Record the batch size — how many examples we're processing together.2. Z1 = X @ W1 + b1: Multiply input features by the first layer weights and add biases — compute raw signals into hidden neurons.3. A1 = relu(Z1): Apply ReLU (zero-out negatives) — this introduces nonlinearity so the network can learn complex patterns.4. Z2 = A1 @ W2 + b2: Multiply hidden activations by second-layer weights and add biases — compute scores for each class.5. logits = Z2: Name the raw class scores (logits) — inputs to the probabilistic step.6. probs = softmax(logits): Convert logits to probabilities that sum to 1 across classes for each example.7. loss = -np.mean(np.sum(Y * np.log(probs), 1)): Compute average cross-entropy loss comparing predicted probs to true one-hot labels Y — this is the objective we minimize during training.8. dlogits = (probs - Y) / N: Gradient of loss w.r.t. logits — measures how predicted probabilities differ from true labels, averaged over the batch.9. dW2 = A1.T @ dlogits: Gradient w.r.t. second-layer weights — shows how to change W2 to reduce loss; it's correlation of hidden activations with output errors.10. db2 = np.sum(dlogits, axis=0, keepdims=True): Gradient for second-layer biases — aggregate needed bias adjustments across the batch.11. dA1 = dlogits @ W2.T: Backpropagate error into the hidden layer — tells how much each hidden neuron contributed to output error.12. dZ1 = dA1 * (Z1 > 0).astype(float): Apply ReLU derivative — only hidden units that were "on" pass gradients backward.13. dW1 = X.T @ dZ1: Gradient w.r.t. first-layer weights — how input features should be adjusted to reduce loss.14. db1 = np.sum(dZ1, axis=0, keepdims=True): Gradient for first-layer biases — aggregate adjustments for hidden biases.15. grads = {...}: Package all computed gradients for use by an optimizer (SGD, Adam, etc.).16. return loss, grads: Output the loss (training signal) and gradients (instructions for updating parameters).What data and parameters flow mean:- Data X flows forward through weights (W1, W2) and nonlinearities to become probabilities.- Parameters (W1, b1, W2, b2) transform data; training updates them to improve predictions.- Loss quantifies mismatch between predictions and truth; lower is better.- Gradients are directional signals: they tell the optimizer how to nudge each parameter to reduce loss on average across the batch.