Aditya Sridhar

Model · Oct 2026 · runs in your browser

Small CNN

The MLP on the front page treats every pixel as its own input, so a digit shifted two pixels is a new problem. A convolution slides the same small filter everywhere. This network has 14× fewer parameters than the MLP and makes fewer mistakes.

Test accuracy
99.41%
Parameters
7,578
Model file
32 KB, ONNX
Training time
39 s, GPU

Try it

Waiting for a digit

  1. 0
  2. 1
  3. 2
  4. 3
  5. 4
  6. 5
  7. 6
  8. 7
  9. 8
  10. 9

Loading the runtime…

Layer 1 · 8 filtersEdges and strokes at each position
Layer 4 · 16 filtersParts of digits, coarser and more abstract

The exported ONNX graph runs in onnxruntime-web on WebAssembly. Besides the probabilities it returns two intermediate activations, drawn above: the first layer responds to strokes and edges at every position, the fourth to larger parts of the digit.

Training run

Accuracytraintest
70.0%80.0%90.0%100.0%147101315epoch 1 · train 64.8%epoch 2 · train 96.3%epoch 3 · train 97.4%epoch 4 · train 98.0%epoch 5 · train 98.3%epoch 6 · train 98.4%epoch 7 · train 98.5%epoch 8 · train 98.5%epoch 9 · train 98.7%epoch 10 · train 98.7%epoch 11 · train 98.8%epoch 12 · train 98.9%epoch 13 · train 99.0%epoch 14 · train 99.1%epoch 15 · train 99.2%epoch 1 · test 95.9%epoch 2 · test 96.5%epoch 3 · test 97.9%epoch 4 · test 98.8%epoch 5 · test 98.9%epoch 6 · test 98.5%epoch 7 · test 98.8%epoch 8 · test 99.1%epoch 9 · test 99.1%epoch 10 · test 99.0%epoch 11 · test 99.1%epoch 12 · test 99.2%epoch 13 · test 99.3%epoch 14 · test 99.4%epoch 15 · test 99.4%
Losstraintest
0.0000.5001.000147101315epoch 1 · train 1.224epoch 2 · train 0.150epoch 3 · train 0.090epoch 4 · train 0.071epoch 5 · train 0.060epoch 6 · train 0.056epoch 7 · train 0.053epoch 8 · train 0.051epoch 9 · train 0.047epoch 10 · train 0.044epoch 11 · train 0.042epoch 12 · train 0.039epoch 13 · train 0.036epoch 14 · train 0.031epoch 15 · train 0.029epoch 1 · test 0.184epoch 2 · test 0.114epoch 3 · test 0.074epoch 4 · test 0.041epoch 5 · test 0.037epoch 6 · test 0.051epoch 7 · test 0.041epoch 8 · test 0.034epoch 9 · test 0.032epoch 10 · test 0.032epoch 11 · test 0.031epoch 12 · test 0.027epoch 13 · test 0.022epoch 14 · test 0.021epoch 15 · test 0.020

Results

MetricValueSource
Test accuracy, 10,000 images99.41%training/cnn_mnist/log.csv, epoch 15
Test loss0.0199training/cnn_mnist/log.csv, epoch 15
Train accuracy (augmented)99.19%training/cnn_mnist/log.csv, epoch 15
Test accuracy of the exported ONNX file99.41%public/models/cnn-mnist/meta.json
MLP on the same test set, for comparison98.69%training/mlp_numpy/log.csv

How it works

Architecture. Five 3×3 convolutions with batch norm and ReLU, one max pool after the second, then a 1×1 convolution to ten channels and a global average pool. The spatial size goes 28, 26, 24, 12, 10, 8, 6. There is no fully connected layer: averaging each class map over the image is the classifier, which is where most of the parameter saving comes from.

Training. SGD with Nesterov momentum 0.9, batch 128, weight decay 0.0005, a one-cycle learning rate peaking at 0.05 over 15 epochs. Each batch is rotated by up to ±10° and shifted by up to 10% of the image, on the GPU.

Export. The model is exported to ONNX with the softmax and the two feature-map taps built into the graph, then the exported file itself is scored on all 10,000 test images, because that file is what runs here, not the PyTorch model.

Reproduce

Hardware
NVIDIA GeForce RTX 3050 Laptop GPU, PyTorch 2.6.0+cu124
Commit
eeb3064
Data
MNIST, 60,000 train / 10,000 test, downloaded and MD5-checked by the script

Environment

pip install torch numpy onnx onnxruntime

Train, export and score the ONNX file

python training/cnn_mnist/train.py