TAI BUI
Back to curriculum
BuildPython

Transfer Learning & Fine-Tuning

Someone else already spent a million GPU-hours teaching a network what edges, textures, and object parts look like. Borrow those features before you train your own from scratch.

TIME: ~75 minutesPREREQUISITES: Phase 4 Lesson 03 (CNNs), Phase 4 Lesson 04 (Image Classification)

Learning Objectives

  • Distinguish feature extraction from fine-tuning and pick the right one based on dataset size, domain distance, and compute budget
  • Load a pretrained backbone, replace its classifier head, and train only the head to a working baseline in under 20 lines
  • Progressively unfreeze layers with discriminative learning rates so early generic features get smaller updates than late task-specific ones
  • Diagnose the three common failures: feature drift from too-high LR on unfrozen blocks, BN statistics collapse on tiny datasets, and catastrophic forgetting

The Problem

Training a ResNet-50 on ImageNet costs around 2,000 GPU-hours. Very few teams have that budget for every task they ship. What almost every team actually ships instead is a pretrained backbone with a new head, trained on a few hundred or a few thousand task-specific images.

This is not a shortcut. The first conv block of any ImageNet-trained CNN learns edges and Gabor-like filters; the next few blocks pick up textures and simple motifs. Object parts show up in the middle blocks. Only the final blocks combine all of that into something that starts to look like one of the 1,000 ImageNet categories. That first 90% of the hierarchy transfers almost unchanged to medical imaging, industrial inspection, satellite data, and every other vision task, because nature only has so many edges and textures to offer. The last 10% is what you actually train.

Getting transfer right means dodging three bugs: destroying pretrained features with a too-high learning rate, starving the model of information by freezing too much, and letting BatchNorm's running statistics drift toward a tiny dataset that the rest of the network's weights never actually update from. This lesson walks into each of them on purpose.

Warm-up0/3
1. From your CNN lessons: early convolutional layers tend to detect simple, low-level patterns like edges and textures, while deeper layers detect more complex, task-specific patterns. Which layers would you expect to reuse across very different image tasks?Warm-up
Four boxes left to right labelled layer 1 to layer 4: layer 1 detects edges and gradients, layer 2 textures and motifs, layer 3 object parts, layer 4 task-specific combinations.
Sketch of the feature hierarchy named in the CNN lessons, no specific network.
2. In PyTorch, what does setting requires_grad = False on a parameter do?Warm-up
The lesson's two-line loop setting p.requires_grad = False on every parameter, above the four stages of one training step (forward, loss, backward, optimizer.step()) and a question mark.
Lesson's own Build It Step 2 code.
3. You raise the learning rate and the training loss shoots up to huge values, then becomes NaN within a few steps. What is the most likely cause?Warm-up
Line chart of loss against step for a toy f(w) = w squared example with w 0.6 and lr 1.3: the loss grows from 0.36 to 101.3 within six steps, with a note that it goes to NaN after that.
Illustrative toy curve, computed by the script, not from the lesson's own numbers.
Check: The Problem0/1
1. Why do early conv layers of an ImageNet-pretrained network transfer well to medical images even though ImageNet contains no X-rays?Explain
Two 7 by 7 grids of real resnet18 conv1 filter weights after ImageNet pretraining, each cell shaded by the value of that weight.
Real conv1 kernel weights read from torchvision's pretrained resnet18.

The Concept

Feature extraction vs fine-tuning

Feature extraction freezes the pretrained backbone and trains only a new head on top of it. Fine-tuning unfreezes some or all of that backbone and updates it too. Which one you pick comes down to how much you trust the pretrained features and how much data you have to move them with.

Mermaid diagram

Rules of thumb:

Dataset sizeDomain distanceRecipe
< 1k imagesclose to ImageNetFreeze backbone, train head only
1k-10kcloseFreeze first 2-3 stages, fine-tune the rest
10k-100kanyFine-tune end-to-end with discriminative LR
100k+farFine-tune everything; consider training from scratch if domain is far enough

"Close to ImageNet" roughly means natural RGB photos with object-like content. Medical CT scans, overhead satellite imagery, and microscopy count as far domains: the features still help, but you'll need to let more layers adapt.

Compute budget compounds with both axes above. A frozen-head run finishes in minutes on a single GPU; fine-tuning even the last two stages end-to-end can take hours. If compute is the actual constraint and not data, that alone is a real reason to lean toward feature extraction, even on a dataset that would otherwise tolerate more.

Why freezing works at all

The features an ImageNet-trained CNN learns aren't specialised to the 1,000 categories; they're specialised to the statistics of natural images: edges at specific orientations, textures, contrast patterns, shape primitives. Those statistics hold across almost every visual domain a human can name. That's why a model trained on ImageNet and evaluated zero-shot on CIFAR-10, with nothing but a new linear head and no fine-tuning of the backbone, reaches 80%+ accuracy. The head is just learning which of those already-learnt features to weight for the task in front of it.

Discriminative learning rates

When you do unfreeze, early layers should train slower than late layers. Early layers encode generic features that you want to preserve; late layers encode task-specific structure that you need to move a lot.

Typical recipe:

  stage 0 (stem + first group): lr = base_lr / 100    (mostly fixed)
  stage 1:                       lr = base_lr / 10
  stage 2:                       lr = base_lr / 3
  stage 3 (last backbone group): lr = base_lr
  head:                          lr = base_lr  (or slightly higher)

In PyTorch this is just a list of parameter groups passed to the optimizer. One model, five learning rates, zero extra code.

Get this wrong and the failure has a name: catastrophic forgetting. Push the backbone's learning rate too high and the first few gradient steps overwrite the pretrained features before the new head has learned anything useful from them. Once those features are gone, no amount of further training brings them back; you're left training a nearly-random network with extra steps.

The BatchNorm problem

BN layers hold running_mean and running_var buffers that were computed on ImageNet. If your task has a different pixel distribution (different lighting, different sensor, different colour space), those buffers are wrong. Three options in order of preference:

  1. Fine-tune with BN in train mode. Let BN update its running statistics along with everything else. Default choice when the task dataset is medium-sized (>= 5k examples).
  2. Freeze BN in eval mode. Keep the ImageNet statistics and train only the weights. Correct when your dataset is small enough that BN's moving average would be noisy.
  3. Replace BN with GroupNorm. Removes the moving-average problem entirely. Used in detection and segmentation backbones where batch size per GPU is tiny.

Getting this wrong silently tanks accuracy by 5-15%.

Head design

The classifier head is 1-3 linear layers plus an optional dropout. Every torchvision backbone ships a default head that you replace:

backbone.fc = nn.Linear(backbone.fc.in_features, num_classes)          # ResNet
backbone.classifier[1] = nn.Linear(..., num_classes)                    # EfficientNet, MobileNet
backbone.heads.head = nn.Linear(..., num_classes)                       # torchvision ViT

For small datasets, a single linear layer is usually enough. Adding a hidden layer (Linear -> ReLU -> Dropout -> Linear) helps when the task distribution is farther from the backbone's training distribution.

Layer-wise LR decay

A smoother version of discriminative LR used in modern fine-tuning (BEiT, DINOv2, ViT-B fine-tunes). Instead of grouping layers into stages, give every layer a slightly smaller LR than the one above it:

lr_layer_k = base_lr * decay^(L - k)

With decay = 0.75 and L = 12 transformer blocks, the first block trains at 0.75^11 ≈ 0.04x the head's LR. Matters more for transformer fine-tunes than for CNNs, where stage-grouped LRs are usually enough.

What to evaluate

Transfer-learning runs need two numbers you would not track on a scratch run:

  • Pretrained-only accuracy: the head's accuracy with the backbone frozen. This is your floor.
  • Fine-tuned accuracy: the same model after end-to-end training. This is your ceiling.

If fine-tuned comes in below pretrained-only, you have a learning-rate or BN bug. Always print both.

There's a third failure this pair of numbers won't catch by itself: freezing too much. A backbone left frozen on a dataset large enough, or far enough from ImageNet, to warrant more unfreezing doesn't crash or diverge. It just plateaus below the accuracy the data could actually support, and both numbers look reasonable, just capped. If your dataset size and domain distance land you past feature extraction in the rules-of-thumb table above and accuracy has stalled anyway, that's the first thing to check before you conclude you've hit a real ceiling.

Check: The Concept0/21
1. What is feature extraction in transfer learning?Define
Two horizontal bars for the lesson's make_feature_extractor(10): requires_grad = True covers 5,130 parameters, requires_grad = False covers 11,176,512.
Real counts from the lesson's own make_feature_extractor(10).
2. What is fine-tuning, as opposed to feature extraction?Define
Two horizontal bars for the lesson's make_fine_tune(10): requires_grad = True covers all 11,181,642 parameters, requires_grad = False covers 0.
Real counts from the lesson's own make_fine_tune(10).
3. What is a discriminative learning rate setup?Define
Table of the six real stage group names discriminative_param_groups returns for resnet18, with each group's real parameter count and its learning rate left as a question mark.
Real group names and parameter counts, from a run of the lesson's discriminative_param_groups.
4. What is layer-wise LR decay?Define
A row of 12 identical encoder-block boxes feeding a classifier head box, with the first and last blocks labelled block 1 and block 12, and no learning rates shown.
Real encoder-block count read from torchvision's vit_b_16, the architecture behind the lesson's worked example.
5. What is BatchNorm statistics drift in transfer learning?Define
Table of the real running_mean and running_var that resnet18's bn1 carries for channels 0, 1, 3, 5 after loading the ImageNet1K_V1 weights.
Real buffer values read from torchvision's pretrained resnet18.
6. What is a linear probe?Define
Code for a linear probe using backbone.fc.in_features, noted as 512, above a diagram of a frozen resnet18 backbone producing 512-d features that feed a new Linear(512, k) layer.
512 is resnet18's real fc.in_features, printed by the lesson's Step 1 code.
7. What does catastrophic collapse look like when fine-tuning?Define
A card describing a 2-epoch fine-tune of the lesson's resnet18 on 80 images, beside a list of the four things the run lets you read afterwards, every one left as a question mark.
Setup only, from the lesson's own make_fine_tune model.
8. What is catastrophic forgetting in transfer learning?Define
A bar for the mean absolute value of resnet18's real pretrained conv1 weights, 0.0762, above an empty dashed bar for the same statistic after the first few gradient steps of a fine-tune.
Real weights of torchvision's pretrained resnet18.
9. Replacing a pretrained torchvision model's classifier head uses a different attribute depending on the architecture family. Which pairing is correct?Define
Table of four torchvision families with the real shape of the Linear head each one ships, and an attribute column masked as backbone.??? on every row.
Setup only; see Head design in the lesson for the real attribute names.
10. When fine-tuning end-to-end with discriminative learning rates, why should early layers get a smaller LR than late layers?Explain
resnet18's six real stage names in order from conv1_bn1 to fc, labelled earlier on the left and later on the right, with no learning rates shown.
Real stage names from the lesson's discriminative_param_groups, order only.
11. When would you replace BatchNorm with GroupNorm during transfer learning?Explain
Three cards for the lesson's BatchNorm options in order, with the stated condition filled in for fine-tuning BN in train mode and for freezing BN in eval mode, and the condition for replacing BN with GroupNorm left as a question mark.
Conditions quoted from the lesson's BatchNorm problem section.
12. Why can a frozen ImageNet backbone with only a new linear head reach 80%+ accuracy on CIFAR-10?Explain
A card stating that a frozen ImageNet backbone with a new linear head and no backbone fine-tuning reaches 80 percent or more accuracy on CIFAR-10.
Figure taken directly from the lesson's Why freezing works at all.
13. You have 500 labelled images for a new task close to the ImageNet distribution. Which regime makes the most sense?Apply
A card stating 500 labelled images, close to ImageNet, above the rules-of-thumb table with dataset size and domain distance columns filled in but every recipe cell replaced with a question mark.
500 images and close domain are from the stem; the table's recipe column is masked.
14. Layer-wise LR decay uses lr_layer_k = base_lr * decay^(L - k). With decay = 0.75 and L = 12 blocks, roughly what fraction of the head's LR does the first block train at?Apply
A row of twelve blocks feeding a classifier head, the head labelled lr = base_lr and the first block's rate left as a question mark, above a card restating decay 0.75 and L 12.
Setup from the lesson's own numbers; the power is not evaluated here.
15. You fine-tune a backbone for a 3-class task with only 300 images, all fairly close to the ImageNet distribution. Per the lesson's head-design guidance, what is the right starting point for the classifier head?Apply
A card describing 300 images, 3 classes, close to ImageNet, beside a pretrained backbone feeding a classifier-head box whose contents are a question mark.
Setup only; the task's numbers are from the stem.
16. You have 80k images in a domain moderately far from ImageNet. You freeze the whole backbone and train only a linear head. Accuracy is decent but has plateaued for several epochs. What should you check first?Analyze
A validation-accuracy curve over 14 epochs that rises then flattens and stays flat for several epochs, for a run with 80,000 images, a domain moderately far from ImageNet, and the backbone frozen the whole time.
Illustrative curve shaped to match the stem's plateau; not a measured run.
17. You fine-tune a ResNet on a 10-class dataset of 800 grayscale images (replicated to 3 channels) whose pixel distribution looks nothing like ImageNet photos. Training runs without errors, but accuracy is 5-15 points below what you expected. What is the most likely cause?Analyze
A card describing 800 grayscale images replicated to 3 channels for a 10-class task whose pixel statistics look nothing like ImageNet, beside a box noting accuracy lands 5 to 15 points below expectation, with a question mark for the cause.
800 images, 10 classes and the 5-15 point gap are from the stem.
18. You compare two runs: (a) linear probe on a frozen ImageNet backbone, 82% accuracy; (b) end-to-end fine-tune, 78% accuracy. What should you conclude?Analyze
Two bars: a linear probe on a frozen backbone reaching 82 percent, and an end-to-end fine-tune reaching 78 percent on the same task.
The 82% and 78% figures are exactly as given in the stem.
19. Two epochs into end-to-end fine-tuning with one shared LR on every layer, the loss is large and the model predicts a single class for every image. Which ingredient would most likely have prevented this?Analyze
Bars for a second real 2-epoch fine-tune with one shared lr 3.0: mean loss rises from 266.3 to 452.0, and a histogram shows every image predicted as class 8.
A separate real run of the lesson's make_fine_tune model, different seed and lr from any other question's run.
20. You have 20k close-to-ImageNet images, easily enough to fine-tune end-to-end, but only a few GPU-hours of budget this week. What is the sensible choice?Design
A card stating 20,000 images close to ImageNet and a budget of a few GPU-hours this week, above the rules-of-thumb table with dataset size and domain distance filled in but every recipe cell replaced with a question mark.
The dataset size and the compute budget are from the stem; the table's recipe column is masked.
21. You have 200k overhead satellite images and a generous GPU budget. Which plan fits the lesson's guidance?Design
A card describing 200,000 overhead satellite images with a generous GPU budget, above the rules-of-thumb table with every recipe cell masked as a question mark.
The image count and the budget are from the stem; the lesson's own text counts satellite imagery as a far domain.

Build It

Step 1: Load a pretrained backbone and inspect it

python
import torch
import torch.nn as nn
from torchvision.models import resnet18, ResNet18_Weights
 
backbone = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
print(backbone)
print()
print("classifier head:", backbone.fc)
print("feature dim:", backbone.fc.in_features)

ResNet18 has four stages (layer1..layer4) plus a stem and a fc head. Every torchvision classification backbone has an analogous structure.

Step 2: Feature extraction

python
def make_feature_extractor(num_classes=10):
    model = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
    for p in model.parameters():
        p.requires_grad = False
    model.fc = nn.Linear(model.fc.in_features, num_classes)
    return model
 
model = make_feature_extractor(num_classes=10)
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
frozen = sum(p.numel() for p in model.parameters() if not p.requires_grad)
print(f"trainable: {trainable:>10,}")
print(f"frozen:    {frozen:>10,}")

Only model.fc is trainable. The backbone is a frozen feature extractor.

Step 3: Discriminative fine-tuning

A utility that builds parameter groups with stage-specific learning rates.

python
def discriminative_param_groups(model, base_lr=1e-3, decay=0.3):
    stages = [
        ["conv1", "bn1"],
        ["layer1"],
        ["layer2"],
        ["layer3"],
        ["layer4"],
        ["fc"],
    ]
    groups = []
    for i, names in enumerate(stages):
        lr = base_lr * (decay ** (len(stages) - 1 - i))
        params = [p for n, p in model.named_parameters()
                  if any(n.startswith(k) for k in names)]
        if params:
            groups.append({"params": params, "lr": lr, "name": "_".join(names)})
    return groups
 
model = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
model.fc = nn.Linear(model.fc.in_features, 10)
for p in model.parameters():
    p.requires_grad = True
 
groups = discriminative_param_groups(model)
for g in groups:
    print(f"{g['name']:>10s}  lr={g['lr']:.2e}  params={sum(p.numel() for p in g['params']):>8,}")

decay=0.3 means each stage trains at 30% of the rate of the next one. fc gets base_lr, layer4 gets 0.3 * base_lr, conv1 gets 0.3^5 * base_lr ≈ 0.00243 * base_lr. Extreme sounding; empirically it works.

Step 4: BatchNorm handling

Helper to freeze BN running statistics without freezing its weights.

python
def freeze_bn_stats(model):
    for m in model.modules():
        if isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d)):
            m.eval()
            for p in m.parameters():
                p.requires_grad = False
    return model

Call it after you set model.train() at the start of every epoch. model.train() flips everything to training mode; this reverses it only for BN layers.

Step 5: A minimal end-to-end fine-tuning loop

Everything above is a building block on its own. Wire discriminative parameter groups, optional BN freezing, and a cosine schedule into one loop and you have something you can actually run end to end.

python
from torch.optim import SGD
from torch.utils.data import DataLoader
from torch.optim.lr_scheduler import CosineAnnealingLR
import torch.nn.functional as F
 
def fine_tune(model, train_loader, val_loader, device, epochs=5, base_lr=1e-3, freeze_bn=False):
    model = model.to(device)
    groups = discriminative_param_groups(model, base_lr=base_lr)
    optimizer = SGD(groups, momentum=0.9, weight_decay=1e-4, nesterov=True)
    scheduler = CosineAnnealingLR(optimizer, T_max=epochs)
 
    for epoch in range(epochs):
        model.train()
        if freeze_bn:
            freeze_bn_stats(model)
        tr_loss, tr_correct, tr_total = 0.0, 0, 0
        for x, y in train_loader:
            x, y = x.to(device), y.to(device)
            logits = model(x)
            loss = F.cross_entropy(logits, y, label_smoothing=0.1)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            tr_loss += loss.item() * x.size(0)
            tr_total += x.size(0)
            tr_correct += (logits.argmax(-1) == y).sum().item()
        scheduler.step()
 
        model.eval()
        va_total, va_correct = 0, 0
        with torch.no_grad():
            for x, y in val_loader:
                x, y = x.to(device), y.to(device)
                pred = model(x).argmax(-1)
                va_total += x.size(0)
                va_correct += (pred == y).sum().item()
        print(f"epoch {epoch}  train {tr_loss/tr_total:.3f}/{tr_correct/tr_total:.3f}  "
              f"val {va_correct/va_total:.3f}")
    return model

Five epochs with the above recipe on CIFAR-10 takes ResNet18-IMAGENET1K_V1 from ~70% zero-shot linear-probe accuracy to ~93% fine-tuned accuracy. The head alone would plateau around 86% without ever touching the backbone.

Step 6: Progressive unfreezing

A schedule that unfreezes one stage per epoch from the end toward the beginning. Mitigates feature drift at the cost of some extra epochs.

python
def progressive_unfreeze_schedule(model):
    stages = ["layer4", "layer3", "layer2", "layer1"]
    yielded = set()
 
    def start():
        for p in model.parameters():
            p.requires_grad = False
        for p in model.fc.parameters():
            p.requires_grad = True
 
    def unfreeze(epoch):
        if epoch < len(stages):
            name = stages[epoch]
            yielded.add(name)
            for n, p in model.named_parameters():
                if n.startswith(name):
                    p.requires_grad = True
            return name
        return None
 
    return start, unfreeze

Call start() once before the first epoch, then unfreeze(epoch) at the start of every epoch after that. Rebuild the optimizer whenever the set of trainable parameters changes; otherwise the frozen params still hold cached moments that confuse it.

Check: Build It0/6
1. A progressive unfreezing schedule starts with only the head trainable. In what order does the lesson's version unfreeze the backbone stages, and why?Explain
The lesson's start and unfreeze function signatures above four empty epoch boxes labelled epoch 0 to 3, each asking which stage gets unfrozen, with no order shown.
Setup only; the function is called once per epoch starting at epoch 0.
2. In progressive unfreezing, why should the optimizer be rebuilt whenever a new stage is unfrozen?Explain
Two cards: at epoch 0 the optimizer is built from fc's parameters alone, at epoch 1 one more backbone stage has become trainable, with a question mark asking whether to keep the same optimizer object or rebuild it.
Setup only, following the lesson's Step 6 code pattern.
3. With base_lr = 1e-3 and decay = 0.3, discriminative_param_groups builds six stage groups. What LR does the layer3 group get?Apply
Table of the six stage names from discriminative_param_groups with base_lr 1e-3 and decay 0.3, every lr cell replaced with a question mark, an arrow pointing at the layer3 row as the one asked about.
Setup only; stage names from the lesson's own function.
4. How many parameter groups does discriminative_param_groups return for a resnet18 whose fc was replaced?Apply
A card listing resnet18's ten real top-level children, the stem's replaced fc layer, and a call to discriminative_param_groups whose returned group count is left as a question mark.
Setup only; the children are resnet18's real named_children().
5. In fine_tune with freeze_bn=True, freeze_bn_stats(model) runs right after model.train() every epoch. What happens if you call it only once before the loop?Apply
The lesson's fine_tune epoch loop with freeze_bn_stats moved above the loop so it runs once, and a question mark standing in for bn1's training flag during epoch 1.
Setup only, from the lesson's own Build It Step 5 code.
6. You call start(), then unfreeze(0), unfreeze(1) and unfreeze(2), once at the start of each of the first three epochs of progressive unfreezing. Which parameters are trainable during the third epoch?Apply
The lesson's start and unfreeze calls for epochs 0, 1 and 2, each result left as a question mark, ending with trainable now: question mark.
Setup only, from the lesson's own Build It Step 6 code.

Use It

For most real tasks, torchvision.models plus three lines is enough. The heavier machinery above only matters once you hit a problem the library defaults can't fix.

python
from torchvision.models import resnet50, ResNet50_Weights
 
model = resnet50(weights=ResNet50_Weights.IMAGENET1K_V2)
model.fc = nn.Linear(model.fc.in_features, num_classes)
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4)

Two other production-grade defaults:

  • timm ships ~800 pretrained vision backbones with a consistent API (timm.create_model("resnet50", pretrained=True, num_classes=10)). For any fine-tune beyond the torchvision zoo, it's the standard.
  • For transformers, transformers.AutoModelForImageClassification.from_pretrained(name, num_labels=N) gives you ViT / BEiT / DeiT with the same loading semantics as text models.

Ship It

This lesson produces:

  • outputs/prompt-fine-tune-planner.md: a prompt that picks feature-extraction vs progressive vs end-to-end fine-tuning based on dataset size, domain distance, and compute budget.
  • outputs/skill-freeze-inspector.md: a skill that, given a PyTorch model, reports which parameters are trainable, which BatchNorm layers are in eval mode, and whether the optimizer is actually being fed those trainable parameters.

Exercises

  1. (Easy) Train a ResNet18 as a linear probe (backbone frozen) and as a full fine-tune on the same synthetic-CIFAR dataset. Report both accuracies side by side. Explain which gap tells you the features transfer well and which tells you they do not.
  2. (Medium) Introduce a bug on purpose: set base_lr = 1e-1 on the backbone stage instead of the head. Show the training loss explode, then recover by applying the discriminative_param_groups helper. Record the LR at which each stage starts diverging.
  3. (Hard) Take a medical imaging dataset (e.g. CheXpert-small, PatchCamelyon, or HAM10000) and compare three regimes: (a) ImageNet-pretrained frozen backbone + linear head; (b) ImageNet-pretrained fine-tune end-to-end; (c) scratch training. Report accuracy and compute cost for each. At what dataset size does scratch training become competitive?

Key Terms

TermWhat people sayWhat it actually means
Feature extraction"Freeze and train head"Backbone parameters frozen, only the new classifier head receives gradient
Fine-tuning"Retrain end-to-end"All parameters trainable, usually with much smaller LR than scratch training
Discriminative LR"Smaller LR for early layers"Optimizer parameter groups where early-stage LR is a fraction of late-stage LR
Layer-wise LR decay"Smooth LR gradient"Per-layer LR multiplied by decay^(L - k); common in transformer fine-tunes
Catastrophic forgetting"The model lost ImageNet"A too-high LR overwrites pretrained features before the new task signal is learnt
BN statistics drift"Running mean is wrong"BatchNorm running_mean/var computed on a different distribution than the current task, silently hurting accuracy
Linear probe"Frozen backbone + linear head"Evaluation of pretrained features: accuracy of the best linear classifier on top of the frozen representation
Catastrophic collapse"Everything predicts one class"Happens when fine-tuning with an LR high enough to destroy features before gradients from the head can stabilise

Further Reading