Val och finjustering av embedding-modeller
Kunna jämföra embedding-modeller på egen data och finjustera med kontrastiv förlust.
Förkunskaper
Intuition
Valet av embedding-modell påverkar RAG mer än valet av LLM. Fyra dimensioner att jämföra:
| Dimension | Frågor |
|---|---|
| Kvalitet på din data | recall@10 på ditt eget testset — inte på MTEB |
| Språk | är svenska med i träningsdatan? flerspråkig eller engelskcentrerad? |
| Dimension | 384, 768, 1 024, 4 096 — påverkar minne och hastighet linjärt |
| Kontextlängd | 512 tokens räcker inte för långa chunkar |
MTEB-rankningen är en startpunkt, inte ett svar. Modeller optimeras mot den, och din domän finns inte i den. Kör alltid ditt eget testset — det tar en timme och ger ett annat svar förvånansvärt ofta.
Formellt
Kontrastiv träning är hur embeddingmodeller lärs: fråga och relevant passage ska ligga nära, fråga och irrelevant passage långt bort. InfoNCE-förlusten:
(temperatur, ofta 0,02–0,05) styr hur hårt modellen straffar nära negativa exempel.
Hard negatives är avgörande. Slumpmässiga negativa exempel är för lätta — modellen lär sig ingenting av att skilja «hur byter jag lösenord» från «recept på pannkakor». Ta i stället passager som den nuvarande modellen rankar högt men som är fel. De ger mest signal per exempel.
När lönar sig finjustering? När du har ≥ 1 000 par av (fråga, rätt passage) från din domän — ofta utvinningsbara ur loggar eller genererade med en LLM och granskade. Typisk vinst: 5–15 procentenheter recall@10 på domändata. Med färre par än så är hybrid retrieval och bättre chunkning billigare vägar till samma vinst.
Kod
import numpy as np
from sentence_transformers import SentenceTransformer, InputExample, losses
from torch.utils.data import DataLoader
# 1. Jämför kandidater på EGET testset
def jamfor(modeller, fragor, passager, facit, k=10):
for namn in modeller:
m = SentenceTransformer(namn)
P = m.encode(passager, normalize_embeddings=True)
Q = m.encode(fragor, normalize_embeddings=True)
topk = np.argsort(-(Q @ P.T), axis=1)[:, :k]
recall = np.mean([f in rad for rad, f in zip(topk, facit)])
print(f"{namn:45s} recall@{k} {recall:.3f} dim {P.shape[1]}")
jamfor(["intfloat/multilingual-e5-large", "KBLab/sentence-bert-swedish-cased",
"BAAI/bge-m3"], fragor, passager, facit)
# 2. Finjustera med hard negatives
exempel = [InputExample(texts=[q, p_pos, p_hard_neg]) for q, p_pos, p_hard_neg in trippel]
m = SentenceTransformer("intfloat/multilingual-e5-large")
m.fit(train_objectives=[(DataLoader(exempel, batch_size=16, shuffle=True),
losses.MultipleNegativesRankingLoss(m))],
epochs=2, warmup_steps=100)
Fällan: e5- och bge-modeller kräver prefix ("query: " respektive "passage: "). Glömmer du dem tappar du flera procentenheter — och det syns inte som ett fel någonstans.
Behärskning innebär
- Jämför embedding-modeller på egen data
- Förstår kontrastiv träning och hard negatives
- Vet när finjustering lönar sig
Logga in för att göra övningarna och bygga upp din behärskning.
Källor
- MTEB: Massive Text Embedding Benchmark — fri läsning
- arXiv — Text Embeddings by Weakly-Supervised Contrastive Pre-training (E5) — arXiv (öppen åtkomst; licens per artikel)
- Sentence-Transformers (Apache-2.0) — Apache-2.0