🧠 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.

πŸ“Œ Pre-trained ModelsπŸ“Œ Few-Shot LearningπŸ“Œ Knowledge ReuseπŸ“Œ Foundation AI
# 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.

πŸ” Edges🧡 TexturesπŸ”Ί Parts🎯 Objects
# 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.

πŸ“Š InductiveπŸ”„ Transductive🎯 Featureβš™οΈ Parameter
# 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.

🧊 FrozenπŸ“Š Feature Vectors🎯 New Classifier⚑ Fast
# 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.

πŸ”§ Full❄️ PartialπŸ“‰ Layer-wise🎯 Discriminative
# 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.

🧊 FreezeπŸ”₯ Unfreeze❄️ PartialπŸ“ˆ Progressive
# 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.

πŸ“‰ Small LRπŸ“ˆ Large LR🎯 Layer-wise⚑ Better
# 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.

πŸ“Š Covariate🏷️ Label🎯 ConceptπŸ“ˆ Prior
# 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.

πŸ“Š N-way K-shot🧠 Meta-Learning🎯 Metric🌟 Zero-Shot
# 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.

πŸ€– GPT-4πŸ“ BERTπŸ–ΌοΈ CLIP🎨 DALL-E
# 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.

🎨 LoRAπŸ“Ž Adapters🎯 Prefix⚑ Fast
# 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.

⚠️ Domain Gap🎯 Task GapπŸ“‰ OverfittingπŸ”€ Shift
# 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.

πŸ‘οΈ VisionπŸ’¬ NLP🎀 SpeechπŸ₯ Healthcare
# 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.

🌐 MultimodalπŸ€– Universal⚑ Efficient🎯 Aligned
# 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! """)