Ismeretlen gráf felderítése

Ismeretlen gráf felderítése

Probléma: #

Hogyan tudok egy ismeretlen gráfban (pl. film adatbázis) lekérdezéseket végrehajtani, ha nem ismerem a szerkezetét?

Megoldás #

Az alábbiakban végrehajtunk egy lehetséges lépéssorozatot, aminek a végén rendelkezni fogunk az ahhoz szükséges ismeretekkel, hogy az ismeretlen gráf tartalmát lekérdezzük, vagy módosítsuk. Mindehhez nincs szükségünk arra, hogy ismerjük a gráfban tárol modell sémáját. Ugyanakkor feltételezzük, hogy a gráf nem véletlenszerűen létrehozott csomópontokból, és kapcsolatokból áll, hanem van benne “rendszer”, létezik hozzá legalább egy képzeletbeli séma, és a benne található predikátum elnevezése is követ bizonyos konvenciókat, azokat valamilyen logika mentén hozták létre, és amelyek utalnak azok szerepére.

A felderített sémát egy diagramon fogjuk ábrázolni. Azokat az IRI típusú csomópontokat amiknek ismerjük a konkrét azonosítóját, a diagramon sárgával fogjuk jelölni. A literál típusú csomópontokat zöld színnel, és a típus megnevezésével. Az ismeretlen csomópontokat pedig kék színnel, és egy változó névvel jelöljük.

0. lépés: Indítsuk el a REPL-t az adatbázissal #

Indítsunk el a REPL paranccsal a Cayley -t egy in-memory adatbázissal, amibe rögtön betöltjük az adatokat:


    $ cayley repl --load data/30kmoviedata.nq.gz 
    I0917 19:12:42.483992   18114 cayley.go:63] Cayley version: 0.7.5 (cf576babb7db)
    I0917 19:12:42.484240   18114 database.go:187] using backend "memstore"
    I0917 19:12:46.897340   18114 database.go:250] loaded "data/30kmoviedata.nq.gz" in 4.413006871s
    cayley>

A továbbiakban ezzel a segédprogrammal fogjuk a feltérképezést végezni.

2. lépés: Csoportosítsuk a predikátumokat együttes előfordulás szerint #

Azonosítsuk azokat a predikátum csoportokat, amelyek együttesen fordulnak elő.

2.1. lépés: Kimenő élek csoportosítása #

Először gyűjtsük össze, csoportonként a kimenő élként együtt előforduló predikátumokat.


    cayley> g.V().In("</film/performance/actor>").OutPredicates().All()

    ****
    id : </film/performance/actor>
    ****
    id : </film/performance/character>
    -----------
    2 Results
    Elapsed time: 174.893162 ms

Az eredmény alapján a </film/performance/actor> és a </film/performance/character> predikátumok azonos csoportban vannak, tehát többnyire mindannyian ugyanabból a csomópontból indulnak ki.

Ugyanazt az eredményt kell kapnunk az együtt szereplő predikátumok esetében, akármelyikkel is indítjuk a lekérdezést:


    cayley> g.V().In("</film/performance/character>").OutPredicates().All()

    ****
    id : </film/performance/actor>
    ****
    id : </film/performance/character>
    -----------
    2 Results
    Elapsed time: 45.116628 ms

Nézzük a következő predikátumot, ami nem szerepel az imént beazonosított csoportban:


    cayley> g.V().In("<name>").OutPredicates().All()

    ****
    id : <name>
    ****
    id : <type>
    ****
    id : </film/film/directed_by>
    ****
    id : </film/film/starring>
    -----------
    4 Results
    Elapsed time: 178.340908 ms

Sikerült egy másik csoportot azonosítanunk, ami tartalmazza az összes eddig nem besorolt predikátumot. Ezzel lefedtük az összes predikátumot, tehát két csoportba lehet őket rendezni első közelítésben.

Az együttesen előforduló kimenő éleket rendeljük csomópontokhoz, a csomópontoknak adjunk változó nevet, majd, és rajzoljuk fel az eddig kapott eredményt, ahogy az a 2. ábrán látható:

2. ábra: A kimenő élként együtt előforduló predikátumok

2.2. lépés: Bemenő élek csoportosítása #

Térképezzük fel a bemenő élként előforduló predikátumokat, és rendeljük hozzá őket azokhoz a csoportokhoz (csomóponti változókhoz) amiket a kimenő élek esetében létrehoztunk. Ehhez nézzük végig minden egyes kimenő predikátumra vonatkozóan, hogy az milyen bejövő predikátumokkal társul. Ehhez hajtsuk végre a következő lekérdezés sorozatot:


    cayley> g.V().In("</film/performance/actor>").InPredicates().All()

    ****
    id : </film/film/starring>
    -----------
    1 Result
    Elapsed time: 190.712052 ms


    cayley> g.V().In("</film/performance/character>").InPredicates().All()

    ****
    id : </film/film/starring>
    -----------
    1 Result
    Elapsed time: 42.079836 ms


    cayley> g.V().In("<type>").InPredicates().All()

    ****
    id : </film/performance/actor>
    ****
    id : </film/film/directed_by>
    -----------
    2 Results
    Elapsed time: 149.433856 ms


    cayley> g.V().In("<film/film/directed_by>").InPredicates().All()

ebben az esetben nincs találat.


    cayley> g.V().In("<film/film/starring>").InPredicates().All()

és ebben az esetben sincs.

Helyezzük fel fel a lekérdezések eredményeképpen kapott, bemenő éleket is a gráfra, hozzákapcsolva a csomóponti változókhoz. Az eredményt a 3. ábrán láthatjuk.

3. ábra: A bemenő és kimenő élekként együtt előforduló predikátumok

Bemenő és kimenő predikátum csoportok egyesítése #

Fésüljük össze az eredményhalmazokat, és a gráfot. Az eredményt a 4. ábrán láthatjuk.

4. ábra: Az együtt előforduló predikátumok egyesített ábrája.

Fontos megjegyzés:

Az eredményül kapott x és y node változók nem feltétlenül egyetlen node-típust reprezentálnak, mert a keresés során a bejövő és kimenő predikátumokat közösítettük.

3. lépés: Keressük meg a terminális csomópontokat #

Ezek azok a csomópontok, akikből már nem indul ki további predikátum.

Egyenként vizsgáljuk meg a kimenő predikátumokat, hogy azok milyen csomópontokhoz érkeznek.

Kezdjük a </film/performance/character> predikátummal:


    cayley> g.V().Out("</film/performance/character>").All()

    ****
    id : Luther Krank
    ****
    id : Roland
    ****
    id : Tomás de Torquemada
    ****
    id : Ferdinand VII of Spain
    ****
    id : Christopher Columbus
    ...

Az eredménylista kiírása az első 100 elem után befejeződik, alapértelmezés szerint. Látható, hogy az eredmények string literál értékek, a filmben szereplő karakterek nevei.

Nézzük meg, hogy hányan vannak:


    cayley> g.V().Out("</film/performance/character>").Count()

    => 15058
    -----------
    1 Result
    Elapsed time: 15.394561 ms

Most ellenőrizzük, hogy indul-e belőlük további predikátum:


    cayley> g.V().Out("</film/performance/character>").Out().Count()

     => 0
    -----------
    1 Result
    Elapsed time: 65.014376 ms

Az eredmény 0, tehát ezek terminális csomópontok, mivel további kimenő kapcsolatok már nincsenek.

Most vizsgáljuk meg a <name> predikátumot:


    cayley> g.V().Out("<name>").All()

    ****
    id : David Fisher
    ****
    id : 002 Operazione Luna
    ****
    id : 008: Operation Exterminate
    ****
    id : 02:37:00 AM
    ****
    id : 06/05
    ****
    id : 10,000 BC
    ****
    ...

Ezek string literálok.


    cayley> g.V().Out("<name>").Count()

    => 74950
    -----------
    1 Result
    Elapsed time: 44.480211 ms

A számuk 74950.


    cayley> g.V().Out("<name>").Out().Count()

    => 0
    -----------
    1 Result
    Elapsed time: 52.163808 ms

Ezek szintén terminális csomópontok.

Folytassuk a sort a <type> predikátummal:


    cayley> g.V().Out("<type>").All()

    ****
    id : </people/person>
    ****
    id : </film/film>
    ****
    id : </film/film>
    ...

Ebből több is van, és nem literálok, hanem IRI-k.

Állapítsuk meg egyedileg, hogy pontosan melyek ezek az IRI-k:


    cayley> g.V().Out("<type>").Unique().All()
     
    ****
    id : </people/person>
    ****
    id : </film/film>
    -----------
    2 Results
    Elapsed time: 43.759725 ms

Összesen két ilyen IRI létezik, a </people/person> és a </film/film>. Ellenőrizzük ezek esetében is, hogy terminális csomópontok-e, vagy sem:


    cayley> g.V().Out("<type>").Out().Count()

    => 0
    -----------
    1 Result
    Elapsed time: 48.857924 ms

Terminálisok.

Vezessük fel az újonnan felfedezett literál és IRI csomópontokat a gráfra, a hozzájuk vezető predikátumokkal együtt, ahogyan az 5. ábrán látható.

5. ábra: Terminális csomópontokkal kiegészített séma.

4. válasszuk szét a különálló csomópont típusokat #

Lehetőség szerint válasszuk szét azokat a csomópont változókat, amelyek hasonló beérkező és kimenő predikátumokat fogadnak, de eltérő típusú entitásokat azonosítanak.

Az y csomóponti változó elemzése #

Az 5. ábrán megfigyelhetjük, hogy az y csomópont <type> kimenő irányú predikátuma, a következő értékeket veheti fel: </people/person> és </film/film>. Ebből következően feltételezhetjük, hogy y csomópont több különböző entitás típust fedhet. Válasszuk szét a feltételezésnek megfelelően az y csomópontot kétfelé, a 6. ábra szerint:

6. ábra: A person és a film csomópont változók szétválasztása.

Alkalmazzunk több (2, 3, …n) lépésből álló kapcsolati láncokat, a további elemzéshez.

Vizsgáljuk meg, hogy milyen predikátumok indul és érkeznek y-ból származtatott, szétválasztott csomópontokhoz, ha azok értelmezését úgy korlátozzuk, hogy a <type> egyszer az egyik, másszor a másik értéket veszi fel:


    cayley> y_film = g.V().Has("<type>", "</film/film>")

    => [internal Iterator]
    -----------
    1 Result
    Elapsed time: 0.469018 ms

    cayley> y_film.OutPredicates().All()

    ****
    id : </film/film/directed_by>
    ****
    id : </film/film/starring>
    ****
    id : <name>
    ****
    id : <type>
    -----------
    4 Results
    Elapsed time: 113.54646 ms

A nevükből ítélve ezek a predikátumok egy film esetében, normálisnak tűnnek.

Most vizsgáljuk meg a person típusú csomópont jelöltek esetében, hogy mi a helyzet:


    cayley> y_person = g.V().Has("<type>", "</people/person>")

    => [internal Iterator]
    -----------
    1 Result
    Elapsed time: 0.552285 ms

    cayley> y_person.OutPredicates().All()

    ****
    id : <name>
    ****
    id : <type>
    ****
    id : </film/film/directed_by>
    ****
    id : </film/film/starring>
    -----------
    4 Results
    Elapsed time: 95.020065 ms

Ugyanazt az eredményt kaptuk, ami meglepő, mert egy személy esetében nehéz értelmezni mind a </film/film/directed_by>, mind pedig a </film/film/starring> kimenő predikátumokat.

Az eredmény gyanús, ezért vizsgáljuk meg kicsit tüzetesebben azt.

A filmeknek feltehetően van rendezője, tehát értelmes lehet erre vonatkozó kapcsolatokat vizsgálni:


    cayley> y_film.Out("</film/film/directed_by>").Count()

    => 33310
    -----------
    1 Result
    Elapsed time: 108.365305 ms

Ilyenből találunk 33310-et, és ez logikusnak látszik.

Ellenőrizzük, hogy ezek a predikátumok valóban személyekre mutatnak-e!


    cayley> y_film.Out("</film/film/directed_by>").Has("<type>", "</people/person>").Count()

    => 33310
    -----------
    1 Result
    Elapsed time: 168.508677 ms

A számosság alapján ítélve igen. Nézzünk meg néhányat név szerint is:


    cayley> y_film.Out("</film/film/directed_by>").Has("<type>", "</people/person>").Out("<name>").GetLimit(5)

    ****
    id : Lucio Fulci
    ****
    id : Umberto Lenzi
    ****
    id : Murali K. Thalluri
    ****
    id : Theo van Gogh
    ****
    id : Roland Emmerich
    -----------
    5 Results
    Elapsed time: 1.071004 ms

Úgy tűnik, hogy ez a kapcsolat és csomópont típus rendben van.

Most nézzük meg, hogy a </people/person> típusú csomópontoknak van-e “rendezte” kapcsolata:


    cayley> y_person.Out("</film/film/directed_by>").Count()

    => 6
    -----------
    1 Result
    Elapsed time: 96.554532 ms

Meglepő, de van 6 ilyen. Nézzük meg ezt a 6 személyt részletesebben, tag-ekkel kiegészítve, hogy pontosan lássuk mi is a helyzet:


    cayley> y_person.Tag("person").Out("</film/film/directed_by>").Tag("directed_by").All()

    ****
    directed_by : </en/quentin_tarantino>
    id : </en/quentin_tarantino>
    person : </en/death_proof>
    ****
    directed_by : </en/mysterio>
    id : </en/mysterio>
    person : </en/jazmin>
    ****
    directed_by : </en/robert_rodriguez>
    id : </en/robert_rodriguez>
    person : </en/planet_terror>
    ****
    directed_by : </en/keenen_ivory_wayans>
    id : </en/keenen_ivory_wayans>
    person : </en/scary_movie_2>
    ****
    directed_by : </en/david_zucker>
    id : </en/david_zucker>
    person : </en/scary_movie_3>
    ****
    directed_by : </en/ralph_bakshi>
    id : </en/ralph_bakshi>
    person : </en/the_lord_of_the_rings_1978>
    -----------
    6 Results
    Elapsed time: 95.293387 ms

Az eredmény listából látható, hogy ennél a 6 csomópontnál a <directed_by> predikátum valóban személyekre mutat, de a kiinduló csomópontok IRI-jéből látszik, hogy azok valójában nem személyek, hanem filmek. Tehát igen nagy valószínűséggel állíthatjuk, hogy ebben a hat esetben a filmre vonatkozó IRI kimenő <type> predikátumai hibásan a </people/person> csomópontra mutat, a </film/film> csomópont helyett. Ezért ezt a kapcsolatot nem tekintjük érvényesnek, bár az adatbázis kétség kívül tartalmaz ilyen éleket.

A 7. ábra a korrigált séma rajzát szemlélteti, amire felvezettük a legutóbbi megállapításainkat, azaz a y_film típusú csomópont </film/film/directed_by> predikátuma y_person típusú csomópontra mutat, a y_person-nak pedig nincs ilyen kimenő predikátuma.

7. ábra: A `y_film` csomópont kimenő predikátumai.

Most nézzük meg mi a helyzet a </film/film/starring> predikátummal.


    cayley> y_film.Out("</film/film/starring>").Count()

    => 136737
    -----------
    1 Result
    Elapsed time: 140.230179 ms

    cayley> y_film.Out("</film/film/starring>").GetLimit(5)

    ****
    id : _:117646
    ****
    id : _:117647
    ****
    id : _:117648
    ****
    id : _:117649
    ****
    id : _:117240
    -----------
    5 Results
    Elapsed time: 1.052197 ms

A y_film típusú csomópontnak van 136737 ilyen kapcsolata, amelyek Blank Node-ok.

Nézzük meg, hogy ezek a “starring” node-ok milyen bemenő és kimenő predikátumokkal rendelkeznek:


    cayley> y_film.Out("</film/film/starring>").OutPredicates().All()

    ****
    id : </film/performance/actor>
    ****
    id : </film/performance/character>
    -----------
    2 Results
    Elapsed time: 255.873653 ms

    cayley> y_film.Out("</film/film/starring>").InPredicates().All()

    ****
    id : </film/film/starring>
    -----------
    1 Result
    Elapsed time: 242.836008 ms

A “starring”-ra utaló Blank node-ok bejövő és kimenő predikátumai megegyeznek azzal a csomópont változóval, amit a séma diagramon x-szel jelöltünk.

Ellenőrzésképpen nézzük meg, hogy tudunk-e filmekhez szerepeket és konkrét színészeket találni, a predikátumok segítségével:


    cayley> y_film = g.V().Has("<type>", "</film/film>")

    => [internal Iterator]
    -----------
    1 Result
    Elapsed time: 0.897193 ms

    cayley> filmWithTitle = y_film.Tag("film").Out("<name>").Tag("filmTitle").Back("film")

    => [internal Iterator]
    -----------
    1 Result
    Elapsed time: 0.680148 ms

    cayley> filmStar = filmWithTitle.Out("</film/film/starring>").Tag("star")

    => [internal Iterator]
    -----------
    1 Result
    Elapsed time: 0.340275 ms

    cayley> {t("</film/performance/character>").Tag("character").Back("star").Out("</film/performance/actor>").Out("<name>").Tag("actorName").GetLimit("10")

    ****
    actorName : Katherine Heigl
    character : Arlene
    film : </en/100_girls>
    filmTitle : 100 Girls
    id : Katherine Heigl
    star : _:239
    ****
    actorName : Joely Richardson
    character : Anita
    film : </en/101_dalmatians>
    filmTitle : 101 Dalmatians
    id : Joely Richardson
    star : _:457
    ****
    actorName : Susanne Blakeslee
    character : Cruella de Vil
    film : </en/101_dalmatians_ii_patchs_london_adventure>
    filmTitle : 101 Dalmatians II: Patch's London Adventure
    id : Susanne Blakeslee
    star : _:71373
    ****
    actorName : Brian Markinson
    character : Daniel
    film : </en/10_5_apocalypse>
    filmTitle : 10.5: Apocalypse
    id : Brian Markinson
    star : _:218
    ****
    actorName : Heath Ledger
    character : Patrick Verona
    film : </en/10_things_i_hate_about_you>
    filmTitle : 10 Things I Hate about You
    id : Heath Ledger
    star : _:503
    ****
    actorName : James Marsden
    character : Tommy
    film : </en/10th_wolf>
    filmTitle : 10th & Wolf
    id : James Marsden
    star : _:284
    ****
    actorName : Jeana Tomasino
    character : Karen
    film : </en/10_to_midnight>
    filmTitle : 10 to Midnight
    id : Jeana Tomasino
    star : _:100658
    ****
    actorName : Jason Segel
    character : Leon (paramedic 1)
    film : </en/11_14>
    filmTitle : 11:14
    id : Jason Segel
    star : _:108561
    ****
    actorName : Henry Fonda
    character : Juror #8
    film : </en/12_angry_men>
    filmTitle : 12 Angry Men
    id : Henry Fonda
    star : _:462
    ****
    actorName : Lee J. Cobb
    character : Juror #3
    film : </en/12_angry_men>
    filmTitle : 12 Angry Men
    id : Lee J. Cobb
    star : _:463
    -----------
    10 Results
    Elapsed time: 7.233902 ms

Ez határozottan működőképesnek tűnik.

Tehát kimondhatjuk, hogy az x csomópont változó képviseli a film-sztárokat.

Azt is kijelenthetjük, hogy az x kimenő predikátumai közül mind a y_person-ra mutató </film/performance/actor> predikátum, és a </film/performance/character> literál értékre mutató predikátum is valós kapcsolatokat jelölnek.

Felmerül a kérdés, hogy az x-ből mutat-e </film/performance/actor> predikátum a y_film csomópont típus irányába?


    filmStar.Out("</film/performance/actor>").Has("<type>", "</film/film>").Count()

    => 1
    -----------
    1 Result
    Elapsed time: 1315.917655 ms

Fura módon ebből is találunk egyet.

Ki is ez a film-sztár, és kire mutat, mint színész a kapcsolat?


    cayley> filmStar.Out("</film/performance/actor>").Has("<type>", "</film/film>").All()

    ****
    film : </en/jazmin>
    filmTitle : Jazmin
    id : </en/jazmin>
    star : _:23742
    -----------
    1 Result
    Elapsed time: 1340.16577 ms

Jól látható, hogy ennek a filmsztárnak az azonosítója a _:23742 Blank Node, ami színészként személy helyett egy filmre mutat. Nézzük meg, hogy személyre vonatkozóan is létezik-e ilyen kapcsolata:


    cayley> g.V("_:23742").Out("</film/performance/actor>").Has("<type>", "</people/person").Count()

    => 0
    -----------
    1 Result
    Elapsed time: 0.68618 ms

Nincs ilyen kapcsolata. Kijelenthetjük, hogy egy újabb hibát fedeztünk fel az adatbázisban, és átvezethetjük a tapasztalatainkat a séma rajzra, a 8. ábrának megfelelően.

8. ábra: Az `y_person` és `x_character` csomópont típusok kapcsolatai.

Végül elemezzük az utolsó kapcsolatot, amit még nem vizsgáltunk meg eddig. Az y_person-ból kiinduló </film/film/starring> predikátumot.


    cayley> y_person = g.V().Has("<type>", "</people/person>").Out("</film/film/starring>").Count()

    => 96
    -----------
    1 Result
    Elapsed time: 85.149164 ms

Meglepő, de ebből is találunk 96 darabot. Járjunk végére, hogy hová mutatnak ezek a kapcsolatok!


    cayley> y_person = g.V().Has("<type>", "</people/person>").Out("</film/film/starring>").GetLimit(5)

    ****
    id : _:95530
    ****
    id : _:95531
    ****
    id : _:95532
    ****
    id : _:95533
    ****
    id : _:95534
    -----------
    5 Results
    Elapsed time: 12.859301 ms

Jól látható, hogy Blank node-ok, tehát valószínűleg x_character típusú csomópontokra mutatnak.

Hajtsunk végre egy olyan lekérdezést, ahol személy típusú csomópontból (“originPerson”) kiindulva, a </film/film/starring> kapcsolaton keresztül továbbmegy a színész (“actor”) irányába, és nézzük meg a lánc végén álló csomópont nevét és típusát. Minden kiinduló csomópontot csak egyszer jelenítsen meg.


    cayley> g.V().Tag("originPerson").Has("<type>", "</people/person>").Out("<type>").Tag("originType").Back("originPerson").Out("</film/film/starring>").Out("</film/performance/actor>").Tag("actor").Out("<type>").Tag("actorType").Back("person").All()

    ****
    actor : </en/jazmin>
    actorType : </film/film>
    id : </en/jazmin>
    originPerson : </en/jazmin>
    originType : </film/film>
    ****
    actor : </en/jazmin>
    actorType : </people/person>
    id : </en/jazmin>
    originPerson : </en/jazmin>
    originType : </film/film>
    ****
    actor : </en/jazmin>
    actorType : </people/person>
    id : </en/jazmin>
    originPerson : </en/jazmin>
    originType : </people/person>
    ****

    ...


    ****
    actor : </en/anthony_daniels>
    actorType : </people/person>
    id : </en/the_lord_of_the_rings_1978>
    originPerson : </en/the_lord_of_the_rings_1978>
    originType : </people/person>
    ****
    actor : </guid/9202a8c04000641f800000000112e07f>
    actorType : </people/person>
    id : </en/the_lord_of_the_rings_1978>
    originPerson : </en/the_lord_of_the_rings_1978>
    originType : </film/film>
    -----------
    100 Results
    Elapsed time: 965.035817 ms

Ha végignézzük az eredménylistát, láthatjuk, hogy ismét sikerült felfedeznünk ellentmondásos kapcsolatokat az adatbázisban. Többféle hibát is felfedezhetünk. Gyanítható, hogy bizonyos y egyszerre rendelkeznek </film/film> és </people/person> csomópontokra mutató <type> predikátumokkal.

Ellenőrizzük, hogy feltevésünk megfelel-e a valóságnak:


    cayley> g.V().Has("<type>", "</people/person>").And(g.V().Has("<type>", "</film/film>")).All()

    ****
    id : </en/death_proof>
    ****
    id : </en/jazmin>
    ****
    id : </en/planet_terror>
    ****
    id : </en/scary_movie_2>
    ****
    id : </en/scary_movie_3>
    ****
    id : </en/the_lord_of_the_rings_1978>
    -----------
    6 Results
    Elapsed time: 72.999153 ms

Sajnos feltevésünk helyesnek bizonyult. Ezek azonban egyértelműen hibák. Kijelenthetjük tehát, hogy a person típusú csomópontból, normális esetben nem indul ki </film/film/starring> predikátum sem a character, sem más csomópont irányába.

A végleges séma tehát, ami csak az értelmes kapcsolatokat szemlélteti, a 9. ábrán látható.

9. ábra: A film adatbázis sémája.