A 28.2. Sziámi és triplet cél alfejezetben felépített sziámi és triplet cél egy beágyazó (embedding) függvényt tanít: . A tanítás után a modell minőségét nem osztályozási pontossággal, hanem hasonlósági keresés (retrieval) feladatban mérjük: egy lekérdezéshez megkeressük a hozzá tartozó, releváns elemet egy nagy adatbázisban. Ez a fejezet azt a kérdést járja körül, hogyan lehet ezt a kiértékelést korrektül elvégezni.
Az előismeret a 28.2. Sziámi és triplet cél alfejezetből a pozitív/negatív pár és a triplet margin fogalma; a 27.3. Dice, IoU és aggregálás alfejezetből a csoportos aggregálás tanulsága; a 24.3. Mixed precision és nagyobb batch alfejezetből a numerikus kontroll; a 16.3. Elméleti és effektív recepciós mező alfejezetből pedig a reprezentáció minőségének szerepe. Induló kérdés: ha 30 query-kép mindegyikéhez pontosan egy releváns gallery-kép tartozik, és a modell a releváns elemet átlagosan a 4. helyre rangsorolja, mekkora a Recall@1 és a Recall@3? A válaszhoz először pontos definíciók kellenek.
Legyen a query (lekérdezés) halmaz, a gallery (keresési adatbázis) halmaz. Mindkettő ugyanabból a képtérből származik, de a felhasználási szerepük eltér: a query az, amit a felhasználó bead, a gallery az, amiben keresünk. Az embeddingeket és mátrixokba rendezzük; minden sor egy -dimenziós vektor.
A relevancia egy bináris reláció: . A GlyphLab példánkban a relevanciafeltétel a scene_id egyezés: két kép akkor és csak akkor releváns pár, ha ugyanabból a jelenetből származik. Az osztálycímke (vertical/horizontal/diagonal) itt nem relevancia, mert a retrieval cél a konkrét jelenet felismerése, nem az orientációé.
A ranking (rangsor) egy query esetén a gallery elemeinek egy permutációja, amelyre a hasonlósági pontszám csökkenő: A pontszám lehet koszinusz-hasonlóság, negatív euklideszi távolság vagy dot product. A halmaz az első találat indexeit tartalmazza: .
A Recall@k (röviden ) azt méri, hogy a releváns elem(ek) hányad része kerül be az első találat közé. A duplicate olyan elem, amely tartalmilag azonos vagy közel azonos egy másikkal, például ugyanannak a képnek két másolata, vagy ugyanannak a jelenetnek két, alig eltérő view-ja. A group split olyan adatfelosztás, amelyben a független mintavételi egység (itt a scene_id) nem keveredik a részhalmazok között.
Az ANN (Approximate Nearest Neighbor) közelítő legközelebbi szomszéd keresést jelent. Nagy gallery (milliós nagyságrend) esetén a pontos brute-force összehasonlítás költsége túl nagy; az ANN indexek (például HNSW, IVF-PQ) gyors, de közelítő találatot adnak. A kiértékelésnél ezért külön kell választani a reprezentáció minőségét és a keresési index közelítési hibáját.
A fejezet levezetési kötelezettsége a Recall@k pontos definíciója a GlyphLab példában. A kiinduló objektumok: és halmazok, embedding, hasonlósági pontszám, valamint a relevancia reláció. A definícióhoz ki kell mondani a nevezőt és a találati feltételt.
Minden query-hez rendeljük a releváns gallery indexek halmazát: A GlyphLab esetén pontosan akkor, ha . Mivel a laborban minden jelenetből pontosan egy view kerül a query halmazba és pontosan egy másik view a gallery halmazba, a releváns halmaz minden query-re egyelemű: minden .
A query-level Recall@k definíciója: A nevező , a query-k száma. Az indikátor értéke 1, ha legalább egy releváns elem az első találat között van, különben 0. Ez a konvenció akkor természetes, ha egy query-hez kevés releváns elem tartozik, és a felhasználót az érdekli, hogy megtalálja-e valamelyiket.
Az item-level Recall@k alternatív definíció: Itt a nevező az összes releváns pár száma. A két definíció akkor esik egybe, ha minden query-re , mert ekkor a számlálóban és a nevezőben is ugyanannyi elem van, és minden query legfeljebb egyszer számíthat találatnak. A GlyphLab kiegyenlített felépítése miatt a laborban a query-level használjuk, de a fogalmi különbséget mindig jelezzük.
Lemma (monotonitás). A query-level k-ban nem csökkenő: ha , akkor .
Bizonyítás. Mivel , ha , akkor is fennáll. Ezért az indikátor értéke minden -re nem csökken növelésével, így az átlaguk sem csökken.
A lemma triviális, de fontos: az értékek önmagukban csak a ismeretében értelmesek, és a nagyobb felé monoton nőnek.
A Recall@k nem érzékeny arra, hol van a releváns elem az első között. Ezt a rangsorérzékeny átlagos pontosság (AP) méri. Jelölje a gallery-elem rangját (1-től) a rangsorában, és a precisiont az -edik helyig.
Definíció (query AP, AP@k, mAP@k). Egy query átlagos pontossága
a -nál csonkolt változata
ahol a nevező , hogy a tökéletes rangsor értéke esetén is legyen. A query-re az AP nem értelmezett; ezeket kizárjuk az átlagból, és a számukat külön közöljük. A mAP@k a releváns elemmel rendelkező query-k -jának átlaga.
A GlyphLabban , ezért , ha a releváns elem rangja , különben : a 2. helyen talált pár -öt, az 5. helyen talált -t ér, míg a Recall@5 mindkettőt -nek számolja. A query/gallery irány választás kérdése; a laborban mindkét irányt kiértékeljük (view 0 → view 1 és view 1 → view 0), külön közöljük, és az összesített érték a query átlaga. A bizonytalanságot jelenetszintű bootstrap adja, amely egy jelenet mindkét irányú query-jét együtt húzza.
Tekintsünk egy háromelemű példát, amelyben a releváns párok , , . A dimenzió legyen , a hasonlóság pedig a normalizálatlan dot product. Az embeddingek:
| Vektor | Érték |
|---|---|
A dot product mátrix :
A rangsorok csökkenő hasonlóság szerint: - : (3.4), (1.6), (1.0); a releváns a 3. helyen van. - : (3.4), (1.8), (1.1); a releváns a 3. helyen van. - : (2.25), (2.2), (1.7); a releváns a 3. helyen van.
A találati indikátorok: , mert egyik releváns elem sincs az első helyen; ; . Az AP-alapú mérték ugyanerről: minden query-re , így , , tehát és , miközben . Ez a példa azt szemlélteti, hogy az önmagában nem jelenti azt, hogy a reprezentáció használhatatlan; a releváns elemek mind benne vannak a rangsorban, csak nem elöl.
A labor a GlyphLab adatot használja. A cél egy olyan query/gallery split elkészítése, amely tiszteletben tartja a scene_id mint független mintavételi egységet, majd egy egyszerű visszakeresési riport előállítása. A labor nem tanít modellt; egy rögzített, nem tanult feature-t használunk (a nyers pixel vektor L2-normalizálva), hogy a kiértékelési protokollt izoláljuk a modellválasztástól. Ez a baseline gyengének várható, de a split és a riport szerkezete korrekt.
Input: a lab/data/images.npy
(360,1,24,24) float32 tömb, a lab/data/metadata.csv. A
munkakönyvtár a lab/ gyökér, ahol a data/
mappa található. A split a test címkéjű sorokra szűkül: 30
jelenet, 60 kép. A query halmaz a view_id == 0, a gallery
halmaz a view_id == 1 sorokból áll. A relevancia a scene_id
egyezés. A rendezés scene_id szerint garantálja, hogy a releváns pár a
főátlón legyen.
A kód NumPy-t és a Python szabványos csv modulját
használja; nincs GPU, nincs véletlen szám, így seed nem szükséges. A
dtype float32.
# lab/retrieval_report.py — futtatás: cd lab && python retrieval_report.py
import csv
import numpy as np
DATA = "data"
K_VALUES = [1, 3, 5, 10]
def load_metadata(path):
rows = []
with open(path, newline="") as f:
for r in csv.DictReader(f):
rows.append(r)
return rows
def main():
# 1. Adat betöltése és test-szűrés
rows = load_metadata(f"{DATA}/metadata.csv")
test_rows = [r for r in rows if r["split"] == "test"]
# 2. Csoportszintű query/gallery split: view_id rögzített
query_rows = sorted(
[r for r in test_rows if r["view_id"] == "0"],
key=lambda r: r["scene_id"] # a scene_id szöveg, pl. "G0-0003"
)
gallery_rows = sorted(
[r for r in test_rows if r["view_id"] == "1"],
key=lambda r: r["scene_id"] # a scene_id szöveg, pl. "G0-0003"
)
# 3. Ellenőrzés: ugyanazok a scene_id-k, mindkét halmazban 30 elem
q_scenes = {r["scene_id"] for r in query_rows}
g_scenes = {r["scene_id"] for r in gallery_rows}
assert q_scenes == g_scenes, "A query és gallery scene_id halmaza eltér!"
assert len(query_rows) == 30 and len(gallery_rows) == 30
# 4. Képek betöltése index szerint, float32
images = np.load(f"{DATA}/images.npy").astype(np.float32) # (360,1,24,24)
q_idx = np.array([int(r["index"]) for r in query_rows])
g_idx = np.array([int(r["index"]) for r in gallery_rows])
Q = images[q_idx].reshape(len(q_idx), -1) # (30, 576)
G = images[g_idx].reshape(len(g_idx), -1) # (30, 576)
# 5. Nyers pixel feature: L2-normalizálás
Qf = Q / np.linalg.norm(Q, axis=1, keepdims=True)
Gf = G / np.linalg.norm(G, axis=1, keepdims=True)
# 6. Hasonlóság: koszinusz (dot product normalizált vektorokon)
S = Qf @ Gf.T # (30, 30)
# 7. Ranking: csökkenő sorrend
rank = np.argsort(-S, axis=1) # (30, 30)
# 8. Relevancia: a rendezés miatt a főátló; 1-től számozott rang, mindkét irányban
def relevant_rank(sim):
order = np.argsort(-sim, axis=1, kind="stable")
return np.array([int(np.where(order[i] == i)[0][0]) + 1 for i in range(len(sim))])
ranks = {"view0->view1": relevant_rank(S), "view1->view0": relevant_rank(S.T)}
def ap_at_k(rho, k): # |Rel| = 1: AP@k = 1/rang, ha rang <= k
return np.where(rho <= k, 1.0 / rho, 0.0)
# 9. Visszakeresési riport
print("GlyphLab test retrieval riport (nyers pixel baseline)")
print(f"Query: {len(q_idx)}, Gallery: {len(g_idx)}")
for name, rho in ranks.items():
print(name, " ".join(f"R@{k}={np.mean(rho <= k):.4f}" for k in K_VALUES),
f"mAP@5={ap_at_k(rho, 5).mean():.4f}")
both = np.concatenate(list(ranks.values()))
print("mindkét irány:", " ".join(f"R@{k}={np.mean(both <= k):.4f}" for k in K_VALUES),
f"mAP@5={ap_at_k(both, 5).mean():.4f}")
if __name__ == "__main__":
main()A lab-profilban (NumPy 2.3.5) mért kimenet:
GlyphLab test retrieval riport (nyers pixel baseline)
Query: 30, Gallery: 30
view0->view1 R@1=1.0000 R@3=1.0000 R@5=1.0000 R@10=1.0000 mAP@5=1.0000
view1->view0 R@1=1.0000 R@3=1.0000 R@5=1.0000 R@10=1.0000 mAP@5=1.0000
mindkét irány: R@1=1.0000 R@3=1.0000 R@5=1.0000 R@10=1.0000 mAP@5=1.0000
Az eredmény elsőre meglepő, de a generátorból következik: egy jelenet
két nézete ugyanazt a vonalat ugyanott, ugyanakkora vastagsággal
tartalmazza, csak a zaj más, ezért már a nyers pixelvektorok
koszinusz-hasonlósága is a saját párt teszi az első helyre. Ez a feladat
így nem méri a tanult reprezentáció minőségét; csak a
split, a relevanciafeltétel és a riport helyességét ellenőrzi (a
releváns elem a főátlón van, a Recall@k monoton). Ha a query a
corrupted_test.npy 0-s nézete, a gallery a tiszta 1-es
nézet, a nyers pixel baseline még mindig
és mAP@5
.
Egy tanult embedding értékeléséhez ezért nehezebb relevanciafeltétel
kell, például azonos osztályú, de más jelenetből származó képek
visszakeresése, vagy erősebb torzítás; ezt a laborfeladatok tervezik
meg.
A leggyakoribb hiba, hogy a query/gallery splitet a view_id-k figyelembevétele nélkül, véletlenszerűen készítik, vagy a relevanciát nem a scene_id-hoz, hanem valamilyen gyenge címkéhez kötik. Ha például a test halmaz 60 képét véletlenszerűen kettéosztjuk 30 query-re és 30 gallery-re, akkor előfordulhat, hogy ugyanannak a jelenetnek mindkét view-ja a query halmazba kerül, és a gallery-ben nincs releváns párja. Ennél alattomosabb eset, ha a query és gallery ugyanazt a view-ot tartalmazza (például mindkét halmazban a view0 szerepel). Ekkor a nyers pixel baseline koszinusz-hasonlósága 1.0, így a Recall@1 triviálisan 1.0 lesz, függetlenül a modell minőségétől. Az ilyen közel duplikált nézet átengedése túl optimista retrieval eredményt ad.
Ellenpéldaként vegyünk egy olyan splitet, ahol a query és gallery is a view0 képekből áll, csak más scene-ekből, és a relevanciafeltétel tévesen az osztálycímkére épül. Ekkor a modellnek nem kell megtanulnia a jelenet azonosítását; elég az orientációt felismernie, ami egy sokkal könnyebb feladat. A Recall@k így a kép-osztályozás pontosságát méri, nem a hasonlósági keresést. A helyes protokoll ezért: a query minden jelenetből az egyik view, a gallery a másik view, és a relevancia a scene_id egyezés.
A fejezet állításai ellenőrizhetők: (1) a képlet nevezője , és a találati feltétel explicit; (2) a monotonitási lemma bizonyítása a halmaz bővülésén alapul; (3) a labor kódja assert-tel védi a scene_id egyezést; (4) a gyakori tévedés ellenpéldája bemutatja a view_id keverés következményét.
Visszautalás 27.3. Dice, IoU és aggregálás alfejezetre: ahogyan a per-image és a globális IoU/Dice aggregálás más súlyozást ad, itt a query-level és item-level Recall@k is eltérhet, ha a releváns elemek száma nem egyenletes. Visszautalás 24.3. Mixed precision és nagyobb batch alfejezetre: az ANN index közelítési hibája ugyanolyan numerikus kontrollt igényel, mint az AMP gradiensskálázás; a pontos brute-force ranking a referencia. Visszautalás 16.3. Elméleti és effektív recepciós mező alfejezetre: a nyers pixel feature nem lát szemantikus kontextust, ezért a recepciós mező és a tanult reprezentáció minősége döntő a retrieval sikeréhez.
F1 (fogalmi, nehézség 1). Definiáld a query, gallery és relevancia szerepét a GlyphLab retrieval feladatban. Miért nem az osztálycímke (vertical/horizontal/diagonal) a relevanciafeltétel? Adj egy konkrét példát, amelyben ugyanaz az osztálycímke két különböző jelenethez tartozik.
F2 (levezetési, nehézség 2). Adott egy 4 query-ből és 5 gallery-ből álló kiértékelés. A relevancia mátrix sorai: A ranking mátrix sorai (a gallery indexek csökkenő hasonlóság szerint): Számítsd ki a query-level , és értékeket. Indokold minden lépésben a találati feltételt.
F3 (implementációs, nehézség 2). Írj egy
recall_at_k(rank_matrix, rel_matrix, k) függvényt, amely a
fenti query-level definíciót valósítja meg. Az input
rank_matrix
egész, rel_matrix
0/1, k pozitív egész. Ellenőrizd a függvényt az F2 adatain,
egy üres relevancia mátrixon (0 eredmény), és egy olyan mátrixon, ahol
minden releváns elem az első helyen van (1 eredmény).
F4 (hibakeresési, nehézség 2). Az alábbi kódrészlet a test halmazból készít query/gallery splitet. Mi a hiba, mi a következménye, és hogyan javítanád? Ellenőrzésként mutasd meg, hogy a javított verzió garantálja a releváns pár jelenlétét.
# Hibás split: véletlenszerű keverés scene_id és view_id nélkül
test_meta = meta[meta.split == "test"].sample(frac=1, random_state=42)
query_meta = test_meta.iloc[:30]
gallery_meta = test_meta.iloc[30:]F5 (kísérleti/transzfer, nehézség 3). Tegyük fel, hogy a gallery mérete 1 millió kép, és a pontos brute-force ranking túl lassú. Tervezz kísérletet, amely egy ANN index (például HNSW) bevezetése előtt és után méri a Recall@k-t, és elkülöníti az index közelítési hibáját a reprezentáció minőségétől. Add meg a protokollt, a baseline-t, a metrikát és az érvényességi korlátot.
F1 megoldása. A query a felhasználó által beadott keresési kép, a gallery a keresési adatbázis. A relevancia azt mondja meg, hogy egy gallery elem a query helyes találata-e. A GlyphLab példában a relevanciafeltétel a scene_id egyezés: minden jelenetből két view készült, és a query–gallery pár akkor releváns, ha ugyanabból a jelenetből származik. Az osztálycímke nem relevancia, mert például két különböző vertical jelenet (két különböző függőleges alakzat, más pozícióban, más vastagsággal) azonos osztályú, de a retrieval feladatban nem felcserélhetők: a felhasználó a konkrét jelenet másik nézetét keresi. Az osztálycímke alapú relevancia egy könnyebb, osztályozási feladatot mérne.
F2 megoldása. A query-level definíció szerint minden query-re azt nézzük, hogy legalább egy releváns elem az első találat között van-e. A ranking mátrix és a relevancia mátrix alapján:
Összegzés: ; ; . Az azért egyezik meg az -gyel, mert a releváns elemei közül egyik sincs az első két találatban.
F3 megoldása. Az alábbi függvény a query-level definíciót valósítja meg.
import numpy as np
def recall_at_k(rank_matrix, rel_matrix, k):
"""Query-level Recall@k.
rank_matrix: (Nq, Ng) int32, minden sor a gallery indexek csökkenő sorrendben.
rel_matrix: (Nq, Ng) 0/1, relevancia mátrix.
"""
Nq = rank_matrix.shape[0]
hits = 0
for i in range(Nq):
top_k = rank_matrix[i, :k]
if np.any(rel_matrix[i, top_k]):
hits += 1
return hits / Nq
# Teszt az F2 adataival
rank = np.array([[2,0,1,3,4],
[1,4,0,2,3],
[3,2,0,1,4],
[0,1,2,3,4]], dtype=np.int64)
rel = np.array([[0,1,0,0,0],
[1,0,1,0,0],
[0,0,0,1,0],
[0,0,0,0,0]], dtype=np.int64)
assert recall_at_k(rank, rel, 1) == 0.25
assert recall_at_k(rank, rel, 2) == 0.25
assert recall_at_k(rank, rel, 3) == 0.75
# Üres relevancia: 0
assert recall_at_k(rank, np.zeros_like(rel), 3) == 0.0
# Minden releváns elem az első helyen: 1
rel_perfect = np.zeros_like(rel)
rel_perfect[np.arange(rank.shape[0]), rank[:, 0]] = 1
assert recall_at_k(rank, rel_perfect, 1) == 1.0
print("Minden ellenőrzés sikeres.")A függvény alakellenőrzése: a rank_matrix soronkénti
permutáció, a rel_matrix bináris. A k értékét
a kód nem ellenőrzi, de a feladatban
;
éles kódban ezt érdemes asserttel védeni.
F4 megoldása. A hiba oka, hogy a
sample(frac=1) véletlenszerűen keveri a sorokat, és az első
30 sor query-nek, a többi gallery-nek való kijelölése nem veszi
figyelembe sem a scene_id-t, sem a view_id-t.
Következmény: ugyanannak a jelenetnek mindkét view-ja kerülhet ugyanabba
a halmazba, így egy query-nek nem lesz releváns párja a gallery-ben,
vagy éppen ugyanaz a view lesz query és gallery is (ha a halmazok
átfednek, ami itt nem, de más keverésnél előfordulhat). A relevancia
mátrix nem garantáltan főátló, és a kiértékelés nem méri a modell valódi
hasonlósági teljesítményét.
A javítás: a query halmaz legyen a view_id == 0 sorok
halmaza, a gallery a view_id == 1 sorok halmaza, mindkettő
a test splitből, és mindkettőt rendezzük
scene_id szerint. Ez garantálja, hogy minden query-hez
pontosan egy releváns gallery elem tartozik, és a releváns pár a főátlón
van. A javított kód megegyezik a labor kódjának 2. lépésével.
Ellenőrzés: set(query_scene_ids) == set(gallery_scene_ids)
és mindkét halmaz mérete 30.
F5 megoldása (megoldásminta). A kísérlet célja annak kimutatása, hogy az ANN index bevezetése mennyivel rontja a Recall@k-t a pontos brute-force rankinghez képest. Protokoll:
ef_search, IVF-PQ kódkönyvek
száma). Az indexet a gallery embeddingeken építjük.A hasonlósági keresés kiértékelése a query/gallery split, a relevanciafeltétel és a ranking pontos definícióján alapul. A query-level Recall@k nevezője a query-k száma, találati feltétele, hogy legalább egy releváns elem az első k találat között legyen; a GlyphLab példában a relevancia a scene_id egyezés. A kézi példa megmutatta, hogy az R@1=0 mellett az R@3=1 is előfordulhat. A labor csoportszintű splitet készített view0/view1 alapon, és nyers pixel baseline riportot adott. A leggyakoribb hiba a view_id-k keverése, amely közel duplikált nézetekkel irreálisan magas recallt okoz. Nagy gallery esetén az ANN index közelítési hibáját külön kell mérni a reprezentáció minőségétől.
A következő alkalmazás 28.4. VII. ellenőrző pont: CNN alkalmazási szerződés, a VII. ellenőrző pont: a CNN alkalmazási szerződés számonkéri a retrieval split, a metrika és az ANN kontroll együttes használatát egy teljes alkalmazási döntésben.
Felhasznált források: