Advanced19 of 21· 5 min read

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 StoryReal Technical Term
Chef's 10 years of trainingPretraining
Accumulated culinary knowledgePretrained weights
Teaching them Nigerian foodFine-tuning
Nigerian ingredients to learnTask-specific training data
Tiny adjustments to existing skillsSmall weight updates
Not erasing French trainingSmall learning rate (2e-5)
Adding specialised Nigerian techniquesClassification head
Teaching only the Nigerian adaptersLoRA

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.

← All Articles