← All Models

Beard or Badger

A scratch-built CNN image classifier built in vanilla JS β€” no frameworks, no GPU, just convolutions

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:

or
πŸ–ΌοΈ

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:

  1. Searched for β€œbeards” and β€œbadgers” on image search engines
  2. Used a JS snippet in the browser console to scrape all the image URLs from the results page into JSON
  3. Wrote a Python script to download and resize each image to 32Γ—32 pixels
  4. 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:

InputRGB image
3 Γ— 32 Γ— 32
Conv13 β†’ 8 channels, 3Γ—3 kernel
8 Γ— 30 Γ— 30
ReLU
MaxPool2Γ—2 stride 2
8 Γ— 15 Γ— 15
Conv28 β†’ 16 channels, 3Γ—3 kernel
16 Γ— 13 Γ— 13
ReLU
MaxPool2Γ—2 stride 2
16 Γ— 6 Γ— 6
Flatten
576
Dense576 β†’ 2
2
Softmaxbeard vs badger
2 probabilities

Training

I ended up keeping training as minimalist as the model. I started with the following, basic set up:

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.