Keenan Pereira
CV (PDF) Mumbai

Lights

Write-ups

A CNN that shrinks a face to 512 numbers

How a four-block convolutional network turns a 48 × 48 grey face into 512 features and then seven emotion scores, where its 4.2 million parameters sit, and why dropout follows every block.

27 Oct 2026 3 min readFrom the project Live emotion detection

The problem

Expression cues live in a few pixels of a 48 × 48 grey face.

The fix

Four widening convolution blocks pool the face down to 512 features, with dropout throughout.

4
convolution blocks
4.2M
trainable parameters
48 → 1
pixels per side after four blocks

A FER-2013 face is 2,304 grey pixels. Somewhere in there is a frown or half a smile, and the network has to find it with no pre-trained model to lean on: every weight starts random and learns from 28,709 faces.

Credit where it’s due

The layer stack isn’t my invention. It follows a well-known FER-2013 design, and I’d rather say that plainly. My part was training it, reading what it actually did and building the app around it. Knowing the design inside out still mattered, because the app has to feed it exactly the input it expects.

The stack

input: 48 × 48 × 1 (one grey channel)
4 blocks: Conv 3×3 → MaxPool 2×2 → Dropout 0.4
          filters 128 → 256 → 512 → 512
Flatten → Dense 512 → Dropout 0.4
        → Dense 256 → Dropout 0.3
        → Dense 7, softmax

In plain terms:

  • A convolution slides a small 3 × 3 window over the image and learns to light up for a pattern: an edge at first, later the curve of a brow or the corner of a mouth.
  • Max pooling keeps the strongest response in each 2 × 2 patch, halving the image’s width and height.
  • Dropout switches off a random 40% of units on each training step, so the network can’t rely on any single one.
  • Softmax turns the final seven scores into probabilities that add up to one.

The filters widen as the image shrinks: 128, 256, 512, 512. Fewer pixels, more kinds of pattern per pixel.

Following one face through

Each 3 × 3 convolution trims a pixel from every edge, and each pooling halves what’s left:

  • Block 1: 48 → 46 → 23
  • Block 2: 23 → 21 → 10
  • Block 3: 10 → 8 → 4
  • Block 4: 4 → 2 → 1

By the end, the whole face is a 1 × 1 grid with 512 channels: 512 numbers, each saying how strongly one learned pattern appeared. Two dense layers turn those into seven scores, one per emotion.

Where the weight sits

Adding up the layers gives 4,232,199 trainable parameters, about 4.2 million. The interesting part is where they live. The fourth convolution block, working on a 4 × 4 grid, holds 2.36 million of them, 56% of the model. The two hidden dense layers hold under 400,000. Most of the capacity sits late in the network, where the features are most abstract.

How it trains

  • Labels are one-hot: seven slots with a 1 in the right one.
  • Categorical cross-entropy measures how much probability the model gave the correct emotion, and the Adam optimiser nudges every weight to raise it.
  • Batches of 128 faces mean 225 updates per pass over the data. 200 passes make 45,000 updates.

Why dropout everywhere

There are six dropout layers: 0.4 after each convolution block and the first dense layer, and 0.3 after the second. With about 147 parameters for every training face, the model has room to memorise, and dropout is the brake.

It slowed memorisation without stopping it. By epoch 200, training accuracy was about 82% while validation sat in the low 60s.

What I’d change

My project presentation listed the next steps, and they still stand:

  • Batch normalisation after each convolution, to steady training.
  • Data augmentation: flips, small rotations and zooms, to stretch 28,709 faces further.
  • Early stopping, so training ends when validation stops improving.
  • Transfer learning from a pre-trained network such as VGG16 or ResNet50.

Lessons

  1. Start from a known-good design, and say so. Credit costs nothing.
  2. Trace the shapes by hand. It shows where the parameters live and what the last layers actually see.
  3. Dropout delays overfitting. It doesn’t replace a stopping rule.

Keep reading

Esc
↑↓ moveEnter openTry “party”