Library / SDK
DFin/Neural-Network-Visualisation avatar
DFin/Neural-Network-Visualisation

Neural-Network-Visualisation: a drawing pad wired to a live MNIST classifier

Interactive web visualisation for handwritting detection using a simple neural network

688 stars78 forksJavaScriptApache-2.0

At a glance

What is it?
This project is a browser-based 3D view of a small multi-layer perceptron trained on MNIST, where you draw a digit and watch activation flow through the layers. It is explicitly marked as work in progress, so the realistic use is teaching and inspection rather than a finished museum install.
Who is it for?
Adopt this if you teach perceptrons, want a concrete answer to what activations look like inside a small MNIST network, and are comfortable serving static files and occasionally retraining a PyTorch model. Do not adopt it for a public kiosk, a tablet-based exhibit, or any environment where you cannot run Python and point an nginx root at a directory.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A drawing pad that feeds a real MNIST classifier

The idea is narrow and legible. You draw a digit on a 28 by 28 grid, the browser sends that image into a compact multi-layer perceptron that was trained on the MNIST handwritten digit dataset, and you watch the activations move through the network in 3D while prediction probabilities update. Nothing about the tool is a metaphor. The weights are the weights, exported from a real PyTorch training run, and the front-end builds the scene from the layer metadata in the same file.

The audience is stated by the project itself, and it is unusual. The project describes itself as still in a rough state and under active development, and it says that if you want something usable for a museum, check back later. The named future feature is connecting a tablet so a visitor can draw a number. That framing sets expectations correctly. This is an educational visualisation for a laptop, a lecture, a blog post, or a curious engineer, and the author says so rather than dressing it up.

The deployed site sits at nn-vis.noelith.dev. The project is JavaScript on the front end, Apache-2.0 licensed, with a NOTICE.MD file at the root alongside the LICENSE.

What the scene actually shows

The visual encoding is where the teaching value lives. Colours encode activation sign and magnitude, with cool tones for negative or low values and warm tones for strong positive contributions. The visualiser also highlights the top-N strongest incoming connections per neuron, and that count is configurable, so you can dial the legibility of the picture down when the network gets busy.

The prediction side shows real-time prediction probabilities as you draw, which is the part that makes the tool a feedback loop rather than an animation. Left-click paints on the grid and right-click erases, so you can watch how a single stroke changes the distribution across the ten output classes and how the intermediate layers react.

The architecture is not fixed in the code. The front-end builds the scene dynamically from the metadata in the weights export, so the number of layers and their widths come from the model you trained. The documentation is explicit that if you change the architecture you have to make the JSON export reflect the new layer sizes, otherwise the scene and the model disagree. That is a real coupling: the visualisation is a view over one specific export file, and regenerating it is part of the workflow.

Serving it takes one static file server

There is no build step for the front end. The repository layout is small: `index.html` and `assets/` hold the static Three.js visualiser and its UI assets, `exports/mlp_weights.json` holds the default weights with timeline snapshots, and `training/mlp_train.py` is the PyTorch helper that trains the model and exports those weights.

To view the default model, start a static file server from the repository root. The README notes that any server works and uses Python as the example:

bash
python3 -m http.server 8000

Then open `http://localhost:8000` in a browser, draw on the grid and explore the 3D network with a mouse or trackpad. The file server has to be the root of the checkout, because the front-end fetches the weights export by relative path from the same tree.

Training is optional. If you want to regenerate the weights yourself, the Python dependencies are PyTorch and torchvision:

bash
python3 -m pip install torch torchvision

That separation between viewing and training is the main practical shape of the project. A visitor or a student needs a browser and a file server. A maintainer needs Python as well.

The timeline streams snapshots instead of shipping the whole run

The most interesting engineering decision in the project is how the training timeline is stored. Every exported JSON includes a `timeline` array spanning 35 checkpoints. Early snapshots are densely packed at roughly 50, 120, 250, 500, 1k, 2k, 3.5k, 5.8k, 8.7k, 13k, 19.5k, 28.5k and 40k images, and after that come dataset-multiple milestones from 1x through 50x.

Each snapshot's weights live in a separate float16-encoded file under `exports/<stem>/NNN_<id>.json`, and the manifest stays small. The front-end streams those files on demand, which is what allows you to scrub the timeline without downloading the entire 50x run up front. The design goal is legible: the manifest describes the run, the payload files carry the numbers.

Float16 encoding is worth pausing on. It halves the size of the weight payloads compared with a float32 export, and it also means the visualised weights are not bit-identical to the trained ones. For a teaching tool that is a good trade. For anyone planning to use the export as a source of truth for a model they intend to ship, it is worth checking the precision before trusting the file.

The default export in the repository already carries timeline milestones from a multi-epoch training run, so you can scrub without training anything yourself.

Retraining the MLP and pointing the front-end at the new file

Training is a PyTorch script with a small set of options, and the notes are candid about why the defaults are small. The network should stay modest so the visualisation stays responsive.

A typical run looks like this:

bash
python3 training/mlp_train.py \
  --epochs 5 \
  --hidden-dims 128 64 \
  --batch-size 256 \
  --export-path exports/mlp_weights.json

The documented options are `--hidden-dims` for hidden layer sizes, defaulting to `128 64`; `--epochs` for a minimum number of epochs, default `5`, where the script extends the run automatically so the timeline reaches the 50x dataset milestone; `--batch-size`, default `128`; and `--device`, which can force `mps`, `cuda` or `cpu` when you do not want the automatic choice. There is also `--skip-train`, which exports randomly initialised weights without training at all, and the notes describe it as useful for debugging the pipeline.

The script uses Metal (MPS) automatically on Apple Silicon, and otherwise falls back to CUDA or CPU. After you export, you update `VISUALIZER_CONFIG.weightUrl` in `assets/main.js` if the file is at a different path or name, then refresh the browser. That single config key is the only wiring point between the Python side and the browser.

Deployment keeps stamped rollback copies under releases/

Production hosting is organised around a repo-local `releases/` tree, described as ideal for `/srv/server/nn-vis` on a new host. Three directories carry the model. `releases/current/` holds the files served by nginx. `releases/backups/<timestamp>/` holds immutable snapshots for quick rollback, each stamped with the commit hash in a `.commit` file. `releases/.deploy_tmp/` is the staging area used during deployment.

A single script does the work:

bash
./deploy.sh [commit-ish]

With no commit supplied it deploys `HEAD`. The script exports the requested commit, stages it under `.deploy_tmp/`, then rsyncs into `releases/current/` and a timestamped backup. The server block that points nginx at the right directory is short:

bash
server {
    server_name nn-vis.noelith.dev;
    root /srv/server/nn-vis/releases/current;
    index index.html;
    location / {
        try_files $uri $uri/ /index.html;
    }
}

TLS is handled separately with `certbot --nginx -d nn-vis.noelith.dev` once the site block is enabled. The rollback story is therefore concrete rather than aspirational: because every backup carries its commit hash in `.commit`, restoring a previous state means pointing `root` at the matching `releases/backups/<timestamp>/` directory.

What is still missing and what to use instead

The project is honest about its gaps, and the gap is a real constraint for anyone planning an exhibit. The project calls itself rough and says the tablet input feature is still only an idea, so a touch-based kiosk is out of reach. There are no GitHub releases, so there is no published version to pin, and the last push was on 2026-04-24, which means the deployment script, the front-end config key and the training script may have drifted apart since then. Any adoption plan should start by reading `deploy.sh` and `assets/main.js` as they exist in the checkout rather than assuming the documented steps still match.

The alternative approach depends on what you are after. If the goal is to teach what a perceptron does, most notebooks and plotting libraries already give you loss curves and per-sample predictions, and they stay current with the framework they belong to. What those do not give you is the spatial view of activation flow through individual neurons, and that is precisely the gap this project fills. If the goal is a production classifier, a trained model behind a small service is the better route, since the visualisation adds weight streaming and a JSON export pipeline you would otherwise not need.

Judged as a teaching tool with an interactive, geometric explanation, it is a distinctive piece of work. Judged as a finished product, the author is right to call it unfinished.

Editorial conclusion

Adopt this if you teach perceptrons, want a concrete answer to what activations look like inside a small MNIST network, and are comfortable serving static files and occasionally retraining a PyTorch model. Do not adopt it for a public kiosk, a tablet-based exhibit, or any environment where you cannot run Python and point an nginx root at a directory. First check that `exports/mlp_weights.json` is present in your checkout, that the timeline snapshots resolve under `exports/<stem>/`, and that the last push of 2026-04-24 left the deployment script and the front-end config in a state you can run.

Frequently asked questions

How do I run Neural-Network-Visualisation locally?

Serve the repository root with any static file server and open it in a browser. The README uses `python3 -m http.server 8000` and then `http://localhost:8000`. No build step is required for the front end.

Do I need to train the model before I can use the visualiser?

No. The repository ships a default export at `exports/mlp_weights.json` that already includes timeline milestones from a multi-epoch run. Retraining is only needed if you want a different architecture or a different training progression.

What do the colours and the highlighted connections mean in Neural-Network-Visualisation?

Colours encode activation sign and magnitude, with cool tones for negative or low activations and warm tones for strong positive contributions. The scene highlights the top-N strongest incoming connections per neuron, and that N is configurable.

How do I retrain the MLP and load the new weights in the browser?

Run `python3 training/mlp_train.py` with options such as `--epochs`, `--hidden-dims` and `--export-path`, then point `VISUALIZER_CONFIG.weightUrl` in `assets/main.js` at the exported file and refresh the page. The script uses Metal (MPS) on Apple Silicon and otherwise falls back to CUDA or CPU.

Can I roll back a deployment of Neural-Network-Visualisation?

Yes. `deploy.sh` rsyncs into `releases/current/` and also writes a timestamped copy under `releases/backups/<timestamp>/`, with the commit hash stored in a `.commit` file, so a previous state can be restored by pointing the server root at the matching backup directory.

Official sources

  1. DFin/Neural-Network-Visualisation on GitHub
  2. Issues
  3. License: Apache-2.0
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dfin-neural-network-visualisation.svg)](https://hysenlabs.com/projects/dfin-neural-network-visualisation)

Was this useful?