This is a convolutional neural network I built from scratch in JavaScript, no dependencies, trained on the CPU. It helps you decide if a given image is of a beard β¦or a badger.
A lot of the ideas for toy models I build come from conversations with my children.
I implemented every layer myself: convolution, max pooling, ReLU, dense layers, softmax, cross-entropy loss, and wrote a small gradient engine to support backpropagation.
I cover the code and concepts in more detail in the notebook, see the following pages for a deeper dive:
The idea was to end up with something small enough to run in the browser console,
so that you could inspect any image and simply run beardOrBadger($0) to classify it.
Try it out
The model runs entirely in your browser (only ~50KB of weights).
Pick a random image from the dataset, or drop/paste your own:
Drop or paste an image here, or click Random image to pick from the dataset
Tip: copy any image to your clipboard and press Ctrl+V anywhere on this box
You can also search images from Wikimedia Commons below. Click a result to classify it. Be warned that I donβt control what images Wikimedia serves up, so you may get some weird results!
Or search for something elseβ¦
Model details
The dataset
I built the dataset by hand:
- Searched for βbeardsβ and βbadgersβ on image search engines
- Used a JS snippet in the browser console to scrape all the image URLs from the results page into JSON
- Wrote a Python script to download and resize each image to 32Γ32 pixels
- Did a manual sweep to remove anything that wouldnβt work (broken images, irrelevant results, etc.)
The final dataset has 273 beards and 258 badgers β 531 tiny images in total:
Loading datasetβ¦
32Γ32 is very small, but it turns out thatβs enough for a CNN to learn the difference. Badgers tend to have a distinctive black-and-white stripe pattern, while beards are usually warmer tones against skin.
I was very pleased with the model size:performace trade-off here.
Architecture
The model is a classic small CNN with 2,546 parameters:
Training
I ended up keeping training as minimalist as the model. I started with the following, basic set up:
- train directly in Node.js on the CPU
- SGD updates the weights (no batching, no momentum, no learning rate scheduling β just the bare essentials)
The results were good enough after this first pass that I didnβt feel the need to add any bells and whistles.
Splits
I used a file name convention (e.g. beard_001.jpg, badger_001.jpg), and the
label was inferred from the file name. I got this idea from the way FastAI approaches
image classification in their Practical Deep Learning for Coders course.
The data was then shuffled and split 80/20 into training and test sets (~425 train, ~106 test).
There was no separate validation set β just train and evaluate. A proper setup would have a three-way split, but for a toy model on 531 images it does the job.
The weights were exported as a flat JSON array and the inference code mirrors the training architecture, minus the autograd machinery.