AI Hints
Home›Deep Learning Tutorial›Using Pretrained Transformers
Deep Learning · Lesson 56 of 92

Fine-Tuning a Transformer

Transfer learning for language.

⏱ 2 min read▶ Video at 1:49:24🎓 Free
Watch this lesson in the Deep Learning courseWatch on YouTube ↗Full Deep Learning course

Your client wants a tool that sorts documents into categories. You've got seven hundred labelled examples. That's nowhere near enough to train a language model from scratch. But it's plenty, if you stand on the shoulders of a pretrained transformer.

Fine-Tuning a Transformer
From the video: Fine-Tuning a Transformer
  • Seven hundred examples
  • Not enough from scratch
  • Plenty with transfer learning

A classifier from a transformer

Same recipe as with images. We take a pretrained transformer, and freeze it. We run our documents through it, keeping the first token's vector as a summary of each one. Then we train a tiny new layer, on those vectors. Run it. On held-out posts, accuracy is very strong, with only seven hundred examples, and a few seconds of training. For real projects, you'd also unfreeze the top layers. But this approach is a great, cheap start.

finetune.pypython
import torch
from torch import nn
from sklearn.datasets import fetch_20newsgroups
from transformers import AutoModel, AutoTokenizer

kinds = ["sci.space", "sci.med"]
data = fetch_20newsgroups(subset="all", categories=kinds,
                          remove=("headers", "footers", "quotes"))
texts = [t[:300] for t in data.data[:700]]
labels = torch.tensor(data.target[:700])

name = "distilbert-base-uncased"
tok = AutoTokenizer.from_pretrained(name)
bert = AutoModel.from_pretrained(name).eval()

def embed(batch):
    enc = tok(batch, padding=True, truncation=True, max_length=64, return_tensors="pt")
    with torch.no_grad():
        return bert(**enc).last_hidden_state[:, 0]

X = torch.cat([embed(texts[i:i + 32]) for i in range(0, 700, 32)])
tr, te = X[:550], X[550:]
y_tr, y_te = labels[:550], labels[550:]

torch.manual_seed(0)
head = nn.Linear(768, 2)
opt = torch.optim.AdamW(head.parameters(), lr=1e-2)
for _ in range(100):
    loss = nn.functional.cross_entropy(head(tr), y_tr)
    opt.zero_grad(); loss.backward(); opt.step()

print("Accuracy:", (head(te).argmax(1) == y_te).float().mean().item())
Output
Accuracy: 0.9133333563804626

[transformers] DistilBertModel LOAD REPORT from: distilbert-base-uncased
Key                     | Status     |  |
------------------------+------------+--+-
vocab_layer_norm.bias   | UNEXPECTED |  |
vocab_transform.weight  | UNEXPECTED |  |
vocab_transform.bias    | UNEXPECTED |  |
vocab_projector.bias    | UNEXPECTED |  |
vocab_layer_norm.weight | UNEXPECTED |  |

Notes:
- UNEXPECTED:	can be ignored when loading from different task/architecture; not ok if you expect identical arch.

LoRA: fine-tuning on a budget

Large models have huge layers, so fine tuning all of the weights is costly. LoRA has a clever idea. Freeze the original layer, and add a tiny pair of small matrices next to it. Only these are trained. Their product acts as a small correction. Run it. The full layer has about sixteen million weights, and LoRA trains only a few tens of thousands, a tiny fraction. That's why anyone can customise huge language models and image generators today.

lora.pypython
import torch
from torch import nn

class LoRA(nn.Module):
    def __init__(self, layer, rank=4):
        super().__init__()
        self.layer = layer
        for p in layer.parameters():
            p.requires_grad = False
        self.a = nn.Parameter(torch.randn(rank, layer.in_features) * 0.01)
        self.b = nn.Parameter(torch.zeros(layer.out_features, rank))

    def forward(self, x):
        return self.layer(x) + x @ self.a.T @ self.b.T

big = nn.Linear(4096, 4096)
lora = LoRA(big)
full = sum(p.numel() for p in big.parameters())
small = lora.a.numel() + lora.b.numel()

print(f"Full layer weights: {full:,}")
print(f"LoRA weights:       {small:,}")
print(f"Share to train:     {small / full:.2%}")
Output
Full layer weights: 16,781,312
LoRA weights:       32,768
Share to train:     0.20%

Summary

Freeze, replace the head, and train a little. Or use LoRA to change a huge model cheaply. Those skills cover a large share of real language projects. Now let's turn back to images, and teach Aria to do far more than simply naming them. Next, finding objects.

Key takeaways
  • Pretrained body, new head
  • Few examples are enough
  • LoRA trains tiny corrections
Next up: Object Detection — finding things in images