Cambridge 2026 · Project brief

About this project

A domain-adaptation practical: train a headline topic classifier on general AG News titles, evaluate on a frozen UK BBC test set, then fine-tune on UK data and measure the lift — all on a Mac Studio with Apple Silicon MPS.

What we’re asking

Can a classifier trained on broad, mostly US-centric news titles transfer to UK BBC headlines — and does continued fine-tuning on UK text close the gap?

We compare five systems on the same frozen UK test set: Naive Bayes @ AG, DistilBERT @ AG, Naive Bayes @ UK, DistilBERT UK-only (no AG), and DistilBERT AG→UK continued fine-tuning. Charts live on the classification page.

Hardware: Mac Studio

Training and inference ran locally on Apple Silicon — not a cloud GPU box. DistilBERT is small enough that this machine is more than enough; the practical constraint was getting PyTorch’s MPS backend working cleanly.

Machine
Mac StudioApple M3 Ultra
Memory
96 GB unifiedCPU + GPU share the same pool
Compute path
PyTorch MPStorch.backends.mps when available
Why not MLX-LM?
Classifier, not LLMHugging Face Transformers + Torch

MPS notes: we avoid fp16 mixed-precision quirks on MPS, keep batches modest (32 / 16), and set dataloader_pin_memory=False. Stage 1 is typically under 1–2 hours; Stage 2 is minutes; Naive Bayes is seconds.

Model: DistilBERT

Backbone: distilbert-base-uncased with a fresh 4-way sequence-classification head (AutoModelForSequenceClassification).

Lower-level architecture

DistilBERT is a compressed Transformer encoder. It keeps the BERT-style stack but drops the next-sentence prediction objective and halves the depth, then trains the student to match a BERT teacher.

Encoder stack

Input ids + attention mask │ ▼ Token + position embeddings (dim 768) │ ▼ 6 × Transformer blocks each: Multi-Head Self-Attention (12 heads) → residual + LayerNorm → Feed-Forward (768 → 3072 → 768) → residual + LayerNorm │ ▼ [CLS] pooled representation (768-d) │ ▼ Linear classification head 768 → 4 logits │ ▼ Softmax → predicted topic

What “fine-tuning” updates

We do full continued fine-tuning: every DistilBERT weight plus the classification head receive gradients. LoRA / adapters were not needed — the model is small, the UK set is small, and full updates finish quickly on MPS.

Why DistilBERT (not a generative LLM)

This is single-label topic classification. An encoder with a linear head is the right inductive bias: cheap, stable, and easy to compare against a Naive Bayes bag-of-words baseline. We deliberately do not train a large generative model from scratch.

Fine-tuning techniques

The core method is two-stage domain adaptation (continued fine-tuning), with an ablated UK-only run to separate “UK data helps” from “AG pre-training still helps.”

1

Stage 1 — AG News titles (“before”)

Init from distilbert-base-uncased. Train on stratified AG titles (~28.5k train / 1.5k val). 2 epochs, batch 32. Save artifacts/model_ag_headlines/. Evaluate on frozen UK test.

2

Stage 2 — continue on UK (“after”)

Load the Stage-1 checkpoint (do not re-init from base). Train on UK train; early-stop on UK val (patience 1, up to 3 epochs), batch 16. Save artifacts/model_uk_finetuned/. Re-evaluate on the same UK test.

3

Ablation — UK only (no AG)

Fresh distilbert-base-uncased fine-tuned only on UK train/val with the same Stage-2 recipe. Isolates whether gains come from UK data alone vs AG→UK continued training.

4

Baselines — Multinomial Naive Bayes

Bag-of-words NB trained on AG titles, then separately on UK train; both scored on the same UK test. A cheap ceiling/floor check for the neural models.

Knob Stage 1 (AG) Stage 2 / UK-only
Init Hub base model Stage-1 ckpt / Hub base
Learning rate 2e-5 2e-5
Batch size 32 16
Epochs 2 ≤3 + early stop
Max length 64 64
Best model metric macro-F1 macro-F1

Not used: LoRA, QLoRA, prefix-tuning, or frozen-encoder linear probes. Full updates are appropriate here; parameter-efficient methods would mostly add complexity without a clear win on this size of model and data.

Data & label mapping

Source Role Notes
fancyzhx/ag_news Stage 1 train/val Title / short prefix only; stratified ~30k subset
SetFit/bbc-news UK fine-tune + frozen test 70 / 15 / 15 train/val/test, seed 42; drop entertainment

Unified 4-way labels

Unified AG News BBC
business Business business
sport Sports sport
tech Sci/Tech tech
politics World politics

AG World → BBC politics is an imperfect bridge on purpose — that mismatch is part of what UK fine-tuning has to fix. BBC rows often lack a clean title field; we use title/headline when present, else the first sentence or first ~16 tokens as a headline proxy.

Software pipeline

Scripts are meant to be run in order after source .venv/bin/activate. Prepared CSVs already live in data/; model weights stay gitignored while metrics and charts are committed for GitHub Pages.

  1. prepare_data.py — optional regenerate of splits
  2. train_nb.py — Naive Bayes @ AG and @ UK
  3. train_stage1.py → evaluate.py (before metrics)
  4. train_stage2.py → evaluate.py (after metrics)
  5. train_uk_only.py → evaluate.py (UK-only metrics)
  6. assemble_results.py, embed_similarity.py, novel_words.py

Stack: PyTorch, Hugging Face Transformers / Datasets / Accelerate, scikit-learn, pandas, matplotlib. See requirements.txt and AGENT_PLAN.html for the locked brief.

Evaluation design

Side analyses dig into why transfer is hard: embedding centroid similarity, headline length distributions, and UK words missing from AG train.

Caveats & design risks