Some sections of this article were generated by AI and are maintained by the author.
Building a neural network for MNIST from scratch without library like pytorch, keras, tensorflow, only use numpy. We will break down the mechanics step by step—from raw pixels to calculus-driven learning—so you have complete clarity before writing a single line of code. I’ll guide you through each piece with questions along the way.
To classify handwritten digits ( through ), a standard feedforward neural network relies on three core conceptual pillars:
-
Architecture & Dimensions 📐: Turning a grayscale image into vectors, and defining the matrix shapes for layers, weights (), and biases ().
-
Forward Propagation & Activation Functions ⚡: Computing the linear transformations () and applying non-linearities like ReLU for hidden layers and Softmax for multi-class probabilities.
-
Loss & Backpropagation 🔄: Measuring prediction error using Cross-Entropy Loss, applying the multivariable Chain Rule to compute gradients, and updating parameters via Gradient Descent.
Let’s set up the network architecture on paper! 📝
1. The Input Layer 🖼️
Each MNIST image is a grayscale image with a grid size of pixels. Before feeding an image into a standard fully connected (dense) neural network, we need to flatten that 2D grid into a single 1D column vector, which we’ll call .
To get started with our dimensions:
If we flatten a pixel image into a single column vector, how many elements (features) will the vector have? What are its matrix dimensions ?
, so our input vector has 784 features with dimensions .
2. The Hidden Layer 🧠
Let’s design a simple 2-layer network (1 hidden layer + 1 output layer).
Suppose we choose our hidden layer to have neurons.
In the linear equation for this first layer:
- has dimensions .
We want the output of this layer, , to have a value for each of the neurons, giving it dimensions .
Using the rules of matrix multiplication (), what must the dimensions of the weight matrix and the bias vector be?
For the weight matrix , having dimensions of allows to produce a output.
The bias vector is added directly to that result, so it matches the output shape: .
But how the matrix multiplication works mechanically and geometrically?
Instead of imagining an abstract block of math, look at it as 128 separate template matchers running in parallel across the image.
1. Break the Matrix Down into Rows
When you perform:
- is a vector of 784 numbers (the unrolled image pixels).
- is a matrix of size .
- is a vector of 128 numbers (the hidden layer activations).
A matrix multiplication is just 128 dot products.
Each single neuron in that 128-neuron layer owns exactly one row of 784 weights in :
2. Every Neuron is an “Image” Itself
Because each row has 784 weights, you can reshape those 784 weights back into a grid.
-
Think of each of the 128 hidden neurons as carrying a ghostly blueprint or filter.
-
When that neuron does its dot product with the input image, it is computing a spatial cross-correlation:
- Where the input pixel is bright and the neuron’s weight is positive, the activation shoots up.
- Where the input pixel is bright but the weight is negative, it penalizes the activation.
- Where both are zero, nothing happens.
3. What the 128 Neurons Actually “Look” For

In an untrained network, these 128 templates are static noise. But once trained via backpropagation on MNIST digits:
- Neuron #1 might have high weights along a diagonal slash (detecting the stroke in a
7or/). - Neuron #2 might develop positive weights arranged in a top loop (detecting the loop of an
8or9). - Neuron #3 might activate on empty space in the center (ruling out
0s).
4. The Dimensionality Shift ()
You are not “losing” information arbitrarily; you are projecting the representation into a space of features:
- Input space (784 dimensions): “Is pixel (14, 12) dark?” (Raw sensory data, brittle to minor shifts).
- Hidden space (128 dimensions): “Does this image have a loop on top? A vertical spine? An open bottom?”(Semantic components).
By the time the next layer processes those 128 numbers to output the final 10 digits, it is no longer looking at raw pixels—it is just asking: “If it has a top circle (Neuron A = high) and a vertical stem (Neuron B = high), it’s probably a 9.”
3. Hidden Layer Activation ⚡
Before passing to the next layer, we apply a non-linear activation function element-wise, commonly ReLU(Rectified Linear Unit):
Because it operates element-by-element, the dimensions of the activated hidden layer remain .
4. The Output Layer 🎯
Now we need to project from the hidden layer ( features) to our final predictions:
We want the final output to produce a prediction score for each possible digit in MNIST ().
How many output neurons do we need, and what must the dimensions of the second weight matrix and bias vector be?
We need 10 output neurons (one for each digit ). We now have our full network structure mapped out:
- Input :
- Layer 1: ,
- Layer 2: ,
5. Output Activation: Softmax 📊
The vector gives us 10 raw real-numbered scores (logits), but we want output probabilities that sum to for the classes through .
We apply the Softmax function to compute the final activation :
Now we have a probability vector of size .
6. The Target Label 🎯
To calculate our loss (how wrong the prediction is), we need to compare our output probability vector to the true label .
If the true image is the digit :
How do we represent this target digit as a vector (known as a one-hot encoded vector) so it matches the shape of ? What would that vector look like?
Because index represents the digit , the digit actually sits at the 4th position (index 3):
With 0-based indexing, the goes at index 3, and all other entries are .
7. Measuring Error: Categorical Cross-Entropy Loss 📉
Now that we have the prediction vector and the true one-hot vector , we compute the loss for a single sample using Categorical Cross-Entropy:
Since has a only at the true class index (index 3 here) and everywhere else, what does this entire summation simplify to?
The loss simplifies down to:
In general, for any true label class , the cross-entropy loss simply measures the negative natural log of our predicted probability for that correct class: . If , ; if , .
8. Backpropagation: The Output Gradient 🔄
To update our weights using gradient descent, we need to calculate how changing each parameter affects the loss using the multivariable Chain Rule ⛓️.
We start at the very end and work backwards. The first key quantity is the derivative of the loss with respect to the output logits , which we denote as :
When you combine the derivative of the Categorical Cross-Entropy loss with the Softmax activation function using the chain rule (), an algebraic cancellation occurs.
In terms of our prediction vector and the true label vector , do you know (or would you like to derive) the formula for ?
When you combine Categorical Cross-Entropy with Softmax, the calculus cancels out cleanly to:
This is the error vector 🎯: it is simply our predicted probability vector minus the true one-hot vector.
- Its dimensions are , exactly matching .
9. Gradients for Layer 2 ( and ) 📐
Now we use to find how the loss changes with respect to the weights and biases .
Recall the forward relationship:
By the chain rule:
- The bias gradient is simply: (shape: ).
- For the weight gradient , we combine (shape ) and the hidden activation (shape ).
We know must match the shape of , which is .
How should we multiply and (using the transpose ) so that the resulting matrix has dimensions ?
To obtain a matrix from a vector and a vector, we multiply by the transpose of :
- Matrix dimensions: .