Faza 04 · lecția 20
Regăsirea imaginilor și învățarea metrică
Scopul lecției: Un sistem de regăsire ordonează candidații după o distanță în spațiul reprezentărilor vectoriale (embedding). Învățarea metrică este disciplina care modelează acel spațiu astfel încât distanțele să însemne ceea ce este necesar.
Versiunea curentă AlexBred.com: primele 100 de lecții ale programului în limba română.
Cuprinsul lecției
- Obiective de învățare
- Problema
- Conceptul
- Regăsirea dintr-o privire
- Cele patru familii de pierderi
- Pierderea triplet, formal
- Similitudinea cosinusului versus L2
- Recall@K
- FAISS într-un paragraf
- Regăsirea la nivel de instanță versus la nivel de categorie
- Construiți
- Pasul 1: Pierderea triplet
- Pasul 2: Minarea semi-hard
- Pasul 3: Recall@K
- Pasul 4: Puneți totul cap la cap
- Utilizați
- Livrați
- Exerciții
- Termeni-cheie
- Lecturi suplimentare
Un sistem de regăsire ordonează candidații după o distanță în spațiul reprezentărilor vectoriale (embedding). Învățarea metrică este disciplina care modelează acel spațiu astfel încât distanțele să însemne ceea ce este necesar.
Tip: Construiți Limbaje: Python Cerințe prealabile: Faza 4 Lecția 14 (ViT), Faza 4 Lecția 18 (CLIP) Timp: ~45 de minute
Obiective de învățare
- Explicați pierderile de învățare metrică triplet, contrastive și bazate pe proxy și alegeți-o pe cea potrivită pentru un set de date dat
- Implementați corect normalizarea L2 și similitudinea cosinusului și verificați diferența dintre regăsirea „aceluiași obiect” și cea din „aceeași clasă”
- Construiți un index FAISS, interogați-l cu text și cu imagini și raportați recall@K pentru un set de interogări păstrat separat
- Utilizați DINOv2, CLIP și SigLIP ca backbones de reprezentări vectoriale gata de utilizare și știți când are avantaj fiecare
Problema
Regăsirea apare pretutindeni în viziunea folosită în producție: detectarea duplicatelor, căutarea inversă de imagini, căutarea vizuală („găsiți produse similare”), reidentificarea fețelor, reidentificarea persoanelor pentru supraveghere, potrivirea la nivel de instanță pentru comerț electronic. Întrebarea produsului este mereu aceeași: „dată această imagine de interogare, ordonați catalogul meu”.
Două decizii de proiectare modelează întregul sistem. Reprezentarea vectorială — ce model produce vectorii. Indexul — cum sunt găsiți cei mai apropiați vecini la scară. Ambele sunt tehnologii standard în 2026 (DINOv2 pentru reprezentare, FAISS pentru index), ceea ce ridică standardul: partea dificilă este definirea a ceea ce este similar pentru aplicația dumneavoastră, apoi modelarea spațiului de reprezentare astfel încât distanțele să corespundă definiției.
Această modelare este învățarea metrică. Este o disciplină restrânsă, dar cu efect mare.
Conceptul
Regăsirea dintr-o privire
Cele patru familii de pierderi
| Pierdere | Necesită | Avantaje | Dezavantaje |
|---|---|---|---|
| Contrastivă | (ancoră, pozitiv) + negative | Simplă, funcționează cu orice etichetă de pereche | Convergență lentă fără multe negative |
| Triplet | (ancoră, pozitiv, negativ) | Intuitivă; control direct al marjei | Minarea tripletelor hard este costisitoare |
| NT-Xent / InfoNCE | Perechi + negative extrase din lot | Se extinde la loturi mari | Necesită un lot mare sau o coadă de momentum |
| Bazată pe proxy (ProxyNCA) | Numai etichete de clasă | Rapidă, stabilă, fără minare | Poate supraajusta la proxy pe seturi de date mici |
Pentru majoritatea cazurilor de utilizare din producție, începeți cu un backbone preantrenat și adăugați ajustare fină (fine-tuning) prin învățare metrică numai dacă reprezentările gata de utilizare au rezultate slabe pe setul dumneavoastră de test.
Pierderea triplet, formal
L = max(0, ||f(a) - f(p)||^2 - ||f(a) - f(n)||^2 + margin)
Aduceți ancora a aproape de pozitivul p, îndepărtați-o de negativul n, cu o margin care asigură un decalaj. Structura cu trei imagini se generalizează la orice ordonare a similitudinii.
Minarea contează: tripletele ușoare (n deja departe de a) contribuie cu pierdere zero; numai tripletele hard învață rețeaua. Minarea semi-hard (n mai departe decât p, dar în interiorul marjei) este rețeta FaceNet din 2016 și încă domină.
Similitudinea cosinusului versus L2
Două metrici, două convenții:
- Cosinus: unghiul dintre vectori. Necesită reprezentări vectoriale normalizate L2.
- L2: distanța euclidiană. Funcționează cu reprezentări brute sau normalizate, dar este de obicei asociată cu normalizare L2 + L2 pătrat.
Pentru majoritatea rețelelor moderne, cele două sunt echivalente: ||a - b||^2 = 2 - 2 cos(a, b) când ||a|| = ||b|| = 1. Alegeți convenția care corespunde antrenării reprezentării vectoriale; amestecarea lor schimbă în tăcere ce înseamnă „cel mai apropiat”.
Recall@K
Metrica standard pentru regăsire:
recall@K = fracția interogărilor pentru care cel puțin o potrivire corectă se află în primele K rezultate
Raportați recall@1, @5, @10 alăturat. Un recall@10 peste 0.95 cu recall@1 sub 0.5 înseamnă că spațiul de reprezentare are structura corectă, însă ordonarea este zgomotoasă — încercați ajustări fine mai lungi sau un pas de reordonare.
Pentru detectarea duplicatelor, precision@K contează mai mult, deoarece fiecare fals pozitiv este o greșeală vizibilă utilizatorului. Pentru căutarea vizuală, recall@K este semnalul produsului.
FAISS într-un paragraf
Facebook AI Similarity Search. Biblioteca de facto pentru căutarea celor mai apropiați vecini. Trei alegeri de index:
IndexFlatIP/IndexFlatL2— forță brută, exact, fără antrenare. Utilizați până la aproximativ 1M vectori.IndexIVFFlat— partiționează în K celule, caută numai în cele mai apropiate câteva celule. Aproximativ, rapid, necesită date de antrenare.IndexHNSW— bazat pe graf, cel mai rapid pentru multe interogări, dimensiune mare a indexului.
Pentru 100k vectori probabil doriți IndexFlatIP cu similitudine cosinus. Pentru 10M doriți IndexIVFFlat. Pentru 100M+ combinat cu cuantizare de produs (IndexIVFPQ).
Notă tehnică a traducerii: Numerele de vectori de mai sus sunt reguli orientative, nu praguri universale: alegerea depinde de dimensionalitate, hardware, bugetul de latență, recall-ul dorit și parametrii indexului. Documentația FAISS arată, de asemenea, că memoria pentru HNSW crește odată cu
M; prin urmare, formularea „small memory overhead” din tabelul de termeni de mai jos nu trebuie interpretată ca o proprietate generală.
Regăsirea la nivel de instanță versus la nivel de categorie
Două probleme foarte diferite cu același nume:
- La nivel de categorie — „găsiți pisici în catalogul meu”. Similitudine condiționată de clasă; reprezentările CLIP / DINOv2 gata de utilizare funcționează bine.
- La nivel de instanță — „găsiți acest produs exact în catalogul meu”. Necesită discriminare fină între obiecte vizual similare din aceeași clasă; reprezentările gata de utilizare au rezultate sub nivelul dorit; ajustarea fină cu învățare metrică contează.
Întrebați întotdeauna care dintre cele două probleme este rezolvată înainte de alegerea modelului.
Construiți
Pasul 1: Pierderea triplet
import torch
import torch.nn.functional as F
def triplet_loss(anchor, positive, negative, margin=0.2):
d_ap = F.pairwise_distance(anchor, positive, p=2)
d_an = F.pairwise_distance(anchor, negative, p=2)
return F.relu(d_ap - d_an + margin).mean()
O singură linie. Funcționează cu reprezentări vectoriale normalizate L2 sau brute.
Notă tehnică a traducerii: Formula de mai sus folosește distanțe L2 pătrate, însă
F.pairwise_distance(..., p=2)din cod produce norma L2 nepătrată. Ambele variante pot fi utilizate, dar scala numerică amargindiferă; nu transferați aceeași marjă între ele fără validare.
Pasul 2: Minarea semi-hard
Dat un lot de reprezentări vectoriale și etichete, găsiți pentru fiecare ancoră cel mai dificil negativ semi-hard.
def semi_hard_negatives(emb, labels, margin=0.2):
dist = torch.cdist(emb, emb)
same_class = labels[:, None] == labels[None, :]
diff_class = ~same_class
N = emb.size(0)
positives = dist.clone()
positives[~same_class] = float("-inf")
positives.fill_diagonal_(float("-inf"))
pos_idx = positives.argmax(dim=1)
semi_hard = dist.clone()
semi_hard[same_class] = float("inf")
d_ap = dist[torch.arange(N), pos_idx].unsqueeze(1)
semi_hard[dist <= d_ap] = float("inf")
neg_idx = semi_hard.argmin(dim=1)
fallback_mask = semi_hard[torch.arange(N), neg_idx] == float("inf")
if fallback_mask.any():
hardest = dist.clone()
hardest[same_class] = float("inf")
neg_idx = torch.where(fallback_mask, hardest.argmin(dim=1), neg_idx)
return pos_idx, neg_idx
Fiecare ancoră primește cel mai dificil pozitiv din clasă și un negativ semi-hard, mai departe decât pozitivul, însă în interiorul marjei.
Notă tehnică a traducerii: În definiția FaceNet, un negativ semi-hard îndeplinește
d(a, p) < d(a, n) < d(a, p) + margin. Codul original exclude numai negativele cu distanță cel multd_ap; nu aplică limita superioară dată de marjă și nici nu utilizează parametrulmargin, deci poate selecta un negativ aflat în afara marjei. În plus, fiecare clasă trebuie să aibă cel puțin două exemple în lot: altfel, după mascarea diagonalei nu există un pozitiv valid pentru ancoră. Pentru minare strict semi-hard sunt necesare filtrul superior și tratarea explicită a acestor ancore.
Pasul 3: Recall@K
def recall_at_k(query_emb, gallery_emb, query_labels, gallery_labels, k=1):
sim = query_emb @ gallery_emb.T
_, top_k = sim.topk(k, dim=-1)
matches = (gallery_labels[top_k] == query_labels[:, None]).any(dim=-1)
return matches.float().mean().item()
Top-k după produsul scalar intern pentru reprezentări vectoriale normalizate L2 este egal cu top-k după cosinus. Raportați proporția medie a interogărilor cu cel puțin un vecin corect.
Notă tehnică a traducerii: Această implementare definește „corect” prin egalitatea din
query_labelsșigallery_labels. Pentru regăsire la nivel de instanță sau detectarea duplicatelor, aceste etichete trebuie să fie identificatori de instanță; dacă sunt etichete de clasă, metrica măsoară regăsirea la nivel de categorie. Funcția nu normalizeazăquery_embsaugallery_emb; egalitatea dintre produsul scalar intern și cosinus este valabilă numai după normalizarea L2 a ambelor colecții, inclusiv când vectorii sunt furnizați din afara claseiEncoderdin exemplu.
Pasul 4: Puneți totul cap la cap
import torch
import torch.nn as nn
from torch.optim import Adam
class Encoder(nn.Module):
def __init__(self, in_dim=128, emb_dim=64):
super().__init__()
self.net = nn.Sequential(
nn.Linear(in_dim, 128), nn.ReLU(),
nn.Linear(128, emb_dim),
)
def forward(self, x):
return F.normalize(self.net(x), dim=-1)
torch.manual_seed(0)
num_classes = 6
protos = F.normalize(torch.randn(num_classes, 128), dim=-1)
def sample_batch(bs=32):
labels = torch.randint(0, num_classes, (bs,))
x = protos[labels] + 0.15 * torch.randn(bs, 128)
return x, labels
enc = Encoder()
opt = Adam(enc.parameters(), lr=3e-3)
for step in range(200):
x, y = sample_batch(32)
emb = enc(x)
pos_idx, neg_idx = semi_hard_negatives(emb, y)
loss = triplet_loss(emb, emb[pos_idx], emb[neg_idx])
opt.zero_grad(); loss.backward(); opt.step()
După câteva sute de pași, clusterele de reprezentări vectoriale formează câte un cluster pentru fiecare clasă.
Utilizați
Stive pentru producție în 2026:
- DINOv2 + FAISS — regăsire vizuală de uz general. Funcționează gata de utilizare.
- CLIP + FAISS — când interogările sunt text.
- DINOv2 ajustat fin + FAISS — regăsire la nivel de instanță, reidentificarea fețelor, modă, comerț electronic.
- Milvus / Weaviate / Qdrant — învelișuri de baze de date vectoriale gestionate în jurul FAISS sau HNSW.
Notă tehnică a traducerii: Milvus, Weaviate și Qdrant nu sunt, în general, simple învelișuri în jurul FAISS sau HNSW. Sunt baze de date vectoriale cu propriile alegeri de indici, stocare, filtrare și operații de administrare; de exemplu, Weaviate oferă HNSW, Flat, Dynamic și HFresh, iar Qdrant extinde HNSW pentru căutări filtrate. Selectarea lor trebuie evaluată separat de alegerea unei biblioteci de căutare ANN.
Pentru regăsire la nivel de instanță de ultimă generație (SOTA), rețeta este: backbone DINOv2, adăugați un cap de reprezentare vectorială, ajustați fin cu o pierdere triplet sau InfoNCE pe perechi etichetate la nivel de instanță, indexați în FAISS.
Notă tehnică a traducerii: „SOTA” aici nu este o rețetă universală: rezultatul depinde de setul de date, definiția identității, protocolul de evaluare, augmentări, backbone și bugetele de latență sau memorie. Comparați întotdeauna cu un baseline al domeniului dumneavoastră și cu metrici care separă regăsirea la nivel de instanță de cea la nivel de categorie.
Livrați
Această lecție produce:
outputs/prompt-retrieval-loss-picker.md— un prompt care alege triplet / InfoNCE / ProxyNCA pentru o problemă de regăsire dată.outputs/skill-recall-at-k-runner.md— o abilitate care scrie un cadru de evaluare curat pentru recall@K cu separări train/val/gallery și contract de date corect.
Exerciții
- (Ușor) Rulați exemplul de jucărie de mai sus. Reprezentați grafic reprezentările vectoriale cu PCA înainte și după antrenare pentru a vedea formarea celor șase clustere.
- (Mediu) Adăugați o implementare a pierderii ProxyNCA: un „proxy” învățat pentru fiecare clasă, entropie încrucișată standard pe similitudinea cosinusului. Comparați viteza de convergență cu pierderea triplet în exemplul de jucărie.
- (Dificil) Luați 1.000 de imagini de validare ImageNet, generați reprezentări vectoriale cu DINOv2 prin HuggingFace, construiți un index FAISS flat și raportați recall@{1, 5, 10} față de aceleași imagini ca interogări (ar trebui să fie 1.0) și față de o separare păstrată pentru evaluare, cu etichetele ImageNet ca adevăr de referință.
Notă tehnică a traducerii: Valoarea 1.0 pentru interogarea acelorași imagini este așteptată numai dacă fiecare interogare rămâne în galerie, corespondentul identic este acceptat ca rezultat și nu apar erori de preprocesare sau indexare. Dacă se exclud auto-potrivirile sau se utilizează o separare distinctă, valoarea nu este garantată.
Termeni-cheie
| Termen | Cum este numit în practică | Ce înseamnă de fapt |
|---|---|---|
| Învățare metrică | „Modelați spațiul” | Antrenarea unui codificator astfel încât distanțele din spațiul său de ieșire să reflecte o similitudine-țintă |
| Pierdere triplet | „Trageți și împingeți” | L = max(0, d(a, p) - d(a, n) + margin); pierderea canonică de învățare metrică |
| Minare semi-hard | „Negative utile” | Negative mai departe de ancoră decât pozitivul, însă în interiorul marjei; empiric, cele mai informative |
| Pierdere bazată pe proxy | „Prototipuri de clasă” | Un proxy învățat pentru fiecare clasă; entropie încrucișată peste similitudinea cu proxy; fără minare de perechi |
| Recall@K | „Rata de reușită Top-K” | Fracția interogărilor cu cel puțin un rezultat corect în primele K |
| Regăsire de instanțe | „Găsiți exact acest obiect” | Potrivire fină; caracteristicile gata de utilizare au de obicei performanțe sub nivelul dorit |
| FAISS | „Biblioteca NN” | Biblioteca Facebook pentru cei mai apropiați vecini; acceptă indici exacți și aproximați |
| HNSW | „Index pe graf” | Hierarchical Navigable Small World; NN aproximativ rapid cu overhead mic de memorie |
Lecturi suplimentare
- FaceNet: A Unified Embedding for Face Recognition (Schroff et al., 2015) — lucrarea despre pierderea triplet / minarea semi-hard
- In Defense of the Triplet Loss for Person Re-Identification (Hermans et al., 2017) — ghid practic pentru ajustarea fină cu triplet
- Documentația FAISS — fiecare index, fiecare compromis
- SMoT: Metric Learning Taxonomy (Kim et al., 2021) — sondaj despre pierderile moderne și legăturile dintre ele
Notă tehnică a traducerii: Adresa URL păstrată literal în ultima lectură a originalului (
https://arxiv.org/abs/2010.06927) identifică o lucrare de optică cuantică, nu un sondaj despre învățarea metrică; prin urmare, nu verifică atribuirea „SMoT: Metric Learning Taxonomy”.
Sursă: Originalul în limba engleză
Navigare: ← Lecția 04.19 — OCR și înțelegerea documentelor · Faza 4 — Viziune computerizată · Lecția 04.21 — Detectarea punctelor-cheie și estimarea poziției → · Catalog complet