Drills

Drills

Four tasks. You do not need to create any files — write and run in a python console or a throwaway scratch.py, in the folder with transfer.py. Each has a check.


1. The size of the head

Write head_params(num_classes) — how many parameters the new head has (512 inputs into each output, and one bias each).

Check: head_params(4) → 2,052, head_params(7) → 3,591, head_params(1000) → 513,000 (what the original fc had).

Now compare with count_trainable(build_resnet(7))[0]. They must agree.


2. Which tensors are trained

build_resnet(4, finetune_last_block=True). Print the names of every parameter whose requires_grad is True (model.named_parameters()), and how many there are.

Check: 17 tensors; the first three are layer4.0.conv1.weight, layer4.0.bn1.weight, layer4.0.bn1.bias, and the last two fc.weight and fc.bias. 45 tensors are frozen.

Notice that there is a bn inside layer4 too — the BatchNorm weights are unlocked as well.


3. Keep BatchNorm frozen

Write train_keeping_bn_frozen(model): it calls model.train() and then puts every nn.BatchNorm2d back into eval() mode (model.modules()).

Check: build build_resnet(4), save model.bn1.running_mean.clone(), call your function, pass torch.randn(8, 3, 224, 224) through and show that the mean did not change (difference 0.0). With a plain model.train() that difference was 0.0128.

Then try putting it into your training and compare — over several seeds, because steps 5 and 7 showed that one seed proves nothing.


4. A feature cache

When the backbone is frozen, computing the same 512 numbers again in every epoch is a waste. Write:

  1. extract(model, loader) → (features, labels): goes through all pictures once in eval() mode with no gradients and returns an [N, 512] tensor (to get 512, drop the final fc layer: nn.Sequential(*list(model.children())[:-1], nn.Flatten())).
  2. Train an nn.Linear(512, num_classes) only on those numbers — no DataLoader, no ResNet.

Check with standin_data (224 px, the same split): the features are (640, 512) and (160, 512); in our run the extraction took 11 s, and 30 epochs of the head with Adam lr 0.01 took 0.4 s. Validation accuracy was 93.1 % after 5 epochs, and 93.8 % after 10 and 30.

Compare with step 5's 92.5 % in 118 s. This is not the same set-up: here BatchNorm is in eval() mode throughout (see task 3) and there is no augmentation. One seed, 160 pictures — do not conclude "better", but the speed is real: you can repeat the same experiment hundreds of times.