A newly graduated radiologist doesn't start their first day knowing nothing.
They bring twelve years of education, thousands of hours of anatomy,
an entire medical school's worth of pattern recognition.
On their first day, they apply all of that knowledge to the specific cases in front of them.
A pre-trained neural network is that radiologist on day one.
It already knows how to see. You just need to show it what to look for.
01 — The Core Idea: Don't Start from Zero
Training a powerful neural network from scratch requires enormous amounts of labelled data. Most real-world problems don't have that luxury. Medical imaging datasets are expensive to annotate — expert radiologists must label each scan. Legal document classifiers need expert review. Industrial defect detectors rely on rare failure examples. The most valuable tasks are often exactly where data is scarcest.
Transfer learning is the solution. Instead of initialising weights randomly and training entirely on your small dataset, you start from a model already trained on a large, related dataset — and transfer its learned knowledge to your task. You keep what it already knows, then adapt it to what you need.
Any deep neural network learns in layers of abstraction. Early layers capture low-level patterns — edges and textures in a vision model, grammar and syntax in a language model. These low-level patterns are universal: they appear across domains because edges exist in every image, and grammar exists in every text, regardless of subject matter.
Transfer learning exploits this: borrow the general-purpose layers, replace only the task-specific output head. The model already knows how to perceive — you just teach it what to look for in your domain.
This isn't a computer vision trick. It's the operating principle behind the entire modern AI landscape. GPT-4, Claude, Gemini — all pre-trained foundation models fine-tuned for specific tasks. The CNN fine-tuning we'll use as an example today and the LoRA fine-tuning of LLMs in Day 25 are the same idea at different scales.
02 — What Fine-Tuning Actually Means
Fine-tuning is taking a pre-trained model and continuing to train it — partially or fully — on a new, smaller dataset. The key is that not all layers need the same treatment. Layers that learned general knowledge should barely change. Layers that learned task-specific patterns need more adaptation. The output head, trained for the original task's classes, must be replaced entirely.
Yosinski et al. (2014) measured this experimentally. The first layers of any neural network trained on natural data learn nearly identical features regardless of the specific task. CNNs trained on ImageNet, Places, or medical images all develop similar edge and texture detectors in layer 1. The generality of early representations is a fundamental property of how gradient descent shapes deep networks — not an accident of architecture. You can rely on it.
03 — Feature Extraction vs Fine-Tuning
Transfer learning has two operating modes. The right choice depends on how different your task is from the pre-training task, and how much data you have.
| Dimension | Feature Extraction | Fine-Tuning |
|---|---|---|
| What trains | New output head only | Head + some or all backbone |
| Pre-trained weights | Completely frozen | Unfrozen, updated at tiny LR |
| Data needed | ✓ Works with very little | ~ Needs more data |
| Speed | ✓ Fast — no backbone gradients | ✗ Slower — full backprop |
| Risk | ✓ Zero — weights untouched | ✗ Catastrophic forgetting if LR too large |
| Accuracy ceiling | ~ Good | ✓ Best possible |
| Best for | Similar domain, tiny dataset | Different domain, more data, max accuracy |
In practice, do both — sequentially. Start frozen (head only) for 5–10 epochs until the head converges. Then unfreeze and fine-tune at a much smaller learning rate. Skipping Phase 1 is dangerous: a randomly-initialised head produces large gradients that flow backward and overwrite the pre-trained backbone in the very first updates.
When you unfreeze for fine-tuning, the learning rate must drop dramatically. Pre-trained weights only need tiny nudges — they're already near a good solution. Using the same rate as head training overwrites them in just a few steps.
Rule of thumb: fine-tuning LR = head LR ÷ 10 to ÷ 100. When in doubt, go smaller — slow convergence is recoverable, destroyed weights are not.
04 — Data Augmentation: The Other Lever
Transfer learning solves the "not enough data to train deep features" problem. Augmentation solves the second problem: not enough data to prevent the new head from memorising your small training set.
Augmentation teaches invariances — the label shouldn't change when you rotate, flip, or adjust brightness. You're enforcing: "a glioma is still a glioma at 15 degrees." Each augmented view isn't new ground truth — it's a different perspective on the same example. Over many epochs with a few stochastic transforms, a 3,000-image dataset can produce ~360,000 distinct training views: a 400% effective expansion at zero labelling cost.
One rule is absolute: augmentation is for training only. Validation transforms must be deterministic. Augmenting validation makes accuracy non-deterministic across runs, which makes overfitting undetectable.
05 — The General Workflow
06 — CNN Example: Brain Tumour Classifier at 95%
Here's the workflow applied to a concrete case — the brain tumour project from yesterday's CNN post. The dataset has 3,000 MRI scans (glioma, meningioma, pituitary, no tumour). Too few to train a deep CNN from scratch without severe overfitting. A plain CNN trained from random weights achieves ~63%. With a pre-trained ResNet50 and the two-phase strategy, it reaches 95.5%. The 32-point gap is entirely borrowed knowledge.
import torch
import torch.nn as nn
import torchvision
from torchvision import transforms, datasets
from torch.utils.data import DataLoader
import numpy as np
# ── Hyperparameters ──────────────────────────────────────────────────
NUM_CLASSES = 4 # glioma, meningioma, pituitary, no_tumor
IMG_SIZE = 224
BATCH_SIZE = 32
HEAD_LR = 1e-3 # Phase 1: head-only training
FINETUNE_LR = 1e-5 # Phase 2: full fine-tuning
HEAD_EPOCHS = 8 # Phase 1 epochs
FT_EPOCHS = 22 # Phase 2 epochs
DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
# ── Data Transforms ──────────────────────────────────────────────────
train_tfm = transforms.Compose([
transforms.Resize(256),
transforms.RandomCrop(IMG_SIZE),
transforms.RandomHorizontalFlip(),
transforms.RandomVerticalFlip(p=0.3),
transforms.RandomRotation(20),
transforms.ColorJitter(brightness=0.4, contrast=0.4),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
val_tfm = transforms.Compose([
transforms.Resize(256), transforms.CenterCrop(IMG_SIZE),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
# ── Data Loaders ─────────────────────────────────────────────────────
# Dataset structure: data/train/{glioma,meningioma,pituitary,no_tumor}/
train_ds = datasets.ImageFolder('data/train', transform=train_tfm)
val_ds = datasets.ImageFolder('data/val', transform=val_tfm)
train_dl = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, num_workers=4)
val_dl = DataLoader(val_ds, batch_size=BATCH_SIZE, shuffle=False, num_workers=4)
print(f"Train: {len(train_ds)} | Val: {len(val_ds)}")
print(f"Classes: {train_ds.classes}")
# ── Model Definition ─────────────────────────────────────────────────
class TumourClassifier(nn.Module):
def __init__(self, num_classes=NUM_CLASSES):
super().__init__()
# Load ResNet50 with ImageNet weights — NEVER use random init for backbone
self.backbone = torchvision.models.resnet50(weights='IMAGENET1K_V2')
in_features = self.backbone.fc.in_features # 2048 for ResNet50
# Replace final layer with task-specific head
self.backbone.fc = nn.Sequential(
nn.Linear(in_features, 256),
nn.ReLU(),
nn.Dropout(0.4),
nn.Linear(256, num_classes)
)
def freeze_backbone(self):
"""Phase 1: freeze everything except the new head."""
for name, param in self.backbone.named_parameters():
if 'fc' not in name: # 'fc' is our new head
param.requires_grad = False
def unfreeze_backbone(self):
"""Phase 2: enable gradients for all layers."""
for param in self.backbone.parameters():
param.requires_grad = True
def trainable_params(self):
return sum(p.numel() for p in self.parameters() if p.requires_grad)
def forward(self, x):
return self.backbone(x)
# ── Training Helper ──────────────────────────────────────────────────
def run_epoch(model, loader, criterion, optimizer=None):
training = optimizer is not None
model.train() if training else model.eval()
total_loss, correct = 0.0, 0
ctx = torch.enable_grad() if training else torch.no_grad()
with ctx:
for imgs, labels in loader:
imgs, labels = imgs.to(DEVICE), labels.to(DEVICE)
logits = model(imgs)
loss = criterion(logits, labels)
if training:
optimizer.zero_grad(); loss.backward(); optimizer.step()
total_loss += loss.item() * imgs.size(0)
correct += (logits.argmax(1) == labels).sum().item()
n = len(loader.dataset)
return total_loss / n, correct / n
# ── Main Training Loop ───────────────────────────────────────────────
model = TumourClassifier().to(DEVICE)
criterion = nn.CrossEntropyLoss()
# ─── PHASE 1: Head-only training (frozen backbone) ───────────────────
print("\n=== Phase 1: Head Training (frozen backbone) ===")
model.freeze_backbone()
print(f"Trainable params: {model.trainable_params():,}")
optimizer = torch.optim.AdamW(
filter(lambda p: p.requires_grad, model.parameters()), lr=HEAD_LR
)
for epoch in range(1, HEAD_EPOCHS + 1):
tr_loss, tr_acc = run_epoch(model, train_dl, criterion, optimizer)
va_loss, va_acc = run_epoch(model, val_dl, criterion)
if epoch % 2 == 0:
print(f" ep {epoch:02d} | tr_acc={tr_acc:.4f} | va_acc={va_acc:.4f} | va_loss={va_loss:.4f}")
# ─── PHASE 2: Fine-tuning (unfrozen backbone, tiny lr) ───────────────
print("\n=== Phase 2: Fine-Tuning (unfrozen backbone) ===")
model.unfreeze_backbone()
print(f"Trainable params: {model.trainable_params():,}")
optimizer = torch.optim.AdamW(model.parameters(), lr=FINETUNE_LR, weight_decay=1e-4)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=FT_EPOCHS)
best_acc, best_state = 0.0, None
for epoch in range(1, FT_EPOCHS + 1):
tr_loss, tr_acc = run_epoch(model, train_dl, criterion, optimizer)
va_loss, va_acc = run_epoch(model, val_dl, criterion)
scheduler.step()
if va_acc > best_acc:
best_acc = va_acc
best_state = {k: v.cpu().clone() for k, v in model.state_dict().items()}
if epoch % 5 == 0:
print(f" ep {epoch:02d} | tr_acc={tr_acc:.4f} | va_acc={va_acc:.4f} | best={best_acc:.4f}")
print(f"\nBest val accuracy: {best_acc:.4f}")
Classes: ['glioma', 'meningioma', 'no_tumor', 'pituitary']
=== Phase 1: Head Training (frozen backbone) ===
Trainable params: 525,060 (only the new 4-class head — backbone is frozen)
──────────────────────────────────────────────────────
ep 02 | tr_acc=0.7481 | va_acc=0.8264 | va_loss=0.5831
ep 04 | tr_acc=0.8203 | va_acc=0.8576 | va_loss=0.4152
ep 06 | tr_acc=0.8519 | va_acc=0.8750 | va_loss=0.3714
ep 08 | tr_acc=0.8701 | va_acc=0.8854 | va_loss=0.3487
=== Phase 2: Fine-Tuning (unfrozen backbone) ===
Trainable params: 24,089,092 (full ResNet50 + head)
──────────────────────────────────────────────────────
ep 05 | tr_acc=0.9041 | va_acc=0.9132 | best=0.9132
ep 10 | tr_acc=0.9278 | va_acc=0.9340 | best=0.9340
ep 15 | tr_acc=0.9481 | va_acc=0.9479 | best=0.9479
ep 20 | tr_acc=0.9612 | va_acc=0.9514 | best=0.9514
ep 22 | tr_acc=0.9648 | va_acc=0.9549 | best=0.9549
Best val accuracy: 0.9549 (95.5%)
Compare — training from scratch (same architecture, same data):
Best val accuracy: 0.6312 (63.1%)
Transfer learning advantage: +32.4 percentage points
Notice the Phase 1 progression: val accuracy jumps from 74% to 89% in just 8 epochs — even though the backbone is completely frozen. This is the power of borrowed representations. The head is simply learning a linear decision boundary in a rich 2048-dimensional feature space that ImageNet pre-training already organised well. Then Phase 2 fine-tuning pushes it from 89% to 95.5% by allowing the backbone to shift its representations toward brain MRI–specific features. The two-phase training is 6× faster than training from scratch and achieves 32 percentage points higher accuracy.
07 — The Decision Framework: Which Approach for Which Situation
Transfer learning isn't one-size-fits-all. The right approach depends on two axes: how similar your target domain is to the pre-training domain, and how much labelled data you have. Every combination has a recommended strategy.
08 — The Same Idea Powers the Entire LLM Ecosystem
Transfer learning for CNNs is not a historical curiosity — it is the foundational concept underlying modern language AI, and understanding the vision version gives you deep intuition for why language fine-tuning works.
When OpenAI fine-tunes GPT-4 on medical Q&A, or Anthropic trains Claude on human preference data, the same two-phase logic applies: pre-train on massive general data → adapt to specific task. The "frozen backbone" in LLMs corresponds to keeping most model weights frozen and only training a small adapter. The "new head" corresponds to the LoRA low-rank matrices added to specific weight matrices.
LoRA (Low-Rank Adaptation), covered in Day 25, is essentially the vision "freeze backbone, train head" idea taken to an extreme: rather than training any full layers, it adds tiny rank-decomposed matrices (A and B) to existing weight matrices. The number of trainable parameters drops from billions to millions — the same efficiency principle as training only the 525K-parameter head instead of the full 24M-parameter ResNet.
Even RLHF (Reinforcement Learning from Human Feedback) fits the pattern: a base LLM (the pre-trained backbone) is fine-tuned on human preference signals (the domain-specific dataset), with careful learning rate management to avoid catastrophic forgetting of general language capabilities. Transfer learning for CNNs is not a separate topic from LLMs — it's the same idea at a different scale.
09 — A Thought to Sit With
The brain tumour classifier reached 95% accuracy using transfer learning from a model trained on everyday photographs — dogs, cars, landscapes. At some level, the visual features that distinguish a glioma from a meningioma were latent in a model that had never seen a brain scan. That's a remarkable and slightly unsettling fact about the universality of learned representations.
The model was never told: "look for irregular boundaries" or "focus on the brightness distribution in the tumour core." It discovered that glioma boundaries look a certain way by gradient descent alone — guided by the visual primitives that ImageNet had already taught it to detect.
If the same visual representations that recognise cat ears also help detect tumours, what does that suggest about the nature of visual intelligence? And should we trust a medical model whose "prior knowledge" came from Instagram-style photographs?
10 — Coming Up Next
Tomorrow — Day 18: Object Detection, Segmentation & YOLO. Classification answers "what is in this image?" Detection answers "where is it, and what is it?" Segmentation goes further: "which exact pixels belong to it?" These three tasks form the hierarchy of computer vision understanding — and YOLO made real-time detection possible by reframing detection as a single-shot regression problem rather than a two-stage pipeline.
11 — Go Deeper
▶ Transfer Learning with PyTorch — Aladdin Persson
YouTube · 30 min · Full practical walkthrough of feature extraction and fine-tuning on a real dataset.
🔗 Transfer Learning for Computer Vision — PyTorch Official Tutorial
Complete code walkthrough training ResNet on a real 2-class dataset with visualisation.
📄 How Transferable are Features in Deep Neural Networks? — Yosinski et al. (2014)
The foundational paper quantifying exactly which layers transfer well and which don't — the experimental basis for everything in §02.
🔗 Hugging Face Model Hub — timm (PyTorch Image Models)
Ross Wightman's library with 700+ pre-trained vision models: pip install timm then timm.create_model('efficientnet_b3', pretrained=True).
- The core idea: Don't start from zero. Borrow a model pre-trained on a large dataset, keep its learned representations, replace only the output head, and adapt to your task with a fraction of the data and compute.
- Why it works: Early layers always learn general, universal patterns — edges in vision, grammar in language. These transfer across domains. Only deep, task-specific layers need adaptation.
- Feature extraction = frozen backbone, train new head only. Fast, safe, works with very little data. The backbone is a fixed feature converter.
- Fine-tuning = unfreeze backbone, continue training at a tiny learning rate. Better accuracy, needs more data, catastrophic forgetting risk if LR is too large.
- Always do both sequentially: freeze → train head (5–10 epochs) → unfreeze → fine-tune at 1/10–1/100 of head LR. Skipping Phase 1 destroys pre-trained weights on the first updates.
- The learning rate rule: fine-tuning LR = head LR ÷ 10 to ÷ 100. At 1e-3 for the head, fine-tune the backbone at 1e-4 to 1e-5. Slow convergence is recoverable; destroyed weights are not.
- Augmentation is the primary regulariser for small datasets — teaches invariances and gives 4–5× effective data expansion. Training only, never validation.
- CNN example: 3,000 brain MRIs, ResNet50 backbone → 95.5% accuracy vs. 63% from scratch. The 32-point gap is entirely borrowed knowledge from ImageNet photographs.
- Domain-specific pre-training (RadImageNet, BioBERT, CheXNet) beats ImageNet when your target domain diverges significantly from natural photos or Wikipedia text.
- The same concept powers LLMs: LoRA freezes most weights and trains tiny rank-decomposed adapters; RLHF fine-tunes on human preferences. Transfer learning is the unifying idea behind all of modern AI.
- What is transfer learning and why does it work?
Transfer learning reuses weights from a model pre-trained on a large dataset as the starting point for a new, smaller task. It works because neural networks learn in layers of abstraction — early layers capture universal patterns (edges, grammar) that appear across domains. These transfer directly; only the deeper, task-specific layers need adaptation. It's most useful when your target dataset is small and the source and target domains share low-level structure. - What is the difference between feature extraction and fine-tuning?
Feature extraction freezes all pre-trained weights and trains only a new output head — the backbone is a fixed feature converter. Fine-tuning unfreezes the backbone and continues training with a very small learning rate, allowing representations to adapt to the new domain. Feature extraction is faster and needs less data; fine-tuning achieves better accuracy when data allows. Best practice: do both sequentially — freeze first, then fine-tune. - Why use a very small learning rate when fine-tuning?
Pre-trained weights encode millions of gradient steps of useful information. A large learning rate overwrites them in just a few updates — catastrophic forgetting. A tiny rate (1e-5) makes small nudges: the backbone adapts gradually toward the new domain while preserving the general-purpose knowledge that made transfer learning valuable in the first place. - What is catastrophic forgetting and how do you prevent it?
Catastrophic forgetting occurs when backpropagation with a large learning rate overwrites pre-trained weights, destroying accumulated knowledge. Prevented with two-phase training: Phase 1 freezes the backbone so no destructive gradients reach it while the randomly-initialised head stabilises. Phase 2 unfreezes at 1e-5 so each update is a tiny adjustment rather than an overwrite. - How does CNN transfer learning relate to LoRA for LLMs?
Both exploit the same insight: most of a pre-trained model's weights encode general knowledge that doesn't need to change. "Freeze backbone, train head" does this literally. LoRA does it more surgically — instead of a full output layer, it adds tiny rank-decomposed matrices (A × B) alongside frozen weight matrices. Only A and B train, cutting trainable parameters from billions to millions. Same principle, different implementation, same source of efficiency.
📄 How Transferable are Features in Deep Neural Networks? — Yosinski et al. (2014)
NeurIPS 2014 · The paper that formally quantified layer-by-layer transferability and established the experimental basis for freezing early layers.
📄 EfficientNet: Rethinking Model Scaling — Tan & Le (2019)
ICML 2019 · The architecture behind EfficientNetB0–B7, the most widely used transfer learning backbones for vision tasks today.
📚 Hands-On Machine Learning — Aurélien Géron
Chapter 14 covers pretrained models and fine-tuning with excellent worked examples in TensorFlow/Keras.