let navbar = [];
let loadingMessage = dv.el("span", "**Loading navigation...**", {attr: {style: "font-size:13px; color:gray"}});
let allPages = dv.pages("#dashboard").sort(page => page.file.folder, "asc");
let filteredPages = allPages.filter(p =>
p.file.tags.values.includes("#dashboard") && p?.include_in_navbar == true
);
for(let page of filteredPages){
let navItem = '';
let navName = 'Untitled';
let navLink = '';
if(page.navbar_name === undefined){
navName = page.file.name;
} else {
navName = page.navbar_name;
}
navLink = page.file.path;
// Format the nav item link
if(dv.current().file.path === page.file.path){
navItem = `**[[${navLink}|${navName}]]**`
} else {
navItem = `[[${navLink}|${navName}]]`
}
navbar.push(navItem)
}
dv.paragraph(navbar.join(' | '))
if(filteredPages.values.length > 0){
loadingMessage.remove();
}
🎯 Fine-tuning
🌟 Start With a Real-World Scenario
Imagine you're building a medical AI assistant:
Using ChatGPT (General Model):
You: "What's the treatment for hypertension?"
ChatGPT: "Hypertension can be treated with lifestyle changes and medications..."
✅ Correct, but generic
You: "What's the ICD-10 code for essential hypertension?"
ChatGPT: "I believe it's I10, but please verify..."
❌ Uncertain, not confident
You: "Draft a prescription for lisinopril 10mg"
ChatGPT: "I cannot provide medical prescriptions..."
❌ Refuses (too cautious)
Problem: ChatGPT is a generalist. It knows a bit about everything but isn't an expert in YOUR domain!
Using Fine-Tuned Medical Model:
You: "What's the treatment for hypertension?"
MedicalGPT: "For stage 1 hypertension (130-139/80-89 mmHg), initiate with:
1. Lifestyle modifications (DASH diet, exercise)
2. If no improvement in 3 months, start ACE inhibitor or ARB
3. Monitor BP weekly, adjust dosage as needed
Reference: 2023 AHA/ACC Guidelines"
✅ Specific, confident, with references!
You: "What's the ICD-10 code for essential hypertension?"
MedicalGPT: "I10 - Essential (primary) hypertension"
✅ Instant, accurate!
You: "Draft a prescription for lisinopril 10mg"
MedicalGPT: "Lisinopril 10mg PO daily
Indication: Essential hypertension
Monitoring: Check BP in 2 weeks, renal function in 1 month
Contraindications: Pregnancy, bilateral renal artery stenosis"
✅ Professional, detailed!
The Difference? Fine-Tuning!
You took GPT-3.5 and trained it on:
- 50,000 medical Q&A pairs
- 10,000 clinical notes
- 5,000 prescription examples
- Medical textbooks and guidelines
Result: A specialized medical AI that:
- Knows medical terminology perfectly
- Follows clinical guidelines
- Provides confident, accurate answers
- Understands your specific workflow
Real-World Examples:
GitHub Copilot:
- Base model: GPT (general code)
- Fine-tuned on: Billions of lines of GitHub code
- Result: Writes code in your style, knows your frameworks
Jasper AI (Content Writing):
- Base model: GPT-3
- Fine-tuned on: Marketing copy, blog posts, ads
- Result: Writes in specific brand voices, follows marketing best practices
Bloomberg GPT (Finance):
- Base model: GPT
- Fine-tuned on: Financial news, reports, market data
- Result: Understands financial jargon, analyzes markets
Your Company's Customer Support Bot:
- Base model: GPT-3.5
- Fine-tuned on: Your product docs, support tickets, FAQs
- Result: Answers questions about YOUR product specifically
The Economics:
Option 1: Use ChatGPT API (No fine-tuning)
- Cost: $0.002 per 1K tokens
- Quality: 70% accuracy on your domain
- Speed: Fast
- Problem: Generic answers, frequent mistakes
Option 2: Fine-tune GPT-3.5
- Training cost: $200 (one-time)
- Inference cost: $0.012 per 1K tokens (6x more expensive)
- Quality: 95% accuracy on your domain
- Speed: Same
- Benefit: Specialized, accurate, confident
When to fine-tune:
- ✅ You have 500+ high-quality examples
- ✅ Your domain is specialized (medical, legal, finance)
- ✅ You need consistent output format
- ✅ Generic models make too many mistakes
- ✅ You're making millions of API calls (cost savings)
When NOT to fine-tune:
- ❌ You have <100 examples
- ❌ Your task is general (summarization, translation)
- ❌ Prompt engineering works well enough
- ❌ You're just prototyping
By the end of this guide, you'll understand:
- How to fine-tune GPT, BERT, and other models
- When fine-tuning is worth the cost vs. prompt engineering
- How to prepare training data (quality > quantity!)
- LoRA and other efficient fine-tuning techniques
- How to avoid catastrophic forgetting
- How to evaluate fine-tuned models
- How to deploy fine-tuned models in production
The power: Fine-tuning turns a jack-of-all-trades into a master of YOUR trade! 🎯
Let's dive in! 🚀
🗺️ Concept Mind Map
FINE-TUNING
|
+-------------------+-------------------+
| | |
APPROACHES TECHNIQUES USE CASES
| | |
| | |
Full Fine-Tuning LoRA Domain Adaptation
- Update all - Low-Rank - Medical AI
parameters Adaptation - Legal AI
- High cost - 0.1% params - Finance AI
- Best quality - 90% cost saving - Code generation
- Needs GPU - Same quality
| | |
| | |
Partial Fine-Tuning QLoRA Task Specialization
- Freeze early - Quantized LoRA - Sentiment analysis
layers - 4-bit precision - Named entity
- Update last - Even cheaper recognition
layers - Runs on CPU - Text classification
- Medium cost - Great for edge - Question answering
| | |
| | |
Adapter Layers Prompt Tuning Style Transfer
- Add small - Optimize soft - Brand voice
modules prompts - Writing style
- Keep base - No model - Tone adjustment
frozen changes - Format consistency
- Very efficient - Ultra cheap
Fine-Tuning vs Alternatives:
APPROACH COST QUALITY FLEXIBILITY WHEN TO USE
Prompting $ ⭐⭐ ⭐⭐⭐⭐⭐ <100 examples
Few-Shot $ ⭐⭐⭐ ⭐⭐⭐⭐ 100-500 examples
Fine-Tuning $$$ ⭐⭐⭐⭐⭐ ⭐⭐ 1000+ examples
LoRA $$ ⭐⭐⭐⭐⭐ ⭐⭐⭐ 1000+ examples
Training Pipeline:
STEP 1: Prepare Data
Training Examples (1000+)
↓
Format: {"prompt": "...", "completion": "..."}
↓
Split: Train (80%) / Val (10%) / Test (10%)
↓
STEP 2: Choose Method
Full Fine-Tune vs LoRA vs QLoRA
↓
STEP 3: Train
Epochs: 3-5
Learning Rate: 2e-5 (low!)
Batch Size: 8-16
↓
STEP 4: Evaluate
Accuracy, F1, BLEU, ROUGE
↓
STEP 5: Deploy
Save model → API → Production
Key Considerations:
- Data Quality > Quantity: 500 good examples beat 5000 bad ones
- Learning Rate: Too high = catastrophic forgetting
- Overfitting: Use early stopping and validation set
- Cost: LoRA is 90% cheaper than full fine-tuning
🧠 Core Concept
What is Fine-tuning?
Fine-tuning continues training a pre-trained model on your specific dataset:
# Pre-training (done by OpenAI, Anthropic, etc.)
model = train_on_massive_dataset(trillions_of_tokens)
# Fine-tuning (what you do)
specialized_model = fine_tune(model, your_specific_data)
When to Fine-tune vs Prompt
graph TD
A[Need AI for Task] --> B{Have Examples?}
B -->|Few| C[Use Prompting]
B -->|Many 1000+| D{Need Consistency?}
D -->|Yes| E[Fine-tune]
D -->|No| C
E --> F{Have Budget?}
F -->|Yes| G[Full Fine-tune]
F -->|Limited| H[Use LoRA]
style E fill:#4CAF50
style G fill:#2196F3
style H fill:#FF9800
Use Prompting When:
- You have < 100 examples
- Task changes frequently
- Need flexibility
- Limited budget
Use Fine-tuning When:
- You have 1000+ examples
- Need consistent behavior
- Domain-specific terminology
- Want to reduce prompt length
- Need faster inference
💻 Code Examples
Example 1: Fine-tune BERT for Classification
from transformers import BertForSequenceClassification, BertTokenizer, Trainer, TrainingArguments
from datasets import load_dataset
import torch
# Load pre-trained BERT
model_name = "bert-base-uncased"
model = BertForSequenceClassification.from_pretrained(model_name, num_labels=2)
tokenizer = BertTokenizer.from_pretrained(model_name)
# Prepare your dataset
def prepare_data(examples):
"""Tokenize and prepare data for training."""
return tokenizer(
examples['text'],
padding='max_length',
truncation=True,
max_length=128
)
# Load and prepare dataset
dataset = load_dataset('imdb') # Example: sentiment analysis
tokenized_dataset = dataset.map(prepare_data, batched=True)
# Set up 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',
logging_steps=100,
evaluation_strategy="epoch",
save_strategy="epoch",
load_best_model_at_end=True,
)
# Create Trainer
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset['train'],
eval_dataset=tokenized_dataset['test'],
)
# Fine-tune the model
print("Starting fine-tuning...")
trainer.train()
# Evaluate
results = trainer.evaluate()
print(f"Evaluation results: {results}")
# Save the fine-tuned model
model.save_pretrained('./fine_tuned_bert')
tokenizer.save_pretrained('./fine_tuned_bert')
# Use the fine-tuned model
def predict(text):
inputs = tokenizer(text, return_tensors='pt', padding=True, truncation=True)
outputs = model(**inputs)
prediction = torch.argmax(outputs.logits, dim=1)
return "Positive" if prediction == 1 else "Negative"
# Test
print(predict("This movie was amazing!")) # Positive
print(predict("Terrible waste of time.")) # Negative
Example 2: Fine-tune GPT for Text Generation
from transformers import GPT2LMHeadModel, GPT2Tokenizer, TextDataset, DataCollatorForLanguageModeling
from transformers import Trainer, TrainingArguments
# Load pre-trained GPT-2
model_name = "gpt2"
model = GPT2LMHeadModel.from_pretrained(model_name)
tokenizer = GPT2Tokenizer.from_pretrained(model_name)
# Add padding token
tokenizer.pad_token = tokenizer.eos_token
# Prepare your custom dataset
def create_dataset(file_path, tokenizer, block_size=128):
"""Create dataset from text file."""
dataset = TextDataset(
tokenizer=tokenizer,
file_path=file_path,
block_size=block_size
)
return dataset
# Data collator for language modeling
data_collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer,
mlm=False # GPT uses causal LM, not masked LM
)
# Training arguments
training_args = TrainingArguments(
output_dir='./gpt2_finetuned',
overwrite_output_dir=True,
num_train_epochs=3,
per_device_train_batch_size=4,
save_steps=1000,
save_total_limit=2,
prediction_loss_only=True,
learning_rate=5e-5,
)
# Create dataset (you need a text file with your training data)
train_dataset = create_dataset('./your_training_data.txt', tokenizer)
# Create Trainer
trainer = Trainer(
model=model,
args=training_args,
data_collator=data_collator,
train_dataset=train_dataset,
)
# Fine-tune
print("Fine-tuning GPT-2...")
trainer.train()
# Save model
model.save_pretrained('./gpt2_finetuned')
tokenizer.save_pretrained('./gpt2_finetuned')
# Generate text with fine-tuned model
def generate_text(prompt, max_length=100):
inputs = tokenizer.encode(prompt, return_tensors='pt')
outputs = model.generate(
inputs,
max_length=max_length,
num_return_sequences=1,
no_repeat_ngram_size=2,
temperature=0.7,
top_p=0.9,
)
return tokenizer.decode(outputs[0], skip_special_tokens=True)
# Test
print(generate_text("Once upon a time"))
Example 3: LoRA Efficient Fine-tuning
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig, get_peft_model, TaskType
import torch
# Load base model
model_name = "gpt2"
model = AutoModelForCausalLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# Configure LoRA
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=8, # Rank of update matrices
lora_alpha=32, # Scaling factor
lora_dropout=0.1,
target_modules=["c_attn"], # Which layers to apply LoRA to
)
# Apply LoRA to model
model = get_peft_model(model, lora_config)
# Print trainable parameters
model.print_trainable_parameters()
# Output: trainable params: 294,912 || all params: 124,734,720 || trainable%: 0.24%
# Training setup (same as before)
training_args = TrainingArguments(
output_dir='./lora_finetuned',
num_train_epochs=3,
per_device_train_batch_size=8,
learning_rate=3e-4, # Higher LR for LoRA
logging_steps=100,
)
# Train (much faster and uses less memory!)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
)
trainer.train()
# Save LoRA weights (only ~1-2MB instead of full model!)
model.save_pretrained('./lora_weights')
# Load LoRA weights later
from peft import PeftModel
base_model = AutoModelForCausalLM.from_pretrained(model_name)
lora_model = PeftModel.from_pretrained(base_model, './lora_weights')
# Use the model
def generate_with_lora(prompt):
inputs = tokenizer(prompt, return_tensors='pt')
outputs = lora_model.generate(**inputs, max_length=100)
return tokenizer.decode(outputs[0])
print(generate_with_lora("Hello, I am"))
Example 4: Evaluation and Metrics
from sklearn.metrics import accuracy_score, precision_recall_fscore_support, confusion_matrix
import numpy as np
class ModelEvaluator:
"""Comprehensive model evaluation."""
def __init__(self, model, tokenizer):
self.model = model
self.tokenizer = tokenizer
def evaluate_classification(self, test_data, test_labels):
"""Evaluate classification model."""
predictions = []
for text in test_data:
inputs = self.tokenizer(text, return_tensors='pt', padding=True, truncation=True)
outputs = self.model(**inputs)
pred = torch.argmax(outputs.logits, dim=1).item()
predictions.append(pred)
# Calculate metrics
accuracy = accuracy_score(test_labels, predictions)
precision, recall, f1, _ = precision_recall_fscore_support(
test_labels, predictions, average='weighted'
)
# Confusion matrix
cm = confusion_matrix(test_labels, predictions)
results = {
'accuracy': accuracy,
'precision': precision,
'recall': recall,
'f1': f1,
'confusion_matrix': cm
}
return results
def evaluate_generation(self, prompts, references):
"""Evaluate text generation quality."""
from rouge import Rouge
from nltk.translate.bleu_score import sentence_bleu
rouge = Rouge()
bleu_scores = []
rouge_scores = []
for prompt, reference in zip(prompts, references):
# Generate
generated = self.generate(prompt)
# BLEU score
bleu = sentence_bleu([reference.split()], generated.split())
bleu_scores.append(bleu)
# ROUGE score
scores = rouge.get_scores(generated, reference)[0]
rouge_scores.append(scores)
return {
'avg_bleu': np.mean(bleu_scores),
'avg_rouge_1': np.mean([s['rouge-1']['f'] for s in rouge_scores]),
'avg_rouge_2': np.mean([s['rouge-2']['f'] for s in rouge_scores]),
'avg_rouge_l': np.mean([s['rouge-l']['f'] for s in rouge_scores]),
}
def generate(self, prompt, max_length=100):
"""Generate text."""
inputs = self.tokenizer(prompt, return_tensors='pt')
outputs = self.model.generate(**inputs, max_length=max_length)
return self.tokenizer.decode(outputs[0], skip_special_tokens=True)
# Example usage
evaluator = ModelEvaluator(model, tokenizer)
# Evaluate classification
test_texts = ["Great movie!", "Terrible film."]
test_labels = [1, 0]
results = evaluator.evaluate_classification(test_texts, test_labels)
print(f"Accuracy: {results['accuracy']:.4f}")
print(f"F1 Score: {results['f1']:.4f}")
print(f"Confusion Matrix:\n{results['confusion_matrix']}")
Example 5: Production Fine-tuning Pipeline
import wandb
from transformers import TrainerCallback
import json
class ProductionFineTuner:
"""Complete production fine-tuning pipeline."""
def __init__(self, model_name, task_type='classification'):
self.model_name = model_name
self.task_type = task_type
self.model = None
self.tokenizer = None
# Initialize Weights & Biases for tracking
wandb.init(project="fine-tuning", name=f"{model_name}_{task_type}")
def prepare_data(self, train_file, val_file, test_file):
"""Load and prepare datasets."""
from datasets import load_dataset
dataset = load_dataset('json', data_files={
'train': train_file,
'validation': val_file,
'test': test_file
})
# Tokenize
def tokenize_function(examples):
return self.tokenizer(
examples['text'],
padding='max_length',
truncation=True,
max_length=512
)
tokenized_dataset = dataset.map(tokenize_function, batched=True)
return tokenized_dataset
def setup_model(self, num_labels=2):
"""Initialize model and tokenizer."""
from transformers import AutoModelForSequenceClassification, AutoTokenizer
self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
self.model = AutoModelForSequenceClassification.from_pretrained(
self.model_name,
num_labels=num_labels
)
def train(self, train_dataset, eval_dataset, output_dir='./output'):
"""Train with best practices."""
# Custom callback for logging
class WandbCallback(TrainerCallback):
def on_log(self, args, state, control, logs=None, **kwargs):
if logs:
wandb.log(logs)
training_args = TrainingArguments(
output_dir=output_dir,
num_train_epochs=3,
per_device_train_batch_size=16,
per_device_eval_batch_size=64,
warmup_ratio=0.1,
weight_decay=0.01,
logging_dir=f'{output_dir}/logs',
logging_steps=100,
evaluation_strategy="steps",
eval_steps=500,
save_strategy="steps",
save_steps=500,
load_best_model_at_end=True,
metric_for_best_model="eval_loss",
greater_is_better=False,
fp16=True, # Mixed precision training
gradient_accumulation_steps=2,
learning_rate=2e-5,
lr_scheduler_type="cosine",
)
trainer = Trainer(
model=self.model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
callbacks=[WandbCallback()],
)
# Train
trainer.train()
# Save
trainer.save_model(f'{output_dir}/final_model')
self.tokenizer.save_pretrained(f'{output_dir}/final_model')
return trainer
def evaluate_and_export(self, test_dataset, output_dir='./output'):
"""Evaluate and export model."""
evaluator = ModelEvaluator(self.model, self.tokenizer)
# Evaluate
results = evaluator.evaluate_classification(
test_dataset['text'],
test_dataset['label']
)
# Log to wandb
wandb.log(results)
# Save results
with open(f'{output_dir}/evaluation_results.json', 'w') as f:
json.dump(results, f, indent=2)
# Export for deployment
self.model.save_pretrained(f'{output_dir}/deployment')
self.tokenizer.save_pretrained(f'{output_dir}/deployment')
print(f"Model ready for deployment at {output_dir}/deployment")
return results
# Usage
finetuner = ProductionFineTuner('bert-base-uncased', 'classification')
finetuner.setup_model(num_labels=2)
# Prepare data
dataset = finetuner.prepare_data(
'train.json',
'val.json',
'test.json'
)
# Train
trainer = finetuner.train(
dataset['train'],
dataset['validation'],
output_dir='./production_model'
)
# Evaluate and export
results = finetuner.evaluate_and_export(
dataset['test'],
output_dir='./production_model'
)
print(f"Final accuracy: {results['accuracy']:.4f}")
📝 Assignments & Practice Problems
Assignment 1: Fine-tune for Sentiment Analysis ⭐⭐
Difficulty: Medium | Time: 60 minutes
Task: Fine-tune BERT on a sentiment analysis dataset and achieve >85% accuracy.
Requirements:
- Use IMDb or Twitter sentiment dataset
- Implement proper train/val/test split
- Track metrics with wandb
- Save best model
- Create inference function
Expected Output:
- Training accuracy: >90%
- Validation accuracy: >85%
- Test accuracy: >85%
- Model size: ~400MB
Assignment 2: Implement LoRA from Scratch ⭐⭐⭐
Difficulty: Hard | Time: 90 minutes
Task: Implement the LoRA algorithm without using the PEFT library.
Requirements:
class LoRALayer:
def __init__(self, original_layer, r=8, alpha=32):
"""
Implement LoRA for a linear layer.
LoRA decomposes weight updates as:
W' = W + BA
where B is (d x r) and A is (r x k)
"""
# TODO: Implement
pass
Assignment 3: Multi-task Fine-tuning ⭐⭐⭐
Difficulty: Hard | Time: 120 minutes
Task: Fine-tune a single model on multiple tasks simultaneously.
Requirements:
- Handle 3+ different tasks
- Implement task-specific heads
- Balance training across tasks
- Evaluate on each task separately
Assignment 4: Cost-Effective Fine-tuning ⭐⭐
Difficulty: Medium | Time: 60 minutes
Task: Fine-tune a model with minimal compute budget.
Requirements:
- Use gradient accumulation
- Implement mixed precision training
- Use LoRA or QLoRA
- Optimize batch size
- Track GPU memory usage
Assignment 5: Production Deployment Pipeline ⭐⭐⭐
Difficulty: Hard | Time: 120 minutes
Task: Create end-to-end pipeline from training to deployment.
Requirements:
- Data preprocessing
- Training with checkpoints
- Evaluation and metrics
- Model optimization (quantization)
- API endpoint creation
- Docker containerization
❌ High learning rate destroys pre-trained knowledge
optimizer = Adam(model.parameters(), lr=1e-3) # Too high!
✅ Use lower learning rate for fine-tuning
optimizer = Adam(model.parameters(), lr=2e-5) # Better
### Pitfall 2: Overfitting on Small Datasets
```python
# ❌ Training too long on small dataset
num_epochs = 10 # Will overfit!
# ✅ Use early stopping and regularization
training_args = TrainingArguments(
num_train_epochs=3,
weight_decay=0.01,
load_best_model_at_end=True,
)
Pitfall 3: Not Freezing Layers
# ✅ Freeze early layers, fine-tune later layers
for param in model.bert.embeddings.parameters():
param.requires_grad = False
for param in model.bert.encoder.layer[:8].parameters():
param.requires_grad = False
🔗 Related Concepts
- Previous: LLMs - Models to fine-tune
- Next: Few-shot Prompting - Alternative to fine-tuning
- Related: Self-Supervised Learning - Pre-training
- Advanced: Distillation - Compress fine-tuned models
📚 Resources
Essential Papers
- 📄 LoRA: Low-Rank Adaptation
- 📄 QLoRA: Efficient Finetuning of Quantized LLMs
- 📄 Parameter-Efficient Transfer Learning
Video Tutorials
Code Libraries
pip install transformers
pip install peft # For LoRA
pip install bitsandbytes # For QLoRA
pip install wandb # For tracking
