Kihagyás

Adatgyűjtés

Adatgyűjtés alatt alapvetően kicsit zavaros, hogy mit is értünk. Ez egy nagyon átfogó fogalom és lépés, mely a RAG problémáját próbálja megoldani a Guru esetén. A feladat tehát valahogy a következőképp fogalmazható meg:

Szöveges visszakeresés

Adott: Szöveges chunk-ok egy \(\mathcal{D}\) halmaza, egy \(q\) kérdés, és \(C\) kérdést támogató segédinformációk („kontextus“).

Feladat: Keressük meg \(q\) és \(C\) segítségével azon \(\mathcal{D}\)-beli chunk-okat, melyek tartalmazzák a pontos válaszadáshoz szükséges információt.

A chunk-ok előállításának módszerét a Dokumentum chunk-olás szócikk tárgyalja. Most magát a keresést fogjuk részletezni technikai oldalról.

A hibrid keresés a kulcsszavas és szemantikus keresések, illetve az átrangsorolás vegyítése. Menete nagy vonalakban a következő.

  1. Beérkező \(q\) üzenetre meghívjuk a kulcsszavas és szemantikus keresést, és lekérjük a

    • a top \(k_k\) és \(k_s\) találatot, ezek a hibrid keresés kimenetébe kerülnek

    • top \(k'_k\) és \(k'_s\) találatot, ezeken átrangsorolást hajtunk végre

  2. Átrangsoroljuk az így kapott \(k'_k+k'_s\) találatot (szintén \(q\) alapján), és ebből vesszük a top \(k_r\) találatot.

  3. Végül összefésüljük a találatokat, ezzel kapva \(k_k+k_s+k_r\) sokat

Mint ahogy azt a különböző adatgyűjtési fázisok leírásában látható lesz, a logika árnyaltabb, ugyanis folyik egy témával megtámogatott keresési fázis is, illetve a különböző keresések találatainak aggregációja korántsem nyilvánvaló. Az erre vonatkozó technikai leírás lentebb található.

A hibrid keresést a HybridSearchServcice vezényli, az arra vonatkozó paraméterek pedig a tool-configuration.yml fájl project.tools.hybrid-search mezőben, illetve a különböző keresési megoldások megfelelő mezőiben találhatók.

Jelenleg \(k_k = k_s = k_r = 3\) és \(k'_k = k'_s = 10\) értékeket alkalmazunk, azaz az átrangsorolás 20 chunk-ra fut, és a hibrid keresés (legfeljebb) 9 chunk-ot ad vissza.

Mikor lehet ennél kevesebb?

Amennyiben a különböző lépésekből ugyanaz a chunk kerülne a végső halmazba, ez deduplikálásra kerül, azaz a végső kimenetben 2-3 példány helyett csak 1-szer fog szerepelni. Következik, hogy a hibrid keresés kimenete legalább \(\max(k_k,k_s,k_r)\) chunk-ot mindenképp tartalmaz (ami jelenleg 3).

Kezdeti adatgyűjtés

Előfeltételek: Topic Extractor (opcionális)
Ráépül: Tool Selector, Relevance Extractor

A hibrid keresés minden QA ágra érkezett kérdésen automatikusan lefut. Ezt a folyamatot nevezzük kezdeti adatgyűjtésnek. Amennyiben a Topic Extractor képes témákat meghatározni, úgy futtatunk egy hibrid keresést ezekkel, mint szűrők. Így a kezdeti adatgyűjtés legfeljebb két hibrid keresésnyi találattal térhet vissza.

Utólagos adatgyűjtés

Előfeltételek: Question Reform, Tool Selector, Topic Extractor (opcionális)
Ráépül: Relevance Extractor

Előfordulhat, hogy a kezdeti adatgyűjtés nem találja meg a megfelelő információkat, vagy nem mindet. Ekkor jön képbe az utólagos adatgyűjtés.

Az „eszközt“ a Tool Selector dönti el, hogy szükséges-e meghívni. Amennyiben igen ugyanúgy lefuttatjuk a hibrid keresést, ugyanakkor az eredeti kérdés helyett a Question Reform által újrafogalmazott kérdésre (továbbá hasonlóan a Topic Extractor eredménye alapján egy szűréssel ellátott verziót is futtathatunk).

Az így kapott chunk-halmaz kiegészíti a korábban találtakat, és ez után a következő lépés már mindenképp a Relevance Extractor.

Találatok és összefűzésük

Mint ahogy azt már említettük, bár az algoritmus egészen kézzelfoghatóan vázolható, de a valóságban gyorsan problémák merülhetnek fel, ha rosszul összesítjük a különböző elemi keresési lépések kimenetét. Ennek fő oka, hogy a kontrollált működés érdekében értelmes módon limitálni szeretnénk az egyes lépések találatainak számát, azaz kiemelt figyelmet kell fordítanunk arra, hogy megfelelően kiegyensúlyozottan válogassunk a különböző aggregációs fázisokban. Emellett érdemes figyelnünk a megfelelő deduplikációra is, hogy a legjobb találataink ne tartalmazzanak redundáns információt.

A rendezéstartás is szempont lehet

Amennyiben a keresési algoritmusaink rendezetten adnak vissza találatokat, úgy az összesítés során az is szempont, hogy szeretnénk-e, hogy a végső eredmény egy körülbelüli rendezésben maradjon. Jelenleg ezzel expliciten nem törődünk, de a megoldásunk természetes módon kezeli ezt bizonyos mértékig.

Most tehát a teljes adatgyűjtési folyamat részletes leírása következik. Elöljáróban megjegyezzük, hogy az eljárások paraméterei mind a tool-configuration.yml-ben találhatók, azon belül is a project.tools mezőben, így amikor egy konfigurációs mezőre hivatkozunk, azt ezen belül találjuk. Továbbá „összefésülés“ alatt a összefésüléses rendezésben is használt összefésülési eljárást értjük („merge“, „round-robin merge“).

Először a következő elemi kereséseket futtatjuk végig, ezek párhuzamosan végezhetők.

Nyers kulcsszavas keresés

A kulcsszavas keresést lefuttatjuk \(q\)-ra, bármiféle szűrő nélkül. A kinyert találatok számát a keyword-search.number-of-chunks-without-label mező szabályozza.

Kulcsszavas keresés témákkal kiegészítve

A kulcsszavas keresést lefuttatjuk a Topic Extractor által kinyert téma információra. A találatok számát a keyword-search.number-of-chunks-with-label mező szabályozza, ez az érték kerül kiegyensúlyozottan felosztásra minden egyes megtalált altéma között. Az egyes altémákra vonatkozó találatokat összefésüljük, ezzel áll össze a rendezett kimenet. A találatok összegyűjtése során egy chunk-ot ahhoz az altémához rendeljük, amelynek először felel meg; az ebből következő potenciális problémát itt részletezzük.

Amennyiben csak témát talál a rendszer, vagy több témát is talál, a keresés működése definiált, de nem következetes, erre itt térünk ki.

Nyers szemantikus keresés

A szemantikus keresést lefuttatjuk \(q\)-ra, bármiféle szűrő nélkül. A kinyert találatok számát a semantic-search.number-of-chunks-without-label mező szabályozza.

Szemantikus keresés témákkal kiegészítve

A szemantikus keresést lefuttatjuk a Topic Extractor által kinyert téma információra. A találatok számát a semantic-search.number-of-chunks-per-label mező szabályozza, ez az érték kerül körülbelül-kiegyensúlyozottan felosztásra minden egyes megtalált altéma között. Az egyes altémákra vonatkozó találatokat összefésüljük, ezzel áll össze a rendezett kimenet.

Amennyiben csak témát talál a rendszer, a keresés működése definiált, de nem következetes, erre itt térünk ki.

Ezek után futtatunk egy elsődleges összesítést a két részegységre.

Kulcsszavas keresés találatainak összefésülése

Vesszük a nyers kulcsszavas keresés és a témákkal kiegészített kulcsszavas keresés eredményét, és összefűzzük őket, és megszorítjuk az első néhány elemre (keyword-search.number-of-chunks). Deduplikációt nem végzünk, ez kivételes esetekben teljesítményromlást okozhat.

Szemantikus keresés találatainak összefésülése

Vesszük a nyers szemantikus keresés és a témákkal kiegészített szemantikus keresés eredményét, és összefűzzük őket, és megszorítjuk az első néhány elemre (semantic-search.number-of-chunks). Deduplikációt nem végzünk, ez kivételes esetekben teljesítményromlást okozhat.

Ezen a ponton a futásban átszegmentáljuk a szöveghalmazainkat. Ennek oka, hogy feltételezzük, hogy ha egy chunk-ot mindkét keresési módszer megtalált, az „jobb“, mint azok, amiket csak az egyik. A maradékot ezután átrendezzük.

Találatok szétválasztása megjelenés szerint

A keresések összefűzött találatait 3 részre bontjuk: - Melyeket csak a kulcsszavas keresés talált meg - Melyeket csak a szemantikus keresés talált meg - Melyeket mindkét keresés megtalált

Az szétválogatás során tartjuk az egyes szöveghalmazok rendezését, azaz ha a kulcsszavas keresés egy szöveget előbbre helyezett egy másiknál, akkor a kizárólagosan kulcsszavas kereséshez tartozó halmazban is ilyen lesz a sorrendjük (persze csak akkor, ha mindkettő ide tartozik). A közös részben a sorrendet jelenleg a szemantikus keresésnek megfelelően őrizzük meg.

Egyedi találatok átrangsorolása

A kizárólagosan kulcsszavas és kizárólagosan szemantikus keresés által megtalált chunk-okat a Cross Encoder segítségével átrendezzük. Ennek eredménye egy újabb rendezett szöveghalmaz lesz, melyből kizárólag az első néhány találatot vesszük (cross-encoder.number-of-chunks).

Szöveghalmazok összefűzése és kimenet előállítása

Vesszük kizárólagosan kulcsszavas és kizárólagosan szemantikus keresés által megtalált chunk-ok halmazát, illetve az előző lépésben a Cross Encoder által rendezett halmazt, majd ezt a hármat összefűzzük.

A végső kimenetet úgy kapjuk, hogy kiindulásnak tekintjük a szétválasztásnál kapott közös halmazt, majd az összefűzés eredményeként kapott lista elejéről ehhez elemeket adunk, amíg el nem érjük a kívánt méretet (hybrid-search.number-of-chunks). A folyamat során deduplikációt nem végzünk, ami teljesítményromlást okozhat.

Így áll elő tehát a hybrid search kimenete. Fontos megjegyezni, hogy ezt az utolsó lépésben már nem próbáljuk rendezett formában tartani.

Teljesítményjavítási lehetőségek

Az előző fejezetben vázolt folyamat igen széleskörű módon próbálja kiegyensúlyozni a különböző módszerek által adott találatokat. Ezen fejezet célja, hogy ismert implementációs „hibákat“, hiányosságokat, esetleges javítási lehetőségeket írjon le.

Keyword search témaszűrése

A kulcsszavas keresésben a Python kód jelenleg SourceFilter objektumokkal dolgozik valójában, nem altémákkal, és így a rendszer nem úgy van megvalósítva, hogy altémákra egyenletesen osztjuk szét a keresendő chunk-mennyiséget, hanem hogy szűrőkre. Egy szűrő egyszerre tud témára, altémára és dokumentum névre (ID-re, de ez jelenleg a név) szűrni.

Ez két szempontból problémás. Az egyik, hogy amennyiben a Backend nem talál altémát, egy témához hozzárendeli az ahhoz tartozó „általános“ altémát. Ez teljesítményromláshoz vezethet, hiszen ha a Topic Extractor például meghatározza, hogy a téma az, hogy „bankkártyák“, de altémát nem talál, az valószínűleg azt jelenti, hogy a felhasználó általánosságban kérdezett a bankkártyákról, nem azt, hogy a bankkártyák „általános“ altémájára kíváncsi (ami többnyire egy gyűjtőkategória). Ez Python oldalon kezelve lenne, hiszen ha egy szűrőnek nincs altémája, kizárólag a téma egyezését nézi a rendszer. Ugyanakkor mivel a backend altémát ad hozzá, ez a logika felülíródik. Megjegyezzük, hogy a probléma egyezik a szemantikus keresés esetén leírt jelenséggel.

A Backend által létrehozott szűrők akkor is problémásak, amikor esetleg több témát is megállapít a Topic Extractor. Ekkor ugyanis az összes megállapított témát és altémát összekapcsoljuk egymással a szűrők létrehozásakor, validáció nélkül, ami azt jelenti, hogy több olyan szűrő is készül, melynek nem felelhet meg semmi (mivel az adott altéma nem tartozik a témához). Mivel a kulcsszavas keresés egyenlő módon osztja szét a keresendő kérdések számát a szűrők között, a felesleges szűrők átadása csökkenti a helyesekhez tartozó találatok számát.

Semantic search témaszűrése

Amikor a Topic Extractor nem állapít meg altémát, csak témát, a Backend úgy veszi, hogy az altéma az adott témához tartozó „általános“ altéma. Ez teljesítményromláshoz vezethet, hiszen ha a Topic Extractor például meghatározza, hogy a téma az, hogy „bankkártyák“, de altémát nem talál, az valószínűleg azt jelenti, hogy a felhasználó általánosságban kérdezett a bankkártyákról, nem azt, hogy a bankkártyák „általános“ altémájára kíváncsi (ami többnyire egy gyűjtőkategória).

Emellett az egyes altémákhoz tartozó elvárt találatok mennyiségét egészrészes osztással számoljuk, melynek eredményeképp a valóságban sokszor kevesebb chunk-kal tér vissza a rendszer, mint a konfigurált mennyiség.

Deduplikáció

A folyamat során expliciten nem deduplikálunk. Ez a pipeline futása során néhány helyen implicit kezelve van:

  • A kulcsszavas keresés több szűrővel (lásd. fentebb) való futtatása esetén egy chunk-ot az első olyan szűrőhöz rendelünk, amelynek megfelel, így se téma nélkül, se témával keresve nem ad duplikált chunk-okat.

  • A szétválasztás során rendezett halmazok vannak használva (LinkedHashSet és LinkedHashMap), melyek szűrik az azonos elemeket.

Ugyanakkor a következő helyeken nincs deduplikáció:

  • A szementikus keresés több altémával való hívása után a különböző altémákhoz tartozó eredmények aggregációjánál nem deduplikálunk, így előfordulhat, hogy a valóságban kevesebb (egyedi) chunk-kal tér vissza a szemantikus keresés, mint amennyit várunk. (Ez a probléma a multi-tagging kapcsán jöhet elő.)

  • A szétválogatási lépés után nem deduplikálunk, így előfordulhat, hogy az egyedi találatok és a Cross Encoder kimenetének összefűzésével kapott rendezett lista első elemei között néhány chunk többször szerepel (mert a Cross Encoder és valamelyik keresés is magas helyre tette).

Összességben ezek akkora teljesítményromlást nem okoznak, ugyanakkor előfordul, hogy a rendszer valójában kevesebb chunk-ból dolgozik, mint amit várnánk. Emellett nem nyilvánvaló, hogy a szétválogatási lépés valójában deduplikál is.

Multi-tagging inkonzisztenciák

Jelenleg egy dokumentumnak már több altémája is lehet egy típuson belül. Ez deduplikációs problémákat okoz, mivel a szemantikus keresés több altémára is képes megtalálni ugyanazt a chunk-ot, ami azt jelenti, hogy jelentősen csökkenhet a találatok valós száma. Emellett a kulcsszavas keresés találatait is jelentősen ferdítheti, mivel ez rendezett módon keresi az egyező chunk-okat.

Példa a kulcsszavas keresés romlására

Tegyük fel, hogy van 5 darab [A, B] és 5 darab [B] témával rendelkező chunk-unk. Keressünk most két altéma szerint. Tegyük fel, hogy két szűrőt adunk meg, az egyiket az A, a másikat a B témára, és 10 dokumentumot keresünk. Végül tegyük fel, hogy a legjobb találatok az [A, B] címkések.

Ekkor ha a szűrők sorrendje (A, B), akkor az összes chunk-ot megtaláljuk. Ugyanakkor ha a szűrők sorrendje (B, A), akkor a legjobb 5 találat (azaz az [A, B] témájú chunk-ok) megfelel a B szűrőnek, ezzel betelítve azt, és az A szűrőnek innentől már semmi nem felel meg. Következik, hogy a rendszer a szűrők sorrendjétől függően jelentősen eltérő számú chunk-ot adhat vissza, eltérő témabeli eloszlásokkal.

Súlyos probléma alapvetően csak akkor jöhet elő, ha sok dokumentum van, ami több altémával rendelkezik, illetve ha néhány gyakori dokumentum nagyon sok altémával rendelkezik. Ez előreláthatólag nem lesz gyakori eset, ugyanakkor érdemes észben tartani, esetleg valamilyen módon előzetesen kezelni.