Project 09 β Transfer Learning & Fine-tuning
Level: Intermediate | Dataset: Oxford Flowers102 (torchvision) | Framework: PyTorch
Objective
Fine-tune a pretrained ResNet-50 on a 102-class flower dataset. Cover: feature extraction vs fine-tuning, layer freezing, differential LR, Optuna HPT, ONNX export.
Project Structure
09_transfer_learning/
βββ notebooks/
β βββ 01_data_setup.ipynb
β βββ 02_feature_extraction.ipynb
β βββ 03_finetuning.ipynb
β βββ 04_optuna_hpt.ipynb
βββ data/
βββ models/
β βββ feature_extract.pkl
β βββ finetuned.pkl
β βββ best_optuna.pkl
βββ charts/
βββ path_utils.py
βββ dashboard_core.py
βββ app.py
Notebook 01 β Data Setup (01_data_setup.ipynb)
STOP 1 β Load Flowers102
torchvision.datasets.Flowers102(download=True)- 102 categories, 8189 total images, ~80 per class
- Visualize 3 images per class for 6 classes
- Agent stops here. Explain:
- Why Flowers102 is a good transfer learning benchmark (small dataset, many classes)
- The fundamental premise of transfer learning: ImageNet features generalize
- Why building a CNN from scratch on 8189 images would fail (not enough data)
- What ImageNet is: 1.2M images, 1000 classes β what ResNet was trained on
- Wait for user confirmation before continuing
STOP 2 β ImageNet Normalization
- Always use ImageNet mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225]
- Resize to 224Γ224 (ResNet input requirement)
- Train augmentations:
RandomResizedCrop(224),RandomHorizontalFlip,ColorJitter - Val/Test:
Resize(256),CenterCrop(224) - Agent stops here. Explain:
- Why we MUST use ImageNet normalization with pretrained models
- What happens if we use different normalization: activations are in wrong range, pretrained features break
- Why 224Γ224: ResNet was designed for this input size
- What RandomResizedCrop does vs CenterCrop
- Wait for confirmation
STOP 3 β DataLoader Setup
- batch_size=32, num_workers=4, pin_memory=True
- Check class balance: Flowers102 is roughly balanced
- Agent stops here. Explain:
- Why balanced datasets are rare in practice
- What to do when class counts vary in a 102-class problem
- Wait for confirmation
Notebook 02 β Feature Extraction (02_feature_extraction.ipynb)
STOP 4 β Load Pretrained ResNet-50
import torchvision.models as models
model = models.resnet50(weights='IMAGENET1K_V1')
print(model) # understand the architecture
- Print total parameters
- Agent stops here. Explain:
- ResNet-50 architecture overview: stem, 4 layer groups, global avg pool, FC
- What residual connections are: skip connections that allow gradients to flow directly
- Why ResNet solved the vanishing gradient problem for deep nets (50+ layers)
- What IMAGENET1K_V1 weights are: trained on 1.2M ImageNet images
- Total params: ~25M β why we don't want to train all of them on 8k images
- Wait for confirmation
STOP 5 β Freeze All Layers
for param in model.parameters():
param.requires_grad = False
model.fc = nn.Linear(2048, 102) # replace final FC only
- Verify: only
model.fcparams are trainable - Print trainable vs frozen parameter counts
- Agent stops here. Explain:
- What
requires_grad=Falsedoes: no gradient computed, no weight update - Why we freeze: use ResNet as a fixed feature extractor
- Why only replace
model.fc: all other layers produce ImageNet features we reuse - What 2048 is: ResNet-50's global avg pool output dimension
- Feature extraction: fast (only FC trains), lower accuracy
- What
- Wait for confirmation
STOP 6 β Feature Extraction Training
- Train ONLY the new FC layer
- 20 epochs, Adam lr=0.001
- Expected accuracy: ~70-75% (fast to converge)
- Agent stops here. Explain:
- Why feature extraction trains so fast (only 102*2048 + 102 = 210k params update)
- Why accuracy plateaus quickly (backbone frozen, can't adapt to flowers)
- When feature extraction is enough vs when fine-tuning is needed
- Wait for confirmation
Notebook 03 β Fine-tuning (03_finetuning.ipynb)
STOP 7 β Selective Layer Unfreezing
# Unfreeze only last 2 ResNet layer groups + FC
for param in model.layer3.parameters():
param.requires_grad = True
for param in model.layer4.parameters():
param.requires_grad = True
- Agent stops here. Explain:
- Why we don't unfreeze ALL layers: early layers (edges, textures) are universal β don't need updating
- Why we unfreeze later layers: they encode high-level features that are ImageNet-specific
- The general rule: the more domain-specific your data, the more layers to unfreeze
- Risk of unfreezing too many layers on small data: catastrophic forgetting
- Wait for confirmation
STOP 8 β Differential Learning Rates
optimizer = Adam([
{'params': model.layer3.parameters(), 'lr': 1e-5},
{'params': model.layer4.parameters(), 'lr': 1e-4},
{'params': model.fc.parameters(), 'lr': 1e-3},
])
- Agent stops here. Explain:
- Why differential LR: pretrained layers need tiny updates (they're already good)
- New FC needs larger LR (random initialization, needs to train fast)
- The 10Γ rule: each layer group gets ~10Γ smaller LR than the one above it
- This is the same trick used in ULMFiT (NLP) and discriminative fine-tuning
- Wait for confirmation
STOP 9 β Fine-tuning Training Loop
- 30 epochs, CosineAnnealingLR
- Compare accuracy: feature extraction vs fine-tuning
- Expected improvement: ~5-10% accuracy gain from fine-tuning
- Agent stops here. Explain:
- Why fine-tuning outperforms feature extraction: domain adaptation
- The risk of fine-tuning without careful LR: destroying pretrained knowledge
- What "catastrophic forgetting" looks like: accuracy spikes then crashes
- How CosineAnnealing helps: prevents large late-epoch gradient updates on pretrained layers
- Wait for confirmation
Notebook 04 β Optuna HPT (04_optuna_hpt.ipynb)
STOP 10 β Optuna Setup
import optuna
def objective(trial):
lr_fc = trial.suggest_float('lr_fc', 1e-4, 1e-2, log=True)
lr_backbone = trial.suggest_float('lr_backbone', 1e-6, 1e-4, log=True)
dropout = trial.suggest_float('dropout', 0.2, 0.6)
unfreeze_from = trial.suggest_categorical('unfreeze_from', ['layer3', 'layer4'])
# build model, train 5 epochs, return val accuracy
...
return val_accuracy
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=20)
- Agent stops here. Explain:
- What Optuna is: Bayesian hyperparameter optimization framework
- How
suggest_float(log=True)works: searches in log space (better for LR) - What
n_trials=20means: 20 different hyperparameter combinations tried - What direction='maximize' means: we want highest val accuracy
- Optuna vs grid search: smarter sampling (TPE algorithm), needs fewer trials
- Wait for confirmation
STOP 11 β Optuna Visualization
optuna.visualization.plot_param_importances(study)optuna.visualization.plot_optimization_history(study)- Print best trial params
- Agent stops here. Explain:
- How to read param importance: which HP matters most
- What optimization history shows: does accuracy improve with more trials?
- When to stop HPT: diminishing returns after ~20 trials for 4 params
- Wait for confirmation
STOP 12 β Final Model with Best Params
- Retrain with best Optuna params, full 30 epochs
- Compare: feature extraction β fine-tuning β HPT fine-tuning accuracies
- ONNX export of final model
- Agent stops here. Explain:
- The three-stage accuracy improvement and why each stage helps
- Why ONNX export is important for production (as covered in Project 05)
- Dynamic axes for variable batch size in ONNX
- Wait for confirmation
STOP 13 β Confusion Matrix on 102 Classes
- Compute test accuracy, per-class recall
- Plot top-10 most confused class pairs
- Agent stops here. Explain:
- Why 102-class confusion matrix is unreadable as a full heatmap
- How to extract most confused pairs: find off-diagonal cells with highest values
- What visually similar flowers (e.g. roses, peonies) being confused means
- How to improve: more data, larger model, specific augmentations
- Wait for confirmation
dashboard_core.py
Functions:
load_model()β model, class_namespredict_flower(pil_image)β top-3 class predictions with confidenceget_training_comparison()β 3 accuracy curves (feature extract, finetune, best HPT)get_top_confused_pairs()β list of (class_a, class_b, count)
app.py β Streamlit (~80 lines)
Sections:
- Upload flower image
- Show top-3 predictions with confidence bars
- Tab 1: Training comparison (3 curves on one chart)
- Tab 2: Top confused class pairs
- Tab 3: Optuna best params summary
Key Concepts Covered
- ResNet-50 architecture and residual connections
- Feature extraction vs fine-tuning
- Layer freezing with requires_grad=False
- Differential learning rates per layer group
- Catastrophic forgetting prevention
- Optuna Bayesian HPT (suggest_float log=True, n_trials)
- 102-class confusion matrix analysis
- Full transfer learning workflow