Fine-Tuning — Explained Simply
What Fine-Tuning Actually Is
You Are Training a World-Class Chef to Cook Nigerian Food
A French-trained chef knows everything about cooking — knife skills, flavour combinations, heat control, plating, sauce-making, food science. Extraordinary at French cuisine. Never cooked Nigerian food.
Option A — Teach From Scratch Forget everything. Start from zero. Take years and produce a worse result.
Option B — Targeted Nigerian Food Training Keep all existing culinary expertise. Introduce Nigerian ingredients — palm oil, ogiri, uziza leaves, suya spice. Practice specific dishes. Correct French instincts when they lead wrong. After a few weeks: excellent Nigerian food.
Option B is fine-tuning.
What Actually Changes During Fine-Tuning
Layer 1 attention weight before: 0.3847
Layer 1 attention weight after: 0.3851 ← tiny change (0.0004)
Layer 24 attention weight before: -0.2193
Layer 24 attention weight after: -0.1847 ← slightly larger change
Classification head before: 0.0000 ← initialised to zero
Classification head after: 0.7823 ← learned from your data
Early layers barely change — general language patterns apply to everything. Late layers change more — adapting to Nigerian Pidgin sentiment. Classification head changes most — learning your specific task from scratch.
Why the Learning Rate Must Be Very Small
Normal lr (0.001):
weight: 0.3847 → 0.2731 ← dramatic change, erases pretraining
Fine-tuning lr (0.00002 = 2e-5):
weight: 0.3847 → 0.3843 ← tiny adjustment, preserves pretraining
# ❌ Too high — destroys pretrained knowledge
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3)
# ✅ Standard fine-tuning range
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5)
# ✅ Conservative — for very small datasets
optimizer = torch.optim.AdamW(model.parameters(), lr=5e-6)
Complete Fine-Tuning Code for Your Nigerian Pidgin Paper
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from torch.utils.data import DataLoader, TensorDataset
import torch
# 1. Load model and tokeniser
model = AutoModelForSequenceClassification.from_pretrained(
"Davlan/afro-xlmr-large", num_labels=3
)
tokenizer = AutoTokenizer.from_pretrained("Davlan/afro-xlmr-large")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
# 2. Tokenise your Nigerian Pidgin reviews
reviews = ["Dis product too good", "E no work", "Package fine but so so"]
labels = [0, 1, 2] # 0=Positive 1=Negative 2=Neutral
encodings = tokenizer(
reviews, truncation=True, padding=True,
max_length=128, return_tensors="pt"
)
# 3. DataLoader
dataset = TensorDataset(
encodings["input_ids"],
encodings["attention_mask"],
torch.tensor(labels)
)
train_loader = DataLoader(dataset, batch_size=16, shuffle=True)
# 4. Optimiser — very small learning rate
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5)
# 5. Fine-tuning loop
for epoch in range(10):
model.train()
total_loss = 0
for input_ids, attention_mask, batch_labels in train_loader:
input_ids = input_ids.to(device)
attention_mask = attention_mask.to(device)
batch_labels = batch_labels.to(device)
optimizer.zero_grad()
outputs = model(input_ids=input_ids,
attention_mask=attention_mask,
labels=batch_labels)
loss = outputs.loss
total_loss += loss.item()
loss.backward()
optimizer.step()
print(f"Epoch {epoch+1} | Loss: {total_loss/len(train_loader):.4f}")
# 6. Save
model.save_pretrained("nijaifeel-afro-xlmr")
tokenizer.save_pretrained("nijaifeel-afro-xlmr")
# 7. Evaluate
from sklearn.metrics import classification_report
model.eval()
all_preds, all_labels = [], []
with torch.no_grad():
for input_ids, attention_mask, batch_labels in test_loader:
outputs = model(input_ids=input_ids.to(device),
attention_mask=attention_mask.to(device))
preds = outputs.logits.argmax(dim=1).cpu().numpy()
all_preds.extend(preds)
all_labels.extend(batch_labels.numpy())
print(classification_report(all_labels, all_preds,
target_names=["Positive", "Negative", "Neutral"]))
LoRA — Fine-Tuning Only a Tiny Fraction of Weights
For large models like Mistral 7B, even 2e-5 updates to 7 billion parameters is expensive. LoRA adds tiny trainable adapters alongside frozen weights.
from peft import LoraConfig, get_peft_model
config = LoraConfig(
r = 8,
lora_alpha = 32,
target_modules = ["q_proj", "v_proj"],
lora_dropout = 0.1,
bias = "none"
)
model = get_peft_model(model, config)
model.print_trainable_parameters()
# trainable: 0.1118% ← only 0.1% of parameters update ✅
Use LoRA when fine-tuning Mistral 7B in Phase 2 of your plan.
Signs Fine-Tuning Is Working
Epoch 1 | Loss: 1.0982 | Val F1: 0.42 ← starting
Epoch 3 | Loss: 0.6127 | Val F1: 0.71 ← improving fast
Epoch 7 | Loss: 0.2431 | Val F1: 0.83 ← strong
Epoch 10 | Loss: 0.1892 | Val F1: 0.84 ← converged ← save this
Epoch 12 | Loss: 0.1201 | Val F1: 0.83 ← overfitting starting → stop
Problems and fixes:
Val F1 never improves → lr too low or data quality issue
Val F1 collapses suddenly → lr too high — destroying pretraining
Train down but val flat → overfitting → need more data
Loss = NaN immediately → lr way too high
Pretraining vs Fine-Tuning — The Clear Distinction
Pretraining:
Data: Billions of words — no labels needed
Goal: Learn general language understanding
Cost: Millions of dollars
Who: Google, Meta, Anthropic, large research groups
Fine-tuning:
Data: Hundreds to thousands of labelled examples
Goal: Adapt general understanding to specific task
Cost: Hours on a single GPU
Who: You — starting from a pretrained model ✅
The Real Words Mapped to the Story
| In the Story | Real Technical Term |
|---|---|
| Chef's 10 years of training | Pretraining |
| Accumulated culinary knowledge | Pretrained weights |
| Teaching them Nigerian food | Fine-tuning |
| Nigerian ingredients to learn | Task-specific training data |
| Tiny adjustments to existing skills | Small weight updates |
| Not erasing French training | Small learning rate (2e-5) |
| Adding specialised Nigerian techniques | Classification head |
| Teaching only the Nigerian adapters | LoRA |
The One Thing to Remember
Fine-tuning makes tiny targeted adjustments to pretrained weights using your specific labelled data. Learning rate must be very small to preserve pretraining. Early layers barely change. Later layers adapt more. Classification head learns from scratch. After 10 epochs your 1,000 Nigerian Pidgin reviews teach AfroXLMR to recognise Pidgin sentiment without erasing its 17 billion words of accumulated language knowledge.