π§ Transfer Learning Fundamentals
π― What is Transfer Learning?
Transfer Learning is a machine learning technique where knowledge learned from one task (source) is reused to improve learning on a different but related task (target). Instead of training from scratch, we leverage pre-trained models, saving time, data, and compute. It's the foundation of modern AI β from BERT and GPT to ResNet and CLIP.
Why Transfer Learning Matters
Data efficiency: Learn with less data. Time savings: Train in hours instead of weeks. Compute savings: No need for massive GPU clusters. Better performance: Pre-trained features are powerful. Democratization: Small teams can build SOTA models. Foundation: Powers all modern AI systems.
# Transfer Learning β the big picture
import torch
import torch.nn as nn
from torchvision import models
# 1. TRADITIONAL LEARNING (from scratch)
def train_from_scratch(num_classes):
"""Train a CNN from random initialization."""
model = models.resnet50(weights=None)
model.fc = nn.Linear(2048, num_classes)
# Need: millions of images, weeks of training, huge compute
return model
# 2. TRANSFER LEARNING (reuse pre-trained)
def transfer_learning(num_classes, freeze_backbone=True):
"""Reuse ImageNet pre-trained ResNet-50."""
# Load pre-trained model
model = models.resnet50(weights='IMAGENET1K_V2')
# Freeze backbone (optional)
if freeze_backbone:
for param in model.parameters():
param.requires_grad = False
# Replace final layer for new task
model.fc = nn.Linear(2048, num_classes)
# Only train the new classifier
# Need: thousands of images, hours of training
return model
# THE DIFFERENCE
print("=== Traditional vs Transfer Learning ===")
print("""
Traditional (from scratch):
- Random initialization
- Need 1M+ labeled images
- Weeks of training on GPUs
- Cost: $10,000+
- Accuracy: baseline
Transfer Learning:
- Pre-trained on ImageNet (1.2M images)
- Fine-tune on target task
- Need 1,000-10,000 images
- Hours of training
- Cost: $10-100
- Accuracy: often better!
""")
# COMPLETE EXAMPLE
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
def fine_tune_resnet(train_loader, val_loader, num_classes=10, epochs=10):
"""Complete transfer learning example."""
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Load pre-trained ResNet
model = models.resnet50(weights='IMAGENET1K_V2')
# Replace final layer
model.fc = nn.Linear(2048, num_classes)
# Move to device
model = model.to(device)
# Optimizer for all parameters
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()
# Training loop
for epoch in range(epochs):
model.train()
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
return modelπ― Why Transfer Learning Works
π― The Science Behind It
Transfer learning works because neural networks learn hierarchical features. Early layers learn generic features (edges, textures), while later layers learn task-specific features. We can reuse generic features and adapt specific ones.
Hierarchical Feature Learning
Layer 1: Edges, colors, simple patterns. Layer 2-3: Textures, shapes, corners. Layer 4-5: Object parts (eyes, wheels, leaves). Final layers: Full objects, task-specific. Insight: Early layers are universal. Strategy: Reuse early, retrain late.
# Why transfer learning works β the feature hierarchy
import torch
import torch.nn as nn
from torchvision import models
# VISUALIZE WHAT EACH LAYER LEARNS
def analyze_layer_features(model):
"""Show what each layer learns."""
print("=== Layer-wise Feature Analysis ===")
print("""
ResNet-50 trained on ImageNet:
Early layers (conv1, layer1):
- Learn: edges, colors, Gabor filters
- Universal: same for any image task
- Transfer: excellent
Middle layers (layer2, layer3):
- Learn: textures, shapes, patterns
- Somewhat universal: useful across domains
- Transfer: good
Late layers (layer4):
- Learn: object parts, semantics
- Task-specific: ImageNet categories
- Transfer: moderate
Final layer (fc):
- Learn: ImageNet 1000 classes
- Completely task-specific
- Transfer: replace entirely
""")
# DEMONSTRATE WITH SIMILARITY METRICS
def compute_feature_similarity(model):
"""Compute similarity between ImageNet and target task features."""
print("\n=== Feature Similarity Analysis ===")
print("""
For different target tasks:
Task 1: Fine-grained classification (dog breeds)
- Source: ImageNet (dog classes)
- Similarity: HIGH
- Strategy: Fine-tune whole network
- Expected: Excellent transfer
Task 2: Medical imaging (X-rays)
- Source: ImageNet (natural images)
- Similarity: LOW
- Strategy: Freeze early, retrain late
- Expected: Good transfer
Task 3: Satellite imagery
- Source: ImageNet (natural images)
- Similarity: LOW
- Strategy: Freeze fewer layers
- Expected: Moderate transfer
Task 4: Handwritten digits (MNIST)
- Source: ImageNet
- Similarity: VERY LOW
- Strategy: Only use early layers
- Expected: Small transfer
General rule:
Similar tasks β transfer more
Different tasks β transfer less
""")
# QUANTIFY TRANSFER BENEFIT
print("\n=== Quantifying Transfer Benefit ===")
print("""
Amount of source data vs target performance:
Without transfer:
- Need 100,000+ images for good performance
- Need weeks of training
With transfer (similar task):
- 1,000 images β 95% of max accuracy
- 10,000 images β 99% of max accuracy
- Hours of training
With transfer (different task):
- 10,000 images β 90% of max accuracy
- Still much better than from scratch
Empirical rule:
Transfer saves 10-1000x in data and compute
Often gives 5-30% better accuracy
""")
# PRACTICAL DEMONSTRATION
def show_layer_activations():
"""Show layer activations for different tasks."""
print("\n=== Layer Activation Patterns ===")
print("""
Given an image of a dog:
Early layer activations:
- Same patterns active as for a cat, car, or tree
- Detects edges, curves, textures
- Universal building blocks
Middle layer activations:
- Detects dog-like shapes (ears, snout)
- Some overlap with other animals
- Partially transferable
Late layer activations:
- Specific to "Golden Retriever"
- Not useful for "German Shepherd"
- Must be retrained
""")
show_layer_activations()
# THEORETICAL FOUNDATIONS
print("\n=== Theoretical Foundations ===")
print("""
1. MANIFOLD HYPOTHESIS
- Real-world data lies on low-dim manifold
- Similar tasks share manifolds
- Pre-training learns the manifold
2. FEATURE REUSE
- Natural features are hierarchical
- Early features are universal
- Later features are specific
3. REGULARIZATION EFFECT
- Pre-training acts as regularizer
- Prevents overfitting on small data
- Provides better initialization
4. INFORMATION BOTTLENECK
- Network compresses information
- Early layers: general info
- Later layers: specific info
5. TRANSFERABILITY METRICS
- Domain similarity (source vs target)
- Task similarity (labels, objectives)
- Feature similarity (layer-wise)
""")
# EXAMPLE: CNN FEATURE VISUALIZATION
print("\n=== Example: What Layers Learn ===")
print("""
ResNet-50 layer names and what they capture:
conv1: 7x7 filters, edges & colors
bn_conv1: Batch norm
maxpool: Downsampling
layer1.0-2: 3x3 filters, simple textures
layer2.0-3: Complex textures, patterns
layer3.0-5: Object parts (eyes, wheels)
layer4.0-2: Full objects, categories
avgpool: Global pooling
fc: 1000 ImageNet classes
Transfer strategy by task similarity:
Very similar (e.g., dogβcat):
β Fine-tune all layers
β Small learning rate
Moderately similar (e.g., naturalβmedical):
β Freeze early layers
β Fine-tune middle & late
β Medium learning rate
Very different (e.g., naturalβsatellite):
β Freeze only very early layers
β Retrain most of network
β Larger learning rate
Very small target dataset:
β Freeze more layers
β Train only classifier
β Lower risk of overfitting
""")π Types of Transfer
π― Different Transfer Scenarios
Transfer learning comes in many flavors depending on: what is transferred, how source and target relate, and what data is available. Understanding these helps choose the right approach.
Taxonomy of Transfer
Inductive: Different tasks, same domain. Transductive: Same task, different domains. Unsupervised: No labels in either. Instance: Reuse samples. Feature: Reuse representations. Parameter: Reuse weights. Relational: Reuse relationships.
# Types of transfer learning
import torch
import torch.nn as nn
from torchvision import models
# 1. INDUCTIVE TRANSFER LEARNING
# Different tasks, same domain
# Example: ImageNet β Dog breeds
print("=== Inductive Transfer Learning ===")
print("""
Source task β Target task
Source domain = Target domain
Example:
Source: Classify 1000 ImageNet classes
Target: Classify 120 dog breeds
Approaches:
- Fine-tune pre-trained model
- Feature extraction + new classifier
- Multi-task learning
Data requirement:
- Labeled target data needed
- Can be small (few-shot)
Common use case: Transfer learning for vision
""")
# 2. TRANSDUCTIVE TRANSFER LEARNING
# Same task, different domains
print("\n=== Transductive Transfer Learning ===")
print("""
Source task = Target task
Source domain β Target domain
Example:
Source: Sentiment analysis on reviews
Target: Sentiment analysis on tweets
Approaches:
- Domain adaptation
- Distribution matching
- Adversarial training
Data requirement:
- Unlabeled target data
- Some labeled source data
Common use case: Cross-domain NLP
""")
# 3. UNSUPERVISED TRANSFER LEARNING
print("\n=== Unsupervised Transfer Learning ===")
print("""
No labels in source or target
Example:
Pre-train on unlabeled images
Fine-tune on labeled target
Approaches:
- Self-supervised learning
- Contrastive learning
- Autoencoders
Modern examples:
- BERT (masked language modeling)
- GPT (next token prediction)
- SimCLR, MoCo (contrastive)
""")
# 4. PARAMETER TRANSFER
print("\n=== Parameter Transfer ===")
def parameter_transfer_example():
"""Transfer learned weights."""
# Load pre-trained model
model = models.resnet50(weights='IMAGENET1K_V2')
# Freeze all layers
for param in model.parameters():
param.requires_grad = False
# Replace classifier
model.fc = nn.Linear(2048, 10)
# Only new classifier is trained
return model
print("""
Transfer: Model weights
Approach: Load pre-trained, freeze, retrain head
Example: ResNet-50 ImageNet β Custom classifier
Best for: Small target datasets
""")
# 5. FEATURE TRANSFER
print("\n=== Feature Transfer ===")
def feature_transfer_example():
"""Extract features and train new classifier."""
# Load pre-trained model
backbone = models.resnet50(weights='IMAGENET1K_V2')
# Remove classifier
feature_extractor = nn.Sequential(*list(backbone.children())[:-1])
# Freeze backbone
for param in feature_extractor.parameters():
param.requires_grad = False
# New classifier
classifier = nn.Linear(2048, 10)
return feature_extractor, classifier
print("""
Transfer: Learned features
Approach: Extract features, train separate classifier
Example: ResNet features β SVM / Logistic Regression
Best for: Very small datasets, quick prototyping
""")
# 6. INSTANCE TRANSFER
print("\n=== Instance Transfer ===")
print("""
Transfer: Individual samples
Approach: Reweight or select source samples
Example: Transfer specific relevant examples
Best for: When some source samples are more relevant
Methods:
- Importance weighting
- Sample selection
- TrAdaBoost
""")
# 7. RELATIONAL TRANSFER
print("\n=== Relational Transfer ===")
print("""
Transfer: Relationships between entities
Approach: Transfer graph structure or logic
Example: Social network β Different social network
Best for: Relational data, graphs
Methods:
- Markov Logic Networks
- Graph neural networks
- Transfer across domains
""")
# 8. COMPARISON TABLE
print("\n=== Comparison Table ===")
print(f"{'Type':<25} {'Source':<15} {'Target':<15} {'Labels':<15}")
print("-" * 70)
print(f"{'Inductive':<25} {'Task A':<15} {'Task B':<15} {'Both labeled':<15}")
print(f"{'Transductive':<25} {'Domain A':<15} {'Domain B':<15} {'Source only':<15}")
print(f"{'Unsupervised':<25} {'No labels':<15} {'No labels':<15} {'None':<15}")
print(f"{'Parameter':<25} {'Weights':<15} {'Weights':<15} {'Target':<15}")
print(f"{'Feature':<25} {'Features':<15} {'Classifier':<15} {'Target':<15}")
print(f"{'Instance':<25} {'Samples':<15} {'New task':<15} {'Both':<15}")
print(f"{'Relational':<25} {'Graph A':<15} {'Graph B':<15} {'Both':<15}")
# 9. MODERN TRENDS
print("\n=== Modern Trends ===")
print("""
2020s: Foundation Models dominate
Pre-train once on massive data:
- GPT-4: 1.7T parameters, web text
- CLIP: 400M image-text pairs
- DINOv2: 142M images
- SAM: 1B masks
Fine-tune for many tasks:
- Classification, detection, segmentation
- Generation, translation, summarization
- Question answering, dialogue
- Robotics, code, math
Transfer paradigm shift:
Old: Train from scratch (AlexNet era)
New: Fine-tune a foundation model
Key insight:
Scale + transfer = modern AI
""")
# 10. CHOOSING THE RIGHT TYPE
print("\n=== Choosing the Right Type ===")
print("""
Questions to ask:
1. Do you have labeled target data?
Yes β Inductive or Parameter transfer
No β Transductive or Unsupervised
2. Is the target domain similar to source?
Yes β Fine-tune more layers
No β Freeze more layers
3. How much target data?
Lots β Fine-tune more
Little β Feature extraction or freeze more
4. What's the goal?
Best accuracy β Fine-tune with care
Quick baseline β Feature extraction
Production deployment β PEFT methods
5. What's available?
Pre-trained model β Parameter/Feature transfer
Only data β Multi-task or meta-learning
""")π¨ Feature Extraction
π― Reuse Features, Train Classifier
Feature Extraction is the simplest form of transfer learning: use a pre-trained network as a fixed feature extractor and train a new classifier on top. No backprop through the pre-trained network.
How It Works
1. Freeze: Pre-trained network's weights stay fixed. 2. Extract: Run inputs through, get features. 3. Train: Train new classifier on features. Fast: No backprop through backbone. Works well: When target data is small. Best for: Very different tasks or tiny datasets.
# Feature extraction with pre-trained models
import torch
import torch.nn as nn
from torchvision import models, transforms
from torch.utils.data import DataLoader
import numpy as np
# 1. LOAD PRE-TRAINED MODEL
def create_feature_extractor(model_name='resnet50'):
"""Create a feature extractor from pre-trained model."""
if model_name == 'resnet50':
model = models.resnet50(weights='IMAGENET1K_V2')
# Remove the final classification layer
feature_extractor = nn.Sequential(*list(model.children())[:-1])
feature_dim = 2048
elif model_name == 'vgg16':
model = models.vgg16(weights='IMAGENET1K_V1')
feature_extractor = nn.Sequential(*list(model.features))
feature_extractor.add_module('avgpool', nn.AdaptiveAvgPool2d((1, 1)))
feature_dim = 512
elif model_name == 'efficientnet_b0':
model = models.efficientnet_b0(weights='IMAGENET1K_V1')
feature_extractor = nn.Sequential(*list(model.children())[:-1])
feature_dim = 1280
# Freeze all parameters
for param in feature_extractor.parameters():
param.requires_grad = False
return feature_extractor, feature_dim
# 2. EXTRACT FEATURES FROM DATASET
def extract_features(model, dataloader, device='cuda'):
"""Extract features for entire dataset."""
model.eval()
model = model.to(device)
all_features = []
all_labels = []
with torch.no_grad():
for images, labels in dataloader:
images = images.to(device)
# Extract features
features = model(images)
features = features.flatten(start_dim=1)
all_features.append(features.cpu().numpy())
all_labels.append(labels.numpy())
# Concatenate
features = np.concatenate(all_features, axis=0)
labels = np.concatenate(all_labels, axis=0)
return features, labels
# 3. TRAIN SIMPLE CLASSIFIER ON FEATURES
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
def train_classifier_on_features(train_features, train_labels,
test_features, test_labels):
"""Train multiple classifiers on extracted features."""
classifiers = {
'Logistic Regression': LogisticRegression(max_iter=1000),
'SVM (RBF)': SVC(kernel='rbf', C=1.0),
'SVM (Linear)': SVC(kernel='linear', C=1.0),
'Random Forest': RandomForestClassifier(n_estimators=100)
}
results = {}
for name, clf in classifiers.items():
clf.fit(train_features, train_labels)
predictions = clf.predict(test_features)
accuracy = accuracy_score(test_labels, predictions)
results[name] = accuracy
print(f"{name}: {accuracy:.4f}")
return results
# 4. COMPLETE PIPELINE
def feature_extraction_pipeline():
"""Complete feature extraction workflow."""
# Setup transforms
transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
# Create feature extractor
feature_extractor, feature_dim = create_feature_extractor('resnet50')
print(f"Feature dimension: {feature_dim}")
# Extract features (assumes dataloaders are ready)
# train_features, train_labels = extract_features(feature_extractor, train_loader)
# test_features, test_labels = extract_features(feature_extractor, test_loader)
# Train classifier
# results = train_classifier_on_features(
# train_features, train_labels,
# test_features, test_labels
# )
return feature_extractor
# 5. PYTORCH APPROACH (end-to-end)
class FeatureExtractionModel(nn.Module):
"""PyTorch module for feature extraction."""
def __init__(self, backbone_name='resnet50', num_classes=10):
super().__init__()
# Load backbone
if backbone_name == 'resnet50':
backbone = models.resnet50(weights='IMAGENET1K_V2')
self.features = nn.Sequential(*list(backbone.children())[:-1])
feature_dim = 2048
# Freeze backbone
for param in self.features.parameters():
param.requires_grad = False
# New classifier
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(feature_dim, 256),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(256, num_classes)
)
def forward(self, x):
with torch.no_grad():
features = self.features(x)
return self.classifier(features)
# 6. TRAINING
def train_feature_extraction(model, train_loader, val_loader,
epochs=20, lr=1e-3):
"""Train only the classifier."""
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
# Only optimize classifier
optimizer = torch.optim.Adam(
model.classifier.parameters(),
lr=lr
)
criterion = nn.CrossEntropyLoss()
for epoch in range(epochs):
model.train()
total_loss = 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
# Validation
model.eval()
correct = 0
total = 0
with torch.no_grad():
for images, labels in val_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
accuracy = 100 * correct / total
print(f"Epoch {epoch+1}: Loss={total_loss/len(train_loader):.4f}, "
f"Val Acc={accuracy:.2f}%")
return model
# 7. ADVANTAGES AND DISADVANTAGES
print("=== Feature Extraction: Pros and Cons ===")
print("""
Advantages:
β Very fast (no backprop through backbone)
β Low memory (no gradients for backbone)
β Works with tiny datasets (< 1000 samples)
β Can use any classifier (sklearn, XGBoost, etc.)
β Easy to debug and understand
β Reproducible
Disadvantages:
β Not optimal for target task
β Features may not be ideal
β Ceiling on performance
β Can't adapt features
When to use:
- Very small dataset (< 1000)
- Target task very different from source
- Need quick baseline
- Limited compute
- Interpretability needed
- Multiple classifiers to compare
Typical accuracy:
- On similar task: 90-95% of fine-tuning
- On different task: 80-90% of fine-tuning
""")
# 8. FEATURE EXTRACTION WITH DIFFERENT LAYERS
print("\n=== Extracting from Different Layers ===")
print("""
Pre-trained networks have multiple useful layers:
Early layers (conv1, layer1):
- Low-level features: edges, colors
- Best for: very different tasks
- Example: medical images, satellite
Middle layers (layer2, layer3):
- Mid-level features: textures, patterns
- Best for: moderately different tasks
- Example: fine-grained classification
Late layers (layer4, avgpool):
- High-level features: objects, semantics
- Best for: similar tasks
- Example: transfer between similar domains
Strategy: try multiple layers, ensemble
""")
# 9. DEMONSTRATION
print("\n=== Quick Demo ===")
print("""
# Pseudocode for complete workflow:
# 1. Setup
feature_extractor = create_feature_extractor('resnet50')
transform = standard_transforms()
# 2. Prepare data
train_dataset = CustomDataset('train/', transform=transform)
test_dataset = CustomDataset('test/', transform=transform)
train_loader = DataLoader(train_dataset, batch_size=32)
test_loader = DataLoader(test_dataset, batch_size=32)
# 3. Extract features (once)
train_features, train_labels = extract_features(
feature_extractor, train_loader
)
test_features, test_labels = extract_features(
feature_extractor, test_loader
)
# 4. Train classifier
clf = LogisticRegression(max_iter=1000)
clf.fit(train_features, train_labels)
# 5. Evaluate
accuracy = clf.score(test_features, test_labels)
print(f"Accuracy: {accuracy:.4f}")
# Time: minutes instead of hours
# Data: 1000 samples instead of 1M
# Result: often competitive with fine-tuning
""")π§ Fine-Tuning Strategies
π― Adapting Pre-trained Weights
Fine-tuning goes beyond feature extraction: we update the pre-trained weights on the target task. This adapts the features themselves, typically giving better performance but requiring more data and care.
Fine-Tuning Strategies
Full fine-tuning: Update all weights. Partial: Freeze early, train late. Layer-wise: Different LR per layer. Progressive: Unfreeze gradually. Discriminative: Lower LR for early layers. PEFT: Train small add-on parameters.
# Fine-tuning strategies
import torch
import torch.nn as nn
from torchvision import models
from torch.optim import Adam, SGD
# 1. FULL FINE-TUNING
def full_fine_tuning(num_classes=10):
"""Update all parameters."""
model = models.resnet50(weights='IMAGENET1K_V2')
model.fc = nn.Linear(2048, num_classes)
# All parameters are trainable
# Use small learning rate (1e-4 to 1e-3)
optimizer = Adam(model.parameters(), lr=1e-4)
return model, optimizer
# 2. PARTIAL FINE-TUNING (freeze early layers)
def partial_fine_tuning(num_classes=10, unfreeze_from='layer3'):
"""Freeze early layers, train late layers + classifier."""
model = models.resnet50(weights='IMAGENET1K_V2')
model.fc = nn.Linear(2048, num_classes)
# Freeze specific layers
frozen_layers = {
'layer4': ['layer4'],
'layer3': ['layer3', 'layer4'],
'layer2': ['layer2', 'layer3', 'layer4'],
}
# Freeze all
for param in model.parameters():
param.requires_grad = False
# Unfreeze chosen layers
layers_to_unfreeze = frozen_layers.get(unfreeze_from, [])
for name, param in model.named_parameters():
if any(layer in name for layer in layers_to_unfreeze):
param.requires_grad = True
# Always unfreeze classifier
if 'fc' in name:
param.requires_grad = True
# Lower learning rate for pre-trained layers
optimizer = Adam(filter(lambda p: p.requires_grad,
model.parameters()), lr=1e-4)
return model, optimizer
# 3. LAYER-WISE FINE-TUNING (different LR per layer)
def layer_wise_fine_tuning(num_classes=10, base_lr=1e-4):
"""Assign different learning rates to different layers."""
model = models.resnet50(weights='IMAGENET1K_V2')
model.fc = nn.Linear(2048, num_classes)
# Group parameters by layer with different LRs
# Early layers: tiny LR
# Late layers: bigger LR
# Classifier: biggest LR
param_groups = [
{'params': model.conv1.parameters(), 'lr': base_lr * 0.1},
{'params': model.layer1.parameters(), 'lr': base_lr * 0.2},
{'params': model.layer2.parameters(), 'lr': base_lr * 0.5},
{'params': model.layer3.parameters(), 'lr': base_lr},
{'params': model.layer4.parameters(), 'lr': base_lr * 2},
{'params': model.fc.parameters(), 'lr': base_lr * 10},
]
optimizer = SGD(param_groups, momentum=0.9, weight_decay=1e-4)
return model, optimizer
# 4. PROGRESSIVE FINE-TUNING
class ProgressiveFineTuning:
"""Gradually unfreeze layers during training."""
def __init__(self, model, num_classes, unfreeze_schedule):
"""
unfreeze_schedule: list of (epoch, layers_to_unfreeze)
Example: [(0, ['fc']), (5, ['layer4']), (10, ['layer3'])]
"""
self.model = model
self.model.fc = nn.Linear(2048, num_classes)
self.unfreeze_schedule = unfreeze_schedule
self.current_epoch = 0
# Initially freeze all except classifier
for param in self.model.parameters():
param.requires_grad = False
for param in self.model.fc.parameters():
param.requires_grad = True
self.optimizer = Adam(
filter(lambda p: p.requires_grad, model.parameters()),
lr=1e-3
)
def step(self, epoch):
"""Unfreeze layers according to schedule."""
for unfreeze_epoch, layers in self.unfreeze_schedule:
if epoch == unfreeze_epoch:
for name, param in self.model.named_parameters():
if any(layer in name for layer in layers):
param.requires_grad = True
# Recreate optimizer to include new params
self.optimizer = Adam(
filter(lambda p: p.requires_grad,
self.model.parameters()),
lr=1e-4
)
print(f"Epoch {epoch}: Unfroze {layers}")
# 5. DISCRIMINATIVE LEARNING RATES
def discriminative_fine_tuning(num_classes=10):
"""
Discriminative learning rates: lower LR for earlier layers.
Rationale: Early layers already well-trained on generic features
Later layers need more adaptation.
"""
model = models.resnet50(weights='IMAGENET1K_V2')
model.fc = nn.Linear(2048, num_classes)
# Base LR
base_lr = 1e-4
# Discriminative LR: base_lr / 2.6 per layer group
lr_mult = 2.6
param_groups = [
{'params': model.conv1.parameters(), 'lr': base_lr / lr_mult**4},
{'params': model.layer1.parameters(), 'lr': base_lr / lr_mult**3},
{'params': model.layer2.parameters(), 'lr': base_lr / lr_mult**2},
{'params': model.layer3.parameters(), 'lr': base_lr / lr_mult**1},
{'params': model.layer4.parameters(), 'lr': base_lr},
{'params': model.fc.parameters(), 'lr': base_lr * lr_mult},
]
optimizer = Adam(param_groups)
return model, optimizer
# 6. TRAINING LOOP
def train_fine_tuned(model, train_loader, val_loader, optimizer,
epochs=10, device='cuda'):
"""Complete fine-tuning training loop."""
model = model.to(device)
criterion = nn.CrossEntropyLoss()
for epoch in range(epochs):
model.train()
total_loss = 0
correct = 0
total = 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
train_acc = 100 * correct / total
# Validation
model.eval()
val_correct = 0
val_total = 0
with torch.no_grad():
for images, labels in val_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = outputs.max(1)
val_total += labels.size(0)
val_correct += predicted.eq(labels).sum().item()
val_acc = 100 * val_correct / val_total
print(f"Epoch {epoch+1}: Loss={total_loss/len(train_loader):.4f}, "
f"Train={train_acc:.2f}%, Val={val_acc:.2f}%")
return model
# STRATEGY SELECTION
print("=== Fine-Tuning Strategy Guide ===")
print("""
Target dataset size vs Strategy:
Very small (< 100 samples):
β Feature extraction only
β No fine-tuning
Small (100-1000):
β Partial fine-tuning
β Freeze most layers
β Train classifier + last block
Medium (1000-10000):
β Partial fine-tuning
β Freeze early, train late
β Discriminative LRs
Large (10000-100000):
β Full fine-tuning
β Small learning rate
β Watch for overfitting
Very large (> 100000):
β Full fine-tuning
β Standard LR
β May match training from scratch
Similarity to source:
Very similar β Fine-tune more
Very different β Fine-tune less
Compute budget:
Limited β Feature extraction
Moderate β Partial fine-tuning
Large β Full fine-tuning
""")
# PRACTICAL TIPS
print("\n=== Practical Tips ===")
print("""
1. START WITH FEATURE EXTRACTION
- Baseline first
- Fast iteration
- Understand the problem
2. THEN TRY PARTIAL FINE-TUNING
- Freeze early layers
- Train late layers
- Better accuracy
3. USE DISCRIMINATIVE LEARNING RATES
- Early layers: 1/10 LR
- Later layers: standard LR
- Classifier: 10x LR
4. WARM UP THE CLASSIFIER
- Train classifier first
- Then unfreeze
- Prevents catastrophic forgetting
5. GRADUAL UNFREEZING
- Start frozen
- Unfreeze progressively
- Better convergence
6. USE SMALL LEARNING RATES
- Fine-tuning LR: 1e-5 to 1e-3
- 10-100x smaller than training from scratch
7. WATCH FOR OVERFITTING
- Small target data
- Track validation loss
- Early stopping
8. REGULARIZE
- Weight decay
- Dropout
- Data augmentation
- Label smoothing
""")
# COMMON MISTAKES
print("\n=== Common Mistakes ===")
print("""
β Using too large learning rate
β Destroys pre-trained features
β Fix: Use 1e-5 to 1e-4
β Not freezing anything
β Overfits on small data
β Fix: Freeze early layers
β Fine-tuning on tiny dataset
β Guaranteed overfitting
β Fix: Feature extraction only
β Same LR for all layers
β Early layers destroyed
β Fix: Discriminative LRs
β Ignoring batch norm
β Statistics shift
β Fix: Careful with BN mode
β Not using data augmentation
β Overfits quickly
β Fix: Strong augmentation
β Not monitoring validation
β Wastes compute, overfits
β Fix: Early stopping
β Forgetting to set model.train()/eval()
β BN and dropout issues
β Fix: Always set mode correctly
""")π§ Freezing & Unfreezing
π― Controlling What Learns
Freezing prevents certain parameters from updating during training. It's a key tool in transfer learning: freeze what's already good, train what needs to adapt.
When to Freeze
Small target data: Freeze more layers. Different domain: Freeze early layers. Similar task: Freeze fewer layers. Limited compute: Freeze to save time. Prevent forgetting: Freeze during warmup. Gradual: Unfreeze progressively.
# Freezing and unfreezing strategies
import torch
import torch.nn as nn
from torchvision import models
# 1. BASIC FREEZING
def freeze_all(model):
"""Freeze all parameters."""
for param in model.parameters():
param.requires_grad = False
def unfreeze_all(model):
"""Unfreeze all parameters."""
for param in model.parameters():
param.requires_grad = True
def freeze_by_name(model, layer_names):
"""Freeze layers matching names."""
for name, param in model.named_parameters():
if any(layer in name for layer in layer_names):
param.requires_grad = False
def unfreeze_by_name(model, layer_names):
"""Unfreeze layers matching names."""
for name, param in model.named_parameters():
if any(layer in name for layer in layer_names):
param.requires_grad = True
# 2. FREEZE UP TO LAYER
def freeze_until(model, layer_name):
"""Freeze all layers up to and including layer_name."""
frozen = True
for name, param in model.named_parameters():
if layer_name in name:
frozen = False
param.requires_grad = not frozen
# 3. GRADUAL UNFREEZING
class GradualUnfreezer:
"""Progressively unfreeze layers during training."""
def __init__(self, model, schedule):
"""
schedule: dict mapping epoch β list of layers to unfreeze
Example: {
0: ['fc'],
5: ['layer4'],
10: ['layer3'],
15: ['layer2']
}
"""
self.model = model
self.schedule = schedule
# Initially freeze all
freeze_all(model)
# Apply epoch 0 unfreezing
if 0 in schedule:
unfreeze_by_name(model, schedule[0])
def step(self, epoch):
"""Call at start of each epoch."""
if epoch in self.schedule:
layers = self.schedule[epoch]
unfreeze_by_name(self.model, layers)
print(f"Epoch {epoch}: Unfroze {layers}")
return True # Signal that optimizer should be recreated
return False
def trainable_params(self):
return sum(p.numel() for p in self.model.parameters()
if p.requires_grad)
# 4. LAYER COUNT ANALYSIS
def analyze_frozen_layers(model):
"""Analyze how many layers are frozen/trainable."""
total_params = sum(p.numel() for p in model.parameters())
trainable = sum(p.numel() for p in model.parameters()
if p.requires_grad)
frozen = total_params - trainable
print(f"Total parameters: {total_params:,}")
print(f"Trainable: {trainable:,} ({100*trainable/total_params:.2f}%)")
print(f"Frozen: {frozen:,} ({100*frozen/total_params:.2f}%)")
# Per-layer breakdown
print("\nPer-layer status:")
for name, param in model.named_parameters():
status = "β trainable" if param.requires_grad else "β frozen"
print(f" {name}: {param.numel():,} params {status}")
# 5. RESNET FREEZING EXAMPLE
def demonstrate_resnet_freezing():
"""Show different freezing strategies for ResNet."""
model = models.resnet50(weights='IMAGENET1K_V2')
model.fc = nn.Linear(2048, 10)
print("=== Strategy 1: Freeze All (Feature Extraction) ===")
freeze_all(model)
unfreeze_by_name(model, ['fc'])
analyze_frozen_layers(model)
print("\n=== Strategy 2: Freeze Early, Train Late ===")
unfreeze_all(model)
freeze_by_name(model, ['conv1', 'bn1', 'layer1'])
analyze_frozen_layers(model)
print("\n=== Strategy 3: Full Fine-Tuning ===")
unfreeze_all(model)
analyze_frozen_layers(model)
demonstrate_resnet_freezing()
# 6. BATCH NORM CONSIDERATIONS
print("=== Batch Norm in Frozen Layers ===")
print("""
Special consideration: BatchNorm layers.
Freezing BN parameters:
- Stops learning mean/variance
- Uses running stats from pre-training
- Good when target distribution is similar
- Bad when distributions differ significantly
Options:
1. Freeze everything (including BN stats)
- Use when distributions match
2. Freeze weights, update BN stats
- Set BN in train mode
- Updates running mean/var
- Better for distribution shift
3. Fully train BN
- Learn new weights + update stats
- Use when target differs a lot
In PyTorch:
# Freeze BN completely
for module in model.modules():
if isinstance(module, nn.BatchNorm2d):
module.eval()
for param in module.parameters():
param.requires_grad = False
# Update BN stats only (freeze weights)
for module in model.modules():
if isinstance(module, nn.BatchNorm2d):
module.train()
module.weight.requires_grad = False
module.bias.requires_grad = False
""")
# 7. PRACTICAL DECISION FRAMEWORK
print("\n=== Freezing Decision Framework ===")
print("""
Questions:
1. How much target data?
<1000 samples β Freeze most layers
1000-10000 β Freeze early layers
>10000 β Maybe full fine-tuning
2. How similar is target to source?
Very similar β Freeze fewer layers
Somewhat similar β Freeze early
Very different β Freeze fewer, retrain more
3. What's the compute budget?
Limited β Freeze more (faster training)
Moderate β Standard strategy
Large β Full fine-tuning
4. What's the risk of overfitting?
High (small data) β Freeze more
Medium β Standard
Low (large data) β Fine-tune more
5. Production needs?
Fast inference β Feature extraction
Best accuracy β Fine-tune
Balanced β Partial fine-tuning
Example strategies:
ResNet-50 for CIFAR-10 (similar to ImageNet):
Freeze: conv1, layer1
Train: layer2, layer3, layer4, fc
LR: 1e-4
ResNet-50 for medical X-rays (different):
Freeze: conv1, layer1, layer2
Train: layer3, layer4, fc
LR: 1e-4
ResNet-50 for tiny dataset (100 images):
Freeze: everything except fc
Train: fc only
LR: 1e-3
""")
# 8. GRADUAL UNFREEZING EXAMPLE
print("\n=== Gradual Unfreezing Example ===")
print("""
Training schedule:
Epoch 0-4: Only classifier (fc)
LR = 1e-3
Epoch 5-9: Unfreeze layer4
LR = 1e-4
Epoch 10-14: Unfreeze layer3
LR = 1e-4
Epoch 15-19: Unfreeze layer2
LR = 5e-5
Epoch 20-29: Unfreeze layer1
LR = 1e-5
Benefits:
- Classifier learns first (warmup)
- Progressive adaptation
- Prevents catastrophic forgetting
- Better final accuracy
Implementation:
unfreezer = GradualUnfreezer(model, {
0: ['fc'],
5: ['layer4'],
10: ['layer3'],
15: ['layer2'],
20: ['layer1'],
})
for epoch in range(30):
if unfreezer.step(epoch):
# Recreate optimizer with new params
optimizer = Adam(
filter(lambda p: p.requires_grad, model.parameters()),
lr=get_lr_for_epoch(epoch)
)
train_one_epoch(model, optimizer, train_loader)
""")
# 9. MONITORING
print("\n=== Monitoring Freezing Strategy ===")
print("""
What to track:
1. Trainable parameters count
- Changes as layers unfreeze
2. Training loss
- Should decrease smoothly
3. Validation accuracy
- Should improve as more layers train
4. Gradient norms
- Detect issues in unfrozen layers
5. Layer-wise learning
- Are new layers learning?
- Compare before/after weights
Warning signs:
- Validation loss increasing
β Overfitting, freeze more
- Training loss not decreasing
β LR too low, or features too different
- Weights changing dramatically
β LR too high, reduce
- No improvement after unfreezing
β Task too different, or need more data
""")π Discriminative Learning Rates
π― Different Rates for Different Layers
Discriminative learning rates assign different learning rates to different layers of the network. Early layers get smaller rates (they're already good), later layers get larger rates (they need more adaptation).
Why Different Rates?
Early layers: Generic features, less change needed. Late layers: Task-specific, more change needed. Classifier: New task, most change needed. Ratio: Typically 2-10x between layers. Result: Better convergence, higher accuracy. FastAI: Popularized this technique.
# Discriminative learning rates
import torch
import torch.nn as nn
from torchvision import models
from torch.optim import Adam, SGD
# 1. BASIC DISCRIMINATIVE LR
def discriminative_lr_basic(model, base_lr=1e-4, lr_ratio=2.6):
"""
Assign decreasing LR to earlier layers.
layer4 β base_lr
layer3 β base_lr / ratio
layer2 β base_lr / ratioΒ²
layer1 β base_lr / ratioΒ³
conv1 β base_lr / ratioβ΄
"""
param_groups = [
{'params': model.conv1.parameters(),
'lr': base_lr / (lr_ratio ** 4)},
{'params': model.bn1.parameters(),
'lr': base_lr / (lr_ratio ** 4)},
{'params': model.layer1.parameters(),
'lr': base_lr / (lr_ratio ** 3)},
{'params': model.layer2.parameters(),
'lr': base_lr / (lr_ratio ** 2)},
{'params': model.layer3.parameters(),
'lr': base_lr / lr_ratio},
{'params': model.layer4.parameters(),
'lr': base_lr},
{'params': model.fc.parameters(),
'lr': base_lr * 10},
]
optimizer = SGD(param_groups, momentum=0.9, weight_decay=1e-4)
return optimizer
# 2. AUTOMATIC DISCRIMINATIVE LR
def auto_discriminative_lr(model, base_lr=1e-4, lr_decay=0.9):
"""
Automatic discriminative LR using layer index.
"""
# Get unique layer prefixes in order
layer_names = []
seen = set()
for name, _ in model.named_parameters():
prefix = name.split('.')[0]
if prefix not in seen:
seen.add(prefix)
layer_names.append(prefix)
# Assign decreasing LR to earlier layers
num_layers = len(layer_names)
param_groups = []
for i, layer_name in enumerate(layer_names):
# Earlier layers get smaller LR
# Later layers get larger LR
layer_lr = base_lr * (lr_decay ** (num_layers - i - 1))
if 'fc' in layer_name or 'classifier' in layer_name:
layer_lr = base_lr * 10 # Classifier gets biggest LR
params = [p for n, p in model.named_parameters()
if n.startswith(layer_name)]
param_groups.append({
'params': params,
'lr': layer_lr
})
print(f"Layer {layer_name}: LR = {layer_lr:.2e}")
optimizer = Adam(param_groups)
return optimizer
# 3. FASTAI-STYLE DISCRIMINATIVE LR
def fastai_style_discriminative_lr(model, base_lr=1e-4, lr_ratio=2.6):
"""
FastAI's approach: split network into groups.
Group 1 (earliest): LR / ratio^(n-1)
Group n (latest): LR
Classifier: LR * ratio
"""
# Group parameters by depth
param_groups = []
# Get all named parameters
named_params = list(model.named_parameters())
# Split into 3 groups (common in FastAI)
# Group 0: earliest layers
# Group 1: middle layers
# Group 2: latest layers + classifier
early_layers = [p for n, p in named_params
if any(x in n for x in ['conv1', 'bn1', 'layer1'])]
middle_layers = [p for n, p in named_params
if any(x in n for x in ['layer2', 'layer3'])]
late_layers = [p for n, p in named_params
if any(x in n for x in ['layer4', 'fc'])]
param_groups = [
{'params': early_layers, 'lr': base_lr / (lr_ratio ** 2)},
{'params': middle_layers, 'lr': base_lr / lr_ratio},
{'params': late_layers, 'lr': base_lr * lr_ratio},
]
optimizer = SGD(param_groups, momentum=0.9)
return optimizer
# 4. SCHEDULED DISCRIMINATIVE LR
class DiscriminativeLRScheduler:
"""Discriminative LR with scheduling."""
def __init__(self, model, base_lr=1e-4, num_groups=3):
self.model = model
self.base_lr = base_lr
self.num_groups = num_groups
# Create param groups
self.param_groups = self._create_param_groups()
self.optimizer = Adam(self.param_groups)
self.current_epoch = 0
def _create_param_groups(self):
"""Split model into groups with different LRs."""
named_params = list(self.model.named_parameters())
# Simple split: divide into num_groups chunks
chunk_size = len(named_params) // self.num_groups
groups = []
for i in range(self.num_groups):
start = i * chunk_size
end = start + chunk_size if i < self.num_groups - 1 else len(named_params)
layer_lr = self.base_lr * (2.0 ** i) # 2x per group
params = [p for _, p in named_params[start:end]]
groups.append({'params': params, 'lr': layer_lr})
return groups
def step(self, epoch, val_loss=None):
"""Update learning rates."""
self.current_epoch = epoch
# Decay all LRs
for group in self.param_groups:
group['lr'] *= 0.9
# Or use cosine annealing
# for i, group in enumerate(self.param_groups):
# group['lr'] = self.base_lr * (2.0 ** i) * \
# (0.5 * (1 + math.cos(math.pi * epoch / 100)))
# 5. FINDING OPTIMAL LR
print("=== Finding Optimal Learning Rate ===")
print("""
LR Range Test (Leslie Smith):
1. Start with tiny LR (1e-7)
2. Increase exponentially each batch
3. Track loss
4. Plot LR vs loss
5. Optimal LR = where loss decreases fastest
Typical optimal range for fine-tuning:
1e-5 to 1e-3
For discriminative:
Early layers: 1e-6 to 1e-5
Late layers: 1e-5 to 1e-4
Classifier: 1e-4 to 1e-3
Practical rule:
Base LR = 1e-4
Ratio = 2.6 (FastAI default)
Classifier = 10x base
""")
# 6. IMPLEMENTATION EXAMPLE
def full_fine_tuning_with_discriminative_lr():
"""Complete fine-tuning with discriminative LRs."""
# Setup
model = models.resnet50(weights='IMAGENET1K_V2')
model.fc = nn.Linear(2048, 10)
# Create optimizer with discriminative LRs
optimizer = discriminative_lr_basic(
model,
base_lr=1e-4,
lr_ratio=2.6
)
# Optional: Learning rate scheduler
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
optimizer,
T_max=20
)
return model, optimizer, scheduler
# 7. MONITORING
def print_lr_status(optimizer):
"""Print current learning rates."""
print("Current learning rates:")
for i, group in enumerate(optimizer.param_groups):
print(f" Group {i}: LR = {group['lr']:.2e}, "
f"params = {sum(p.numel() for p in group['params']):,}")
# 8. COMPARISON
print("\n=== Standard vs Discriminative LR ===")
print("""
Standard single LR:
- All layers updated equally
- Risk: early layers destroyed
- Risk: late layers under-trained
- Simple but suboptimal
Discriminative LR:
- Early layers: small changes
- Late layers: bigger changes
- Classifier: biggest changes
- Preserves pre-trained features
Results (typical):
- Single LR: baseline
- Discriminative: +1-3% accuracy
- Common in practice
Why it works:
- Early features are general β don't change much
- Late features are specific β need adaptation
- Classifier is new β needs most learning
""")
# 9. PYTORCH IMPLEMENTATION DETAILS
print("\n=== PyTorch Implementation ===")
print("""
# Create param groups
param_groups = [
{'params': model.layer1.parameters(), 'lr': 1e-5},
{'params': model.layer2.parameters(), 'lr': 3e-5},
{'params': model.layer3.parameters(), 'lr': 1e-4},
{'params': model.layer4.parameters(), 'lr': 3e-4},
{'params': model.fc.parameters(), 'lr': 1e-3},
]
optimizer = torch.optim.Adam(param_groups)
# With weight decay
param_groups = [
{'params': model.layer1.parameters(), 'lr': 1e-5, 'weight_decay': 1e-4},
{'params': model.layer2.parameters(), 'lr': 3e-5, 'weight_decay': 1e-4},
...
]
# Scheduled decay for each group
scheduler = torch.optim.lr_scheduler.StepLR(
optimizer,
step_size=10,
gamma=0.1
)
# Per-group scheduling
schedulers = [
torch.optim.lr_scheduler.StepLR(optimizer, 10, 0.1)
]
""")
# 10. PRACTICAL TIPS
print("\n=== Practical Tips ===")
print("""
1. START WITH FASTAI DEFAULTS
- Ratio: 2.6
- Groups: 3-4
- Classifier LR: 10x base
2. TUNE THE RATIO
- 2.0: Conservative
- 2.6: Standard
- 3.5: Aggressive
- Depends on task
3. USE WITH FROZEN LAYERS
- Frozen layers don't need LR
- Only specify for trainable layers
4. COMBINE WITH SCHEDULING
- Discriminative LR + cosine annealing
- Even better results
5. MONITOR LEARNING
- Check layer-wise gradient norms
- Early layers shouldn't change much
- Late layers should adapt
6. DEBUG ISSUES
- If early layers change too much β reduce their LR
- If late layers don't learn β increase their LR
- If unstable β reduce all LRs
7. CONSIDER BATCH NORM
- BN params often go in their own group
- Or exclude from weight decay
- Handle carefully
""")π Domain Adaptation
π― Bridging Domain Gaps
Domain Adaptation addresses the case where source and target have the same task but different data distributions. Examples: synthetic β real, daytime β nighttime, US β UK English.
Domain Shift Types
Covariate shift: P(X) changes, P(Y|X) same. Label shift: P(Y) changes, P(X|Y) same. Concept shift: P(Y|X) changes. Prior shift: Class proportions change. Manifestation: Real-world deployments. Solutions: Align, adapt, or retrain.
# Domain Adaptation techniques
import torch
import torch.nn as nn
import torch.nn.functional as F
# 1. FINE-TUNING (simplest)
def domain_adaptation_finetune(source_model, target_data, target_labels,
epochs=10, lr=1e-4):
"""Simple fine-tuning on target domain."""
model = source_model
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
criterion = nn.CrossEntropyLoss()
# Standard fine-tuning
for epoch in range(epochs):
for x, y in target_data:
optimizer.zero_grad()
logits = model(x)
loss = criterion(logits, y)
loss.backward()
optimizer.step()
return model
# 2. ADVERSARIAL DOMAIN ADAPTATION (DANN)
class GradientReversal(torch.autograd.Function):
"""Gradient reversal layer."""
@staticmethod
def forward(ctx, x, alpha):
ctx.alpha = alpha
return x.view_as(x)
@staticmethod
def backward(ctx, grad_output):
return -ctx.alpha * grad_output, None
class DANN(nn.Module):
"""
Domain-Adversarial Neural Network.
Features are learned to:
- Classify source data (task)
- Be indistinguishable between domains (invariance)
"""
def __init__(self, feature_dim=512, num_classes=10):
super().__init__()
# Feature extractor (shared)
self.features = nn.Sequential(
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, feature_dim),
nn.ReLU()
)
# Task classifier
self.classifier = nn.Sequential(
nn.Linear(feature_dim, 128),
nn.ReLU(),
nn.Linear(128, num_classes)
)
# Domain classifier
self.domain_classifier = nn.Sequential(
nn.Linear(feature_dim, 128),
nn.ReLU(),
nn.Linear(128, 2) # 2 domains
)
def forward(self, x, alpha=1.0):
features = self.features(x)
# Task prediction
class_logits = self.classifier(features)
# Domain prediction (with gradient reversal)
reversed_features = GradientReversal.apply(features, alpha)
domain_logits = self.domain_classifier(reversed_features)
return class_logits, domain_logits
# Training DANN
def train_dann(model, source_loader, target_loader, epochs=20):
"""Train domain-adversarial network."""
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
class_criterion = nn.CrossEntropyLoss()
domain_criterion = nn.CrossEntropyLoss()
for epoch in range(epochs):
model.train()
total_loss = 0
# Iterate over both loaders
for (source_x, source_y), (target_x, _) in zip(source_loader, target_loader):
# Combine batches
x = torch.cat([source_x, target_x], dim=0)
# Domain labels: 0 for source, 1 for target
domain_labels = torch.cat([
torch.zeros(source_x.size(0)),
torch.ones(target_x.size(0))
]).long()
# Forward
class_logits, domain_logits = model(x, alpha=1.0)
# Task loss (only on source)
source_class_logits = class_logits[:source_x.size(0)]
task_loss = class_criterion(source_class_logits, source_y)
# Domain loss (on both)
domain_loss = domain_criterion(domain_logits, domain_labels)
# Total loss
loss = task_loss + domain_loss
# Update
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch {epoch+1}: Loss = {total_loss/len(source_loader):.4f}")
return model
# 3. MAXIMUM MEAN DISCREPANCY (MMD)
def mmd_loss(source_features, target_features, kernel='rbf', sigma=1.0):
"""
Maximum Mean Discrepancy loss.
Measures difference between source and target distributions.
"""
def rbf_kernel(x, y):
# Compute RBF kernel matrix
x_norm = (x ** 2).sum(1).view(-1, 1)
y_norm = (y ** 2).sum(1).view(1, -1)
dist = x_norm + y_norm - 2.0 * torch.mm(x, y.t())
return torch.exp(-dist / (2.0 * sigma ** 2))
# MMDΒ² = E[k(x,x')] + E[k(y,y')] - 2 E[k(x,y)]
xx = rbf_kernel(source_features, source_features).mean()
yy = rbf_kernel(target_features, target_features).mean()
xy = rbf_kernel(source_features, target_features).mean()
return xx + yy - 2 * xy
# 4. CORAL (CORRELATION ALIGNMENT)
def coral_loss(source_features, target_features):
"""
CORAL: align second-order statistics (covariances).
"""
d = source_features.size(1)
# Source covariance
xm = source_features - source_features.mean(0, keepdim=True)
xc = (xm.t() @ xm) / (source_features.size(0) - 1)
# Target covariance
ym = target_features - target_features.mean(0, keepdim=True)
yc = (ym.t() @ ym) / (target_features.size(0) - 1)
# Frobenius norm of difference
loss = torch.norm(xc - yc, p='fro') ** 2 / (4 * d * d)
return loss
# 5. DOMAIN ADAPTATION WITH MMD
class MMDNet(nn.Module):
"""Network with MMD domain adaptation loss."""
def __init__(self, feature_dim=512, num_classes=10):
super().__init__()
self.features = nn.Sequential(
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, feature_dim)
)
self.classifier = nn.Linear(feature_dim, num_classes)
def forward(self, x):
features = self.features(x)
logits = self.classifier(features)
return logits, features
def train_mmd(model, source_loader, target_loader, epochs=20,
mmd_weight=1.0):
"""Train with MMD loss."""
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
class_criterion = nn.CrossEntropyLoss()
for epoch in range(epochs):
total_loss = 0
for (source_x, source_y), (target_x, _) in zip(source_loader, target_loader):
# Forward
source_logits, source_features = model(source_x)
_, target_features = model(target_x)
# Classification loss
class_loss = class_criterion(source_logits, source_y)
# MMD loss
mmd = mmd_loss(source_features, target_features)
# Total
loss = class_loss + mmd_weight * mmd
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch {epoch+1}: Loss = {total_loss/len(source_loader):.4f}")
# 6. APPROACHES COMPARISON
print("=== Domain Adaptation Approaches ===")
print("""
1. FINE-TUNING
- Simplest approach
- Need labeled target data
- Works when target data available
2. ADVERSARIAL (DANN)
- Learn domain-invariant features
- Need unlabeled target data
- Powerful but complex
3. MMD
- Match feature distributions
- Kernel-based
- Simpler than adversarial
4. CORAL
- Match second-order statistics
- Very simple to implement
- Surprisingly effective
5. CYCLE-GAN
- Image-to-image translation
- Good for visual domains
- Requires more data
6. SELF-TRAINING
- Pseudo-labels on target
- Iterative refinement
- Simple but effective
""")
# 7. WHEN TO USE WHAT
print("\n=== Choosing an Approach ===")
print("""
Questions:
1. Do you have labeled target data?
Yes β Fine-tune directly
No β Need unsupervised DA
2. How large is the domain shift?
Small β Fine-tune, MMD
Medium β DANN, CORAL
Large β CycleGAN, complex methods
3. What data do you have?
Labeled source + unlabeled target β Standard DA
Labeled source + labeled target β Fine-tune
Only unlabeled β Transfer via pretraining
4. What's the modality?
Images β CycleGAN, style transfer
Features β MMD, CORAL, DANN
Text β Domain-specific embeddings
5. What's the compute budget?
Low β CORAL, MMD
Medium β Fine-tuning
High β Adversarial methods
Real-world scenarios:
Medical imaging (synthetic β real):
- CycleGAN + fine-tuning
- Unsupervised where possible
Autonomous driving (day β night):
- Style transfer + DANN
- Heavy augmentation
NLP (formal β informal):
- Domain-specific pretraining
- Simple fine-tuning
Speech (clean β noisy):
- Data augmentation
- Adversarial training
""")
# 8. PRACTICAL TIPS
print("\n=== Practical Tips ===")
print("""
1. START SIMPLE
- Try fine-tuning first
- Only add complexity if needed
2. ALIGN WITH DATA AUGMENTATION
- Augment source to match target
- Domain randomization
3. USE PRETRAINED FEATURES
- Start from ImageNet/BERT
- Better than training from scratch
4. MONITOR DOMAIN GAP
- Track feature distributions
- Visualize with t-SNE
- Measure MMD, CORAL
5. EVALUATE ON TARGET
- Primary metric: target performance
- Secondary: domain-invariance
- Watch for negative transfer
6. COMBINE METHODS
- Adversarial + MMD
- Multi-task learning
- Ensemble methods
7. BE CAREFUL WITH ADVERSARIAL
- Can be unstable
- GAN-like training issues
- Careful hyperparameter tuning
""")π― Few-Shot & Zero-Shot Learning
π― Learning with Almost No Data
Few-shot learning is the ability to learn new classes from just 1-5 examples each. Zero-shot learning goes further: recognizing classes never seen during training, using semantic descriptions.
Few-Shot Paradigms
N-way K-shot: N classes, K examples each. Meta-learning: Learn to learn. Metric-based: Learn similarity. Optimization-based: MAML, Reptile. Zero-shot: Class attributes, text descriptions. Foundation: CLIP, GPT.
# Few-shot and zero-shot learning
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
# 1. FEW-SHOT LEARNING (prototypical networks)
class PrototypicalNetwork(nn.Module):
"""
Prototypical Networks for few-shot learning.
Learn embedding where each class is represented by a prototype.
Classify by nearest prototype.
"""
def __init__(self, input_dim=784, embedding_dim=128):
super().__init__()
self.encoder = nn.Sequential(
nn.Linear(input_dim, 256),
nn.ReLU(),
nn.Linear(256, embedding_dim),
nn.ReLU()
)
def forward(self, x):
return self.encoder(x)
def compute_prototypes(self, support_x, support_y, n_ways):
"""Compute class prototypes from support examples."""
embeddings = self.forward(support_x)
prototypes = torch.zeros(n_ways, embeddings.size(1))
for c in range(n_ways):
mask = support_y == c
prototypes[c] = embeddings[mask].mean(0)
return prototypes
def forward_episode(self, support_x, support_y, query_x, n_ways):
"""Full episode forward pass."""
prototypes = self.compute_prototypes(support_x, support_y, n_ways)
query_embeddings = self.forward(query_x)
# Compute distances
distances = torch.cdist(query_embeddings, prototypes)
# Negative distance as logits
logits = -distances
return logits
# Training prototypical network
def train_prototypical(model, train_loader, n_ways=5, k_shot=5,
q_queries=15, epochs=20):
"""Train prototypical network with episodic training."""
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
for epoch in range(epochs):
total_loss = 0
n_episodes = 100
for episode in range(n_episodes):
# Sample episode: n_ways classes, k_shot + q_queries each
support_x, support_y, query_x, query_y = sample_episode(
train_loader, n_ways, k_shot, q_queries
)
# Forward
logits = model.forward_episode(
support_x, support_y, query_x, n_ways
)
# Loss
loss = F.cross_entropy(logits, query_y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch {epoch+1}: Loss = {total_loss/n_episodes:.4f}")
def sample_episode(train_loader, n_ways, k_shot, q_queries):
"""Sample an N-way K-shot episode."""
# Collect data by class
class_data = {}
for x, y in train_loader:
for xi, yi in zip(x, y):
if yi.item() not in class_data:
class_data[yi.item()] = []
class_data[yi.item()].append(xi)
# Sample n_ways classes
available_classes = list(class_data.keys())
selected_classes = np.random.choice(available_classes, n_ways, replace=False)
support_x, support_y = [], []
query_x, query_y = [], []
for i, c in enumerate(selected_classes):
examples = class_data[c]
selected = np.random.choice(len(examples), k_shot + q_queries, replace=False)
for j, idx in enumerate(selected):
if j < k_shot:
support_x.append(examples[idx])
support_y.append(i)
else:
query_x.append(examples[idx])
query_y.append(i)
return (torch.stack(support_x), torch.tensor(support_y),
torch.stack(query_x), torch.tensor(query_y))
# 2. MAML (Model-Agnostic Meta-Learning)
class MAML:
"""
MAML: Learn an initialization that can be quickly adapted.
- Inner loop: adapt to task with few gradient steps
- Outer loop: update initialization to be easily adaptable
"""
def __init__(self, model, inner_lr=0.01, outer_lr=1e-3):
self.model = model
self.inner_lr = inner_lr
self.outer_lr = outer_lr
self.outer_optimizer = torch.optim.Adam(
model.parameters(), lr=outer_lr
)
def inner_update(self, support_x, support_y):
"""Adapt to task with a few gradient steps."""
# Clone model parameters
fast_weights = [p.clone() for p in self.model.parameters()]
# Few gradient steps
for _ in range(5):
# Forward
logits = self.model(support_x, weights=fast_weights)
loss = F.cross_entropy(logits, support_y)
# Compute gradients
grads = torch.autograd.grad(
loss, fast_weights, create_graph=True
)
# Update
fast_weights = [
w - self.inner_lr * g
for w, g in zip(fast_weights, grads)
]
return fast_weights
def meta_update(self, tasks):
"""Update initialization."""
meta_loss = 0
for support_x, support_y, query_x, query_y in tasks:
# Inner loop
fast_weights = self.inner_update(support_x, support_y)
# Evaluate on query set
logits = self.model(query_x, weights=fast_weights)
loss = F.cross_entropy(logits, query_y)
meta_loss += loss
meta_loss /= len(tasks)
# Outer loop update
self.outer_optimizer.zero_grad()
meta_loss.backward()
self.outer_optimizer.step()
return meta_loss.item()
# 3. ZERO-SHOT LEARNING (CLIP-style)
class ZeroShotClassifier:
"""
Zero-shot classification using text embeddings.
Idea: CLIP learns joint image-text embedding.
At test time, compare image to class descriptions.
"""
def __init__(self, model_name='openai/clip-vit-base-patch32'):
import clip
self.model, self.preprocess = clip.load(model_name)
def classify(self, image, class_names):
"""
Classify image into classes not seen during training.
image: PIL Image
class_names: list of text descriptions
"""
import clip
# Preprocess image
image_input = self.preprocess(image).unsqueeze(0)
# Tokenize class names
text_inputs = clip.tokenize(class_names)
with torch.no_grad():
# Get embeddings
image_features = self.model.encode_image(image_input)
text_features = self.model.encode_text(text_inputs)
# Normalize
image_features /= image_features.norm(dim=-1, keepdim=True)
text_features /= text_features.norm(dim=-1, keepdim=True)
# Cosine similarity
similarity = (100.0 * image_features @ text_features.T).softmax(dim=-1)
# Return predictions
probs = similarity[0].numpy()
predictions = sorted(zip(class_names, probs),
key=lambda x: -x[1])
return predictions
# 4. FEW-SHOT WITH LLMs (in-context learning)
def few_shot_with_llm(prompt_examples, query):
"""
Few-shot learning with LLMs via in-context examples.
No gradient updates β just examples in the prompt.
"""
prompt = "Classify the following examples:\n\n"
# Add few-shot examples
for text, label in prompt_examples:
prompt += f"Text: {text}\nLabel: {label}\n\n"
# Add query
prompt += f"Text: {query}\nLabel:"
# Call LLM
# response = llm.generate(prompt)
# return response.strip()
return prompt
# Example
examples = [
("I love this product!", "positive"),
("Terrible experience.", "negative"),
("Best thing ever!", "positive"),
("Waste of money.", "negative"),
]
query = "Absolutely amazing!"
prompt = few_shot_with_llm(examples, query)
print(prompt)
# 5. COMPARISON
print("\n=== Few-Shot vs Zero-Shot ===")
print("""
FEW-SHOT LEARNING:
- 1-50 examples per class
- Task: classification, generation
- Methods: metric learning, MAML, in-context
- Data needed: minimal but nonzero
- Accuracy: 60-95% depending on task
ZERO-SHOT LEARNING:
- 0 examples per class
- Task: classification with attributes/text
- Methods: CLIP, GPT, semantic embeddings
- Data needed: none for target class
- Accuracy: 50-80% depending on task
ONE-SHOT LEARNING:
- 1 example per class
- Extreme few-shot
- Accuracy: lower than few-shot
IN-CONTEXT LEARNING (LLMs):
- 0-100 examples in prompt
- No gradient updates
- Works with any LLM
- Emerging capability
""")
# 6. MODERN APPROACHES
print("\n=== Modern Few-Shot Methods ===")
print("""
1. PROMPT-BASED (LLMs)
- In-context learning
- Chain-of-thought
- Instruction tuning
- Examples: GPT-4, Claude
2. VISION-LANGUAGE (CLIP)
- Contrastive pretraining
- Zero-shot classification
- Image-text matching
- Examples: CLIP, ALIGN, SigLIP
3. META-LEARNING
- MAML, Reptile
- Learning to learn
- Fast adaptation
- Examples: MAML, ProtoNet
4. ADAPTERS/PEFT
- Small trainable modules
- LoRA, prefix tuning
- Fast fine-tuning
- Examples: LoRA, Prefix Tuning
5. RETRIEVAL-AUGMENTED
- Retrieve similar examples
- Use as context
- RAG, kNN-LM
- Examples: RAG, REALM
""")
# 7. PRACTICAL TIPS
print("\n=== Practical Tips ===")
print("""
1. START WITH CLIP
- Zero-shot baseline
- Often surprisingly good
- No training needed
2. USE PROMPT ENGINEERING
- "A photo of a {class}"
- Ensemble multiple prompts
- Domain-specific templates
3. FEW-SHOT WITH LLMs
- 5-10 examples enough
- Order matters
- Diverse examples
4. META-LEARNING WHEN
- Many similar tasks
- Fast adaptation needed
- Fixed architecture
5. PEFT FOR FINE-TUNING
- LoRA, adapters
- Small data
- Preserves base model
6. DATA AUGMENTATION
- Simple but effective
- Increases effective sample size
- Combine with few-shot
7. EVALUATION
- Same N-way K-shot setup
- Multiple episodes
- Report mean Β± std
""")ποΈ Foundation Models & LLMs
π― The New Paradigm
Foundation Models are large pre-trained models that serve as a base for many downstream tasks. Examples: GPT-4, BERT, CLIP, DINOv2, SAM. They've transformed transfer learning β pre-train once on massive data, adapt to anything.
What Makes Them Special
Scale: Billions of parameters, trillions of tokens. Emergence: Capabilities not in smaller models. General: Work across many tasks. Transferable: Fine-tune for anything. Accessible: Via APIs or local. Foundation: New AI paradigm.
# Foundation models and transfer learning
import torch
from transformers import AutoModel, AutoTokenizer, pipeline
# 1. USING PRE-TRAINED TRANSFORMERS
def demonstrate_bert():
"""BERT for text classification."""
from transformers import AutoTokenizer, AutoModelForSequenceClassification
# Load pre-trained BERT
model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(
model_name, num_labels=2
)
# Fine-tune on your data
def tokenize_function(examples):
return tokenizer(examples["text"], padding="max_length",
truncation=True, max_length=128)
# Training happens on your specific task
return model, tokenizer
# 2. GPT FOR FEW-SHOT
def demonstrate_gpt_few_shot():
"""GPT for few-shot classification."""
from transformers import pipeline
generator = pipeline('text-generation', model='gpt2')
def classify(prompt, examples):
# Build few-shot prompt
prompt_text = ""
for text, label in examples:
prompt_text += f"Text: {text}\nLabel: {label}\n\n"
prompt_text += f"Text: {prompt}\nLabel:"
# Generate
result = generator(prompt_text, max_length=100)
return result[0]['generated_text']
# 3. CLIP FOR ZERO-SHOT VISION
def demonstrate_clip():
"""CLIP for zero-shot image classification."""
try:
import clip
from PIL import Image
model, preprocess = clip.load("ViT-B/32")
image = preprocess(Image.open("image.jpg")).unsqueeze(0)
text = clip.tokenize(["a dog", "a cat", "a bird"])
with torch.no_grad():
image_features = model.encode_image(image)
text_features = model.encode_text(text)
# Zero-shot classification
logits_per_image = image_features @ text_features.T
probs = logits_per_image.softmax(dim=-1)
return probs
except ImportError:
print("CLIP not installed")
# 4. FOUNDATION MODEL ECOSYSTEM
print("=== Foundation Model Ecosystem ===")
print("""
LANGUAGE MODELS:
- GPT-4 (OpenAI): 1.7T params, general text
- Claude (Anthropic): Constitutional AI
- Gemini (Google): Multimodal, 1M+ context
- Llama (Meta): Open-weight, 7B-70B+
- Mistral: Efficient, open-weight
VISION MODELS:
- CLIP (OpenAI): Image-text alignment
- DINOv2 (Meta): Self-supervised vision
- SAM (Meta): Segment anything
- BLIP-2: Vision-language
- Stable Diffusion: Image generation
MULTIMODAL:
- GPT-4V: Text + images
- Gemini: Text, images, audio, video
- LLaVA: Vision-language assistant
- Flamingo: Few-shot vision-language
AUDIO:
- Whisper (OpenAI): Speech recognition
- AudioLM (Google): Audio generation
- MusicLM: Music generation
ROBOTICS:
- RT-2 (Google): Vision-language-action
- Octo: Open-source robot policy
BIOLOGY:
- AlphaFold: Protein structure
- ESM: Protein language models
CODE:
- Codex: Code generation
- StarCoder: Open code model
""")
# 5. TRANSFER LEARNING WITH FOUNDATION MODELS
print("\n=== Transfer Learning Strategies ===")
print("""
1. ZERO-SHOT (no training)
- Use model directly
- Prompt engineering
- Best for: quick prototypes
- Example: CLIP, GPT-4
2. FEW-SHOT (in-context)
- Provide examples in prompt
- No gradient updates
- Best for: LLMs, structured tasks
- Example: GPT-4, Claude
3. LINEAR PROBING (freeze)
- Extract features
- Train linear classifier
- Best for: limited compute
- Example: CLIP + logistic regression
4. FULL FINE-TUNING
- Update all weights
- Small LR
- Best for: maximum performance
- Example: BERT fine-tuning
5. PEFT (parameter-efficient)
- LoRA, adapters
- Small trainable modules
- Best for: resource constraints
- Example: LoRA on Llama
6. INSTRUCTION TUNING
- Fine-tune on instructions
- Specific formats
- Best for: assistants
- Example: Alpaca, Vicuna
""")
# 6. PRACTICAL: HUGGING FACE
def practical_huggingface():
"""Practical transfer learning with Hugging Face."""
from transformers import (
AutoTokenizer, AutoModelForSequenceClassification,
TrainingArguments, Trainer
)
from datasets import load_dataset
# Load dataset
dataset = load_dataset("imdb")
# Load pre-trained model
model_name = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(
model_name, num_labels=2
)
# Tokenize
def tokenize(examples):
return tokenizer(examples["text"], padding="max_length",
truncation=True, max_length=256)
tokenized = dataset.map(tokenize, batched=True)
# Training arguments
training_args = TrainingArguments(
output_dir="./results",
num_train_epochs=3,
per_device_train_batch_size=16,
per_device_eval_batch_size=64,
warmup_steps=500,
weight_decay=0.01,
logging_dir="./logs",
evaluation_strategy="epoch",
learning_rate=2e-5, # Small LR for fine-tuning
)
# Trainer
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized["train"],
eval_dataset=tokenized["test"],
)
# Fine-tune
trainer.train()
return model, tokenizer
# 7. WHEN TO USE WHICH
print("\n=== Foundation Model Selection ===")
print("""
For VISION tasks:
Zero-shot β CLIP, DINOv2
Few-shot β CLIP + linear probe
Full fine-tune β ResNet, ViT
State-of-the-art β DINOv2, EVA
For NLP tasks:
Zero-shot β GPT-4, Claude
Few-shot β GPT-4, Claude, Llama
Full fine-tune β BERT, RoBERTa, DeBERTa
Generation β GPT-4, Llama, Mistral
For MULTIMODAL:
Vision-language β GPT-4V, Gemini, LLaVA
Image generation β Stable Diffusion, DALL-E
For SPECIALIZED:
Segmentation β SAM
Depth β MiDaS, Depth Anything
Code β Codex, StarCoder
Speech β Whisper
Considerations:
- Cost (API vs local)
- Latency (real-time?)
- Privacy (data sensitivity)
- Customization (fine-tune?)
- Scale (data volume)
""")
# 8. TRANSFER LEARNING TRENDS
print("\n=== Trends in Transfer Learning ===")
print("""
2020: Foundation models emerge
- GPT-3, CLIP
- Pretraining + fine-tuning standard
2021: Prompt engineering
- Few-shot with GPT-3
- Zero-shot with CLIP
- No gradient updates
2022: Multimodal + RLHF
- ChatGPT
- DALL-E 2
- Stable Diffusion
2023: Open + efficient
- Llama, Mistral, Falcon
- LoRA, QLoRA
- Small language models
2024+: Convergence
- Unified multimodal models
- Efficient fine-tuning standard
- Agents + tools
- On-device AI
Key insight:
Transfer learning IS the paradigm
Pretraining is universal
Fine-tuning is task-specific
""")π¬ PEFT (LoRA, Adapters, Prefix)
π― Efficient Fine-Tuning
PEFT (Parameter-Efficient Fine-Tuning) methods adapt large models by training only a small number of additional parameters, keeping the base model frozen. This makes fine-tuning accessible and efficient.
Why PEFT?
Small storage: Adapters are MBs, not GBs. Fast training: Hours instead of days. Low memory: Fits on single GPU. Composable: Multiple adapters per model. Preserves base: No forgetting. Democratizes: Accessible to everyone.
# PEFT methods β parameter-efficient fine-tuning
import torch
import torch.nn as nn
import math
# 1. LoRA (Low-Rank Adaptation)
class LoRALayer(nn.Module):
"""
LoRA: Add low-rank matrices to existing layers.
W' = W + BA
where A β R^(rΓd), B β R^(dΓr), r << d
Only A and B are trained; W is frozen.
"""
def __init__(self, original_layer, rank=8, alpha=16):
super().__init__()
self.original = original_layer
self.rank = rank
self.alpha = alpha
# Freeze original layer
for param in self.original.parameters():
param.requires_grad = False
in_features = original_layer.in_features
out_features = original_layer.out_features
# Low-rank matrices
self.lora_A = nn.Parameter(torch.zeros(rank, in_features))
self.lora_B = nn.Parameter(torch.zeros(out_features, rank))
# Initialize
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
nn.init.zeros_(self.lora_B)
# Scaling factor
self.scaling = alpha / rank
def forward(self, x):
# Original output + LoRA contribution
original_output = self.original(x)
lora_output = (x @ self.lora_A.T) @ self.lora_B.T * self.scaling
return original_output + lora_output
# Apply LoRA to a model
def apply_lora(model, rank=8, alpha=16, target_modules=None):
"""Apply LoRA to specific modules."""
if target_modules is None:
target_modules = ['q_proj', 'v_proj', 'k_proj', 'o_proj']
for name, module in model.named_children():
if isinstance(module, nn.Linear) and any(t in name for t in target_modules):
setattr(model, name, LoRALayer(module, rank, alpha))
else:
apply_lora(module, rank, alpha, target_modules)
return model
# 2. ADAPTERS (Houlsby et al.)
class Adapter(nn.Module):
"""
Adapter: Small bottleneck module inserted after existing layers.
Input β Down-project β Nonlinearity β Up-project β + Input
"""
def __init__(self, input_dim, bottleneck_dim=64):
super().__init__()
self.down_project = nn.Linear(input_dim, bottleneck_dim)
self.up_project = nn.Linear(bottleneck_dim, input_dim)
self.activation = nn.ReLU()
# Initialize near-identity
nn.init.zeros_(self.up_project.weight)
nn.init.zeros_(self.up_project.bias)
def forward(self, x):
residual = x
x = self.down_project(x)
x = self.activation(x)
x = self.up_project(x)
return x + residual
# 3. PREFIX TUNING
class PrefixTuning(nn.Module):
"""
Prefix tuning: prepend learnable tokens to keys and values.
Instead of modifying weights, add learnable prefix to attention.
"""
def __init__(self, num_layers, num_heads, head_dim, prefix_length=10):
super().__init__()
self.prefix_length = prefix_length
self.num_layers = num_layers
# Learnable prefix per layer
self.prefix_keys = nn.ParameterList([
nn.Parameter(torch.randn(num_heads, prefix_length, head_dim))
for _ in range(num_layers)
])
self.prefix_values = nn.ParameterList([
nn.Parameter(torch.randn(num_heads, prefix_length, head_dim))
for _ in range(num_layers)
])
def get_prefix(self, layer_idx):
"""Get prefix for a specific layer."""
return self.prefix_keys[layer_idx], self.prefix_values[layer_idx]
# 4. P-TUNING (Prompt Tuning)
class PromptTuning(nn.Module):
"""
Prompt tuning: prepend learnable soft tokens to input embeddings.
Simplest PEFT method.
"""
def __init__(self, num_tokens=20, embedding_dim=768):
super().__init__()
# Learnable soft prompts
self.prompt_embeddings = nn.Parameter(
torch.randn(num_tokens, embedding_dim)
)
def prepend_prompt(self, input_embeddings):
"""
Prepend learnable tokens to input.
input_embeddings: (batch, seq_len, dim)
output: (batch, num_tokens + seq_len, dim)
"""
batch_size = input_embeddings.size(0)
prompts = self.prompt_embeddings.unsqueeze(0).expand(
batch_size, -1, -1
)
return torch.cat([prompts, input_embeddings], dim=1)
# 5. BITFIT
def bitfit(model):
"""
BitFit: Only train bias terms.
Freeze everything except biases. Extremely simple.
"""
for name, param in model.named_parameters():
if 'bias' in name:
param.requires_grad = True
else:
param.requires_grad = False
return model
# 6. IA3 (Infused Adapter by Inhibiting and Amplifying Inner Activations)
class IA3(nn.Module):
"""
IA3: Learn scaling vectors for keys, values, and FFN.
Very few parameters (0.01% of model).
"""
def __init__(self, hidden_dim):
super().__init__()
self.scale = nn.Parameter(torch.ones(hidden_dim))
def forward(self, x):
return x * self.scale
# 7. COMPARISON
print("=== PEFT Methods Comparison ===")
print(f"{'Method':<15} {'Trainable':<15} {'Storage':<15} {'Speed':<15}")
print("-" * 60)
print(f"{'Full FT':<15} {'100%':<15} {'100%':<15} {'Slow':<15}")
print(f"{'LoRA':<15} {'0.1-1%':<15} {'Small':<15} {'Fast':<15}")
print(f"{'Adapters':<15} {'1-5%':<15} {'Small':<15} {'Fast':<15}")
print(f"{'Prefix':<15} {'0.1-1%':<15} {'Small':<15} {'Fast':<15}")
print(f"{'P-Tuning':<15} {'0.01-0.1%':<15} {'Tiny':<15} {'Very Fast':<15}")
print(f"{'BitFit':<15} {'0.01-0.1%':<15} {'Tiny':<15} {'Very Fast':<15}")
print(f"{'IA3':<15} {'0.01%':<15} {'Tiny':<15} {'Very Fast':<15}")
# 8. USING PEFT LIBRARY
print("\n=== Using Hugging Face PEFT ===")
print("""
from peft import LoraConfig, get_peft_model, TaskType
from transformers import AutoModelForCausalLM, AutoTokenizer
# Load base model
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
# Configure LoRA
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=8, # rank
lora_alpha=32, # scaling
lora_dropout=0.1,
target_modules=["q_proj", "v_proj"],
bias="none",
)
# Apply to model
peft_model = get_peft_model(model, lora_config)
# Check trainable parameters
peft_model.print_trainable_parameters()
# trainable params: 4,194,304 || all params: 6,742,609,920 ||
# trainable%: 0.06%
# Train normally with Trainer API
# Only LoRA parameters update!
""")
# 9. QUANTIZED PEFT (QLoRA)
print("\n=== QLoRA (Quantized LoRA) ===")
print("""
QLoRA: 4-bit quantization + LoRA
Idea:
1. Quantize base model to 4-bit
2. Add LoRA adapters
3. Train adapters in 16-bit
Benefits:
- 65B model on single GPU
- 4x memory savings
- Same quality as full fine-tuning
- Can even train on consumer GPUs
Results:
- Llama-65B fine-tuning on 48GB GPU
- Same performance as full fine-tuning
- Enabled open-source LLM ecosystem
Implementation:
from transformers import BitsAndBytesConfig
from peft import LoraConfig, get_peft_model
import torch
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16,
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=bnb_config,
device_map="auto",
)
lora_config = LoraConfig(r=16, lora_alpha=32, ...)
model = get_peft_model(model, lora_config)
""")
# 10. PEFT BEST PRACTICES
print("\n=== PEFT Best Practices ===")
print("""
1. CHOOSE THE RIGHT METHOD
- LoRA: Best default
- QLoRA: Memory-constrained
- Adapters: Multi-task
- Prefix/P-Tuning: LLMs
- BitFit: Fast prototyping
- IA3: Extreme efficiency
2. LORA HYPERPARAMETERS
- rank (r): 8-64, higher = more capacity
- alpha: usually 2Γrank
- dropout: 0.05-0.1
- target_modules: q,v (or all attention)
3. TRAINING TIPS
- Small learning rate (1e-4)
- Fewer epochs needed
- Watch for overfitting
- Use gradient checkpointing
4. MULTI-ADAPTER SERVING
- Load base model once
- Swap LoRA weights per request
- Efficient multi-tenant
5. COMBINING METHODS
- LoRA + Prefix
- Different ranks
- Ensemble adapters
6. DEPLOYMENT
- Merge LoRA into base for inference
- Or keep separate for flexibility
- Quantization for deployment
7. EVALUATION
- Compare to full fine-tuning
- Measure storage, latency
- Track task-specific metrics
""")
# 11. REAL-WORLD USES
print("\n=== Real-World PEFT Applications ===")
print("""
CHATBOTS:
- Custom persona (LoRA)
- Domain-specific (medical, legal)
- Language-specific
- Roleplay characters
CODE ASSISTANTS:
- Company-specific code
- Language-specific (Python, Rust)
- Framework-specific
- Bug-fixing
CUSTOMER SERVICE:
- Brand voice
- Product knowledge
- Policy compliance
- Multiple languages
VISION:
- Style transfer (LoRA)
- Character consistency
- Product photography
- Medical imaging
ENTERPRISE:
- Multiple departments (multi-adapter)
- On-premise deployment
- Data privacy (no cloud)
- Cost efficiency
""")β οΈ Negative Transfer
π― When Transfer Hurts
Negative transfer occurs when transferring from a source task actually hurts performance on the target task. It's a critical failure mode to watch for.
Negative Transfer Causes
Domain mismatch: Source very different from target. Task mismatch: Different objectives. Overfitting: Pre-trained features too specific. Distribution shift: Data distribution differs. Capacity issues: Model too large/small. Wrong layers: Transferring wrong features.
# Negative transfer β when transfer learning hurts
import numpy as np
import torch
# 1. UNDERSTANDING NEGATIVE TRANSFER
print("=== Negative Transfer ===")
print("""
Definition:
Transfer learning from source S to target T
Results in WORSE performance than training from scratch
on T alone.
Formula:
Transfer: Accuracy_T(S β T)
From scratch: Accuracy_T(random)
If Transfer < From scratch β Negative transfer
Causes:
1. Source and target domains are very different
2. Source task objectives conflict with target
3. Pre-trained features are too specialized
4. Fine-tuning with wrong hyperparameters
5. Small target data + large model
""")
# 2. DEMONSTRATION
def demonstrate_negative_transfer():
"""Show how negative transfer happens."""
print("\n=== Example Scenarios ===")
print("""
Scenario 1: Natural images β Medical X-rays
Source: ImageNet (cats, dogs, cars)
Target: Chest X-rays
Problem: Very different visual domains
Solution:
- Freeze fewer layers
- Use domain-specific pretraining
- Data augmentation
Scenario 2: Face recognition β Fingerprints
Source: Face images
Target: Fingerprint images
Problem: No visual similarity
Solution:
- Don't use face model
- Train from scratch
- Or use generic feature extractor
Scenario 3: English BERT β Chinese NLP
Source: English text
Target: Chinese text
Problem: Different languages
Solution:
- Use multilingual BERT
- Or Chinese-specific BERT
- Language-specific pretraining
Scenario 4: Sentiment β Hate Speech
Source: Movie reviews (positive/negative)
Target: Hate speech detection
Problem: Different concepts
Solution:
- Careful fine-tuning
- Different task heads
- Domain adaptation
""")
demonstrate_negative_transfer()
# 3. DETECTING NEGATIVE TRANSFER
print("\n=== Detecting Negative Transfer ===")
print("""
Warning signs:
1. Training from scratch outperforms transfer
β Classic negative transfer
2. Validation loss higher than from-scratch
β Transfer hurts
3. Predictions systematically biased
β Wrong features
4. Slow convergence
β Features not useful
5. Poor generalization
β Overfit to source
6. Feature visualization shows mismatch
β t-SNE shows separated clusters
Detection methods:
1. Compare to from-scratch baseline
2. Cross-validation on target
3. Ablation: no transfer vs transfer
4. Learning curves comparison
5. Gradient analysis
""")
# 4. AVOIDING NEGATIVE TRANSFER
print("\n=== Avoiding Negative Transfer ===")
print("""
STRATEGIES:
1. CHOOSE SIMILAR SOURCE
- Same domain: use it
- Similar domain: use it
- Different: be careful
- Very different: avoid
2. USE SIMILAR LAYERS
- Early layers: generic
- Late layers: specific
- Match layer to task
3. ADAPT WITH CARE
- Small learning rate
- Discriminative LRs
- Gradual unfreezing
4. VALIDATE TRANSFER
- Always compare to from-scratch
- Use held-out test
- Statistical significance
5. DATA AUGMENTATION
- Bridge domain gap
- Random augmentation
- Domain randomization
6. DOMAIN ADAPTATION
- Explicitly align domains
- Adversarial training
- MMD, CORAL
7. MULTI-SOURCE TRANSFER
- Use multiple sources
- Weight by relevance
- Ensemble predictions
""")
# 5. WHEN TO AVOID TRANSFER
print("\n=== When to Avoid Transfer ===")
print("""
DON'T USE TRANSFER WHEN:
1. Domains are VERY different
- ImageNet β Audio
- Text β Images
- Tabular β Vision
2. Target task is unique
- No similar pre-training exists
- Domain-specific reasoning
- Novel modalities
3. You have LOTS of target data
- Millions of examples
- Can train from scratch
- Transfer less beneficial
4. Compute is not a constraint
- Training from scratch is feasible
- Faster iteration possible
5. Interpretability needed
- Pre-trained features opaque
- From-scratch easier to explain
6. Regulatory constraints
- Can't use external data
- Need full provenance
- Data sovereignty
ALTERNATIVES:
- Train from scratch
- Self-supervised pretraining
- Multi-task learning
- Meta-learning
- Few-shot learning
""")
# 6. CASE STUDIES
print("\n=== Case Studies ===")
print("""
CASE 1: Medical Imaging
Source: ImageNet
Target: Chest X-ray
Result: Slight negative transfer if not careful
Fix: Use RadImageNet or domain-specific pretraining
CASE 2: Satellite Imagery
Source: ImageNet
Target: Land cover classification
Result: Moderate negative transfer
Fix: Use few layers, or pretrain on satellite data
CASE 3: Financial Time Series
Source: ImageNet (vision)
Target: Stock prediction
Result: Complete failure
Fix: Use time-series-specific methods
CASE 4: Handwriting Recognition
Source: Printed text
Target: Handwriting
Result: Often works well
Fix: Fine-tune carefully
CASE 5: Speech Recognition
Source: English speech
Target: Accented English
Result: Small negative transfer
Fix: Data augmentation, domain adaptation
CASE 6: Code Generation
Source: General code
Target: Specific language
Result: Usually positive
Fix: Language-specific fine-tuning
CASE 7: Recommendation
Source: Different platform
Target: New platform
Result: Variable
Fix: Cross-domain recommendation
""")
# 7. PRACTICAL CHECKLIST
print("\n=== Practical Checklist ===")
print("""
Before using transfer learning, ask:
SOURCE QUALITY:
β‘ Is source similar to target?
β‘ Is source model high-quality?
β‘ Are source features useful?
β‘ Is source pretraining recent?
β‘ Is source large enough?
TARGET PROPERTIES:
β‘ How much target data?
β‘ How similar to source task?
β‘ What's the target domain?
β‘ What's the target objective?
β‘ Any constraints?
TRANSFER STRATEGY:
β‘ Freeze or fine-tune?
β‘ Which layers to transfer?
β‘ What learning rate?
β‘ How many epochs?
β‘ What regularization?
VALIDATION:
β‘ Compare to from-scratch
β‘ Hold-out test set
β‘ Statistical significance
β‘ Error analysis
β‘ Production metrics
MONITORING:
β‘ Track performance over time
β‘ Watch for drift
β‘ Retrain when needed
β‘ Collect feedback
β‘ Iterate
""")
# 8. FIXING NEGATIVE TRANSFER
print("\n=== Fixing Negative Transfer ===")
print("""
If you detect negative transfer:
IMMEDIATE FIXES:
1. Unfreeze more layers
2. Use higher learning rate
3. Increase training data
4. Longer training
5. Data augmentation
MEDIUM-TERM FIXES:
1. Different pre-training
2. Domain adaptation
3. Multi-source transfer
4. Ensemble methods
5. Two-stage training
LONG-TERM FIXES:
1. Better source model
2. Task-specific pretraining
3. Self-supervised pretraining
4. Collect more target data
5. Change approach entirely
WHEN TO GIVE UP:
- Transfer hurts significantly
- No improvement with fixes
- From-scratch works better
- Not worth the complexity
It's OK to train from scratch!
""")
# 9. RESEARCH INSIGHTS
print("\n=== Research Insights ===")
print("""
From recent papers:
1. TRANSFERABILITY MEASURES
- Measure feature similarity
- Predict transfer success
- Choose best source
2. TASK SIMILARITY
- Not just domain similarity
- Task objective matters
- Multi-dimensional
3. LAYER-WISE ANALYSIS
- Not all layers transfer equally
- Match layer to task
- Selective transfer
4. SPARSE TRANSFER
- Only useful weights
- Prune useless features
- Better transfer
5. THEORETICAL BOUNDS
- Generalization bounds
- Domain divergence
- Sample complexity
6. NEGATIVE TRANSFER PREDICTION
- Predict before training
- Choose source wisely
- Avoid wasted compute
""")
# 10. FINAL THOUGHTS
print("\n=== Final Thoughts ===")
print("""
Negative transfer is real but manageable.
Key insights:
1. Not all transfer is beneficial
2. Always compare to baseline
3. Source-target similarity matters
4. Layer choice is critical
5. Fine-tuning strategy matters
Practical advice:
- Start with feature extraction
- Compare to from-scratch
- Use validation extensively
- Have fallback plans
- Monitor in production
The reality:
- Transfer usually helps
- Negative transfer is exception
- But happens more than acknowledged
- Careful evaluation prevents issues
Bottom line:
Transfer learning is powerful but not universal.
Choose sources wisely, validate carefully,
and always have a baseline to compare against.
""")πΌ Real-World Applications
π― Transfer Learning in Production
Transfer learning powers almost every modern AI system β from your phone's camera to ChatGPT. Understanding real applications helps you see the breadth of possibilities.
Application Domains
Vision: Classification, detection, segmentation. NLP: Chatbots, translation, summarization. Speech: Recognition, synthesis. Healthcare: Diagnosis, drug discovery. Finance: Fraud, trading. Robotics: Manipulation, navigation.
# Real-world applications of transfer learning
import numpy as np
# 1. COMPUTER VISION
print("=== Computer Vision ===")
print("""
Image Classification:
- ImageNet pretrained β Custom classes
- ResNet, EfficientNet, ViT
- Used in: e-commerce, social media
Object Detection:
- COCO pretrained β Custom objects
- Faster R-CNN, YOLO, DETR
- Used in: autonomous driving, surveillance
Semantic Segmentation:
- Cityscapes pretrained β Medical, satellite
- DeepLab, SegFormer, SAM
- Used in: self-driving, medical imaging
Face Recognition:
- FaceNet, ArcFace pretrained
- Fine-tune on specific individuals
- Used in: security, authentication
Medical Imaging:
- ImageNet pretrained β X-rays, CT
- Careful: domain mismatch
- Used in: radiology, pathology
Real examples:
- Google Photos: Search by content
- Tesla: Vision-based autopilot
- Medical AI: Cancer detection
- Retail: Product recognition
""")
# 2. NATURAL LANGUAGE PROCESSING
print("\n=== NLP ===")
print("""
Text Classification:
- BERT, RoBERTa pretrained β Sentiment, topic
- Used in: reviews, feedback, moderation
Named Entity Recognition:
- BERT pretrained β Custom entities
- Used in: information extraction
Question Answering:
- SQuAD pretrained β Custom QA
- Used in: chatbots, search
Machine Translation:
- Multilingual pretrained β Specific languages
- Used in: Google Translate, DeepL
Text Generation:
- GPT, Llama pretrained β Custom style
- Used in: content creation, coding
Sentiment Analysis:
- IMDB pretrained β Product reviews
- Used in: brand monitoring
Chatbots:
- GPT-4, Claude via API
- Fine-tune for specific domain
- Used in: customer service, therapy
Real examples:
- ChatGPT: GPT-4 with RLHF
- GitHub Copilot: Codex-based
- Grammarly: Grammar checking
- Google Search: BERT-based ranking
""")
# 3. SPEECH AND AUDIO
print("\n=== Speech and Audio ===")
print("""
Speech Recognition:
- Whisper (OpenAI) pretrained
- Wav2Vec2, HuBERT
- Fine-tune for accents, domains
- Used in: transcription, voice assistants
Speech Synthesis:
- VALL-E, Bark, Tortoise
- Voice cloning
- Used in: TTS, accessibility
Music Generation:
- MusicLM, AudioLDM
- Style transfer
- Used in: content creation
Audio Classification:
- AudioSet pretrained
- Sound event detection
- Used in: smart home, monitoring
Real examples:
- Siri, Alexa, Google Assistant
- YouTube auto-captions
- Otter.ai transcription
- Spotify recommendations
""")
# 4. HEALTHCARE
print("\n=== Healthcare ===")
print("""
Medical Imaging:
- Chest X-rays: CheXNet
- CT scans: Detection
- Pathology: Cancer detection
- Mammography: Breast cancer
- Dermatology: Skin lesions
Drug Discovery:
- Protein structure: AlphaFold
- Molecule generation
- Drug-target interaction
- ADMET prediction
Genomics:
- DNA sequence analysis
- Variant calling
- Gene expression
- Protein function
Clinical NLP:
- Medical records
- Symptom extraction
- Diagnosis codes
- Clinical notes
Real examples:
- Google's diabetic retinopathy
- DeepMind's protein folding
- IBM Watson Health
- PathAI for pathology
Challenges:
- Small datasets
- Regulatory approval
- Privacy (HIPAA)
- Domain shift
""")
# 5. FINANCE
print("\n=== Finance ===")
print("""
Fraud Detection:
- Transaction analysis
- Transfer from general fraud
- Real-time scoring
Credit Scoring:
- Transfer between populations
- Adapt to new markets
Algorithmic Trading:
- Market prediction
- Portfolio optimization
- Transfer between assets
Risk Management:
- Credit risk
- Market risk
- Operational risk
Anti-Money Laundering:
- Pattern detection
- Anomaly detection
- Cross-institution
Real examples:
- Stripe, Square fraud detection
- Banks' credit models
- Hedge fund trading
- Insurance underwriting
Challenges:
- Non-stationary
- Low signal-to-noise
- Regulatory compliance
- Interpretability
""")
# 6. ROBOTICS
print("\n=== Robotics ===")
print("""
Manipulation:
- Grasping: from demos
- Assembly: from simulation
- Tool use: from video
Navigation:
- SLAM pretrained
- Path planning
- Obstacle avoidance
Locomotion:
- Walking policies
- Sim-to-real transfer
- Adaptive control
Vision-Language-Action:
- RT-1, RT-2
- Language-conditioned policies
- Generalization
Real examples:
- Boston Dynamics + RL
- Google's RT series
- Amazon warehouse robots
- Autonomous vehicles
Challenges:
- Sim-to-real gap
- Safety
- Real-time constraints
- Physical variability
""")
# 7. OTHER DOMAINS
print("\n=== Other Domains ===")
print("""
Education:
- Personalized learning
- Automated grading
- Content recommendation
E-commerce:
- Product recommendation
- Visual search
- Review analysis
Manufacturing:
- Quality control
- Predictive maintenance
- Process optimization
Agriculture:
- Crop disease detection
- Yield prediction
- Precision farming
Climate:
- Weather prediction
- Climate modeling
- Carbon tracking
Security:
- Face recognition
- Anomaly detection
- Threat assessment
""")
# 8. PRODUCTION CONSIDERATIONS
print("\n=== Production Considerations ===")
print("""
When deploying transfer learning in production:
1. DATA PRIVACY
- Don't send user data to APIs
- Use local models
- Federated learning
- Anonymization
2. LATENCY
- Model size matters
- Quantization
- Pruning
- Distillation
- Edge deployment
3. COST
- API costs vs local
- Training costs
- Inference costs
- Update frequency
4. MAINTENANCE
- Monitor performance
- Detect drift
- Retrain schedule
- Update base models
5. COMPLIANCE
- Model provenance
- Data lineage
- Explainability
- Regulatory requirements
6. VERSIONING
- Base model version
- Fine-tuning version
- Data version
- Full reproducibility
""")
# 9. CASE STUDIES
print("\n=== Success Stories ===")
print("""
1. TESLA AUTOPILOT
- Vision-based (not LiDAR)
- Pretrained on driving data
- Real-time inference
- Continuous improvement
2. GOOGLE TRANSLATE
- Multilingual pretraining
- Transfer between languages
- Zero-shot translation
- 100+ languages
3. GITHUB COPILOT
- Codex (GPT-based)
- Fine-tuned on code
- Multiple languages
- Context-aware
4. CHATGPT
- GPT-3.5/4 base
- RLHF alignment
- Instruction following
- 100M+ users
5. ALPHAFOLD
- Protein structure prediction
- Transformer-based
- Scientific breakthrough
- Open access
6. STABLE DIFFUSION
- Text-to-image
- Open source
- Fine-tuning ecosystem
- LoRA, ControlNet
7. WHISPER
- Speech recognition
- 99 languages
- Robust to noise
- Open source
8. DINOv2
- Self-supervised vision
- Universal features
- No labels needed
- SOTA performance
""")π Future: Unified Foundation Models
π― Where Transfer Learning is Heading
The future of transfer learning is unified foundation models β single models that handle any task, any modality, any domain. We're moving toward true general-purpose AI.
Future Directions
Multimodal: Text, image, audio, video. Universal: One model for everything. Efficient: Small, fast, on-device. Agentic: Models that act. Aligned: Safe, helpful, honest. Open: Democratized access.
# Future of transfer learning
import numpy as np
# 1. UNIFIED MULTIMODAL MODELS
print("=== Unified Multimodal Models ===")
print("""
Current: Separate models for each modality
Future: One model for everything
Examples:
GPT-4V: Text + images
Gemini: Text + images + audio + video
Claude 3: Text + images
LLaVA: Vision-language
Emerging:
Any-to-any: Text β Image β Audio β Video
Universal encoders
Shared representations
Impact on transfer learning:
- One pre-trained model for all
- Cross-modal transfer
- Better generalization
- Simpler deployment
Research directions:
- Unified tokenization
- Cross-modal attention
- Emergent capabilities
- Few-shot across modalities
""")
# 2. FOUNDATION MODELS FOR EVERYTHING
print("\n=== Foundation Models for Everything ===")
print("""
Text: GPT-4, Claude, Llama
Images: DINOv2, CLIP, SAM
Video: Sora, Gen-2, Pika
Audio: Whisper, MusicLM
Code: Codex, StarCoder
3D: Zero-1-to-3, TripoSR
Robotics: RT-2, Octo
Science: AlphaFold, ESM
Future: Unified foundation model
Characteristics:
- Trained on all modalities
- Handles any task
- Transfers across domains
- Emergent capabilities
- Few-shot learning
Challenges:
- Scale (10T+ parameters?)
- Compute (massive)
- Data (all modalities)
- Alignment (safety)
- Deployment (efficiency)
""")
# 3. EFFICIENT TRANSFER
print("\n=== Efficient Transfer ===")
print("""
Trends:
1. SMALL LANGUAGE MODELS
- Phi-3, Gemma, TinyLlama
- 1-7B parameters
- On-device deployment
- Fine-tuning accessible
2. EFFICIENT PEFT
- LoRA, QLoRA, DoRA
- 0.01% parameters trained
- Multi-adapter serving
- Merge for inference
3. QUANTIZATION
- 4-bit, 8-bit
- GGUF, GPTQ, AWQ
- Deploy on consumer hardware
- Minimal accuracy loss
4. DISTILLATION
- Teacher β Student
- Smaller models
- Faster inference
- Preserved capabilities
5. PRUNING
- Remove weights
- Sparse models
- Hardware acceleration
- Task-specific pruning
Result:
- Powerful models on phones
- Private AI (no cloud)
- Low latency
- Cost efficient
""")
# 4. AGENTIC AI
print("\n=== Agentic AI ===")
print("""
Next paradigm: AI agents that act
Capabilities:
- Reason about tasks
- Use tools
- Take actions
- Learn from feedback
- Collaborate with humans
Transfer learning aspects:
- Pre-trained base
- Tool use via prompting
- RLHF for alignment
- Continual learning
Examples:
- AutoGPT
- BabyAGI
- Devin (code)
- Adept (computer use)
Components:
- LLM (base)
- Memory
- Tools (APIs)
- Planning
- Reflection
Transfer learning role:
- Base LLM pretraining
- Tool-specific fine-tuning
- Domain adaptation
- Multi-agent coordination
""")
# 5. CONTINUAL LEARNING
print("\n=== Continual Learning ===")
print("""
Challenge: Learn new tasks without forgetting old ones.
Current transfer learning:
- Train once, deploy
- New task β new model
- Forgetting risk
Future: Continual learning
- Learn continuously
- No forgetting
- Task-incremental
- Domain-incremental
Methods:
- EWC (Elastic Weight Consolidation)
- Replay buffers
- Progressive networks
- LoRA stacking
Applications:
- Personal AI assistants
- Adaptive robots
- Evolving systems
- Lifelong learning
Impact on transfer:
- Continuous transfer
- No retraining
- Adaptive to user
- Personalization
""")
# 6. FEDERATED LEARNING
print("\n=== Federated Learning ===")
print("""
Idea: Train on user devices without sharing data.
Process:
1. Base model on server
2. Deploy to devices
3. Local training on data
4. Send gradients (not data)
5. Aggregate globally
6. Repeat
Benefits:
- Privacy preserving
- Distributed compute
- Personalized models
- Regulatory friendly
Applications:
- Keyboard prediction (Google)
- Health monitoring
- Voice assistants
- Medical AI
Transfer learning role:
- Pre-train globally
- Fine-tune locally
- Share knowledge
- Personalize
Challenges:
- Communication cost
- Heterogeneous data
- Non-IID distributions
- Security
""")
# 7. SELF-SUPERVISED LEARNING
print("\n=== Self-Supervised Learning ===")
print("""
Learn without labels β the future of pre-training.
Methods:
Text:
- BERT (masked LM)
- GPT (next token)
- T5 (span corruption)
Vision:
- SimCLR, MoCo (contrastive)
- DINO, DINOv2 (self-distillation)
- MAE (masked autoencoders)
Speech:
- Wav2Vec2, HuBERT
- Contrastive predictive coding
Multimodal:
- CLIP (image-text)
- Data2Vec (unified)
- ImageBind (multi-modal)
Benefits:
- No labels needed
- Learns general features
- Scales with data
- Foundation for transfer
Future:
- Unified SSL
- All modalities
- General-purpose
- Emergent capabilities
""")
# 8. DEMOCRATIZATION
print("\n=== Democratization of AI ===")
print("""
Transfer learning is democratizing AI:
Before (2015):
- Need 1M+ images
- Need GPU cluster
- Need ML expertise
- Only big tech
After (2024):
- 100 images enough
- Single GPU or free Colab
- Basic ML knowledge
- Anyone can build
Tools:
- Hugging Face (models, datasets)
- Kaggle (compute, data)
- Google Colab (free GPU)
- Replicate (deploy models)
- Ollama (local LLMs)
Consequences:
- Millions of fine-tuned models
- Industry-specific AI
- Personal AI assistants
- Rapid innovation
Future:
- Even more accessible
- No ML expertise needed
- Natural language interfaces
- AI for everyone
""")
# 9. OPEN PROBLEMS
print("\n=== Open Problems ===")
print("""
1. NEGATIVE TRANSFER
- When to avoid transfer
- Predict before training
- Better theory needed
2. EFFICIENT FINE-TUNING
- Even fewer parameters
- Better PEFT methods
- Automatic method selection
3. MULTIMODAL TRANSFER
- Cross-modal transfer
- Unified representations
- Zero-shot across modalities
4. CONTINUAL LEARNING
- No catastrophic forgetting
- Lifelong learning
- Real-world adaptation
5. ALIGNMENT
- Safe transfer
- Value alignment
- Prevent misuse
6. EVALUATION
- Fair comparison
- Generalization tests
- Real-world impact
7. THEORY
- Why transfer works
- When it fails
- Provable guarantees
""")
# 10. RECOMMENDATIONS
print("\n=== Recommendations ===")
print("""
For practitioners:
1. Master PEFT (LoRA)
2. Learn foundation models
3. Understand negative transfer
4. Build efficient pipelines
5. Deploy locally when possible
For researchers:
1. Study transfer theory
2. Develop better PEFT
3. Work on continual learning
4. Multi-modal transfer
5. Alignment research
For organizations:
1. Adopt foundation models
2. Invest in fine-tuning
3. Build data pipelines
4. Consider on-premise
5. Train teams on PEFT
For society:
1. Democratize access
2. Ensure safety
3. Address bias
4. Support research
5. Educate public
""")
# 11. FINAL THOUGHTS
print("\n=== Final Thoughts ===")
print("""
Transfer learning has transformed AI:
Journey:
1993: First transfer learning paper
2012: AlexNet (deep learning era)
2013: DQN (transfer in RL)
2017: BERT (transfer in NLP)
2018: Transfer Learning survey
2020: Foundation models era
2021: CLIP (zero-shot)
2022: ChatGPT (RLHF)
2023: Llama (open models)
2024: Unified multimodal
2025+: Foundation agents
Key insights:
1. Pre-training is universal
2. Transfer learning is the paradigm
3. Foundation models changed everything
4. Efficiency enables democratization
5. Alignment matters
The future:
- One model for everything
- Continuous learning
- Personalized AI
- Aligned and safe
- Accessible to all
"Transfer learning is the key to making
AI systems practical, efficient, and
accessible to everyone."
β Sebastian Thrun
You now understand:
- Fundamentals of transfer
- How to implement it
- When it works (and when it doesn't)
- Modern PEFT methods
- Foundation models
- Future directions
Go build amazing things!
""")