Code

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 Linear input. After two poolings the area is 7 × 7, not 14 × 14. Write c2 * 14 * 14 and you get mat1 and mat2 shapes cannot be multiplied.
  • nn.Flatten(). Without it Linear receives a 4-dimensional tensor [N, 32, 7, 7] instead of a row [N, 1568].
  • The start of forward. All your code so far, and fashion.py, supplies flat [N, 784] rows, while Conv2d wants [N, 1, 28, 28]. One reshape line at the start — and CNN accepts 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.py gained max_pool2d_by_hand with raise NotImplementedError;
  • model.py gained the class CNN — with an __init__ and a forward that do not work yet. Note: the docstring lists everything the test demands.

conv_by_hand.py is 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) — the Linear was built with c2 * 14 * 14 (6,272), but the network delivers c2 * 7 * 7 (1,568).
  • mat1 and mat2 shapes cannot be multiplied (3584x7 and 1568x10) — you forgot nn.Flatten().
  • Expected 3D (unbatched) or 4D (batched) input to conv2d, but got input of size: [4, 784] — you forgot the reshape at the start of forward.
  • AssertionError: 0 != 2 in test_has_the_layers… — you left out the ReLUs. Without them two convolutions in a row are one (lesson 6).
  • Pooling test: Tensor-likes are not close or a wrong shape — blocks taken with stride 1 instead of size, or an average used instead of .max().