Egy korábbi bejegyzésben megnéztük, hogy néz ki a Szózsák modell a Számítógépes nyelvészet ősmodellje. Mai bejegyzésünkbe egy modernebb eljárással fogunk megismerkedni, az ún. word2vec-el. Ami Neurális Hálózatok segítségével old meg, végez számítógépes nyelvészeti feladatokat.
A probléma
A word2vec kidolgozását egy, a Google-nak dolgozó kutatócsoport végezte, akiket Tomáš Mikolov cseh kutató vezetett. Az algoritmust két tudományos cikkben publikálták 2013-ban:
- Distributed Representations of Words and Phrasesand their Compositionality
- Efficient Estimation of Word Representations inVector Space
Ezek hatalmas sikert értek el, 2020 augusztusára 38000 másik tudományos cikk hivatkozik rájuk[1]. Egy tudományos cikk fontossága általában abban mérhető, hogy mennyire tud inspirálni másokat. Ez persze gyakran összekapcsolódik a korszellemmel is. Hiába rejlik jelentős potenciál egy cikkben, ha írásakor a kutatók és az őket finanszírozó cégek, intézmények figyelme más téma felé irányul. Mikolovék cíkkeiről; az eredetiségüket semmiképp nem csökkentve; elmondhatjuk, hogy jó időben, jó témával foglalkozott. A közösségi média felfutásával egyidőben a cégek oldaláról hatalmas igény keletkezett a számítógépes nyelvészetre. Ezekben a médiákban jelentős mennyiségű írott szöveg keletkezik, amit a szolgáltatók igyekeznek értelemszerűen saját üzleti érdekeik szerint felhasználni. Nézzük néhány példát arra, hogy word2vec milyen területeken került alkalmazásra:
- Zenei ajánlórendszer – Spotify ismerős? A gép, ami eldönti, milyen zene tetszik neked.[2]
- Böngészési ajánlások – Megvan az, amikor szállást akarsz foglalni egy oldalon, és a keresés „Relevancia szerint” van rendezve? Gondolkoztál már azon, hogy kalkulálják ezt a relevanciát? Lényegében van néhány keresőszavunk, amit a felhasználó megadott és ez alapján ki kell találnunk mire gondol pontosan. Pl. beírja, hogy Párizs. Oké, Párizsba akar menni. De mennyit akar fizetni, mekkora helyet keres, milyen távol a belvárostól? Az Airbnb word2vec-et használ például erre a problémára.
- Reklámok – Ez gondolom egyértelmű, vagy talán mégse? Tegyük fel, hogy a felhasználó kamera lencsét keres. Az addig tiszta sor, hogy ajánljunk neki lencséket, de mondjuk nem kellene fényképezőgép táskát is? Sok termék esetén, hogy találjuk meg azokat a termékeket amik valahogy kapcsolódnak a felhasználó eredeti kereséséhez? Gondolom nem árulok el titkot, ha azt mondom van olyan cég, ami erre word2vec-et használ, például Yahoo.
Rendben, mi a fenti problémákban a közös? Vegyük észre, hogy egyik esetben sem beszélünk hosszabb, irodalmi minőségű szövegekről. Egy-egy kulcsszó alapján kell a problémát megoldanunk. És a word2vec ezt is csinálja. Szavak kapcsolatát írja le. Vagyis, hogy mekkora a távolság különböző kifejezések között. A modell építése során szeretnénk egy olyan többdimenziós vektorteret előállítani ami logikusan kifejezi az egyes szavak kapcsolatát. Ez talán egy kicsit homályos, de ábrázolva könnyen érthető lesz:

Amit látni kell, hogy a fenti képen a kapcsolat a „HU” és „Budapest” között nagyon hasonló a „FR” és „Párizs” szavak kapcsolatára: ugyanaz a vektorok nagysága és megegyezik az irányuk is. Amennyiben sikerül egy a fenti teret előállítanunk akkor a tanulószövegben nem előforduló kapcsolatokat is könnyen találhatnánk, mondjuk például „UK” és „London”.
Rendben: most már tudjuk a célt, de hogy valósítjuk meg? Ezt nézzük meg a következő bekezdésben.
Megvalósítás
Két egymással szöges ellentétben álló konkrét megvalósítás létezik. Az első a korábban már említett szózsák modell egy változata, a „Folytonos szózsák modell”[3]. A második a „Skip-Gram” modell. Ami közös ugyanakkor, hogy a mindkét algoritmussal egy két rétegű sekély Neurális Hálózatot tanítunk. Most részletezzük egy kicsit ezeket a konkrét megvalósításokat.
Folytonos szózsák modell
Ez tulajdonképpen egy egyszerű mozgó ablakon alapuló szózsák modell. A tanulása során egy x hosszúságú mozgó ablakkal végiglépkedünk a szövegünkön. A modell elvárt értéke mindig az ablak egyik konkrét pozíciójában levő szó lesz, a bemenete meg a fennmaradó szavak. Például legyen a szövegünk:
– Az ember senkit se lőjön agyon hiába. Ezt egy jó barátom mondta nekem. Buck, a csempész, aki tequilát visz Mexikóba. Azt hiszem, ez okos mondás, nem?[4]
Mi legyen az x? Mikolov jegyzetei alapján, 5 egy jó számnak tűnik, használjuk mi is ezt. Ezután még egy kérdés van? Mi legyen az elvárt érték? Ez bármelyik pozícióban lévő szó lehet az ablakon belül. Általában a középsőt használják, tegyünk mi is így. Ekkor a tanuló adat így alakul:
Bemenete | Elvárt érték |
---|---|
Az, ember, lőjön, se, | senkit |
ember, senkit, agyon , lőjön | se |
senkit, se, hiába , agyon | lőjön |
hiszem, ez, mondás, nem | okos |
Persze nem csak az középső szó lehet az elvárt érték. Lehet mondjuk a utolsó, ez rajtunk múlik.
Értelemszerűen ezeket a szavakat nem tudjuk ebben a formában felhasználni, vektorizálni kell őket. Mivel itt csak szavakról van szó és nem szövegről ez egyszerű: fogjuk az összes szót és egy szótárba tesszük őket. Legyen ez a szótár a . A szavak a szótárban elfoglalt sorszáma lesz a vektor amivel a szavakat reprezentáljuk. Ha ez megvan, akkor a lineáris rétegben egyszerűen összeadjuk ezeket az értékeket.
Skip-Gram
A Skip-Gram lényegében a fenti Folytonos szózsák modell megfordítása. Itt egy szóból próbáljuk előrejelezni a többi szót. Mire jó ez? Ez a módszer jobban működik a ritkán előforduló szavakkal. Viszont ez a pontosság nincs ingyen. Ennek az algoritmusnak a tanítása lassabb.
Hogy fog ebben a modellben kinézni a bemenete és elvárt értéke? A korábban már említett jegyzetek alapján 10 egy jó számnak tűnik. Mi viszont most csak használjunk 2, a példa könnyebb átláthatósága kedvéért:
Bemeneti szó indexe az ablakban | Bemeneti szó | Elvárt érték ( |
---|---|---|
ember | az | |
senkit | az | |
az | ember | |
senkit | ember | |
se | ember | |
az | senkit | |
ember | senkit | |
se | senkit | |
lőjön | senkit |
Mint fentebb látható a Skip-gram esetén mindig szópárokat alkot a bemeneti és a elvárt érték. Azt is könnyű észrevenni, hogy sokkal nagyobb lesz a tanulásra használt adathalmaz, mint a Folytonos szózsák modell esetén. Ami egyenesen vezet a lassabb tanuláshoz.
A Neurális hálózat ebben az esetben azt fogja tanulni, hogy az egyes párok mennyi alkalommal fordulnak elő. Ami elvezet minket a következő problémához a Skip-gramm modellel: A bementi és a kimeneti dimenzió ugyanaz. A bementi egy n hosszúságú bináris mátrix, ahol az n a szótárunk hossza. Ez lényegében egy „one-hot encoder”-el előállított megjelenítése a szavainknak a szótárban. A kimenet pedig szintén egy n hosszúságú mátrix. De itt a -t fejezik ki az egyes értéket, vagyis annak a valószínűségét, hogy a szótár i-ik szava a bemeneti szóval egy ablakban fordul elő. A hállózat felépítését a 2. ábra szemlélteti.

A one-hot kódolásnak több következménye van. Egyrészt mivel a bemenet nagy része 0, a Rejtett réteg kimenete is nagyrészt 0 lesz. Lényegében egy egyszerű „lookup” táblázatként funkcionál. Hogy ezt szemléltessük nézzük a következő mátrix műveletett:
A fenti mátrix számításhoz nagyon hasonló számítás történik a lejátszás során a word2vec-ben.
De ez a „one-hot”kódolás azt is jelenti, hogy hiába sekély a hálózat, ennek ellenére rengeteg neuront kell tanítani. Egy közepesen nagy szótár is könnyedén tartalmazhat 10 000 szót, jelöljük ezt -el. Gondoljunk bele mit jelent ez. Tegyük fel például, hogy 300 neuron van a rejtett rétegünkben, jelöljük ezt
-rel . Ebben az esetben az összes súly, amit optimalizálni kell[5]:
Igen, jól látjuk, 6 millió súly! Ez rengetek tanulási adatot és időt jelent. Hogy a rendszert felgyorsítjuk három trükköt használhatunk: a „mintavétel a szótárból”, a „negatív mintavételt” és a „állandósult szókapcsolatok” használatát. Most nézzük meg ezeket.
word2vec normalizálása
Mintavétel a szótárból
A szózsák modell normalizálásánál már beszéltünk róla, hogy nem minden szó ugyanolyan fontos egy szövegben. Például a névelők rengetegszer elő fognak fordulni szópárokban. Ez fontos nekünk? Ha nem kifejezetten helyesírás javító alkalmazást írunk, akkor nem. Ha pedig nem fontos akkor jó ötletnek tűnik, hogy töröljük őket, ugye? De hol húzzuk meg a határt a szavak között, amit törölni akarunk meg amiket nem? A legtriviálisabb megoldás egy lista készítése azokról a szavakról amikre nincs szükségünk. Ennek működését láttuk a korábbi bejegyzésben a „stop szavak” részben.
Ez egy kissé fapados megoldás, és talán sok ember ízlésének nem elég fair. Ezért a word2vec esetén nem ez a bevett megoldás, hanem az ún. negatív mintavétel. Ez egy fix lista helyet minden egyes szóhoz egy „nem-törlési valószínűséget” rendel. Ezt a valószínűséget így szokás számítani:
Ahol:
– a nem-törlési valószínűség
– a szó előfordulási valószínűsége. Vagyis például ha egy 1 millió szó hosszú tanuló szövegben a „supercalifragilisticexpialidocious” szó 2000-szer fordul elő, akkor tudjuk, hogy a Marry Poppins a tanuló szöveg. Ahh nem, bocsánat!!! Azt tudjuk, hogy a
.
– a „sample” paraméter, amivel szabályozzuk, mekkora részét töröljük a szövegnek. Általában 0.001-et szoktak használni.

A fenti függényv vázlata a 3. ábrán látható. Amit vegyünk észre, hogy függvény lényegesen felülsúlyozza a ritka szavakat. Meredeken csökken a nem-törlési valószínűség ahogy nő a szó gyakorisága.
Most, hogy megvan minden egyes szavunk esetén, annak törlési valószínűsége[6], csak végig kell lépkedni minden szón és dobni a kockával, hogy töröljük-e. Ha a 0 és egy közötti véletlenszerűen választott számunk nagyobb mint a , akkor az adott szó minden egyes előfordulást töröljük a szövegből.
Negatív mintavétel
Míg az előző módszer a bemeneti adatokat manipulálja a gyorsabb tanulás érdekéken, ez a módszer a visszajátszást. Vegyük észre, hogy, a one-hot kódolás miatt az elvárt érték egyetlen egy neuronon 1 a többin pedig 0. A negatív mintavétel során a visszajátszás során nem frissítjük minden egyes 0 értékű neuron súlyait. Hanem csak egy töredéküket. Mégis mennyit? Az irodalom kisebb szövegek esetén 5-20 negatív neuron frissítést ajánlja. Hosszabb szövegeknél pedig 2-5 a bevett érték.
Mit jelent ez számítási igény szempontjából? Maradva a fenti 10 000 szavas szótárnál és 300 rejtett neuronnál. Mondjuk csak frissítsük 5 negatív súlyt. Ez azt jelenti, hogy 6 neuronról kell csak a visszajátszást elvégezni[7]. Ami ugye 6*300+6 súlyt jelent a Kimeneti és a Rejtett réteg között. Ami 0.06 százaléka csupán az összes itt lévő súlyoknak. Persze ennek ellenére a Bemenet és a Rejtett réteg közötti súlyokat mind frissíteni kell, így a teljes erőforrás megtakarítás nem lesz ilyen nagy. De ha az összes súlyt vesszük figyelembe, akkor is 50%-al kevesebb számítást kell elvégeznünk.
Már csak az a kérdés, hogy válaszuk ki azt az 5 negatív szót, amit frissíteni szeretnénk? Erre az ún. unigram eloszlást használjuk. Lényegében megmérjük a szavak erőfordulási gyakoriságát és ennek alapján döntünk arról, melyik szavakat válasszuk ki. Homályos? Ne csodálkozz, ez nem teljesen tudományos. A szerzők lényegében kipróbáltak egy rakás variációt és az eredmény alapján döntötték el, hogy konkrétan milyen számítást alkalmazzanak. A konkrét egyenlet, amit ők javasolnak így néz ki:
Ahol:
– a szótárunk nagysága, ugye fentebb 10 000 szó
– az i-ik szó hányszor szerepel a szövegben
Mit keres itt a 3/4-re emelés? Ez arra való, hogy a növeljük a ritkábban erőforduló szavak kiválasztásának valószínűséget.
Állandósult szókapcsolatok
Ugye az egyértelmű, hogy a „Washington Post” két szó ami egyetlen dolgot jelöl. Ha ez a két szó ilyen formában szerepel teljesen mást jelent mint a „Washington” és a „Post” szavak külön külön. Értelemszerűen jó lenne, ha a modellünk ezeket a helyzeteket is tudná kezelni.
De hogy találjuk meg ezeket az állandósult szókapcsolatokat? Az alapötlet a következő: lépkedjünk végig egy mozgó ablakkal a teljes szövegen, és számoljuk meg minden egyes két, vagy többszavas[8] kifejezés gyakoriságát. Ha egy ilyen kifejezés gyakorisága jelentősen nagyobb, mint a kifejezést alkotó szavak gyakorisága külön-külön, akkor egy állandósult szókapcsolatot találtunk.
Ez egy egyszerű ötlet, de a megvalósítása igen problémás tud lenni. A probléma, hogy hogyan számszerűsíthetők a fenti elképzelést. Erre nincs kialakult gyakorlat, így itt én se javasolnák semmit.[9]
Végszó
Fentebb átnéztük a word2vec általános elméletét. Valamikor a közeljövőben megnézzük, hogy lehet ezt a gyakorlatban is megvalósítani. Szintén lesz bejegyzés egy leszármazottjáról a „doc2vec”-ről.
Irodalom
- Chris McCormick – Word2Vec Tutorial – The Skip-Gram Model
- Gidi Shperber – A gentle introduction to Doc2Vec
Végjegyzet
- a Google Schoolar adatbázisa alapján ↩︎
- Amikor művészeti téren engedjük, hogy egy a középszerűségre törekvő gép diktáljon az nagyon szomorú. Lényegében mindig ugyanazt kapjuk csak egy kicsit átcsomagolva. ↩︎
- angolul: Continuous bag of words ↩︎
- Rejtő Jenő: A Néma Revolverek Városa. Tudom-tudom, ez nem olyan szöveg ami feldolgozására a word2vec ajánlott. De Rejtő Jenőt sokkal szívesebben olvasok mint egy vásárlási katalógust, így ennél maradok. ↩︎
- Ne felejtsük el az eltolásokat a Rejtett és Kimeneti rétegben. A Bemenet és a Rejtett réteg között ugye 10 000 neuront kötünk össze 300-al teljesen kapcsolva. Ez 3 millió súlyt jelent, plusz a Rejtett réteg neuronjainak eltolása. A Rejtett réteg és a Kimenet között szintén 3 millió súly van, csak itt a 300 neuront kötjük össze 10 000-el, ami azt jelenti, hogy 10 000 eltolásunk is van ebben a Kimeneti rétegben. ↩︎
- Ez ugye egyszerűen:
. ↩︎
- Az 5 negatív meg az egyetlen pozitív, ugye. ↩︎
- Nem csak 2 szavas szókapcsolatok léteznek, például: New York Times. Ennek megfelelően az ablak mérete lehet 2, 3 stb. ↩︎
- Ha valakit érdekel, itt van egy konkrét megvalósítás példaként: word2vec_commented ↩︎
“Word2vec” bejegyzéshez egy hozzászólás