Skip to content
← All projects

C++ Neural Network → WebAssembly

97.33% on MNIST · runs in your browser
  • C++
  • WebAssembly
  • Neural Networks

Data

Trained on MNIST. The browser demo mirrors MNIST's preprocessing in JavaScript: crop the drawing to its bounding box, scale the longer side to 20px keeping aspect ratio, and centre it by centre of mass in a 28×28 frame.

What I implemented by hand

Backpropagation, the matrix library, and the dense layers written from scratch in C++ (no ML libraries), compiled to WebAssembly with Emscripten. Network: 784 → 128 (ReLU) → 10, softmax on the output. My first from-scratch network; I later rebuilt the same architecture in NumPy to compare optimizers.

How I evaluated it

Two checks on the exact weights shipped to the browser. Native: the C++ network evaluated on the 10,000-image MNIST test set. Browser parity: every one of those 10,000 test digits scaled up onto the demo's canvas and run through the demo's own preprocessing and WebAssembly predict handler in headless Chromium.

Results

The browser pipeline loses 0.1 points to preprocessing. This measures the demo's code path on MNIST digits — not accuracy on people's own handwriting.

Native C++, MNIST test set97.43%
In-browser pipeline, same 10,000 digits97.33%

What broke

  • The live demo was running an untrained network. The page never fetched the trained weights, and nothing checked whether it had — a clean '1' came back as '4'.
  • After I fixed it, browsers kept running the old cached script, because its ?v=1 cache-busting tag never changed.
  • Now the weight loader returns how many parameters it read (it must be 101,770), Predict stays disabled unless the fetch succeeds and the count matches, and the cache-busting tag is derived from each file's contents, so it changes automatically.