Noć je igre, vaši prijatelji su smješteni oko stola za igre i čekaju da vide kakav će lik Dungeons & Dragons (D&D) postati i zadatak u koji će krenuti. Večeras ste Dungeon Master (pripovjedač i vodič), stvaralac uzbudljivih susreta kako biste izazvali i očarali svoje igrače. Vaš pouzdan D&D Monster priručnik sadrži hiljade stvorenja. Pronalaženje savršenog čudovišta za svaku situaciju među bezbroj opcija može biti neodoljivo. Idealan neprijatelj treba da odgovara ambijentu, težini i narativu trenutka.
Šta ako bismo mogli stvoriti alat koji trenutno pronalazi čudovište najprikladnije za svaki scenarij? Alat koji istovremeno razmatra više faktora , osiguravajući da svaki susret bude što impresivniji i uzbudljiviji?
Krenimo u sopstvenu potragu: izgradimo vrhunski sistem za pronalaženje čudovišta, koristeći moć vektorske pretrage sa više atributa!
Vektorsko pretraživanje predstavlja revoluciju u pronalaženju informacija. Vektorsko ugrađivanje – uzimajući u obzir kontekst i semantičko značenje – omogućava pretragu vektora da vrati relevantnije i preciznije rezultate, rukuje ne samo strukturiranim već i nestrukturiranim podacima i više jezika, te mjeri. Ali da bismo generirali visokokvalitetne odgovore u aplikacijama iz stvarnog svijeta, često moramo dodijeliti različite težine specifičnim atributima naših objekata podataka.
Postoje dva uobičajena pristupa pretrazi vektora sa više atributa. Oba počinju odvojenim ugrađivanjem svakog atributa objekta podataka. Glavna razlika između ova dva pristupa je u načinu na koji se naši embeddingi pohranjuju i pretražuju .
spaces
nam takođe omogućavaju da težimo svakom atributu u vreme upita da bismo prikazali relevantnije rezultate, bez naknadne obrade. U nastavku ćemo koristiti ova dva pristupa za implementaciju alata za pretraživanje vektora s više atributa - Dungeons and Dragons nalaz čudovišta! Naše jednostavne implementacije, posebno druga, ilustrovaće kako da kreirate moćnije i fleksibilnije sisteme za pretragu, one koji mogu sa lakoćom da obrađuju složene, višestruke upite, bez obzira na vaš slučaj upotrebe.
Ako ste novi u pretraživanju vektorske sličnosti, ne brinite! Pokrili smo vas - pogledajte naše članke o građevnim blokovima .
Ok, idemo u lov na čudovišta!
Prvo ćemo generirati mali sintetički skup podataka o čudovištima, pozivanjem na model velikog jezika (LLM):
Generate two JSON lists: 'monsters' and 'queries'. 1. 'monsters' list: Create 20 unique monsters with the following properties: - name: A distinctive name - look: Brief description of appearance (2-3 sentences) - habitat: Where the monster lives (2-3 sentences) - behavior: How the monster acts (2-3 sentences) Ensure some monsters share similar features while remaining distinct. 2. 'queries' list: Create 5 queries to search for monsters: - Each query should be in the format: {look: "...", habitat: "...", behavior: "..."} - Use simple, brief descriptions (1-3 words per field) - Make queries somewhat general to match multiple monsters Output format: { "monsters": [ {"name": "...", "look": "...", "habitat": "...", "behavior": "..."}, ... ], "queries": [ {"look": "...", "habitat": "...", "behavior": "..."}, ... ] }
Pogledajmo uzorak skupa podataka koji je generirao naš LLM. Napomena: LLM generacija nije deterministička, tako da se vaši rezultati mogu razlikovati.
Evo naših prvih pet čudovišta:
# | ime | pogledajte | stanište | ponašanje |
---|---|---|---|---|
0 | Luminoth | Stvorenje nalik moljcu sa blistavim krilima i antenom | Guste šume i džungle sa bioluminiscentnom florom | Emituje umirujuće svjetlosne obrasce za komunikaciju i privlačenje plijena |
1 | Aqua Wraith | Prozirna humanoidna figura napravljena od tekuće vode | Rijeke, jezera i priobalna područja | Promjene oblika da se stapaju s vodenim tijelima i kontroliraju struje |
2 | Stoneheart Golem | Masivni humanoid sastavljen od isprepletenih kamenih formacija | Stjenovite planine i drevne ruševine | Hibernira vekovima, budi se da zaštiti svoju teritoriju |
3 | Whispering Shade | Sjenovito, amorfno biće sa blistavim očima | Mračne šume i napuštene zgrade | Hrani se strahom i šapuće uznemirujuće istine |
4 | Zephyr Dancer | Graciozno ptičje stvorenje sa prelivim perjem | Visoki planinski vrhovi i ravnice zavejane vjetrom | Stvara očaravajuće prikaze iz zraka kako bi privukao prijatelje |
...i naši generirani upiti:
| Pogledaj | Stanište | Ponašanje |
---|---|---|---|
0 | Glowing | Mračna mjesta | Lagana manipulacija |
1 | Elemental | Ekstremna okruženja | Kontrola životne sredine |
2 | Promena oblika | Raznovrsni pejzaži | Stvaranje iluzija |
3 | Kristalna | Područja bogata mineralima | Apsorpcija energije |
4 | Eterično | Atmosferski | Uticaj uma |
Pogledajte originalni skup podataka i primjere upita ovdje .
Postavimo parametre koje ćemo koristiti u oba naša pristupa - naivnom i superlinkovanom - u nastavku.
Mi generišemo naše vektorske ugradnje sa:
sentence-transformers/all-mpnet-base-v2.
Radi jednostavnosti, ograničit ćemo naše rezultate na 3 najbolja meča. (Za kompletan kod, uključujući neophodne uvoze i pomoćne funkcije, pogledajte bilježnicu .)
LIMIT = 3 MODEL_NAME = "sentence-transformers/all-mpnet-base-v2"
A sada, krenimo u potragu za čudovištima s više atributa! Prvo ćemo isprobati naivni pristup.
U našem naivnom pristupu, atribute ugrađujemo nezavisno i spremamo ih u različite indekse. U vrijeme upita pokrećemo višestruka kNN-pretraga na svim indeksima, a zatim kombiniramo sve naše djelomične rezultate u jedan.
Počinjemo od definiranja klase
NaiveRetriever
da izvršimo pretragu zasnovanu na sličnosti na našem skupu podataka, koristeći naše ugradnje generisane all-mpnet-base-v2
.
class NaiveRetriever: def __init__(self, data: pd.DataFrame): self.model = SentenceTransformer(MODEL_NAME) self.data = data.copy() self.ids = self.data.index.to_list() self.knns = {} for key in self.data: embeddings = self.model.encode(self.data[key].values) knn = NearestNeighbors(metric="cosine").fit(embeddings) self.knns[key] = knn def search_key(self, key: str, value: str, limit: int = LIMIT) -> pd.DataFrame: embedding = self.model.encode(value) knn = self.knns[key] distances, indices = knn.kneighbors( [embedding], n_neighbors=limit, return_distance=True ) ids = [self.ids[i] for i in indices[0]] similarities = (1 - distances).flatten() # by definition: # cosine distance = 1 - cosine similarity result = pd.DataFrame( {"id": ids, f"score_{key}": similarities, key: self.data[key][ids]} ) result.set_index("id", inplace=True) return result def search(self, query: dict, limit: int = LIMIT) -> pd.DataFrame: results = [] for key, value in query.items(): if key not in self.knns: continue result_key = self.search_key(key, value, limit=limit) result_key.drop(columns=[key], inplace=True) results.append(result_key) merged_results = pd.concat(results, axis=1) merged_results["score"] = merged_results.mean(axis=1, skipna=False) merged_results.sort_values("score", ascending=False, inplace=True) return merged_results naive_retriever = NaiveRetriever(df.set_index("name"))
Upotrijebimo prvi upit s naše generirane liste iznad i pretražimo čudovišta koristeći naš naive_retriever
:
query = { 'look': 'glowing', 'habitat': 'dark places', 'behavior': 'light manipulation' } naive_retriever.search(query)
Naš
naive_retriever
vraća sljedeće rezultate pretraživanja za svaki atribut:
id | score_look | pogledajte |
---|---|---|
Whispering Shade | 0,503578 | Sjenovito, amorfno biće sa blistavim očima |
Pješčana oluja Djinn | 0,407344 | Uskovitlani vrtlog pijeska sa užarenim simbolima |
Luminoth | 0,378619 | Stvorenje nalik moljcu sa blistavim krilima i antenom |
Sjajno! Naši vraćeni rezultati čudovišta su relevantni - svi imaju neke "sjajne" karakteristike.
Hajde da vidimo šta naivni pristup daje kada pretražujemo druga dva atributa.
id | score_habitat | stanište |
---|---|---|
Whispering Shade | 0,609567 | Mračne šume i napuštene zgrade |
Mreža gljivica | 0,438856 | Podzemne pećine i vlažne šume |
Thornvine Elemental | 0,423421 | Zarasle ruševine i guste džungle |
id | score_behavior | ponašanje |
---|---|---|
Živi grafiti | 0,385741 | Pomiče oblik kako bi se stapao s okolinom i upija pigmente |
Crystalwing Drake | 0,385211 | Sakuplja dragocjene dragulje i može prelamati svjetlost u moćne zrake |
Luminoth | 0,345566 | Emituje umirujuće svjetlosne obrasce za komunikaciju i privlačenje plijena |
Sva pronađena čudovišta posjeduju tražene atribute. Na prvi pogled, naivni rezultati pretraživanja mogu izgledati obećavajuće. Ali moramo pronaći čudovišta koja posjeduju sva tri atributa istovremeno . Hajde da spojimo naše rezultate da vidimo koliko dobro naša čudovišta rade u postizanju ovog cilja:
id | score_look | score_habitat | score_behavior |
---|---|---|---|
Whispering Shade | 0,503578 | 0,609567 | |
Pješčana oluja Djinn | 0,407344 | | |
Luminoth | 0,378619 | | 0,345566 |
Mreža gljivica | | 0,438856 | |
Thornvine Elemental | | 0,423421 | |
Živi grafiti | | | 0,385741 |
Crystalwing Drake | | | 0,385211 |
I tu granice naivnog pristupa postaju očigledne. Procijenimo:
look
: Tri čudovišta su vraćena (Šaptajuća senka, Džin iz peščane oluje i Luminot).habitat
: Samo jedno čudovište iz rezultata look
bilo je relevantno (Whispering Shade).behavior
: Samo jedno čudovište iz rezultata look
je relevantno (Luminoth), ali se razlikuje od onog relevantnog za habitat
.Ukratko, naivni pristup pretraživanja ne uspijeva pronaći čudovišta koja zadovoljavaju sve kriterije odjednom. Možda možemo riješiti ovaj problem proaktivnim preuzimanjem više čudovišta za svaki atribut? Hajde da probamo sa 6 čudovišta po atributu, umesto sa 3. Pogledajmo šta ovaj pristup generiše:
id | score_look | score_habitat | score_behavior |
---|---|---|---|
Whispering Shade | 0,503578 | 0,609567 | |
Pješčana oluja Djinn | 0,407344 | 0,365061 | |
Luminoth | 0,378619 | | 0,345566 |
Nebula Meduza | 0,36627 | | 0,259969 |
Dreamweaver Octopus | 0,315679 | | |
Quantum Firefly | 0,288578 | | |
Mreža gljivica | | 0,438856 | |
Thornvine Elemental | | 0,423421 | |
Mist Phantom | | 0,366816 | 0,236649 |
Stoneheart Golem | | 0,342287 | |
Živi grafiti | | | 0,385741 |
Crystalwing Drake | | | 0,385211 |
Aqua Wraith | | | 0,283581 |
Sada smo preuzeli 13 čudovišta (više od polovine našeg malog skupa podataka!), i još uvijek imamo isti problem: nijedno od ovih čudovišta nije preuzeto za sva tri atributa.
Povećanje broja pronađenih čudovišta (iznad 6) moglo bi riješiti naš problem, ali stvara dodatne probleme:
Ukratko, naivni pristup je previše neizvjestan i neefikasan za održivo pretraživanje više atributa, posebno u proizvodnji.
Hajde da implementiramo naš drugi pristup da vidimo da li radi bolje od naivnog.
Prvo, definiramo shemu, razmake, indeks i upit:
@schema class Monster: id: IdField look: String habitat: String behavior: String monster = Monster() look_space = TextSimilaritySpace(text=monster.look, model=MODEL_NAME) habitat_space = TextSimilaritySpace(text=monster.habitat, model=MODEL_NAME) behavior_space = TextSimilaritySpace(text=monster.behavior, model=MODEL_NAME) monster_index = Index([look_space, habitat_space, behavior_space]) monster_query = ( Query( monster_index, weights={ look_space: Param("look_weight"), habitat_space: Param("habitat_weight"), behavior_space: Param("behavior_weight"), }, ) .find(monster) .similar(look_space.text, Param("look")) .similar(habitat_space.text, Param("habitat")) .similar(behavior_space.text, Param("behavior")) .limit(LIMIT) ) default_weights = { "look_weight": 1.0, "habitat_weight": 1.0, "behavior_weight": 1.0 }
Sada pokrećemo executor i učitavamo podatke:
monster_parser = DataFrameParser(monster, mapping={monster.id: "name"}) source: InMemorySource = InMemorySource(monster, parser=monster_parser) executor = InMemoryExecutor(sources=[source], indices=[monster_index]) app = executor.run() source.put([df])
Pokrenimo isti upit koji smo pokrenuli u našoj implementaciji naivnog pristupa iznad:
query = { 'look': 'glowing', 'habitat': 'dark places', 'behavior': 'light manipulation' } app.query( monster_query, limit=LIMIT, **query, **default_weights )
id | rezultat | pogledajte | stanište | ponašanje |
---|---|---|---|---|
Whispering Shade | 0,376738 | Sjenovito, amorfno biće sa blistavim očima | Mračne šume i napuštene zgrade | Hrani se strahom i šapuće uznemirujuće istine |
Luminoth | 0,340084 | Stvorenje nalik moljcu sa blistavim krilima i antenom | Guste šume i džungle sa bioluminiscentnom florom | Emituje umirujuće svjetlosne obrasce za komunikaciju i privlačenje plijena |
Živi grafiti | 0,330587 | Dvodimenzionalno, šareno stvorenje koje nastanjuje ravne površine | Urbana područja, posebno zidovi i bilbordi | Pomiče oblik kako bi se stapao s okolinom i upija pigmente |
Et voila! Ovog puta, svako od naših najboljih vraćenih čudovišta visoko se kotira u ocjeni koja predstavlja neku vrstu "srednje vrijednosti" od sve tri karakteristike koje želimo da naše čudovište ima. Hajde da detaljno razložimo rezultat svakog čudovišta:
id | pogledajte | stanište | ponašanje | ukupno |
---|---|---|---|---|
Whispering Shade | 0,167859 | 0,203189 | 0,005689 | 0,376738 |
Luminoth | 0,126206 | 0,098689 | 0,115189 | 0,340084 |
Živi grafiti | 0,091063 | 0,110944 | 0,12858 | 0,330587 |
Naši drugi i treći rezultat, Luminoth i Living Graffiti, posjeduju sve tri željene karakteristike. Najbolji rezultat, Whispering Shade, iako je manje relevantan u smislu manipulacije svjetlom - što se ogleda u njegovom rezultatu behavior
(0,006), ima "sjajne" karakteristike i mračno okruženje koje čine njegov look
(0,168) i habitat
(0,203) vrlo dobrim visoko, što mu daje najviši ukupni rezultat (0,377), što ga čini najrelevantnijim čudovištem u cjelini. Kakvo poboljšanje!
Možemo li ponoviti naše rezultate? Hajde da pokušamo sa drugim upitom i saznamo.
query = { 'look': 'shapeshifting', 'habitat': 'varied landscapes', 'behavior': 'illusion creation' }
id | rezultat | pogledajte | stanište | ponašanje |
---|---|---|---|---|
Mist Phantom | 0,489574 | Eteričan humanoid nalik magli s promjenjivim osobinama | Močvare, močvare i maglovite obale | Namamljuje putnike na stranputicu iluzijama i šapatom |
Zephyr Dancer | 0,342075 | Graciozno ptičje stvorenje sa prelivim perjem | Visoki planinski vrhovi i ravnice zavejane vjetrom | Stvara očaravajuće prikaze iz zraka kako bi privukao prijatelje |
Whispering Shade | 0,337434 | Sjenovito, amorfno biće sa blistavim očima | Mračne šume i napuštene zgrade | Hrani se strahom i šapuće uznemirujuće istine |
Odlično! Naši rezultati su opet odlični.
Što ako želimo pronaći čudovišta koja su slična određenom čudovištima iz našeg skupa podataka? Pokušajmo s čudovištem koje još nismo vidjeli - Harmonic Coral. Mogli bismo izdvojiti atribute za ovo čudovište i ručno kreirati parametre upita. Ali Superlinked ima metodu with_vector
koju možemo koristiti na objektu upita. Budući da je id svakog čudovišta njegovo ime, naš zahtjev možemo izraziti jednostavno kao:
app.query( monster_query.with_vector(monster, "Harmonic Coral"), **default_weights, limit=LIMIT )
id | rezultat | pogledajte | stanište | ponašanje |
---|---|---|---|---|
Harmonic Coral | 1 | Granasta struktura nalik muzičkom instrumentu sa vibrirajućim viticama | Plitka mora i plimni bazeni | Stvara složene melodije za komunikaciju i utjecaj na emocije |
Dreamweaver Octopus | 0,402288 | Glavonožaci s pipcima koji svjetlucaju poput aurore | Duboki okeanski rovovi i podvodne pećine | Utječe na snove obližnjih stvorenja |
Aqua Wraith | 0,330869 | Prozirna humanoidna figura napravljena od tekuće vode | Rijeke, jezera i priobalna područja | Promjene oblika da se stapaju s vodenim tijelima i kontroliraju struje |
Najbolji rezultat je najrelevantniji, sam Harmonic Coral, kako se i očekivalo. Druga dva čudovišta koje naša pretraga pronalazi su Dreamweaver Octopus i Aqua Wraith. Oba dijele važne tematske ( atributne ) elemente sa Harmonic Coral:
habitat
)behavior
)look
) Pretpostavimo sada da želimo da damo veći značaj atributu look
. Okvir Superlinked nam omogućava da lako prilagodimo težine u trenutku upita. Radi lakšeg poređenja, tražit ćemo čudovišta slična Harmonic Coral-u, ali s našim težinama prilagođenim look
.
weights = { "look_weight": 1.0, "habitat_weight": 0, "behavior_weight": 0 } app.query( monster_query.with_vector(monster, "Harmonic Coral"), limit=LIMIT, **weights )
id | rezultat | pogledajte | stanište | ponašanje |
---|---|---|---|---|
Harmonic Coral | 0,57735 | Granasta struktura nalik muzičkom instrumentu sa vibrirajućim viticama | Plitka mora i plimni bazeni | Stvara složene melodije za komunikaciju i utjecaj na emocije |
Thornvine Elemental | 0,252593 | Stvorenje nalik biljci sa tijelom od iskrivljene loze i trnja | Zarasle ruševine i guste džungle | Brzo raste i kontroliše okolni biljni svet |
Plasma Serpent | 0,243241 | Stvorenje nalik na zmiju napravljeno od energije pucketanja | Električne oluje i elektrane | Hrani se električnom strujom i može izazvati kratki spoj u tehnologiji |
Svi naši rezultati (prikladno) imaju slične izglede - "Granjanje sa vibrirajućim viticama", "Biljko nalik stvorenju sa tijelom od uvijene loze i trnja", "Zmijino".
Sada, napravimo još jednu pretragu, ignorirajući izgled i umjesto toga tražimo čudovišta koja su slična u smislu habitat
i behavior
istovremeno:
weights = { "look_weight": 0, "habitat_weight": 1.0, "behavior_weight": 1.0 }
id | rezultat | pogledajte | stanište | ponašanje |
---|---|---|---|---|
Harmonic Coral | 0,816497 | Granasta struktura nalik muzičkom instrumentu sa vibrirajućim viticama | Plitka mora i plimni bazeni | Stvara složene melodije za komunikaciju i utjecaj na emocije |
Dreamweaver Octopus | 0,357656 | Glavonožaci s pipcima koji svjetlucaju poput aurore | Duboki okeanski rovovi i podvodne pećine | Utječe na snove obližnjih stvorenja |
Mist Phantom | 0,288106 | Eteričan humanoid nalik magli s promjenjivim osobinama | Močvare, močvare i maglovite obale | Namamljuje putnike na stranputicu iluzijama i šapatom |
Opet, Superlinked pristup daje odlične rezultate. Sva tri čudovišta žive u vodenom okruženju i posjeduju sposobnosti kontrole uma.
Konačno, hajde da pokušamo još jednu pretragu, različito ponderišući sva tri atributa - da pronađemo čudovišta koja u poređenju sa Harmonic Coral izgledaju donekle slično, žive u veoma različitim staništima i imaju vrlo slično ponašanje:
weights = { "look_weight": 0.5, "habitat_weight": -1.0, "behavior_weight": 1.0 }
id | rezultat | pogledajte | stanište | ponašanje |
---|---|---|---|---|
Harmonic Coral | 0,19245 | Granasta struktura nalik muzičkom instrumentu sa vibrirajućim viticama | Plitka mora i plimni bazeni | Stvara složene melodije za komunikaciju i utjecaj na emocije |
Luminoth | 0,149196 | Stvorenje nalik moljcu sa blistavim krilima i antenom | Guste šume i džungle sa bioluminiscentnom florom | Emituje umirujuće svjetlosne obrasce za komunikaciju i privlačenje plijena |
Zephyr Dancer | 0,136456 | Graciozno ptičje stvorenje sa prelivim perjem | Visoki planinski vrhovi i ravnice zavejane vjetrom | Stvara očaravajuće prikaze iz zraka kako bi privukao prijatelje |
Opet odlični rezultati! Naša druga dva pronađena čudovišta — Luminoth i Zephyr Dancer — imaju ponašanje slično Harmonic Coral-u i žive u staništima drugačijim od Harmonic Coral-a. Takođe izgledaju veoma različito od Harmonic Corala. (Dok su vitice Harmonic Corala i Luminothova antena donekle slične karakteristike, smanjili smo samo look_weight
za 0,5, a sličnost između dva čudovišta se tu završava.)
Hajde da vidimo kako se ukupni rezultati ovih čudovišta izbijaju u smislu pojedinačnih atributa:
id | pogledajte | stanište | ponašanje | ukupno |
---|---|---|---|---|
Harmonic Coral | 0,19245 | -0,3849 | 0,3849 | 0,19245 |
Luminoth | 0,052457 | -0,068144 | 0,164884 | 0,149196 |
Zephyr Dancer | 0,050741 | -0,079734 | 0,165449 | 0,136456 |
Negativnim ponderisanjem habitat_weight
(-1.0), mi namjerno "odgurujemo" čudovišta sa sličnim staništima i umjesto toga čudovišta na površini čije je okruženje drugačije od Harmonic Coral-a - kao što se vidi u negativnim ocjenama habitat
Luminotha i Zephyr Dancera. Rezultati behavior
Luminotha i Zephyr Dancera su relativno visoki, što ukazuje na njihovu sličnost u ponašanju sa Harmonic Coral. Njihovi rezultati look
su pozitivni, ali niži, odražavajući neku , ali ne ekstremnu vizualnu sličnost s Harmonic Coral.
Ukratko, naša strategija smanjenja težine habitat_weight
na -1.0 i look_weight
na 0.5, ali zadržavanje behavior_weight
na 1.0 pokazala se učinkovitom u otkrivanju čudovišta koja dijele ključne karakteristike ponašanja sa Harmonic Coralom, ali imaju vrlo različita okruženja i izgledaju barem donekle drugačije.
Vektorska pretraga sa više atributa je značajan napredak u pronalaženju informacija, nudeći više tačnosti, kontekstualnog razumijevanja i fleksibilnosti od osnovne pretrage semantičke sličnosti. Ipak, naš naivni pristup (gore) - odvojeno pohranjivanje i pretraživanje vektora atributa, zatim kombinovanje rezultata - ograničen je u sposobnostima, suptilnosti i efikasnosti kada trebamo da dohvatimo objekte sa višestrukim istovremenim atributima. (Štaviše, višestruka kNN pretraživanja oduzimaju više vremena od jedne pretrage sa povezanim vektorima.)
Za rukovanje ovakvim scenarijima, bolje je pohraniti sve svoje vektore atributa u istu vektorsku trgovinu i izvršiti jedno pretraživanje , ponderirajući vaše atribute u vrijeme upita. Pristup Superlinked je tačniji, efikasniji i skalabilniji od naivnog pristupa za bilo koju aplikaciju koja zahtijeva brzo, pouzdano, nijansirano, višeatributno dohvaćanje vektora - bilo da se vaš slučaj upotrebe bavi problemima podataka iz stvarnog svijeta u vašem e-trgovini ili sistemu preporuka ... ili nešto sasvim drugačije, poput borbe protiv čudovišta.
Originalno objavljeno ovdje .