28.3 Retrieval-értékelés és adatfelosztás

28.3.1 Cél, előismeret és induló kérdés

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: f:ℝC×H×W→ℝdf:\mathbb R^{C\times H\times W}\to\mathbb R^d. 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.

28.3.2 Fogalmak és jelölések

Legyen Q={q1,…,qNq}Q=\{q_1,\dots,q_{N_q}\} a query (lekérdezés) halmaz, G={g1,…,gNg}G=\{g_1,\dots,g_{N_g}\} 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 Xq=f(Q)∈ℝNq×dX_q=f(Q)\in\mathbb R^{N_q\times d} és Xg=f(G)∈ℝNg×dX_g=f(G)\in\mathbb R^{N_g\times d} mátrixokba rendezzük; minden sor egy dd-dimenziós vektor.

A relevancia egy bináris reláció: rel(q,g)∈{0,1}rel(q,g)\in\{0,1\}. 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 qiq_i esetén a gallery elemeinek egy πi\pi_i permutációja, amelyre a hasonlósági pontszám csökkenő: s(qi,gπi(1))≥s(qi,gπi(2))≥⋯≥s(qi,gπi(Ng)).s(q_i,g_{\pi_i(1)})\ge s(q_i,g_{\pi_i(2)})\ge\cdots\ge s(q_i,g_{\pi_i(N_g)}). A pontszám lehet koszinusz-hasonlóság, negatív euklideszi távolság vagy dot product. A Topk(i)Top_k(i) halmaz az első kk találat indexeit tartalmazza: Topk(i)={πi(1),…,πi(k)}Top_k(i)=\{\pi_i(1),\dots,\pi_i(k)\}.

A Recall@k (röviden R@kR@k) azt méri, hogy a releváns elem(ek) hányad része kerül be az első kk 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 O(Ngd)O(N_g d) 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.

28.3.3 Levezetés: a Recall@k nevezője és a relevanciafeltétel

A fejezet levezetési kötelezettsége a Recall@k pontos definíciója a GlyphLab példában. A kiinduló objektumok: QQ és GG halmazok, ff embedding, ss 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 qiq_i query-hez rendeljük a releváns gallery indexek halmazát: Rel(i)={j∈{1,…,Ng}:rel(qi,gj)=1}.Rel(i)=\{j\in\{1,\dots,N_g\}: rel(q_i,g_j)=1\}. A GlyphLab esetén rel(qi,gj)=1rel(q_i,g_j)=1 pontosan akkor, ha scene_id(qi)=scene_id(gj)scene\_id(q_i)=scene\_id(g_j). 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ű: |Rel(i)|=1|Rel(i)|=1 minden i∈{1,…,Nq}i\in\{1,\dots,N_q\}.

A query-level Recall@k definíciója: R@k=1Nq∑i=1Nq𝟙[Rel(i)∩Topk(i)≠∅].R@k=\frac{1}{N_q}\sum_{i=1}^{N_q}\mathbb{1}\Big[Rel(i)\cap Top_k(i)\neq\emptyset\Big]. A nevező NqN_q, a query-k száma. Az indikátor értéke 1, ha legalább egy releváns elem az első kk 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ó: Ritem@k=∑i=1Nq|Rel(i)∩Topk(i)|∑i=1Nq|Rel(i)|.R_{item}@k=\frac{\sum_{i=1}^{N_q}|Rel(i)\cap Top_k(i)|}{\sum_{i=1}^{N_q}|Rel(i)|}. Itt a nevező az összes releváns pár száma. A két definíció akkor esik egybe, ha minden query-re |Rel(i)|=1|Rel(i)|=1, 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 R@kR@k használjuk, de a fogalmi különbséget mindig jelezzük.

Lemma (monotonitás). A query-level R@kR@k k-ban nem csökkenő: ha k1≤k2k_1\le k_2, akkor R@k1≤R@k2R@k_1\le R@k_2.

Bizonyítás. Mivel Topk1(i)⊆Topk2(i)Top_{k_1}(i)\subseteq Top_{k_2}(i), ha Rel(i)∩Topk1(i)≠∅Rel(i)\cap Top_{k_1}(i)\neq\emptyset, akkor Rel(i)∩Topk2(i)≠∅Rel(i)\cap Top_{k_2}(i)\neq\emptyset is fennáll. Ezért az indikátor értéke minden ii-re nem csökken kk növelésével, így az átlaguk sem csökken. ▫\square

A lemma triviális, de fontos: az R@kR@k értékek önmagukban csak a kk ismeretében értelmesek, és a nagyobb kk felé monoton nőnek.

A Recall@k nem érzékeny arra, hol van a releváns elem az első kk között. Ezt a rangsorérzékeny átlagos pontosság (AP) méri. Jelölje ρi(j)\rho_i(j) a gjg_j gallery-elem rangját (1-től) a qiq_i rangsorában, és Pi(r)=|Rel(i)∩Topr(i)|/rP_i(r)=|Rel(i)\cap Top_r(i)|/r a precisiont az rr-edik helyig.

Definíció (query AP, AP@k, mAP@k). Egy |Rel(i)|≥1|Rel(i)|\ge1 query átlagos pontossága

AP(i)=1|Rel(i)|∑j∈Rel(i)Pi(ρi(j)),AP(i)=\frac{1}{|Rel(i)|}\sum_{j\in Rel(i)}P_i\bigl(\rho_i(j)\bigr),

a kk-nál csonkolt változata

AP@k(i)=1min(|Rel(i)|,k)∑j∈Rel(i):ρi(j)≤kPi(ρi(j)),AP@k(i)=\frac{1}{\min(|Rel(i)|,k)}\sum_{j\in Rel(i):\,\rho_i(j)\le k}P_i\bigl(\rho_i(j)\bigr),

ahol a nevező min(|Rel(i)|,k)\min(|Rel(i)|,k), hogy a tökéletes rangsor értéke k<|Rel(i)|k<|Rel(i)| esetén is 11 legyen. A |Rel(i)|=0|Rel(i)|=0 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 AP@kAP@k-jának átlaga.

A GlyphLabban |Rel(i)|=1|Rel(i)|=1, ezért AP@k(i)=1/ρiAP@k(i)=1/\rho_i, ha a releváns elem rangja ρi≤k\rho_i\le k, különben 00: a 2. helyen talált pár 0,50{,}5-öt, az 5. helyen talált 0,20{,}2-t ér, míg a Recall@5 mindkettőt 11-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 2Nq2N_q query átlaga. A bizonytalanságot jelenetszintű bootstrap adja, amely egy jelenet mindkét irányú query-jét együtt húzza.

28.3.4 Kézi példa

Tekintsünk egy háromelemű példát, amelyben a releváns párok (q1,g1)(q_1,g_1), (q2,g2)(q_2,g_2), (q3,g3)(q_3,g_3). A dimenzió legyen d=2d=2, a hasonlóság pedig a normalizálatlan dot product. Az embeddingek:

Vektor Érték
q1q_1 (2,0)(2,0)
q2q_2 (0,2)(0,2)
q3q_3 (1,1)(1,1)
g1g_1 (0.5,1.7)(0.5,1.7)
g2g_2 (1.7,0.55)(1.7,0.55)
g3g_3 (0.8,0.9)(0.8,0.9)

A dot product mátrix Sij=qi⊤gjS_{ij}=q_i^\top g_j:

S=[1.03.41.63.41.11.82.22.251.7].S=\begin{bmatrix}1.0 & 3.4 & 1.6\\3.4 & 1.1 & 1.8\\2.2 & 2.25 & 1.7\end{bmatrix}.

A rangsorok csökkenő hasonlóság szerint: - q1q_1: g2g_2 (3.4), g3g_3 (1.6), g1g_1 (1.0); a releváns g1g_1 a 3. helyen van. - q2q_2: g1g_1 (3.4), g3g_3 (1.8), g2g_2 (1.1); a releváns g2g_2 a 3. helyen van. - q3q_3: g2g_2 (2.25), g1g_1 (2.2), g3g_3 (1.7); a releváns g3g_3 a 3. helyen van.

A találati indikátorok: R@1=0R@1=0, mert egyik releváns elem sincs az első helyen; R@2=0R@2=0; R@3=3/3=1R@3=3/3=1. Az AP-alapú mérték ugyanerről: minden query-re ρi=3\rho_i=3, így AP@2(i)=0AP@2(i)=0, AP@5(i)=1/3AP@5(i)=1/3, tehát mAP@2=0\mathrm{mAP}@2=0 és mAP@5=1/3\mathrm{mAP}@5=1/3, miközben R@5=1R@5=1. Ez a példa azt szemlélteti, hogy az R@1=0R@1=0 önmagában nem jelenti azt, hogy a reprezentáció használhatatlan; a releváns elemek mind benne vannak a rangsorban, csak nem elöl.

28.3.5 Labor: csoportszintű split és visszakeresési riport

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 R@1=0,967R@1=0{,}967 és mAP@5 =0,983=0{,}983. 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.

28.3.6 Gyakori tévedés és ellenpélda

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.

28.3.7 Önellenőrzés és visszautalás

A fejezet állításai ellenőrizhetők: (1) a R@kR@k képlet nevezője NqN_q, és a találati feltétel explicit; (2) a monotonitási lemma bizonyítása a TopkTop_k 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.

28.3.8 Feladatok

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: Rel=[01000101000001000000].Rel=\begin{bmatrix}0&1&0&0&0\\1&0&1&0&0\\0&0&0&1&0\\0&0&0&0&0\end{bmatrix}. A ranking mátrix sorai (a gallery indexek csökkenő hasonlóság szerint): π=[20134140233201401234].\pi=\begin{bmatrix}2&0&1&3&4\\1&4&0&2&3\\3&2&0&1&4\\0&1&2&3&4\end{bmatrix}. Számítsd ki a query-level R@1R@1, R@2R@2 és R@3R@3 é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 (Nq,Ng)(N_q,N_g) egész, rel_matrix (Nq,Ng)(N_q,N_g) 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.

28.3.9 Megoldások és indoklás

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ő kk találat között van-e. A ranking mátrix és a relevancia mátrix alapján:

Összegzés: R@1=(0+0+1+0)/4=0.25R@1=(0+0+1+0)/4=0.25; R@2=(0+0+1+0)/4=0.25R@2=(0+0+1+0)/4=0.25; R@3=(1+1+1+0)/4=0.75R@3=(1+1+1+0)/4=0.75. Az R@2=0.25R@2=0.25 azért egyezik meg az R@1R@1-gyel, mert a q2q_2 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 1≤k≤Ng1\le k\le N_g; é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:

  1. Adat és split: Rögzített query halmaz (Nq=500N_q=500) és gallery (Ng=1000000N_g=1\,000\,000). A relevancia címkézett párokból adott.
  2. Baseline: Pontos brute-force koszinusz-hasonlóság, amely a referencia R@kR@k-t adja.
  3. Változó tényező: ANN index típusa és hiperparaméterei (pl. HNSW ef_search, IVF-PQ kódkönyvek száma). Az indexet a gallery embeddingeken építjük.
  4. Metrika: Query-level R@1R@1, R@10R@10, valamint az index által visszaadott lista átlagos átfedése a pontos top-10 listával (overlap@10).
  5. Kiértékelés: A pontos brute-force rankinget a teljes query halmazon kiszámítjuk egyszer. Az ANN indexet különböző hiperparaméterekkel validációs query részhalmazon hangoljuk; a végső konfigurációt a validációs R@10R@10-re választjuk. A teszt query halmazon a kiválasztott indexszel mérjük az R@kR@k-t és az overlapet.
  6. Következtetési határ: Az ANN R@kR@k eltérése a pontos R@kR@k-tól az index közelítési hibája, nem a reprezentáció minősége. A jelentésben a kettőt külön oszlopban kell közölni. Az érvényességi korlát: az eredmény csak az adott indexre és adatméretre érvényes; más index vagy gallery méret esetén a mérést meg kell ismételni.

28.3.10 Rövid összefoglaló

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.

28.3.11 Következő alkalmazás és források

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: