An autoencoder in pure CSS

Draw a digit. An encoder (64→64→32→2) squeezes it into just 2 numbers, a point on the map. A decoder (2→32→64→256) draws a digit back from those 2 numbers. 25,154 integer weights, only @property, calc() and :has(). 0 bytes of JavaScript.

1. Draw 8×8

Press and drag on the grid.Tap cells to paint them, tap again to erase.

2. Encoded to 2 numbers the dot

your drawing

Hover the map to preview any point, click to pin it.Tap the map to pick any point. Colour: the digit that lives there.

3. Decoded 16×16

x y

Draw a digit on the left: it becomes a dot on the map, and a new digit is drawn here from that dot alone. Your 64 pixels became 2 numbers, the dot. This digit was redrawn from those 2 numbers only: that is all the network kept. You picked a point on the map, so the decoder draws what lives there instead of your drawing.

Inside the network

Every cell below is a CSS custom property, recomputed by the browser each time a pixel or the map changes.

Encoder: drawing → 2 numbers

Your drawingcropped to 8×8
Encoder layer 164 ReLU units
Encoder layer 232 ReLU units
The code2 numbers → a map cell

Decoder: 2 numbers → drawing

Cell centre2 numbers
Decoder layer 132 ReLU units
Decoder layer 264 ReLU units
Pixels16×16 hard sigmoid