3.4.11Convolutional Neural Networks

Transfer learning and fine-tuning

3,408 words15 min readdifficulty · medium6 backlinks

Transfer learning does exactly this for neural networks: we take a model trained on a massive dataset (like ImageNet's 14M images) and repurpose its learned features for our smaller task (like classifying 1000 medical scans). The early layers have already learned universal patterns—edges, textures, shapes—so we don't waste time relearning them from scratch.

Why Transfer Learning Works

The key insight: Deep CNs learn hierarchical features:

  • Early layers (near input): Generic low-level features (edges, colors, simple textures)
  • Middle layers: Mid-level patterns (object parts, textures combinations)
  • Late layers (near output): Task-specific high-level concepts (specific object classes)

Early layers are remarkably transferable because edges and textures appear in nearly every visual task. A network trained to distinguish cats from dogs has already learned "what is a vertical edge" and "what is fur texture"—knowledge useful for medical imaging, satellite analysis, or any other vision task.

Standard training minimizes loss L\mathcal{L} over dataset D\mathcal{D}: θ=argminθE(x,y)D[L(fθ(x),y)]\theta^* = \arg\min_{\theta} \mathbb{E}_{(x,y) \sim \mathcal{D}} [\mathcal{L}(f_\theta(x), y)]

With limited data (D|\mathcal{D}| \ll millions), this optimization is severely underconstrained—many parameter configurations θ\theta fit the small training set but generalize poorly (high variance).

Transfer learning decomposes the model: fθ(x)=gθhead(hθbase(x))f_\theta(x) = g_{\theta_{\text{head}}}(h_{\theta_{\text{base}}}(x))

where:

  • hθbaseh_{\theta_{\text{base}}} = feature extractor (pretrained on large source dataset Dsource\mathcal{D}_{\text{source}})
  • gθheadg_{\theta_{\text{head}}} = task-specific head (trained on small target dataset Dtarget\mathcal{D}_{\text{target}})

Why this reduces sample complexity:

  1. θbase\theta_{\text{base}} is initialized at point already near a good local minimum (learned from Dsource\mathcal{D}_{\text{source}})
  2. We only optimize a fraction of parameters (θhead\theta_{\text{head}} typically 0.1-10% of total)
  3. The effective model capacity is constrained by the frozen base, acting as strong regularization

Sample complexity# trainable parametersdataset size\text{Sample complexity} \propto \frac{\text{\# trainable parameters}}{\text{dataset size}}

By freezing most parameters, we drastically reduce the left side of this ratio.

The Three Transfer Learning Strategies

Strategy 1: Feature Extraction (Frozen Base)

When to use:

  • Very small target dataset (< 1000 samples)
  • Target task similar to source task
  • Limited compute budget

How it works:

# Pseudo-code
base_model = load_pretrained_model("ResNet50", weights="imagenet")
base_model.trainable = False  # Freeze all base layers
 
# Add custom head
model = Sequential([
    base_model,
    GlobalAveragePooling2D(),
    Dense(256, activation='relu'),
    Dropout(0.5),
    Dense(num_classes, activation='softmax')
])
 
# Only head parameters update
model.compile(optimizer=Adam(lr=1e-3))

Derivation of why this works:

The pretrained features hθbase(x)h_{\theta_{\text{base}}}(x) form a representation space. We're learning a linear (or shallow nonlinear) mapping from this space to target labels:

y^=softmax(Whθbase(x)+b)\hat{y} = \text{softmax}(W h_{\theta_{\text{base}}}(x) + b)

This is much easier than learning the entire mapping from raw pixels, because h(x)h(x) already captures relevant structure. The optimization is convex (if using only a linear head) or low-dimensional (if adding one hidden layer).

Strategy 2: Fine-tuning (Partial Unfreezing)

When to use:

  • Medium dataset (1k-100k samples)
  • Target task moderately different from source
  • You have compute for full training

How it works step-by-step:

Step 1: Train head with frozen base (as in feature extraction) for a few epochs. This brings the randomly-initialized head to a reasonable state.

Step 2: Unfreeze the last few convolutional blocks:

# Unfreeze last 2 blocks (out of 5) ResNet50
for layer in base_model.layers[-30:]:  # ~2 blocks
    layer.trainable = True

Step 3: Continue training with much smaller learning rate: lrfine-tune=lrinitial10 to lrinitial100\text{lr}_{\text{fine-tune}} = \frac{\text{lr}_{\text{initial}}}{10} \text{ to } \frac{\text{lr}_{\text{initial}}}{100}

Why the small learning rate? Derive from gradient update perspective:

The pretrained weights θbasepretrain\theta_{\text{base}}^{\text{pretrain}} are already at a good local minimum for the source task. With large learning rate η\eta, the gradient update:

θbaset+1=θbasetηθbaseLtarget\theta_{\text{base}}^{t+1} = \theta_{\text{base}}^{t} - \eta \nabla_{\theta_{\text{base}}} \mathcal{L}_{\text{target}}

could overshoot and move far from this good initialization, destroying useful features (catastrophic forgetting).

Small η\eta ensures: θbaset+1θbasepretrain1\|\theta_{\text{base}}^{t+1} - \theta_{\text{base}}^{\text{pretrain}}\| \ll 1

We stay in the basin of attraction around the pretrained solution, only making small adjustments for the target task.

Differential learning rates (layer-wise):

\theta_{\text{early}}^{t+1} &= \theta_{\text{early}}^{t} - \eta_1 \nabla \mathcal{L} \quad &\text{(smallest lr)} \\ \theta_{\text{middle}}^{t+1} &= \theta_{\text{middle}}^{t} - \eta_2 \nabla \mathcal{L} \quad &\eta_2 = 3\eta_1 \\ \theta_{\text{head}}^{t+1} &= \theta_{\text{head}}^{t} - \eta_3 \nabla \mathcal{L} \quad &\eta_3 = 10\eta_1 \text{ (largest lr)} \end{align}$$ **Why?** Earlier layers capture more general features (edges), later layers are more task-specific. We want minimal changes early, larger adaptation late. ### Strategy 3: Full Fine-tuning > [!definition] Full Fine-tuning > Unfreeze all layers and train the entire model end-to-end with differential learning rates. **When to use**: - Large target dataset (> 100k samples) - Target task very different from source (e.g., medical images vs. natural images) - Maximum performance needed ## Worked Example: Bird Species Classification > [!example] Complete Transfer Learning Pipeline **Setup**: Classify 200 bird species with 50 images per species (10k total). Source: ResNet50 on ImageNet. **Step 1: Analyze task similarity** ImageNet contains 120 bird species → high similarity → transfer will work well. Dataset size: 10k images → medium size → fine-tuning appropriate. **Step 2: Feature extraction baseline** ```python base = ResNet50(include_top=False, weights='imagenet', input_shape=(224,3)) base.trainable = False model = Sequential([ base, GlobalAveragePooling2D(), # (7,7,2048) → (2,) Dense(512, activation='relu'), BatchNormalization(), Dropout(0.5), Dense(200, activation='softmax') ]) model.compile( optimizer=Adam(1e-3), loss='categorical_crossentropy', metrics=['accuracy'] ) # Train for 20 epochs # Result: ~75% validation accuracy ``` **Why GlobalAveragePooling2D?** The base outputs spatial feature maps of shape $(7, 7, 2048)$ for224×224 input. GAP computes: $$\text{GAP}(F)_c = \frac{1}{7 \times 7} \sum_{i=1}^{7} \sum_{j=1}^{7} F_{i,j,c}$$ for each channel $c$. This: 1. Reduces parameters (no spatial FC layer needed) 2. Allows variable input sizes 3. Acts as structural regularization **Why this step?** The head is randomly initialized. If we unfreeze base layers immediately, random gradients from the head will corrupt pretrained features. We first get the head into a reasonable state. **Step 3: Fine-tune last block** ```python # Unfreeze last residual block (conv5_x in ResNet50) for layer in base.layers[-33:]: # Empirically determined layer.trainable = True # Reduce learning rate by 10× model.compile( optimizer=Adam(1e-4), # Was 1e-3 loss='categorical_crossentropy', metrics=['accuracy'] ) # Train another 30 epochs with early stopping # Result: ~87% validation accuracy (+12%) ``` **Why +12%?** The last block learns high-level object parts. For birds, this means: - Beak shapes - Feather patterns - Body proportions These are somewhat different between ImageNet's 120 bird species and our 200 species. By fine-tuning, we adapt these features. **Step 4: (Optional) Full fine-tuning with discriminative learning rates** ```python # Unfreeze everything base.trainable = True # Layer-wise learning rates layer_lrs = { 'conv1': 1e-6, # First conv: almost frozen 'conv2_x': 5e-6, # Early residual blocks 'conv3_x': 1e-5, 'conv4_x': 5e-5, 'conv5_x': 1e-4, # Last block: largest adaptation } # Result: ~91% validation accuracy (+4%) ``` **Diminishing returns**: The gain from full fine-tuning is smaller because early layers (edges, colors) are already optimal for bird images. ## Common Mistakes and Fixes > [!mistake] Mistake 1: Too Large Learning Rate When Fine-tuning **Wrong approach**: ```python base.trainable = True model.compile(optimizer=Adam(1e-3)) # Same as initial training! ``` **What happens**: The large gradients from the randomly-initialized head flow backward and destroy pretrained features in the base. Validation accuracy drops below the frozen baseline—==catastrophic forgetting==. **Why it feels right**: "If I'm training the model, I should use a normal learning rate." **The fix**: Reduce LR by 10-100× when unfreezing pretrained layers. Or use discriminative LRs. **Derivation**: Pretrained weights $w_{\text{pretrained}}$ have loss landscape: $$\mathcal{L}_{\text{source}}(w_{\text{pretrained}}) \approx \mathcal{L}_{\text{min}}$$ After unfreezing with large $\eta$, the update is: $$w^{t+1} = w_{\text{pretrained}} - \eta \nabla \mathcal{L}_{\text{target}}(w_{\text{pretrained}})$$ If $\|\eta \nabla \mathcal{L}_{\text{target}}\| \gg \|w_{\text{pretrained}} - w_{\text{min,source}}\|$, we move far outside the basin. The target task gradient may point in a direction that increases source task loss dramatically, erasing general features. > [!mistake] Mistake 2: Unfreezing Too Early **Wrong approach**: ```python # Immediately unfreeze and train everything base.trainable = True model.compile(optimizer=Adam(1e-4)) model.fit(...) # Head is still random! ``` **What happens**: Random gradients from the head (which hasn't learned anything yet) corrupt the pretrained base. The model converges slowly and to a worse solution. **Why it feels right**: "If fine-tuning is better, why not start immediately?" **The fix**: Always train the head first with base frozen for5-20 epochs. Then unfreeze. **Mathematical intuition**: The loss gradient decomposes: $$\nabla_{\theta_{\text{base}}} \mathcal{L} = \nabla_{\theta_{\text{base}}} f_{\text{head}} \cdot \nabla_{f_{\text{head}}} \mathcal{L}$$ Initially, $f_{\text{head}}$ is random, so $\nabla_{f_{\text{head}}} \mathcal{L}$ has high variance and arbitrary direction. By pretraining the head, we ensure $\nabla_{f_{\text{head}}} \mathcal{L}$ points in a meaningful direction before allowing it to modify the base. > [!mistake] Mistake 3: Wrong Data Preprocessing **Wrong approach**: ```python # Using custom normalization train_transform = Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) ``` **What happens**: The pretrained model expects ImageNet normalization: $$x_{\text{normalized}} = \frac{x - \mu_{\text{ImageNet}}}{\sigma_{\text{ImageNet}}}$$ where $\mu = [0.485, 0.456, 0.406]$ and $\sigma = [0.229, 0.224, 0.225]$ (RGB). Using different normalization shifts the input distribution, and the pretrained features respond incorrectly (they were optimized for the ImageNet distribution). **Why it feels right**: "I'm training on new data, I should use its statistics." **The fix**: Always use the same preprocessing as the source dataset. For ImageNet models, use ImageNet's mean and std. **Quantitative impact**: Mismatched normalization can drop accuracy by 10-30% because: $$f_{\theta}(x_{\text{wrong}}) \neq f_{\theta}(x_{\text{correct}})$$ The learned decision boundaries assumed inputs a specific range. Shifting the range moves data to unexplored regions of the feature space. ## Advanced: Gradual Unfreezing > [!formula] Progressive Fine-tuning Schedule Instead of unfreezing all layers at once, gradually unfreeze from top to bottom: **Epoch schedule**: - Epochs 1-10: Freeze all base, train head only - Epochs 11-20: Unfreeze last 1block - Epochs 21-30: Unfreeze last 2 blocks - Epochs 31-40: Unfreeze all with discriminative LR **Why this works**: It's anealing schedule for the effective learning rate per layer. Define **effective learning rate** for layer $\ell$ at epoch $t$: $$\eta_{\text{eff}}^\ell(t) = \begin{cases} 0 & \text{if frozen} \\ \eta_0 \cdot d^\ell \cdot s(t) & \text{if unfrozen} \end{cases}$$ where: - $d^\ell$ = depth discount (smaller for earlier layers) - $s(t)$ = schedule function (learning rate decay) Gradual unfreezing effectively implements: $$\eta_{\text{eff}}^\ell(t) = \eta_0 \cdot d^\ell \cdot \mathbb{1}[t > T_{\text{unfreeze}}^\ell] \cdot s(t)$$ Each layer gets progressively activated, allowing the model to adapt hierarchically: first the task-specific head, then the mid-level features, finally the low-level edges (which usually need minimal adaptation). ## Practical Recipe > [!example] Step-by-Step Transfer Learning **Given**: Dataset with $N$ samples, $C$ classes, task similarity $\alpha \in [0,1]$ to ImageNet. **Step 1**: Choose pretrained model - $N < 1000$: Use lightweight models (MobileNet, EfficientNet-B0) - $1000 < N < 100k$: Use ResNet50 or EfficientNet-B2 - $N > 100k$: Use larger models (ResNet101, EfficientNet-B4) **Step 2**: Feature extraction baseline ```python base.trainable = False lr_head = 1e-3 epochs_1 = 10+ 10 * (1 - α) # More epochs if dissimilar ``` **Step 3**: Decide fine-tuning strategy - $N < 5k$ and $\alpha > 0.7$: Stop here (feature extraction only) - $5k < N < 50k$: Fine-tune last 1-2 blocks - $N > 50k$: Full fine-tuning with discriminative LR **Step 4**: Fine-tune ```python # Unfreeze last k blocks k = floor(log10(N / 1000)) # More blocks as dataset grows base.layers[-k*blocks_per_stage:].trainable = True lr_fine = lr_head / 10 epochs_2 = 20 + 5 * k ``` **Step 5**: Evaluate and iterate - Monitor train vs. val loss: if train >> val, increase regularization (dropout, augmentation) - If improvement plateaus, try unfreezing more layers - If val loss increases, reduce LR or add early stopping ## Why Transfer Learning is Fundamental > [!intuition] The Deeper Principle Transfer learning works because of a fundamental property of hierarchical representations: **compositionality**. Low-level features (edges, textures) compose to form mid-level features (object parts), which compose to form high-level concepts (objects). This composition is largely **task-invariant** at lower levels. $$f_{\text{task}}(x) = g_{\text{task-specific}} \circ h_{\text{universal}}(x)$$ The function $h_{\text{universal}}$ (edges, textures, basic shapes) is shared across almost all visual tasks. Only $g_{\text{task-specific}}$ varies. **Empirical evidence**: Random CNN features fail (10-20% accuracy), but pretrained ImageNet features work immediately (60-80% on new tasks). The representation space learned from ImageNet captures structure useful far beyond the original1000 classes. **The sample efficiency gain**: Training from scratch requires: $$N_{\text{scratch}} \propto d_{\text{VC}}(\text{full model})$$ where $d_{\text{VC}}$ is VC dimension (model capacity). Transfer learning requires: $$N_{\text{transfer}} \propto d_{\text{VC}}(\text{head only}) \ll d_{\text{VC}}(\text{full model})$$ Empirically, transfer learning reduces data requirements by **10-100×**. > [!recall]- Explain to a 12-year-old > Imagine you're learning to play piano after already knowing how to play guitar. Your fingers already know how to press strings/keys with the right timing, you understand rhythm and reading music, and you know what sounds good. You don't start as a complete beginner! Transfer learning is the same for AI. Instead of teaching a computer to recognize images from absolute scratch (which needs millions of examples), we start with a "brain" that already learned useful skills from looking at millions of pictures. It already knows what edges and shapes and textures look like. We just need to teach it the specific thing we care about—like telling the difference between healthy and sick X-rays—and it learns way faster because it's not starting from zero! It's like having a head start in a race. You skip the first mile because you already did that work before. > [!mnemonic] FLAIR for Transfer Learning > - **F**reze first (train head only) > - **L**ow learning rate when unfreezing > - **A**dapt gradually (unfreeze progressively) > - **I**mageNet preprocessing (match source stats) > - **R**egularize heavily (dropout, augmentation) ## Connections - [[3.4.1-Introduction-to-CNs]] - Understanding what features CNs learn - [[3.4.7-Batch-normalization]] - Why BN layers need special handling in fine-tuning - [[3.4.8-Residual-connections]] - ResNet architecture commonly used for transfer - [[3.2.5-Regularization-techniques]] - Dropout and augmentation for small datasets - [[3.2.3-Gradient-descent-and-backpropagation]] - Why small learning rates prevent catastrophic forgetting - [[4.1.3-Data-augmentation]] - Essential when using transfer learning with small datasets --- #flashcards/ai-ml What is transfer learning? :: Using a model pretrained on one task as the starting point for a different but related task, instead of training from scratch. Why does transfer learning work? ::: Because early CNN layers learn generic features (edges, textures, shapes) that are useful across many visual tasks, so we don't need to relearn them. What are the three main transfer learning strategies? ::: 1) Feature extraction (freeze base), 2) Fine-tuning (unfreeze some layers), 3) Full fine-tuning (unfreeze all with differential LR). When should you use feature extraction vs fine-tuning? :: Feature extraction for very small datasets (<1k samples) or high task similarity. Fine-tuning for medium datasets (1k-100k) or moderate similarity. Full fine-tuning for large datasets (>100k). Why use a small learning rate when fine-tuning? ::: To prevent catastrophic forgetting—large learning rates can destroy the useful pretrained features by moving too far from the good initialization. What is catastrophic forgetting? ::: When fine-tuning with too large a learning rate destroys pretrained features, causing performance to drop below the frozen baseline. Why train the head first before unfreezing base layers? ::: Because random gradients from an untrained head would corrupt pretrained features. We need the head in a reasonable state before allowing it to modify the base. What is discriminative learning rate? ::: Using different learning rates for different layers—smallest for early layers (generic features), largest for late layers (task-specific features). Why must you use ImageNet preprocessing with ImageNet-pretrained models? ::: Because the pretrained features were optimized for inputs normalized with ImageNet's mean and std. Different normalization shifts the input distribution and breaks the learned features. What is gradual unfreezing? ::: Progressively unfreezing layers from top to bottom over multiple training phases, allowing hierarchical adaptation. How much can transfer learning reduce data requirements? ::: By 10-100× compared to training from scratch, depending on task similarity and dataset size. What is the mathematical reason transfer learning has lower sample complexity? ::: We only optimize a small fraction of parameters (the head), dramatically reducing the ratio of trainable parameters to dataset size, which determines sample complexity. Why do early CNN layers transfer well across tasks? ::: They learn low-level features (edges, colors, textures) that are compositional building blocks for any visual recognition task—they're task-invariant. What happens if you unfreeze layers too early? ::: Random gradients from the untrained head corrupt pretrained features, causing slow convergence and worse final performance. What is the effective learning rate in gradual unfreezing? ::: The actual learning rate a layer experiences, which is zero when frozen and gradually increases as the layer is unfrozen and the schedule progresses. ![[3.4.11-Transfer-learning-and-fine-tuning.png]] ## 🖼️ Concept Map ```mermaid flowchart TD PT[Pretrained Model on Large Dataset] TL[Transfer Learning] HF[Hierarchical Features] EL[Early Layers Generic Edges Textures] LL[Late Layers Task-Specific Concepts] BASE[Feature Extractor h_base] HEAD[Task Head g_head] TGT[Small Target Task] FE[Feature Extraction Frozen Base] FT[Fine-tuning] REG[Strong Regularization] SC[Lower Sample Complexity] PT -->|provides weights| TL TL -->|exploits| HF HF -->|contains| EL HF -->|contains| LL EL -->|highly transferable| BASE LL -->|replaced by| HEAD TL -->|decomposes into| BASE TL -->|decomposes into| HEAD HEAD -->|trained on| TGT FE -->|freezes| BASE FT -->|updates| BASE BASE -->|frozen acts as| REG REG -->|reduces trainable params| SC ``` ## 🔊 Hinglish (regional understanding) > [!intuition]- Hinglish mein samjho > ![[audio/3.4.11-Transfer-learning-and-fine-tuning.mp3]] ## 🔊 Hinglish (regional understanding) > [!intuition]- Hinglish mein samjho > Dekho yaar, transfer learning ka core idea bilkul simple hai. Socho tumne badminton pehle se seekh liya hai, ab tennis seekhna hai. Toh tum zero se start nahi karte na? Tumhari footwork, hand-eye coordination, racket pakadne ki muscle memory sab kaam aa jati hai. Bas grip aur swing thoda adjust karna padta hai. Neural networks ke saath bhi exactly yehi hota hai. Ek model jo pehle se ImageNet ke 14 million images par train hua hai, uske andar edges, textures, aur shapes pehchan ne ki knowledge already baithi hui hai. Toh jab tumhe apna chota task karna ho (jaise 1000 medical scans classify karna), tum us pretrained model ko utha kar reuse kar lete ho, scratch se sab kuch dobara seekhne ki zaroorat nahi. > > Ye kaam kyun karta hai, iski asli wajah CNN ke hierarchical features mein chupi hai. Early layers (input ke paas wali) generic cheezein seekhti hain jaise edges aur colors—ye har vision task mein common hoti hain, isliye inhe transfer karna easy hai. Middle layers object parts pehchanti hain, aur late layers (output ke paas) task-specific concepts seekhti hain. Mathematically dekho toh jab tumhare paas kam data hota hai, toh model ko train karna underconstrained ho jata hai—matlab bahut saare parameter configurations tumhare chote data pe fit ho jaate hain lekin generalize nahi karte (high variance problem). Transfer learning mein hum model ko base (feature extractor) aur head (task-specific part) mein tod dete hain, base ko freeze kar dete hain, aur sirf head train karte hain. Isse trainable parameters bahut kam ho jaate hain, aur sample complexity ka ratio (trainable params / dataset size) drop ho jata hai—ye ek strong regularization ki tarah kaam karta hai. > > Isliye ye tumhare liye important hai—jab real world mein kaam karoge, tumhare paas hamesha lakhon labeled images nahi hongi, na hi itni compute power. Transfer learning tumhe chote dataset aur limited resources ke saath bhi powerful models banane deta hai. Feature extraction strategy mein tum poora base freeze karke sirf ek chota head train karte ho—perfect jab data 1000 se kam ho aur task source task jaisa ho. Yaad rakho, tum bas pretrained representation space se apne labels tak ka ek simple mapping seekh rahe ho, na ki raw pixels se poori journey. Isiliye ye itna fast aur data-efficient hota hai, aur practically har industry project mein yehi approach use hoti hai.

Go deeper — visual, from zero

Test yourself — Convolutional Neural Networks

Connections