8 Szóbeágyazások
8.1 A szóbeágyazás célja
Az eddigi fejezetekben elsősorban a szózsák (bag of words) alapú módszerek voltak előtérben. A szózsák alapú módszerekkel szemben, amelyek alkalmazása során elveszik a kontextuális tartalom, a szóbeágyazáson (word embedding) alapuló modellek kimondottan a kontextuális információt ragadják meg. A szóbeágyazás a topikmodellekhez hasonlóan a felügyelet nélküli tanulás módszerére épül, azonban itt a dokumentum domináns kifejezéseinek és témáinak feltárása helyett a szavak közötti szemantikai kapcsolat megértése a cél. Vagyis a modellnek képesnek kell lennie az egyes szavak esetén szinonimáik és ellentétpárjaik megtalálására.
A hagyományos topikmodellezés esetén a modell a szavak dokumentumokon belüli együttes megjelenési statisztikái alapján becsül dokumentum-topik, illetve topik-szó eloszlásokat, azzal a céllal, hogy koherens téma-csoportokat képezzen. Ezzel szemben a szóbeágyazás legújabb iskolája már neurális halókon alapul. A neurális háló a tanítási folyamata során az egyes szavak vektorreprezentációját állítja elő. A vektorok jellemzően 100–300 dimenzióból állnak, a távolságuk alapján pedig megállapítható, hogy az egyes kifejezések milyen szemantikai kapcsolatban állnak egymással.
A szóbeágyazás célja tehát a szemantikai relációk feltárása. A szavak vektorizálásának köszönhetően bármely (a korpuszunkban szereplő) tetszőleges számú szóról eldönthetjük, hogy azok milyen szemantikai kapcsolatban állnak egymással, azaz szinonimaként vagy ellentétes fogalompárként szerepelnek. A szóvektorokon dimenziócsökkentő eljárást alkalmazva, s a multidimenzionális (100–300 dimenziós) teret 2 dimenziósra szűkítve könnyen vizualizálhatjuk is a korpuszunk kifejezései között fennálló szemantikai távolságot, és ahogy a lenti ábrákon láthatjuk, azt, hogy az egyes kifejezések milyen relációban állnak egymással – a szemantikailag hasonló tartalmú kifejezések egymáshoz közel, míg a távolabbi jelentéstartalmú kifejezések egymástól távolabb foglalnak helyet. A klasszikus példa, amivel jól lehet szemléltetni a szóvektorok közötti összefüggést: king - man + woman = queen
.
8.2 Word2Vec és GloVe
A társadalomtudományokban szóbeágyazásra a két legnépszerűbb algoritmus – a Word2Vec és a GloVe – a kontextuális szövegeloszláson (distributional similarity based representations) alapul, vagyis abból a feltevésből indul ki, hogy a hasonló kifejezések hasonló kontextusban fordulnak elő, emellett mindkettő sekély neurális hálón (2 rejtett réteg) alapuló modell.43 A Word2Vec-nek két verziója van: Continuous Bag-of-words (CBOW) és SkipGram (SG). Előbbi a kontextuális szavakból jelzi előre (predicting) a kontextushoz legszorosabban kapcsolódó kifejezést, míg utóbbi adott kifejezésből jelzi előre a kontextust Mikolov et al. (2013). A GloVe (Global Vectors for Word Representation) a Word2Vec-hez hasonlóan neurális hálón alapuló, szóvektorok előállítását célzó modell, a Word2Vec-kel szemben azonban nem a meghatározott kontextus-ablakban (context window) megjelenő kifejezések közti kapcsolatokat tárja fel, hanem a szöveg globális jellemzőit igyekszik megragadni az egész szöveget jellemző együttes előfordulási gyakoriságok (co-occurrance) meghatározásával Pennington, Socher, and Manning (2014). Míg a Word2Vec modell prediktív jellegű, addig a GloVe egy statisztikai alapú (count-based) modell, melyek gyakorlati hasznosításukat tekintve nagyon hasonlóak.
A szóvektor modellek között érdemes megemlíteni a fastText-et is, mely 157 nyelvre (köztük a magyarra is) kínál a szóbeágyazás módszerén alapuló, előre tanított szóvektorokat, melyet tovább lehet tanítani speciális szövegkorpuszokra, ezzel jelentősen lerövidítve a modell tanításához szükséges idő- és kapacitásszükségletet (Mikolov et al. (2018)). Habár a GloVe és Word2Vec skip-gram módszerek hasonlóságát a szakirodalom adottnak veszi, a tényleges kép ennél árnyaltabb. A GloVe esetében a ritkán előforduló szavak kisebb súlyt kapnak a szóvektorok számításánál, míg a Word2Vec alulsúlyozza a nagy frekvenciájú szavakat. Ennek a következménye, hogy a Word2Vec esetében gyakori, hogy a szemantikailag legközelebbi szó az egy elütés, nem pedig valid találat. Ennek ellenére a két módszer (amennyiben a Word2Vec algoritmusnál a kisfrekvenciájú tokeneket kiszűrjük) az emberi validálás során nagyon hasonló eredményeket hozott (Spirling and Rodriguez 2021).
A fejezetben a gyakorlati példa során a GloVe algoritmust használjuk majd, mivel véleményünk szerint jobb és könnyebben követhető a dokumentációja az implementációt tartalmazó R csomagnak, mint a többi alternatívának.
8.2.1 GloVe használata magyar média korpuszon
Az elemzéshez a text2vec
csomagot használjuk, ami a GloVe implementációt tartalmazza (Selivanov, Bickel, and Wang 2020). A lenti kód a csomag dokumentáción alapul és a Társadalomtudományi Kutatóközpont által a Hungarian Comparative Agendas Project (CAP) adatbázisában tárolt Magyar Nemzet korpuszt használja.44
library(text2vec)
library(quanteda)
library(quanteda.textstats)
library(readtext)
library(readr)
library(dplyr)
library(tibble)
library(stringr)
library(ggplot2)
library(plotly)
library(HunMineR)
A lenti kód blokk azt mutatja be, hogyan kell a betöltött korpuszt tokenizálni és mátrix formátumba alakítani. A korpusz a Magyar Nemzet 2004 és 2014 közötti címlapos cikkeit tartalmazza. Az eddigi előkészítő lépéseket most is megtesszük: kitöröljük a központozást, a számokat, a magyar töltelékszavakat, illetve kisbetűsítünk és eltávolítjuk a felesleges szóközöket és töréseket.
mn <- HunMineR::data_magyar_nemzet_large
mn_clean <- mn %>%
mutate(
text = str_remove_all(string = text, pattern = "[:cntrl:]"),
text = str_remove_all(string = text, pattern = "[:punct:]"),
text = str_remove_all(string = text, pattern = "[:digit:]"),
text = str_to_lower(text),
text = str_trim(text),
text = str_squish(text)
)
A glimpse
funkció segítségével belepillanthatunk mind a két korpuszba és láthatjuk, hogy sikeres volt a tisztítása, valamint azt is, hogy jelenleg egyetlen metaadatunk a dokumentumok azonosítója.
glimpse(mn)
#> Rows: 35,021
#> Columns: 2
#> $ doc_id <chr> "mn_2002_05_27_00.txt", "mn_2002_05_27_01.txt", "mn_2002_05_27_02.txt", "mn_2002_05_27_03.txt", "mn_2002_05_27_04.t…
#> $ text <chr> "Csere szerb módra\nNagy vihart kavart Szerbiában a kormánykoalíció \nvezetőségének azon döntése, hogy lecseréli az…
glimpse(mn_clean)
#> Rows: 35,021
#> Columns: 2
#> $ doc_id <chr> "mn_2002_05_27_00.txt", "mn_2002_05_27_01.txt", "mn_2002_05_27_02.txt", "mn_2002_05_27_03.txt", "mn_2002_05_27_04.t…
#> $ text <chr> "csere szerb módranagy vihart kavart szerbiában a kormánykoalíció vezetőségének azon döntése hogy lecseréli azokat …
Fontos különbség, hogy az eddigi munkafolyamatokkal ellentétben a GloVe algoritmus nem egy dokumentum-kifejezés mátrixon dolgozik, hanem egy kifejezések együttes előfordulását tartalmazó mátrixot (feature co-occurence matrix) kell készíteni inputként. Ezt a quanteda
fcm()
függvényével tudjuk előállítani, ami a tokenekből készíti el a mátrixot. A tokenek sorrendiségét úgy tudjuk megőrizni, hogy egy dfm
objektumból csak a kifejezéseket tartjuk meg a featnames()
függvény segítségével, majd a teljes token halmazból a tokens_select()
függvénnyel kiválasztjuk őket.
mn_corpus <- corpus(mn_clean)
mn_tokens <- tokens(mn_corpus) %>%
tokens_remove(stopwords(language = "hungarian"))
features <- dfm(mn_tokens) %>%
dfm_trim(min_termfreq = 5) %>%
quanteda::featnames()
mn_tokens <- tokens_select(mn_tokens, features, padding = TRUE)
Az fcm
megalkotása során a célkifejezéstől való távolság függvényében súlyozzuk a tokeneket.
mn_fcm <- quanteda::fcm(mn_tokens, context = "window", count = "weighted", weights = 1/(1:5),
tri = TRUE)
A tényleges szóbeágyazás a text2vec
csomaggal történik. A GlobalVector
egy új „környezetet” (environment) hoz létre. Itt adhatjuk meg az alapvető paramétereket. A rank
a vektor dimenziót adja meg (a szakirodalomban a 300–500 dimenzió a megszokott). A többi paraméterrel is lehet kísérletezni, hogy mennyire változtatja meg a kapott szóbeágyazásokat. A fit_transform
pedig a tényleges becslést végzi. Itt az iterációk számát (a gépi tanulásos irodalomban epoch-nak is hívják a tanulási köröket) és a korai leállás (early stopping) kritériumát a convergence_tol
megadásával állíthatjuk be. Minél több dimenziót szeretnénk és minél több iterációt, annál tovább fog tartani a szóbeágyazás futtatása.
Az egyszerűség és a gyorsaság miatt a lenti kód 10 körös tanulást ad meg, ami a relatíve kicsi Magyar Nemzet korpuszon ~3 perc alatt fut le.45 Természetesen minél nagyobb korpuszon, minél több iterációt futtatunk, annál pontosabb eredményt fogunk kapni. A text2vec
csomag képes a számítások párhuzamosítására, így alapbeállításként a rendelkezésre álló összes CPU magot teljesen kihasználja a számításhoz. Ennek ellenére egy százezres, milliós korpusz esetén több óra is lehet a tanítás.
glove <- GlobalVectors$new(rank = 300, x_max = 10, learning_rate = 0.1)
mn_main <- glove$fit_transform(mn_fcm, n_iter = 10, convergence_tol = 0.1)
#> INFO [14:20:07.100] epoch 1, loss 0.2295
#> INFO [14:20:20.771] epoch 2, loss 0.0963
#> INFO [14:20:33.842] epoch 3, loss 0.0706
#> INFO [14:20:47.761] epoch 4, loss 0.0490
#> INFO [14:21:03.564] epoch 5, loss 0.0411
#> INFO [14:21:16.841] epoch 6, loss 0.0361
#> INFO [14:21:39.893] epoch 7, loss 0.0326
#> INFO [14:21:56.744] epoch 8, loss 0.0298
#> INFO [14:21:56.746] Success: early stopping. Improvement at iterartion 8 is less then convergence_tol
A végleges szóvektorokat a becslés során elkészült két mátrix összegeként kapjuk.
Az egyes szavakhoz legközelebb álló szavakat a koszinusz hasonlóság alapján kapjuk, a sim2()
függvénnyel. A lenti példában „l2” normalizálást alkalmazunk, majd a kapott hasonlósági vektort csökkenő sorrendbe rendezzük. Példaként a „polgármester” szónak a környezetét nézzük meg. Mivel a korpuszunk egy politikai napilap, ezért nem meglepő, hogy a legközelebbi szavak a politikához kapcsolódnak.
teszt <- mn_word_vectors["polgármester", , drop = F]
cos_sim_rom <- text2vec::sim2(x = mn_word_vectors, y = teszt, method = "cosine", norm = "l2")
head(sort(cos_sim_rom[, 1], decreasing = TRUE), 5)
#> polgármester mszps szocialista fideszes politikus
#> 1.000 0.517 0.507 0.464 0.408
A lenti show_vector()
függvényt definiálva a kapott eredmény egy data frame lesz, és az n
változtatásával a kapcsolódó szavak számát is könnyen változtathatjuk.
show_vector <- function(vectors, pattern, n = 5) {
term <- mn_word_vectors[pattern, , drop = F]
cos_sim <- sim2(x = vectors, y = term, method = "cosine", norm = "l2")
cos_sim_head <- head(sort(cos_sim[, 1], decreasing = TRUE), n)
output <- enframe(cos_sim_head, name = "term", value = "dist")
return(output)
}
Példánkban láthatjuk, hogy a „barack” szó beágyazásának eredménye nem gyümölcsöt fog adni, hanem az Egyesült Államok elnökét és a hozzá kapcsolódó szavakat.
show_vector(mn_word_vectors, "barack", 10)
#> # A tibble: 10 × 2
#> term dist
#> <chr> <dbl>
#> 1 barack 1
#> 2 obama 0.726
#> 3 amerikai 0.428
#> 4 elnök 0.393
#> 5 demokrata 0.386
#> 6 republikánus 0.281
#> # ℹ 4 more rows
Ugyanez működik magyar vezetőkkel is.
show_vector(mn_word_vectors, "orbán", 10)
#> # A tibble: 10 × 2
#> term dist
#> <chr> <dbl>
#> 1 orbán 1
#> 2 viktor 0.931
#> 3 miniszterelnök 0.763
#> 4 mondta 0.698
#> 5 kormányfő 0.685
#> 6 fidesz 0.678
#> # ℹ 4 more rows
A szakirodalomban klasszikus vektorműveletes példákat is reprokuálni tudjuk a Magyar Nemzet korpuszon készített szóbeágyazásainkkal. A budapest - magyarország + német + németország
eredményét úgy kapjuk meg, hogy az egyes szavakhoz tartozó vektorokat kivonjuk egymásból, illetve hozzáadjuk őket, ezután pedig a kapott mátrixon a quanteda
csomag textstat_simil()
függvényével kiszámítjuk az új hasonlósági értékeket.
budapest <- mn_word_vectors["budapest", , drop = FALSE] - mn_word_vectors["magyarország", , drop = FALSE] + mn_word_vectors["német", , drop = FALSE] +
+ mn_word_vectors["németország", , drop = FALSE]
cos_sim <- textstat_simil(x = as.dfm(mn_word_vectors), y = as.dfm(budapest), method = "cosine")
head(sort(cos_sim[, 1], decreasing = TRUE), 5)
#> budapest németország német airport kancellár
#> 0.602 0.557 0.542 0.424 0.395
A szavak egymástól való távolságát vizuálisan is tudjuk ábrázolni. Az egyik ezzel kapcsolatban felmerülő probléma, hogy egy 2 dimenziós ábrán akarunk egy 3–500 dimenziós mátrixot ábrázolni. Több lehetséges megoldás is van, mi ezek közül a lehető legegyszerűbbet mutatjuk be.46 Első lépésben egy data frame-et készítünk a szóbeágyazás eredményeként kapott mátrixból, megtartva a szavakat az első oszlopban a tibble
csomag rownames_to_column()
függvényével. Mivel csak 2 dimenziót tudunk ábrázolni egy tradícionális statikus ábrán, ezért a V1
és V2
oszlopokat tartjuk csak meg, amik az első és második dimenziót reprezentálják.
mn_embedding_df <- as.data.frame(mn_word_vectors[, c(1:2)]) %>%
tibble::rownames_to_column(var = "words")
Ezután pedig a ggplot()
függvényt felhasználva definiálunk egy új, embedding_plot()
nevű, függvényt, ami az elkészült data frame alapján bármilyen kulcsszó kombinációt képes ábrázolni.
embedding_plot <- function(data, keywords) {
data %>%
filter(words %in% keywords) %>%
ggplot(aes(V1, V2, label = words)) +
labs(
x = "Első dimenzió",
y = "Második dimenzió"
) +
geom_text() +
xlim(-1, 1) +
ylim(-1, 1)
}
Példaként néhány településnevet megvizsgálva, azt látjuk, hogy a megadott szavak, jelen esetben “budapest”, “debrecen”, “washington”, “moszkva” milyen közel vagy távol vannak egymástól, vagyis milyen gyakorisággal fordulnak elő ugyanazon szavak társaságában. A magyar városok közel helyezkednek el egymáshoz, ám “washington” és “moszkva” távolsága nagyobb. Ennek az oka az lehet hogy a két magyar nagyváros gyakrabban szerepel hasonló kontextusban a belföldi hírekben, míg a két külföldi főváros valószínűleg eltérő külpolitikai környezetben jelenik meg.
words_selected <- c("moszkva", "debrecen", "budapest", "washington")
embedded <- embedding_plot(data = mn_embedding_df, keywords = words_selected)
ggplotly(embedded)
Egy kiváló tanulmányban Spirling and Rodriguez (2021) (könyvünk írásakor még nem jelent meg) összehasonlítják a Word2Vec és GloVe módszereket, különböző paraméterekkel, adatbázisokkal. Azoknak, akiket komolyabban érdekelnek a szóbeágyazás gyakorlati alkalmazásának a részletei, mindenképp ajánljuk elolvasásra.↩︎
A Magyar CAP Project által kezelt adatbázisok regisztrációt követően elérhetőek az elábbi linken: https://cap.tk.hu/adatbazisok. A
text2vec
csomag dokumentációja: https://cran.r-project.org/web/packages/text2vec/vignettes/glove.html↩︎A futtatásra használt PC konfiguráció: CPU: Intel Core i5-4460 (3.2GHz); RAM: 16GB↩︎
Az egyik legelterjedtebb dimenzionalitás csökkentő eljárás a szakirodalomban a főkomponens-analízis (principal component analysis), illetve szintén gyakran használt az irodalomban az úgynevezett t-SNE (t-distributed stochastic neighbor embedding).↩︎