Pooling and the CNN — write them yourself
The convolution works. Two parts remain — this time with no holes, just empty
skeletons: pooling and the CNN itself.
You have already seen both in step 2.
Pooling
out ← table of zeros [H // size, W // size]
for each block (i, j):
out[i, j] ← the largest number in the block
x[i*size .. (i+1)*size, j*size .. (j+1)*size]
return out
Blocks do not overlap and move in steps of size (the convolution moved by 1).
If the width is not divisible by size, the last incomplete column or row is
dropped — nn.MaxPool2d does the same. The test checks it with a 5 × 5
input.
CNN
This is the same stack you saw in the shape table:
Conv2d(1, c1, 3, padding=1) → ReLU → MaxPool2d(2) 28 → 14
Conv2d(c1, c2, 3, padding=1) → ReLU → MaxPool2d(2) 14 → 7
Flatten → Dropout → Linear(c2 · 7 · 7, num_classes)
Three places where people go wrong (and what the test will tell you):
- The
Linearinput. After two poolings the area is 7 × 7, not 14 × 14. Writec2 * 14 * 14and you getmat1 and mat2 shapes cannot be multiplied. nn.Flatten(). Without itLinearreceives a 4-dimensional tensor[N, 32, 7, 7]instead of a row[N, 1568].- The start of
forward. All your code so far, andfashion.py, supplies flat[N, 784]rows, whileConv2dwants[N, 1, 28, 28]. Onereshapeline at the start — andCNNaccepts both shapes.
The parameter count is a check for yourself: 160 (first conv) + 4,640 (second)
- 15,690 (dense) = 20,490.
Your job
Two files grew, one new skeleton in each:
conv_by_hand.pygainedmax_pool2d_by_handwithraise NotImplementedError;model.pygained the classCNN— with an__init__and aforwardthat do not work yet. Note: the docstring lists everything the test demands.
conv_by_hand.pyis still shown with its holes in the panel — the panel shows the skeleton, not your file. In your copy they are already filled; leave it that way.
test_cnn.py grew too: it now checks pooling (its numbers come from step 2: the
ReLU map → [[36, 0], [27, 0]]) and CNN (the parameter count, two each of
Conv2d, ReLU and MaxPool2d, that it accepts [N, 784] and
[N, 1, 28, 28], and that it learns: in 60 steps on 16 random images the loss
must fall by more than a factor of 4).
Run python -m unittest test_cnn. To begin with you will see:
EEEEEE...EEEE
======================================================================
ERROR: test_accepts_images_and_flat_rows (test_cnn.CNNTest.test_accepts_images_and_flat_rows)
NotImplementedError: build the layers
(...)
ERROR: test_a_leftover_row_and_column_are_dropped (test_cnn.MaxPoolByHandTest.test_a_leftover_row_and_column_are_dropped)
NotImplementedError: write max_pool2d_by_hand
(...)
Ran 13 tests in 0.6s
FAILED (errors=10)
The three . are the three convolution tests you already passed. The ten
NotImplementedErrors are what is left to write. (Output shortened.)
When it does not pass
mat1 and mat2 shapes cannot be multiplied (4x1568 and 6272x10)— theLinearwas built withc2 * 14 * 14(6,272), but the network deliversc2 * 7 * 7(1,568).mat1 and mat2 shapes cannot be multiplied (3584x7 and 1568x10)— you forgotnn.Flatten().Expected 3D (unbatched) or 4D (batched) input to conv2d, but got input of size: [4, 784]— you forgot thereshapeat the start offorward.AssertionError: 0 != 2intest_has_the_layers…— you left out theReLUs. Without them two convolutions in a row are one (lesson 6).- Pooling test:
Tensor-likes are not closeor a wrong shape — blocks taken with stride 1 instead ofsize, or an average used instead of.max().