Code

data.py — fill in two holes

Now what you saw in step 2 is written down as data.py — and you fill in two missing pieces. These are exactly the two places where data mistakes are the quietest.

What is in the file

  • class_counts — how many pictures in each class. Ready.
  • build_transform — how a PIL picture becomes a tensor: gray → [1, 28, 28] → normalised. Ready.
  • split_indices — a stratified split: every class gives val_frac of its pictures to validation. The loop is written, one line is missing.
  • mean_std — pixel mean and std over the indices you name. Ready.
  • make_loaders — ties it together and returns (train_loader, val_loader, classes). One line is missing.

Your job

Create data.py and test_data.py (see the right panel). data.py runs without crashing but is wrong — two places are marked HOLE 1 and HOLE 2:

  • HOLE 1 — how many pictures of a class go to validation? val_frac of them, rounded, but never fewer than one. Right now it is 0.
  • HOLE 2 — the normalisation mean and std. mean_std(plain, ...) takes a list of picture indices — which ones? Right now it is 0.0, 1.0 (changes nothing).

Nothing else needs to change.

test_data.py is the verdict. It builds a tiny folder of pictures in a temporary directory itself (three classes of 30 pictures, each with its own average brightness), so it needs neither your photographs nor FashionMNIST.

Run it before filling anything in

$ python -m unittest test_data
FF..FF
======================================================================
FAIL: test_a_tiny_class_still_gets_a_validation_image (test_data.DataTest.test_a_tiny_class_still_gets_a_validation_image)
...
AssertionError: Lists differ: [] != [0, 1]
======================================================================
FAIL: test_batches_have_the_shape_the_cnn_expects (test_data.DataTest.test_batches_have_the_shape_the_cnn_expects)
...
AssertionError: 90 != 72
======================================================================
FAIL: test_split_is_stratified_and_disjoint (test_data.DataTest.test_split_is_stratified_and_disjoint)
...
AssertionError: 0 != 18 : 20 % of 90 images
======================================================================
FAIL: test_training_pixels_are_normalised_with_training_statistics (test_data.DataTest.test_training_pixels_are_normalised_with_training_statistics)
...
AssertionError: 0.4701877236366272 != 0.0 within 4 places (0.4701877236366272 difference)
----------------------------------------------------------------------
Ran 6 tests in 0.6s

FAILED (failures=4)

(Shortened: the Traceback bodies are cut. The first run can take longer — that is the torchvision import.)

Read the messages from the top; they tell a story: AssertionError: 90 != 72 — the training set holds all 90 pictures because nothing was given to validation; 0 != 18 — validation is empty; 0.47 != 0.0 — the training pixel mean is 0.47 because normalisation does nothing.

The two . — class_counts and the repeatability test — already pass because they do not depend on these holes.

When it does not pass

  • Only test_a_tiny_class_still_gets_a_validation_image fails, the rest pass — you wrote round(len(idx) * val_frac) or int(...). For a class of three pictures 10 % rounds to 0 and the class is left without validation. You need max(1, ...).
  • test_training_pixels_are_normalised… fails by a tiny amount (e.g. 0.0007 != 0.0) — HOLE 2 uses more than the training pictures. With all pictures the difference is 0.0007; with the validation ones, 0.0036. That does not look like much — but the test is strict on purpose: validation pictures must not influence a single training number.
  • AssertionError: 72 != 18 — you flipped it: val_frac taken for training rather than validation.