In diesem Tutorial optimieren wir ein Sentence-Transformers-Einbettungsmodell mithilfe von Matryoshka Illustration Studying, sodass die frühesten Dimensionen des Vektors das nützlichste semantische Sign tragen. Wir trainieren mit MatryoshkaLoss anhand von Triplettdaten und validieren dann das Hauptversprechen von MRL, indem wir die Abrufqualität nach der Kürzung der Einbettungen auf 64, 128 und 256 Dimensionen vergleichen. Am Ende speichern wir das optimierte Modell und zeigen, wie man es mit einer kleinen truncate_dim-Einstellung für eine schnelle und speichereffiziente Vektorsuche lädt. Schauen Sie sich das an VOLLSTÄNDIGE CODES hier.
!pip -q set up -U sentence-transformers datasets speed up
import math
import random
import numpy as np
import torch
from datasets import load_dataset
from torch.utils.information import DataLoader
from sentence_transformers import SentenceTransformer, InputExample
from sentence_transformers import losses
from sentence_transformers.util import cos_sim
def set_seed(seed=42):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
set_seed(42)
Wir installieren die benötigten Bibliotheken und importieren alle notwendigen Module für Coaching und Evaluierung. Wir legen einen deterministischen Startwert fest, damit unser Stichproben- und Trainingsverhalten über alle Läufe hinweg konsistent bleibt. Wir stellen außerdem sicher, dass PyTorch- und CUDA-RNGs aufeinander abgestimmt sind, wenn eine GPU verfügbar ist. Schauen Sie sich das an VOLLSTÄNDIGE CODES hier.
@torch.no_grad()
def retrieval_metrics_mrr_recall_at_k(
mannequin,
queries,
corpus,
qrels,
dims_list=(64, 128, 256, None),
okay=10,
batch_size=64,
):
machine = "cuda" if torch.cuda.is_available() else "cpu"
mannequin.to(machine)
qids = listing(queries.keys())
docids = listing(corpus.keys())
q_texts = (queries(qid) for qid in qids)
d_texts = (corpus(did) for did in docids)
q_emb = mannequin.encode(q_texts, batch_size=batch_size, convert_to_tensor=True, normalize_embeddings=True)
d_emb = mannequin.encode(d_texts, batch_size=batch_size, convert_to_tensor=True, normalize_embeddings=True)
outcomes = {}
for dim in dims_list:
if dim is None:
qe = q_emb
de = d_emb
dim_name = "full"
else:
qe = q_emb(:, :dim)
de = d_emb(:, :dim)
dim_name = str(dim)
qe = torch.nn.purposeful.normalize(qe, p=2, dim=1)
de = torch.nn.purposeful.normalize(de, p=2, dim=1)
sims = cos_sim(qe, de)
mrr_total = 0.0
recall_total = 0.0
for i, qid in enumerate(qids):
rel = qrels.get(qid, set())
if not rel:
proceed
topk = torch.topk(sims(i), okay=min(okay, sims.form(1)), largest=True).indices.tolist()
topk_docids = (docids(j) for j in topk)
recall_total += 1.0 if any(d in rel for d in topk_docids) else 0.0
rr = 0.0
for rank, d in enumerate(topk_docids, begin=1):
if d in rel:
rr = 1.0 / rank
break
mrr_total += rr
denom = max(1, len(qids))
outcomes(dim_name) = {f"MRR@{okay}": mrr_total / denom, f"Recall@{okay}": recall_total / denom}
return outcomes
def pretty_print(outcomes, title):
print("n" + "=" * 80)
print(title)
print("=" * 80)
for dim, metrics in outcomes.gadgets():
print(f"dim={dim:>4} | " + " | ".be part of((f"{okay}={v:.4f}" for okay, v in metrics.gadgets())))
Wir implementieren einen leichtgewichtigen Retrieval-Evaluator, der Abfragen und Dokumente kodiert, die Kosinusähnlichkeit berechnet und MRR@10 und Recall@10 meldet. Wir normalisieren Einbettungen nach der Kürzung neu, sodass kleinere Präfixe im Kosinusraum vergleichbar bleiben. Wir haben außerdem einen kompakten Drucker hinzugefügt, um Vorher-/Nachher-Vergleiche leicht lesbar zu machen. Schauen Sie sich das an VOLLSTÄNDIGE CODES hier.
DATASET_ID = "sentence-transformers/msmarco-co-condenser-margin-mse-sym-mnrl-mean-v1"
SUBSET = "triplet-hard"
SPLIT = "prepare"
TRAIN_SAMPLES = 4000
EVAL_QUERIES = 300
stream = load_dataset(DATASET_ID, SUBSET, break up=SPLIT, streaming=True)
train_examples = ()
eval_queries = {}
eval_corpus = {}
eval_qrels = {}
doc_id_counter = 0
qid_counter = 0
for row in stream:
q = (row.get("question") or "").strip()
pos = (row.get("constructive") or "").strip()
neg = (row.get("unfavorable") or "").strip()
if not q or not pos or not neg:
proceed
train_examples.append(InputExample(texts=(q, pos, neg)))
if len(eval_queries) < EVAL_QUERIES:
qid = f"q{qid_counter}"
qid_counter += 1
pos_id = f"d{doc_id_counter}"; doc_id_counter += 1
neg_id = f"d{doc_id_counter}"; doc_id_counter += 1
eval_queries(qid) = q
eval_corpus(pos_id) = pos
eval_corpus(neg_id) = neg
eval_qrels(qid) = {pos_id}
if len(train_examples) >= TRAIN_SAMPLES and len(eval_queries) >= EVAL_QUERIES:
break
print(len(train_examples), len(eval_queries), len(eval_corpus))
Wir streamen einen extrahierten MS MARCO-Triplett-Datensatz und erstellen sowohl einen Trainingssatz (Abfragen, Constructive, Damaging) als auch einen kleinen IR-Benchmark-Satz. Wir ordnen jede Abfrage einem relevanten positiven Dokument zu und fügen ein negatives Dokument hinzu, um den Abruf sinnvoll zu gestalten. Wir stoppen früh, um den Lauf Colab-freundlich zu halten, aber dennoch groß genug, um Kürzungseffekte zu zeigen.
MODEL_ID = "BAAI/bge-base-en-v1.5"
machine = "cuda" if torch.cuda.is_available() else "cpu"
mannequin = SentenceTransformer(MODEL_ID, machine=machine)
full_dim = mannequin.get_sentence_embedding_dimension()
baseline = retrieval_metrics_mrr_recall_at_k(
mannequin,
queries=eval_queries,
corpus=eval_corpus,
qrels=eval_qrels,
dims_list=(64, 128, 256, None),
okay=10,
)
pretty_print(baseline, "BEFORE")
Wir laden ein starkes Foundation-Einbettungsmodell und zeichnen dessen vollständige Einbettungsdimension auf. Wir führen die Basisbewertung über 64/128/256/vollständige Dimensionen durch, um zu sehen, wie sich die Kürzung vor dem Coaching verhält. Wir drucken die Ergebnisse aus, damit wir später vergleichen können, ob MRL die Qualität der frühen Dimension verbessert.
batch_size = 16
epochs = 1
warmup_steps = 100
train_loader = DataLoader(train_examples, batch_size=batch_size, shuffle=True, drop_last=True)
base_loss = losses.MultipleNegativesRankingLoss(mannequin=mannequin)
mrl_dims = (full_dim, 512, 256, 128, 64) if full_dim >= 768 else (full_dim, 256, 128, 64)
mrl_loss = losses.MatryoshkaLoss(
mannequin=mannequin,
loss=base_loss,
matryoshka_dims=mrl_dims
)
mannequin.match(
train_objectives=((train_loader, mrl_loss)),
epochs=epochs,
warmup_steps=warmup_steps,
show_progress_bar=True,
)
after = retrieval_metrics_mrr_recall_at_k(
mannequin,
queries=eval_queries,
corpus=eval_corpus,
qrels=eval_qrels,
dims_list=(64, 128, 256, None),
okay=10,
)
pretty_print(after, "AFTER")
out_dir = "mrl-msmarco-demo"
mannequin.save(out_dir)
m64 = SentenceTransformer(out_dir, truncate_dim=64)
emb = m64.encode(
("what's the liberal arts?", "liberal arts covers humanities and sciences"),
normalize_embeddings=True
)
print(emb.form)
Wir erstellen ein MultipleNegativesRankingLoss und umschließen es mit MatryoshkaLoss, indem wir eine absteigende Liste von Zielpräfixdimensionen verwenden. Wir optimieren das Modell anhand der Tripletts und führen dann denselben Trunkierungs-Benchmark erneut durch, um die Verbesserung der Retention zu messen. Außerdem speichern wir das Modell und laden es mit truncate_dim=64 neu, um die praktische Verwendung für den kompakten Abruf zu bestätigen.
Zusammenfassend lässt sich sagen, dass wir erfolgreich ein Matryoshka-optimiertes Einbettungsmodell trainiert haben, das eine starke Abrufleistung beibehält, selbst wenn wir Vektoren auf kleine Präfixdimensionen wie 64 kürzen. Wir haben den Effekt überprüft, indem wir die Foundation-Abrufmetriken mit denen nach dem Coaching über mehrere Kürzungsgrößen und die vollständige Einbettung verglichen haben. Mit dem gespeicherten Modell und dem Lademuster truncate_dim verfügen wir jetzt über einen sauberen Arbeitsablauf zum Erstellen kleinerer, schnellerer Vektorindizes und behalten gleichzeitig die Choice zum Neuranking mit volldimensionalen Einbettungen bei.
Schauen Sie sich das an VOLLSTÄNDIGE CODES hier. Sie können uns auch gerne weiter folgen Twitter und vergessen Sie nicht, bei uns mitzumachen 100.000+ ML SubReddit und Abonnieren Unser Publication. Warten! Bist du im Telegram? Jetzt können Sie uns auch per Telegram kontaktieren.

