Thuta Learning
ProjectsAIintermediate

Project: Transfer Learning Classifier

What you'll walk away with

  • Explain the core ideas behind Project: Transfer Learning Classifier
  • Run the sample code and verify its output
  • Apply the technique correctly to the Tutorial Platform and production scenarios

Build the mental model

The core idea of transfer learning is that a pretrained resnet18 has already learned generic visual features — edges, textures, shapes — from a huge number of ImageNet images. Freezing every backbone parameter by setting requires_grad = False stops gradients from being computed for those weights, which both saves compute and protects those learned general features from catastrophic forgetting, the risk of a tiny fine-tuning dataset overwriting what a much larger dataset originally taught the network. Replacing the final fully connected layer (model.fc) with a fresh Linear layer swaps out the head that was specialized for ImageNet's 1000 classes for one sized to your actual problem — here, 4 classes.

Building the optimizer over model.fc.parameters() alone, instead of model.parameters(), sharply shrinks the number of trainable parameters from the backbone's millions down to the new fc layer's few thousand — the core promise of transfer learning, that you can get reasonable performance from very few examples and very few training steps. Checking named_parameters() and confirming .grad is None on every non-fc parameter after training is a genuinely good debugging habit: it verifies freezing actually worked, rather than just trusting that setting requires_grad did what you intended.

Connect it to a real scenario

The Tutorial Platform runs a lesson-screenshot quality classifier that flags blurry or low-quality screenshots instructors upload into lessons. Because labeled examples are scarce — they come from a small batch of manual review — a frozen resnet18 backbone with a fine-tuned fc head is exactly what makes this feasible: transfer learning gets a usable classifier out of a handful of labeled examples instead of requiring a large labeled dataset from scratch.

Try the working example

python
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision.models import resnet18, ResNet18_Weights

torch.manual_seed(0)

num_classes = 4
model = resnet18(weights=ResNet18_Weights.DEFAULT)

# Freeze all pretrained layers first
for param in model.parameters():
    param.requires_grad = False

# Then replace the final layer -- its params require_grad=True by default
in_features = model.fc.in_features
model.fc = nn.Linear(in_features, num_classes)

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.fc.parameters(), lr=1e-3)

# Synthetic images shaped like the inputs resnet18 expects
num_samples = 16
images = torch.randn(num_samples, 3, 224, 224)
labels = torch.randint(0, num_classes, (num_samples,))

model.train()
losses = []
for step in range(5):
    optimizer.zero_grad()
    outputs = model(images)
    loss = criterion(outputs, labels)
    loss.backward()
    optimizer.step()
    losses.append(loss.item())
    print(f"Step {step+1}/5 - loss: {loss.item():.4f}")

# Confirm only the new fc layer received gradients
frozen_untouched = all(
    p.grad is None for name, p in model.named_parameters() if not name.startswith("fc")
)
fc_has_grad = all(p.grad is not None for p in model.fc.parameters())
print(f"Backbone parameters untouched (grad is None): {frozen_untouched}")
print(f"New fc layer parameters have gradients: {fc_has_grad}")
You should see
(The first run may print a download progress log for the pretrained resnet18 weights, which requires internet access.) Then five lines print, "Step 1/5 - loss: X.XXXX" through "Step 5/5 - loss: X.XXXX". Loss starts near ln(4)≈1.386, the random-guess value for 4 classes, and because the single fc layer only has to fit 16 samples on top of fixed 512-dimensional features (heavily overparameterized for the task), it should drop noticeably within just 5 Adam steps. The final two lines print "Backbone parameters untouched (grad is None): True" and "New fc layer parameters have gradients: True".

5-minute try-it

Modify the freezing step to leave layer3 and layer4 (the last two convolutional blocks) unfrozen alongside fc, adding all three sets of parameters to the optimizer. Count the total trainable parameters in this version and compare it against the fc-only fine-tuning above.

One important caution

Downloading the pretrained weights on the first call to resnet18(weights=...) requires internet access — this call will error out in an offline environment or a network-restricted CI pipeline.

Replacing model.fc before the freezing loop runs (reversing the order shown) causes the freeze loop to also set requires_grad = False on the new layer, so optimizer.step() has nothing left to update and the loss stays flat across all 5 steps with no error raised.

PyTorch Tutorials — Transfer Learning for Computer VisionComputer Vision

Easy traps

  • Downloading the pretrained weights on the first call to resnet18(weights=...) requires internet access — this call will error out in an offline environment or a network-restricted CI pipeline.
  • Replacing model.fc before the freezing loop runs (reversing the order shown) causes the freeze loop to also set requires_grad = False on the new layer, so optimizer.step() has nothing left to update and the loss stays flat across all 5 steps with no error raised.
  • Validate sample code in a local or test environment before applying it to a production system.

Exercise

Modify the freezing step to leave layer3 and layer4 (the last two convolutional blocks) unfrozen alongside fc, adding all three sets of parameters to the optimizer. Count the total trainable parameters in this version and compare it against the fc-only fine-tuning above.

You'll know it worked when: (The first run may print a download progress log for the pretrained resnet18 weights, which requires internet access.) Then five lines print, "Step 1/5 - loss: X.XXXX" through "Step 5/5 - loss: X.XXXX". Loss starts near ln(4)≈1.386, the random-guess value for 4 classes, and because the single fc layer only has to fit 16 samples on top of fixed 512-dimensional features (heavily overparameterized for the task), it should drop noticeably within just 5 Adam steps. The final two lines print "Backbone parameters untouched (grad is None): True" and "New fc layer parameters have gradients: True".

Project: Transfer Learning Classifier | Thuta Learning