Each hidden unit computes $a_j^{(l)} = \sigma\!\left(\sum_k W_{jk}^{(l)} a_k^{(l-1)} + b_j^{(l)}\right)$, with $\sigma$ a non-linearity. The output applies a logistic squash $\hat{y} = (1+e^{-z})^{-1}$, giving binary cross-entropy
$$\mathcal{L}(\theta) = -\frac{1}{N}\sum_{i=1}^{N}\Big[y_i \log \hat{y}_i + (1-y_i)\log(1-\hat{y}_i)\Big].$$
Gradients are obtained by backpropagation (chain rule) and applied via gradient descent $\theta \leftarrow \theta - \eta\, \nabla_\theta \mathcal{L}$. Weights use Glorot initialisation $W \sim \mathcal{U}(-\sqrt{6/(n_\text{in}+n_\text{out})}, +\sqrt{6/(n_\text{in}+n_\text{out})})$. Data are split 80/20 train/test; the loss curve plots training and test loss per epoch. Each unit's heatmap visualises $a_j^{(l)}(x_1, x_2)$ — its activation as a function of inputs, holding learned weights fixed.
Things to try. XOR with a single hidden unit (cannot solve — non-linearly separable; the lone unit's heatmap is just a half-plane). Spiral with $\eta = 1.5$ (diverges). Spiral with $\eta = 0.1$, hidden $[8,\, 8,\, 4]$ — late-layer units develop curved, dataset-specific selectivities. Switch to ReLU on Spiral and look for "dead" units (uniform light heatmaps, zero gradient flow). Increase noise to 0.3 on Checkerboard — test loss eventually rises while training loss continues to fall, a clean overfitting trace.
Each exercise asks you to commit to a prediction before running the simulation. The "Apply settings" button configures the playground for you, but the prediction step is non-negotiable — write it down before you click anything.