How a network finds edges
A convolution slides a small grid of numbers, called a filter, across an image and records how strongly each patch matches a pattern. It is how a convolutional neural network (CNN) finds edges and shapes in a photo without anyone describing them.
The same idea works on a row of numbers, so we start with one.
By the end of this lesson
Note
I can explain how a convolution finds a pattern in a grid of numbers.
Key words
| Word | What it means | Example |
|---|---|---|
| Pixel | one number in an image: how bright a dot is | |
| Filter | a small grid of weights that looks for a pattern | [-1, 1] |
| Convolution | sliding a filter over the input and adding the products | |
| Feature map | the grid of results a filter gives | |
| Max pooling | keeping the biggest value in each small block |
The cells students fill in are left blank in this preview.
A filter slides and adds products
Take a row of numbers and a short filter. Place the filter at the start. Multiply each number under it by the matching filter weight, and add. That is one output.
Slide the filter one place along and repeat. Our row is dark then bright: 0, 0, 0, 9, 9, 9. Our filter is -1 and 1, so each output says how much brighter the next number is.
Worked example: filter -1, 1 on a row
| Place | Numbers under the filter | Working | Output |
|---|---|---|---|
| 1 | 0, 0 | -1x0 + 1x0 | 0 |
| 2 | 0, 0 | -1x0 + 1x0 | 0 |
| 3 | 0, 9 | -1x0 + 1x9 | 9 |
| 4 | 9, 9 | -1x9 + 1x9 | 0 |
| 5 | 9, 9 | -1x9 + 1x9 | 0 |
The cells students fill in are left blank in this preview.
Slide the filter with NumPy
docker exec jupyter python3 -c "import numpy as np; print(np.correlate([0, 0, 0, 9, 9, 9], [-1, 1], 'valid').tolist())"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
In an image the filter slides two ways
A grey photo is a grid of pixels. Each pixel is one number, here from 0 for black to 9 for bright. A 2 by 2 filter covers four pixels at a time. It moves along a row, then down to the next.
A 4 by 4 image and a 2 by 2 filter give a 3 by 3 result. The size is the image size minus the filter size plus 1.
Worked example: an image with an edge
| Row | Column 1 | Column 2 | Column 3 | Column 4 |
|---|---|---|---|---|
| 1 | 0 | 0 | 9 | 9 |
| 2 | 0 | 0 | 9 | 9 |
| 3 | 0 | 0 | 9 | 9 |
| 4 | 0 | 0 | 9 | 9 |
The cells students fill in are left blank in this preview.
Worked example: the vertical edge filter, top row
| Place | Patch | Working | Output |
|---|---|---|---|
| Column 1 | 0 0 over 0 0 | -1x0 + 1x0 + -1x0 + 1x0 | 0 |
| Column 2 | 0 9 over 0 9 | -1x0 + 1x9 + -1x0 + 1x9 | 18 |
| Column 3 | 9 9 over 9 9 | -1x9 + 1x9 + -1x9 + 1x9 | 0 |
The cells students fill in are left blank in this preview.
Slide a filter over a grid
image = [[0, 0, 9, 9], [0, 0, 9, 9], [0, 0, 9, 9], [0, 0, 9, 9]]
filt = [[-1, 1], [-1, 1]]
for r in range(3):
row = []
for c in range(3):
total = 0
for a in range(2):
for b in range(2):
total = total + image[r + a][c + b] * filt[a][b]
row.append(total)
print(row)
# Change the filter to [[-1, -1], [1, 1]] and press Run.
# This one looks for a horizontal edge. What do you see?
Max pooling shrinks the result
Max pooling keeps the biggest value in each small block. A 2 by 2 pool turns a 4 by 4 feature map into a 2 by 2 map.
It keeps the strongest signal and forgets exactly where it was. That makes the next layer smaller, and less fussy about where a pattern sits.
Worked example: pooling a 4 by 4 map
| Block | Values | Biggest |
|---|---|---|
| Top left | 1 3 over 4 6 | 6 |
| Top right | 2 0 over 1 1 | 2 |
| Bottom left | 0 2 over 1 1 | 2 |
| Bottom right | 5 7 over 3 2 | 7 |
The cells students fill in are left blank in this preview.
Pool the 4 by 4 map with NumPy
docker exec jupyter python3 -c "import numpy as np; print(np.array([[1, 3, 2, 0], [4, 6, 1, 1], [0, 2, 5, 7], [1, 1, 3, 2]]).reshape(2, 2, 2, 2).max(axis=(1, 3)).tolist())"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A convolutional network stacks these steps
A CNN repeats convolution, ReLU and pooling, then ends with ordinary dense layers that give the answer. Early filters tend to find edges. Later ones combine edges into shapes.
A filter's weights are learned by gradient descent, like any other weight. They are shared across the whole image, which keeps the count small.
Worked example: shared weights save parameters
| Layer | Weights | Biases | Parameters |
|---|---|---|---|
| One 3 by 3 filter | 9 | 1 | 10 |
| 32 such filters | 288 | 32 | 320 |
| Dense layer, 784 inputs, 128 neurons | 100,352 | 128 | 100,480 |
The cells students fill in are left blank in this preview.
A common mistake
Warning
Many people think someone chooses a filter's numbers by hand to find cats or faces. We chose ours to show the idea. In a real network the filter weights start random, and gradient descent sets them, the same as every other weight.
Going further
In a notebook cell, call np.correlate([1, 2, 3, 4, 5], [1, 1, 1], 'valid') after import numpy as np. Work out the three answers by hand first.
What does a filter of three ones do to a row? Then try a filter of 1, 0 and -1, and say what it measures.