27.2 Embedding és hasonlósági keresés

27.2.1 Cél és konkrét helyzet

Egy belső IT-hibajegyrendszerben dolgozol. A felhasználó beír egy rövid hibaleírást, például: „Nem tudok nyomtatni, hálózati csatlakozási gond van.” A rendszer feladata, hogy visszaadja a korábbi, már megoldott jegyek közül a leghasznosabbakat, hogy a támogató kolléga gyorsan lássa a korábbi megoldást. Ez klasszikus visszakeresési (retrieval) feladat: nem osztályozunk, hanem rangsorolunk.

A megfigyelési egység egy-egy dokumentum, azaz egy korábbi hibajegy szövege. A bemenet egy új lekérdezés szövege, amelyet a rendszer ugyanabba a vektortérbe képez le, mint a korábbi jegyeket. A kimenet a dokumentumok hasonlóság szerint csökkenő sorba rendezett listája, amelyből a top kk találatot mutatjuk meg. Az ellenőrzés két szinten történik: (1) numerikusan, a koszinusz-hasonlóság kézzel számolt értékeivel; (2) tartalmilag, emberi relevanciajelöléssel, amely megmondja, hogy a találat valóban hasznos volt-e.

Ebben az alfejezetben kis, előre megadott vektorokon dolgozunk, hogy a műveleteket kézzel is ellenőrizni tudd. Az ilyen vektorokat a gyakorlatban egy beágyazó modell (embedding model) állítja elő, de a keresési logika ugyanaz, függetlenül attól, hogy a vektor honnan származik.

Mielőtt a megoldást megírnád, becsüld meg fejben: ha a lekérdezés a (0.8,0.2,0.0)(0.8, 0.2, 0.0) vektor, és a dokumentumok között van egy (1.0,0.1,0.0)(1.0, 0.1, 0.0) vektor, akkor várhatóan ez lesz-e az első találat? A számítás után térj vissza ehhez a hipotézishez.

27.2.2 Fogalmi magyarázat

27.2.2.1 Beágyazás (embedding)

A Szöveges baseline és ritka mátrix alfejezetben látott TF-IDF a szöveget szavankénti ritka vektorrá alakította: minden dimenzió egy-egy szó, és a legtöbb érték nulla. A sűrű beágyazás ezzel szemben a szöveget egy fix hosszúságú, jellemzően 100–1000 dimenziós valós vektorrá képezi le, ahol minden komponens általában nullától különböző, és a dimenziók nem feleltethetők meg közvetlenül egy-egy szónak. A vektor a szöveg használati mintázatát tömöríti: a hasonló kontextusban előforduló, hasonló jelentésű szövegek hasonló vektort kapnak.

A vektor dimenzióját dd-vel jelöljük, tehát 𝐯∈ℝd\mathbf{v} \in \mathbb{R}^d. A dokumentumhalmazban nn dokumentum van, így a teljes mátrix alakja (n,d)(n, d): minden sor egy dokumentum, minden oszlop egy dimenzió. A vektorkomponenseknek itt nincs fizikai mértékegységük; a modell belső reprezentációjának tekintjük őket.

27.2.2.2 Koszinusz-hasonlóság (cosine similarity)

Két dd-dimenziós vektor hasonlóságát a köztük lévő szög koszinuszaként mérjük. A képlet:

cos(𝐚,𝐛)=𝐚⋅𝐛∥𝐚∥∥𝐛∥, \cos(\mathbf{a}, \mathbf{b}) = \frac{\mathbf{a} \cdot \mathbf{b}}{\|\mathbf{a}\| \, \|\mathbf{b}\|},

ahol

A koszinusz nem függ a vektorok hosszától, csak az irányuktól. Ha 𝐚=(1,1)\mathbf{a} = (1, 1) és 𝐛=(2,2)\mathbf{b} = (2, 2), akkor a kézi számítás:

𝐚⋅𝐛=1⋅2+1⋅2=4,∥𝐚∥=2,∥𝐛∥=8, \mathbf{a} \cdot \mathbf{b} = 1 \cdot 2 + 1 \cdot 2 = 4, \qquad \|\mathbf{a}\| = \sqrt{2}, \qquad \|\mathbf{b}\| = \sqrt{8},

cos=428=416=1. \cos = \frac{4}{\sqrt{2}\sqrt{8}} = \frac{4}{\sqrt{16}} = 1.

Az 11 érték azonos irányt jelent. Ha a vektorok merőlegesek, például (1,0)(1,0) és (0,1)(0,1), akkor a skalárszorzat 00, így a koszinusz is 00. Ha ellentétes irányúak, az érték −1-1.

A kereső gyakorlatban előre normalizáljuk a vektorokat: 𝐚′=𝐚/∥𝐚∥\mathbf{a}' = \mathbf{a} / \|\mathbf{a}\|. Ekkor ∥𝐚′∥=1\|\mathbf{a}'\| = 1, és a koszinusz egyszerűen a normalizált vektorok skalárszorzata. Ez azért kényelmes, mert a keresés egyetlen mátrix-skalárszorzássá válik.

27.2.2.3 Legközelebbi szomszéd (nearest neighbor) és visszakeresés

A legközelebbi szomszéd keresés formálisan: adott a 𝐪∈ℝd\mathbf{q} \in \mathbb{R}^d lekérdezési vektor és a {𝐝1,…,𝐝n}\{\mathbf{d}_1, \dots, \mathbf{d}_n\} dokumentumvektorok; a cél a legnagyobb cos(𝐪,𝐝i)\cos(\mathbf{q}, \mathbf{d}_i) értékű dokumentumok megtalálása. A top kk találatot rangsorolva adjuk vissza.

A visszakeresés (retrieval) ennél tágabb feladat: a releváns dokumentumok megtalálása egy gyűjteményben. Fontos különválasztani két kérdést:

  1. A reprezentáció minősége: jól írja-e le az embedding a szövegek hasonlóságát?
  2. A keresőindex működése: milyen gyorsan és milyen kompromisszummal találjuk meg a jelölteket?

Ez a fejezet elsősorban az első kérdést tanítja; a második a nagy adatos, elosztott környezetekben válik kritikussá.

27.2.2.4 Pontos és közelítő keresés (exact vs approximate)

A pontos (exact) keresés minden dokumentumvektort összehasonlít a lekérdezéssel. A költség O(n⋅d)O(n \cdot d): nn dokumentum, dd dimenzió. Néhány ezer dokumentumig ez tökéletesen vállalható egy CPU-s környezetben, és garantáltan megtalálja a legjobb találatot.

Nagyobb gyűjteménynél (nn milliós nagyságrend) a közelítő (approximate) módszerek jönnek szóba: fa-, gráf- vagy hash-alapú indexek, mint a HNSW vagy az LSH. Ezek gyorsabbak, de nem garantálják, hogy az abszolút legközelebbi szomszédot találják meg; a sebesség és a pontosság között trade-off van. A könyv fő útja kis adatos, ezért itt pontos keresést használunk, és az ANN-t csak megemlítjük.

27.2.2.5 Rangsor metrika

A visszakeresés értékeléséhez emberi relevanciajelölés kell. Egy adott lekérdezéshez minden dokumentumra megmondjuk, hogy releváns-e (1) vagy sem (0). A leggyakoribb metrika a találati pontosság (precision@k):

precision@k=releváns találatok a top k listábank. \text{precision@k} = \frac{\text{releváns találatok a top } k \text{ listában}}{k}.

Ez a metrika a Keresztvalidáció a függőségi szerkezethez alfejezetben tanult elvhez hasonlóan a feladat célját méri, nem a modell belső hibáját. Egyetlen lekérdezésre a precision@k értéke {0,1/k,…,1}\{0, 1/k, \dots, 1\} közül kerül ki; több lekérdezésre átlagolunk. A pontosság mellett érdemes a Standard error és konfidenciaintervallum alfejezetben tanult módon intervallumot is számolni, mert 10–20 lekérdezés kis minta.

27.2.3 Teljes minimális munkapélda

A példa bemenete öt szintetikus hibajegy, amelyeket kézzel rendelünk hozzá háromdimenziós vektorokhoz. A dimenziók itt mesterségesek; a módszer mechanizmusát mutatják, nem egy valódi embedding kimenetét.

A dokumentumok és vektoraik:

Azonosító Szöveg Vektor
d1 „A nyomtató nem csatlakozik a hálózathoz” (1.0,0.1,0.0)(1.0, 0.1, 0.0)
d2 „Hálózati hiba a nyomtatónál” (0.9,0.15,0.05)(0.9, 0.15, 0.05)
d3 „A számla értéke hibás” (0.0,1.0,0.1)(0.0, 1.0, 0.1)
d4 „Számlázási probléma” (0.05,0.95,0.0)(0.05, 0.95, 0.0)
d5 „A képernyő villog” (0.1,0.0,1.0)(0.1, 0.0, 1.0)

A lekérdezés: „Nem tudok nyomtatni, hálózati csatlakozási gond van”, vektora 𝐪=(0.8,0.2,0.0)\mathbf{q} = (0.8, 0.2, 0.0).

A keresést NumPy-val hajtjuk végre. A normalizált vektorokon a mátrix-skalárszorzat adja a koszinusz-hasonlóságokat.

import numpy as np

# Dokumentumvektorok, shape: (5, 3)
doc_vectors = np.array([
    [1.0, 0.1, 0.0],   # d1
    [0.9, 0.15, 0.05], # d2
    [0.0, 1.0, 0.1],   # d3
    [0.05, 0.95, 0.0], # d4
    [0.1, 0.0, 1.0],   # d5
])
query = np.array([0.8, 0.2, 0.0])

# Normalizálás: minden sor hossza 1 lesz
doc_norm = doc_vectors / np.linalg.norm(doc_vectors, axis=1, keepdims=True)
query_norm = query / np.linalg.norm(query)

# Normalizált vektorokon a skalárszorzat = koszinusz
cos_sim = doc_norm @ query_norm  # shape: (5,)
ranking = np.argsort(-cos_sim)   # csökkenő sorrend

print("Normalizált dokumentumnormák:", np.round(np.linalg.norm(doc_norm, axis=1), 6))
print("Koszinusz-hasonlóság:", np.round(cos_sim, 3))
print("Rangsor (0-indexelt dokumentum pozíció):", ranking)

Szemléltető, elméletileg várt kimenet:

Normalizált dokumentumnormák: [1. 1. 1. 1. 1.]
Koszinusz-hasonlóság: [0.989 0.986 0.241 0.238 0.096]
Rangsor (0-indexelt dokumentum pozíció): [0 1 2 3 4]

A dokumentumvektorok szándékosan úgy vannak kialakítva, hogy a d1 és d2 a „nyomtatás-hálózat” témához, a d3 és d4 a „számlázás” témához tartozzon, a d5 pedig a „képernyő” témához. A lekérdezés a nyomtatási témához áll közel, ezért az első két találatnak d1-nek és d2-nek kell lennie.

27.2.4 Eredmény értelmezése

A kimenet első sora a normalizálási invariant ellenőrzése: minden normalizált dokumentumvektor hossza 11 (a lebegőpontos kerekítésből adódó 10−610^{-6} tolerancián belül). Ez garantálja, hogy a mátrix-skalárszorzat valóban koszinusz-hasonlóságot ad.

A második sor a hasonlósági értékek. A d1-re kapott 0.9890.989 kézi kontrollja:

𝐪⋅𝐝1=0.8⋅1.0+0.2⋅0.1+0.0⋅0.0=0.82, \mathbf{q} \cdot \mathbf{d}_1 = 0.8 \cdot 1.0 + 0.2 \cdot 0.1 + 0.0 \cdot 0.0 = 0.82,

∥𝐪∥=0.82+0.22=0.68≈0.8246,∥𝐝1∥=1.02+0.12=1.01≈1.0050, \|\mathbf{q}\| = \sqrt{0.8^2 + 0.2^2} = \sqrt{0.68} \approx 0.8246, \qquad \|\mathbf{d}_1\| = \sqrt{1.0^2 + 0.1^2} = \sqrt{1.01} \approx 1.0050,

cos≈0.820.8246⋅1.0050≈0.989. \cos \approx \frac{0.82}{0.8246 \cdot 1.0050} \approx 0.989.

A d3-ra hasonlóan: 𝐪⋅𝐝3=0.2\mathbf{q} \cdot \mathbf{d}_3 = 0.2, ∥𝐝3∥≈1.0050\|\mathbf{d}_3\| \approx 1.0050, így cos≈0.241\cos \approx 0.241. A d5-re: 𝐪⋅𝐝5=0.08\mathbf{q} \cdot \mathbf{d}_5 = 0.08, így cos≈0.096\cos \approx 0.096. A rangsor tehát d1, d2, d3, d4, d5 — ez megfelel a tematikus elrendezésnek: a nyomtatási témájú dokumentumok elöl, a számlázási témájúak középen, a képernyő téma hátul.

A hasonlósági pontszám nem relevanciaítélet. Azt, hogy a d1 vagy a d2 valóban hasznos-e a felhasználó számára, emberi jelölés dönti el. A numerikus sorrend csak jelölteket ad.

Hasonlósági keresésnél különböztessük meg a koszinusz-hasonlóságot és az euklideszi távolságot; normalizált vektorokra a rangsoruk összefügg, nyers vektorokra nem általánosan.

27.2.5 Feladatok

1. Értelmezés. Igazold képlettel, hogy ha mindkét vektort ugyanazzal a pozitív skalárral szorozzuk, a koszinusz-hasonlóság nem változik. Miért fontos ez a gyakorlatban, ha az embedding-modell különböző hosszúságú vektorokat ad vissza?

2. Megvalósítás. Írj egy top_k_search függvényt, amely megkapja a dokumentumvektorok (n,d)(n, d) mátrixát, a lekérdezésvektort és a kk értéket, majd visszaadja a legnagyobb koszinusz-hasonlóságú kk dokumentum azonosítóját (például a "d1" sztringet) csökkenő pontszám szerint. Futtasd a fenti példán k=2k=2 értékkel; az elvárt eredmény ["d1", "d2"].

3. Hibadiagnózis. Egy kolléga a hasonlóságot normalizálás nélkül, np.dot(a, b) értékkel számolta. Adj konkrét ellenpéldát, ahol ez a módszer téves rangsort ad. Használd a 𝐪=(1,1)\mathbf{q} = (1, 1) lekérdezést és a 𝐝1=(10,0)\mathbf{d}_1 = (10, 0), 𝐝2=(2,2)\mathbf{d}_2 = (2, 2) dokumentumokat. Számítsd ki mindkét módszerrel a sorrendet, és magyarázd meg az eltérést.

4. Megváltozott feltétel és relevanciajelölési terv. Cseréld a lekérdezést a „számlázási gond” témára, például 𝐪′=(0.0,0.9,0.0)\mathbf{q}' = (0.0, 0.9, 0.0)-ra. Számítsd ki kézzel a koszinusz-hasonlóságot d3-ra és d4-re, és add meg az új várható rangsort. Ezután írj egy emberi relevanciajelölési tervet: 10 lekérdezés, mindegyikhez a top 3 találat, bináris relevanciajelölés (0/1). Számítsd ki a precision@3 értékét az alábbi fiktív jelölési eredményekre: 10 lekérdezésből 4 esetben 3 releváns találat, 3 esetben 2, 2 esetben 1, 1 esetben 0 releváns találat volt a top 3-ban.

27.2.6 Megoldások és indoklás

1. megoldás. Legyen α>0\alpha > 0 skalár. A skalárszorzat és a norma homogenitásából:

cos(α𝐚,α𝐛)=(α𝐚)⋅(α𝐛)∥α𝐚∥∥α𝐛∥=α2(𝐚⋅𝐛)α∥𝐚∥⋅α∥𝐛∥=𝐚⋅𝐛∥𝐚∥∥𝐛∥. \cos(\alpha \mathbf{a}, \alpha \mathbf{b}) = \frac{(\alpha \mathbf{a}) \cdot (\alpha \mathbf{b})}{\|\alpha \mathbf{a}\| \, \|\alpha \mathbf{b}\|} = \frac{\alpha^2 (\mathbf{a} \cdot \mathbf{b})}{\alpha \|\mathbf{a}\| \cdot \alpha \|\mathbf{b}\|} = \frac{\mathbf{a} \cdot \mathbf{b}}{\|\mathbf{a}\| \|\mathbf{b}\|}.

A α2\alpha^2 mind a számlálóban, mind a nevezőben kiesik. A gyakorlatban ez azt jelenti, hogy ha az embedding-modell egy dokumentumot hosszabb vagy rövidebb vektorral reprezentál, az a hasonlósági sorrendet nem befolyásolja, amíg az irány azonos.

2. megoldás. Függvény azonosítókkal; a dokumentumazonosítókat a mátrixszal párhuzamos listában adjuk át.

import numpy as np

def top_k_search(doc_vectors, doc_ids, query, k):
    doc_norm = doc_vectors / np.linalg.norm(doc_vectors, axis=1, keepdims=True)
    q_norm = query / np.linalg.norm(query)
    cos_sim = doc_norm @ q_norm
    top_indices = np.argsort(-cos_sim)[:k]
    return [(doc_ids[i], round(float(cos_sim[i]), 3)) for i in top_indices]

doc_ids = ["d1", "d2", "d3", "d4", "d5"]
print(top_k_search(doc_vectors, doc_ids, query, 2))

Szemléltető, elméletileg várt kimenet:

[('d1', 0.989), ('d2', 0.986)]

Ellenőrző invariant: a visszaadott lista hossza kk; a pontszámok monoton csökkenőek; minden pontszám [−1,1][-1, 1]-ben van.

3. megoldás. A normalizálatlan skalárszorzat: 𝐪⋅𝐝1=10\mathbf{q} \cdot \mathbf{d}_1 = 10, 𝐪⋅𝐝2=4\mathbf{q} \cdot \mathbf{d}_2 = 4, tehát a téves rangsor d1 előbb, mint d2. A koszinusz-hasonlóság:

cos(𝐪,𝐝1)=102⋅10=12≈0.707,cos(𝐪,𝐝2)=42⋅8=1. \cos(\mathbf{q}, \mathbf{d}_1) = \frac{10}{\sqrt{2} \cdot 10} = \frac{1}{\sqrt{2}} \approx 0.707, \qquad \cos(\mathbf{q}, \mathbf{d}_2) = \frac{4}{\sqrt{2} \cdot \sqrt{8}} = 1.

A helyes rangsor d2 előbb, mint d1. A normalizálatlan skalárszorzatot a vektorok hossza torzítja: a d1 hossza 1010, a d2 hossza 8≈2.83\sqrt{8} \approx 2.83, ezért a hosszú d1 látszólag jobb, pedig az iránya távolabb van a lekérdezéstől. A koszinusz a hosszhatást kiküszöböli.

4. megoldás. A 𝐪′=(0.0,0.9,0.0)\mathbf{q}' = (0.0, 0.9, 0.0) lekérdezés d3-ra és d4-re:

Az új várható rangsor: d4, d3, majd a többi alacsony értékkel. A d4 azért kerül d3 elé, mert a 𝐪′\mathbf{q}' és a d4 iránya közelebb áll egymáshoz, mint a 𝐪′\mathbf{q}' és a d3 iránya.

A relevanciajelölési terv: minden lekérdezéshez előre rögzítjük a top 3 találatot; egy annotátor minden találatra 1-et ad, ha a dokumentum megoldja a hibát, 0-t egyébként. A fiktív eredmények: 44 lekérdezésnél 33 releváns, 33-nál 22, 22-nél 11, 11-nél 00. A releváns találatok száma:

4⋅3+3⋅2+2⋅1+1⋅0=12+6+2=20. 4 \cdot 3 + 3 \cdot 2 + 2 \cdot 1 + 1 \cdot 0 = 12 + 6 + 2 = 20.

A precision@3:

precision@3=2010⋅3=2030≈0.667. \text{precision@3} = \frac{20}{10 \cdot 3} = \frac{20}{30} \approx 0.667.

A pontbecslés mellett érdemes megjegyezni, hogy 10 lekérdezés kicsi minta; a Standard error és konfidenciaintervallum alfejezetben tanultak szerint a konfidenciaintervallum széles lesz.

27.2.7 Gyakori tévedés

A leggyakoribb félreértés az, hogy a magas koszinusz-hasonlóság ténybeli igazságot bizonyít. Ez nem igaz. Az embedding a szövegek nyelvi-fogalmi közelségét tömöríti, nem a világra vonatkozó állítások igazságát. Ellenpélda: a „Franciaország fővárosa Párizs” és a „Franciaország fővárosa Róma” mondatok vektorai nagyon közel lehetnek egymáshoz, mert ugyanaz a nyelvi séma, ugyanazok a szavak — mégis az egyik állítás hamis. A retrieval rendszer visszaadhatja a hamis mondatot, ha a formai hasonlóság nagy. A relevanciaítélet ezért mindig emberi ellenőrzést kíván, és a hasonlósági pontszámot nem szabad kalibrált valószínűségként kezelni, ahogyan azt Kalibrált valószínűség és predikciós bizonytalanság alfejezetben is láttuk.

27.2.8 Önellenőrzés

27.2.9 Rövid összefoglaló

Az embedding a szöveget sűrű, fix dimenziós valós vektorrá képezi; a hasonlóságot a vektorok közötti szög koszinusza méri. A koszinusz a skalárszorzat és a normák hányadosa, értéke [−1,1][-1,1]; normalizált vektorokon egyszerű skalárszorzat. A legközelebbi szomszéd keresés a dokumentumvektorok és a lekérdezés közötti hasonlóságot rendezi csökkenő sorrendbe. Kis adatra a pontos keresés megfelelő; nagy adatra közelítő indexek kellenek, amelyek pontosságot áldoznak a sebességért. A retrieval minőségét emberi relevanciajelöléssel, például precision@k-val mérjük. A következő alfejezetben az LLM-mint sztochasztikus komponens és a külső modellek értékelése felé lépünk tovább.