An interactive handwritten digit, letter, and doodle classifier built with the Burn deep learning framework in Rust. Train a CNN model, run inference from the CLI, or draw in the browser!
- Features
- Model Architecture
- Project Structure
- Getting Started
- Quick, Draw! Classification Details
- References
- License
- MobileNet-style CNN Architecture β Depthwise Separable Convolutions, 1x1 projection and identity residual shortcuts, BatchNorm, GAP, and Dropout
- Interactive Web Demo β Draw on a canvas and get real-time predictions
- WebAssembly Client-Side Inference β Runs entirely in the browser via WASM, no backend required
- CLI Inference β Predict with ASCII art visualization
- Fully in Rust β Training, inference, and web frontend in a unified workspace
- Data Augmentations β Random spatial translation, scale/zoom shifts, and horizontal flips (QuickDraw) applied dynamically during batch collation for robust canvas prediction
Input [1Γ28Γ28]
β Stem: Conv2d(1β24, 3Γ3, stride=1) β BatchNorm β ReLU β [24Γ28Γ28]
β Block 1: SeparableConv(24β48, stride=2) + Proj Shortcut(24β48)
β BatchNorm β ReLU β [48Γ14Γ14]
β Block 2: SeparableConv(48β96, stride=2) + Proj Shortcut(48β96)
β BatchNorm β ReLU β [96Γ7Γ7]
β Block 3: SeparableConv(96β96, stride=1) + Identity Shortcut
β BatchNorm β ReLU β [96Γ7Γ7]
β Classifier:
β Global Average Pooling (GAP) β [96Γ1Γ1]
β Flatten β [96]
β Dropout(0.5)
β Linear(96βnum_classes)
burn-drawing-classifier/
βββ model_shared/ # Shared library workspace crate
β βββ src/lib.rs # CNN model definition & LayerNorm layers
βββ web/ # Rust WASM crate (wasm-pack entry point)
βββ src/ # Training & CLI inference (Burn backend)
β βββ main.rs
β βββ model.rs # Re-exports shared model definition
β βββ training.rs
β βββ ...
βββ docs/ # Static web frontend (served by GitHub Pages)
β βββ index.html # Single-page drawing app with Developer Console
β βββ pkg/ # Compiled WASM output (gitignored, built by CI)
βββ assets/ # README images and training curves
βββ build.rs # Copies model weights at build time
βββ publish-weights.ps1 # Helper script to upload weights to GitHub Releases
βββ .github/workflows/
βββ deploy.yml # CI: build WASM β assert β deploy β verify
Git Branches:
masterβ The only branch you need. All development happens here. Compiled binaries are gitignored and built fresh by CI on every deploy.
By default, training runs on the CPU (NdArray backend):
cargo run --releaseTo train on your GPU (Wgpu backend):
cargo run --release -- --gpuTo train on the EMNIST Letters dataset (26 classes, downloads dynamically):
# CPU
cargo run --release -- --dataset emnist
# GPU
cargo run --release -- --dataset emnist --gpuTo train on the Google Quick, Draw! dataset (25 classes, downloads dynamically):
# CPU
cargo run --release -- --dataset quickdraw
# GPU
cargo run --release -- --dataset quickdraw --gpuDataset Cache: Dataset files are downloaded once and cached at
target/emnist_dataset/andtarget/quickdraw_dataset/. If you change the configurations, delete the caches first so they re-download:rm -rf target/quickdraw_dataset # Linux / macOS Remove-Item -Recurse -Force target\quickdraw_dataset # Windows PowerShell
Note: Always use
--releasefor optimized tensor math performance.
After 5 epochs of training:
| Dataset | Validation Accuracy | Validation Loss |
|---|---|---|
| MNIST (10 classes) | ~98%+ |
~0.05 |
| EMNIST Letters (26 classes) | ~90%+ |
~0.30 |
| Quick, Draw! (25 classes) | ~86%+ |
~0.51 |
cargo testOnce trained, predict from the MNIST test set:
cargo run --release -- --predictPredict from the EMNIST Letters test set:
cargo run --release -- --predict --dataset emnistPredict from the Quick, Draw! test set:
cargo run --release -- --predict --dataset quickdrawπ Example Output (MNIST)
Loading model for inference...
Input Image:
######
################
################
###########
####
####
####
####
####
####
###
####
####
#####
####
#####
####
#####
#####
####
Target Label (Ground Truth): 7
Top Predictions:
1. 7 : 99.42%
2. 9 : 0.35%
3. 2 : 0.11%
π Example Output (EMNIST Letters)
Loading model for inference (dataset: emnist)...
Loading and parsing EMNIST Letters test data...
Input Image:
...
.#####
..######.
..#######.
.########....
.######...####.
.#####...#####.
###############
.###############
.##############.
.###############.
..####.. .####..
.... .######..
.. ...####..
......
Target Label (Ground Truth): A
Top Predictions:
1. A : 88.57%
2. R : 3.80%
3. D : 1.84%
π Example Output (Quick, Draw!)
Loading model for inference (dataset: quickdraw)...
Input Image:
#
###
#####
#######
#########
###########
#############
###############
#################
Target Label (Ground Truth): triangle
Top Predictions:
1. triangle : 96.81%
2. mountain : 2.14%
3. house : 0.45%
Start the browser-based drawing pad backed by the Rust Axum server:
-
MNIST Digits (default):
cargo run --release -- --serve
Open http://127.0.0.1:3000 to draw digits (0β9).
-
EMNIST Letters:
cargo run --release -- --serve --dataset emnist
Open http://127.0.0.1:3000 to draw letters (AβZ).
-
Quick, Draw! Doodles:
cargo run --release -- --serve --dataset quickdraw
Open http://127.0.0.1:3000 to draw and predict doodles (25 classes).
The trained models compile to WebAssembly for fully client-side inference. Model weights are decoupled from Git history to avoid binary bloat:
- Locally: The weights (
*-model.bin) are copied to thedocs/folder (which is ignored by Git). - In CI: The GitHub Actions deployment workflow automatically downloads the weights from GitHub Releases during build.
Make sure you have trained the models first, then:
-
Install wasm-pack:
cargo install wasm-pack
-
Build the WebAssembly module:
- Using the helper binary (Recommended, cross-platform):
cargo run --bin build_web
- Or run the command manually (does not automatically clean up duplicate/redundant folders):
wasm-pack build web --target web --out-dir ../docs/pkg
- Using the helper binary (Recommended, cross-platform):
-
Install a local static file server (needed for the preview):
cargo install basic-http-server
-
Serve locally:
basic-http-server docs
Navigate to http://localhost:4000.
The CI workflow (.github/workflows/deploy.yml) automatically builds and deploys to GitHub Pages on every push to master. It also verifies the live WASM URL after deploying.
To update the model weights used by the CI runner:
-
Ensure the GitHub CLI (
gh) is installed and authenticated:- Install (Windows):
winget install --id GitHub.cli(restart VS Code after) - Authenticate:
gh auth loginβ GitHub.com β HTTPS β browser
- Install (Windows):
-
Upload your local weights to a GitHub Release:
# Default v1.0.0 cargo run --bin publish_weights # Custom version tag cargo run --bin publish_weights -- v2.0.0
The script now updates web/weights-version.txt and docs/weights-version.txt automatically, so you no longer need to edit those files by hand.
-
Commit and push the updated version files along with any code changes:
git add web/weights-version.txt docs/weights-version.txt git commit -m "Update model weights version" git push origin master -
Trigger the deployment:
- Code changes:
git push origin master - Weights only: Go to Actions β Deploy WebAssembly to GitHub Pages β Run workflow
- Code changes:
-
Verify your repository settings under Settings β Pages β Build and deployment:
- Source:
GitHub Actions
βΉοΈ The workflow uses the official GitHub Pages API (
upload-pages-artifact+deploy-pages), so nogh-pagesbranch is needed. - Source:
This project supports doodle classification using the public Google Quick, Draw! Dataset.
Rather than training on all 345 categories (39 GB of raw data), we train on a curated subset of 25 diverse and sketchable classes:
| Group | Classes |
|---|---|
| Nature / Weather | sun, moon, star, tree, flower |
| Animals | cat, dog, fish, butterfly |
| Common Objects | cup, key, umbrella, hat, clock, envelope, toothbrush |
| Structures / Vehicles | house, car |
| Shapes | circle, triangle, square, smiley face |
| Clothing | pants, t-shirt |
| Food | apple |
- Compute & Storage: 25 classes β ~250 MB footprint, fits in memory, trains in minutes on a consumer GPU.
- Model Capacity: A simple CNN easily achieves high accuracy on 25 distinct shapes vs. struggling with all 345.
- Canvas Drawability: These shapes are simple and iconic enough to draw clearly on a
28Γ28canvas. - License & Privacy: CC BY 4.0, no personally identifiable information (PII).
- Burn β Deep Learning Framework for Rust
- The Burn Book
- tracel-ai/burn MNIST example
- wasm-pack β Rust WebAssembly Packager
- Yann LeCun's MNIST Database
- EMNIST Dataset (NIST Special Database 19)
- EMNIST Paper: Cohen et al., "EMNIST: Extending MNIST to handwritten letters"
- Google Quick, Draw! Dataset
This project is licensed under the MIT License.

