How a Neural Network Reads Your Handwritten Digits
After reading this you will know how a small neural network turns a drawn digit into ten confidence scores, be able to reproduce one forward pass by hand, and understand why preprocessing matters more than the network itself.
What the recognizer actually does
Draw a 7 on the canvas and the network prints "7" almost instantly. No magic sits behind that. The drawing becomes a list of 784 numbers, those numbers pass through two matrix multiplications and two simple functions, and the last step produces ten probabilities that sum to 1. The largest one is the guess.
The model here is a 784-128-10 multilayer perceptron. That name is a size, not a mystery: 784 inputs (a 28 by 28 image flattened), 128 hidden units, and 10 outputs (one per digit). It was trained on MNIST, a set of 60,000 labeled handwriting scans, and it scores about 98% on the 10,000 held-out test images. The weights ship with the page as int8-quantized arrays, so the whole computation runs on your device in a few milliseconds.
What the network does not do is "see" a 7 the way you do. It has learned 128 weighted patterns of pixel brightness, and it reports which of ten digit classes best matches the pattern of activations your drawing produces. Nothing more.
When a plain MLP is the right tool
A dense network like this is a good fit when your input is small, fixed in size, and already roughly aligned. MNIST digits are 28 by 28, always one digit, always centered. Under those conditions 784 inputs and 128 hidden units are enough to reach 98% accuracy.
It is the wrong tool when position and scale vary a lot. A dense network treats pixel 200 and pixel 201 as unrelated coordinates, so a digit shifted a few pixels to the right looks like a completely different input vector. Convolutional networks handle that with shared filters that slide across the image. If you want to see convolution build up an output pixel by pixel, try the Convolution Filter Visualizer. For a dataset this clean, though, the extra machinery buys little, and the dense model is small enough to explain end to end.
The forward pass, one layer at a time
Two layers, two operations each. Call the flattened input x, a vector of 784 numbers between 0 and 1. The hidden layer computes a weighted sum plus a bias, then applies ReLU:
Here W_{ji} is the weight connecting input pixel i to hidden unit j, and b_j is that unit's bias. ReLU, written \max(0, z), keeps positive sums and clamps negatives to zero. That gives 128 hidden values h_j. To see why ReLU beats sigmoid for deep stacks, compare them in the Activation Function Explorer.
The output layer does another weighted sum to produce ten scores called logits:
Then softmax turns the ten logits into probabilities:
Every p_k lands between 0 and 1, and the ten of them add to exactly 1. The exponential exaggerates gaps: a logit that is 2 larger than the next becomes about e^2 \approx 7.39 times more probable. That is why a confident network shows one tall bar and nine short ones.
The weights W and V are fixed. Training already set them by gradient descent. This page only runs the forward pass. To watch the backward pass that produced these weights, work through the Backpropagation Step-by-Step tool.
A worked softmax you can check by hand
The full network has too many weights to compute on paper, but the last step is small. Suppose your drawing of a 7 produces these three notable logits and the rest are near zero. Watch softmax convert them.
From logits to confidence
Take logits z_7 = 6.0, z_1 = 3.0, z_9 = 1.0, and treat the other seven as 0.
- Exponentiate the nonzero ones: e^{6.0} \approx 403.4, e^{3.0} \approx 20.09, e^{1.0} \approx 2.718.
- The seven logits at 0 each give e^{0} = 1, contributing
7to the sum. - Total: 403.4 + 20.09 + 2.718 + 7 = 433.2.
- Divide: p_7 = 403.4 / 433.2 \approx 0.931, p_1 = 20.09 / 433.2 \approx 0.0464, p_9 = 2.718 / 433.2 \approx 0.0063.
So the network reports "7" with 93.1% confidence, notes "1" as a runner-up at 4.6%, and the remaining eight digits share under 2%. A gap of 3.0 in logit space became a 20-to-1 ratio in probability.
Why preprocessing decides the outcome
Before the network sees anything, your drawing is forced to match how MNIST looks. This is the step naive demos skip, and skipping it is the usual reason they guess badly.
MNIST scans are not raw 28 by 28 squares. Each digit was scaled to fit a 20 by 20 box preserving aspect ratio, then placed in the 28 by 28 field so its center of mass sits at the middle. The recognizer does the same to your stroke: it crops the ink, resizes the longer side to 20 pixels, and shifts the result so the average lit-pixel position lands at coordinate (13.5, 13.5).
Why the center of mass rather than the bounding-box center? A 1 is a thin vertical bar. Its bounding box centers the bar, but its mass is already there. A 7 has a heavy top and light tail, so mass-centering pulls it up, exactly matching the training distribution. The magnified preview on the tool shows this network's-eye view. If your drawn digit looks off in that preview, the network is reading something different from what you intended.
A digit drawn in the corner, or filling the whole canvas edge to edge, will be recentered and rescaled before the network sees it. Judge the prediction against the preview, not against your original drawing.
Reading the bars and the hidden grid
Three parts of the display carry meaning. The large guess is the argmax: the digit with the highest p_k. The runner-up appears only when the top two probabilities are close, which is honest signaling that the input is ambiguous. A 4 and a 9 often trade the top two slots because both have a closed upper loop and a vertical stroke.
The optional heat grid shows all 128 hidden activations h_j. Because of ReLU, many of them read exactly zero for any given digit. As you extend a stroke you can watch specific cells switch from dark to bright. Do not read a label into any single neuron. A hidden unit here responds to a weighted mix of pixels, not to a nameable feature, and only the output layer combines them into digit scores.
| Display element | Quantity | How to read it |
|---|---|---|
| Large guess | argmax of p_k | Most probable digit |
| Ten bars | softmax p_k | Confidence, sums to 1 |
| Runner-up note | second-highest p_k | Appears when top two are close |
| Heat grid | 128 values of h_j | Which hidden units fired, many are 0 |
Common mistakes and their fixes
Three problems account for most surprising predictions.
- Strokes too thin
- MNIST pens are thick. A one-pixel mouse line barely lights the 20 by 20 box after downscaling, so the network sees faint smudges. Draw with a fuller stroke.
- Unusual proportions
- A 98% test score is on pen-on-paper scans. A
7with a European crossbar, or a very tall narrow2, sits outside the training styles and can flip the guess. - Reading confidence as certainty
- A 99% bar means the training data made that pattern look 99% like one class. It is not a probability that you are correct. The model can be confidently wrong on a style it never saw.
None of these are bugs. They are the honest limits of a small model trained on one dataset and run on inputs from a different device.
Related tools to go deeper
Once the forward pass makes sense, several nearby tools extend it. Train a tiny network and watch its boundary form in the Neural Network Playground. See how depth breaks gradient flow in Vanishing and Exploding Gradients. Fight the overfitting that a 128-unit layer can cause with the Regularization Demo. For a different kind of pattern memory that also stores pixel grids, try the Hopfield Network. And for networks that read sequences rather than fixed images, the Character RNN Text Generator, the Self-Attention Visualizer, and the Word Embedding Trainer each pick up where this one stops.
Frequently asked questions
Why does the network sometimes read my digit as a completely different number?
Usually preprocessing. Check the magnified preview. If your stroke was thin or off to one side, the cropped and mass-centered version can look like a different digit to a model that only ever saw thick, centered scans.
What does the confidence percentage really mean?
It is the softmax probability, the fraction of exponentiated score that landed on that class. In the worked example, e^{6}/433.2 = 0.931. It measures how strongly the learned patterns favor one digit, not the chance that the reading is correct.
Does the model learn from my drawings?
No. The weights are fixed and quantized to int8 before shipping. Each drawing runs a forward pass only, and nothing you draw changes the weights or leaves your device.
Why 128 hidden units and not more?
128 is enough to reach about 98% on MNIST while staying small enough to ship as compressed weights and compute in a few milliseconds. More units would raise accuracy slightly and cost much more download and computation.
Why does a single hidden neuron not correspond to a digit?
Each hidden unit responds to a weighted mix of all 784 pixels, not to a nameable shape. The digit decision comes only from the output layer, which combines all 128 activations. Watching one cell in the heat grid tells you it fired, not what it means.