4 Korpuszépítés és szövegelőkészítés
4.1 Szövegbeszerzés
A szövegbányászati elemzések egyik első lépése az elemzés alapjául szolgáló korpusz megépítése. A korpuszt alkotó szövegek beszerzésének egyik módja a webscraping, melynek során weboldalakról történik az információ kinyerése.
A scrapelést végezhetjük R-ben az rvest
csomag segítségével. Fejezetünkben a scrapelésnek csupán néhány alaplépését mutatjuk meg.8
library(rvest)
library(readr)
library(dplyr)
library(lubridate)
library(stringr)
library(quanteda)
library(quanteda.textmodels)
library(quanteda.textstats)
library(HunMineR)
A szükséges csomagok 9 beolvasása után a read_html()
függvény segítségével az adott weboldal adatait kérjük le a szerverről. A read_html()
függvény argumentuma az adott weblap URL-je.
Ha például a poltextLAB
projekt honlapjáról szeretnénk adatokat gyűjteni, azt az alábbi módon tehetjük meg:
r <- rvest::read_html("https://poltextlab.tk.hu/hu")
r
#> {html_document}
#> <html lang="hu" class="no-js">
#> [1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n<meta charset="utf-8">\n<meta http-equiv="X-UA-C ...
#> [2] <body class="index">\n\n\t<script>\n\t (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n\t (i[ ...
Ezután a html_nodes()
függvény argumentumaként meg kell adnunk azt a HTML címkét vagy CSS azonosítót, ami a legyűjteni kívánt elemeket azonosítja a weboldalon. [^websraping] Ezeket az azonosítókat az adott weboldal forráskódjának megtekintésével tudhatjuk meg, amire a különböző böngészők különböző lehetőségeket kínálnak. Majd a html_text()
függvény segítségével megkapjuk azokat a szövegeket, amelyek az adott weblapon az adott azonosítóval rendelkeznek.
Példánkban a https://poltextlab.tk.hu/hu weboldalról azokat az információkat szeretnénk kigyűjteni, amelyek az <title>
címke alatt szerepelnek.
title <- read_html("https://poltextlab.tk.hu/hu") %>%
rvest::html_nodes("title") %>%
rvest::html_text()
title
#> [1] "MTA TK Political and Legal Text Mining and Artificial Intelligence Laboratory (poltextLAB)"
Ezután a kigyűjtött információkat kiírhatjuk egy csv
fájlba.
A webscraping során az egyik nehézség, ha a weboldal letiltja az automatikus letöltést, ezt kivédhetjük például különböző böngészőbővítmények segítségével, illetve a fejléc (header) vagy a hálózati kliens (user agent) megváltoztatásával. De segíthet véletlenszerű kiszolgáló (proxy) vagy VPN szolgáltatás10 használata is, valamint ha az egyes kérések között időt hagyunk. Mielőtt egy weboldal tartalmának scrapelését elkezdenénk, fontos tájékozódni a hatályos szerzői jogi szabályozásokról. A weboldalakon legtöbbször a legyűjtött szövegekhez tartozó különböző metaadatok is szerepelnek (például egy parlamenti beszéd dátuma, az azt elmondó képviselő neve), melyeket érdemes a scarpelés során szintén összegyűjteni. A scrapelés során fontos figyelnünk arra, hogy később jól használható formában mentsük el az adatokat, például .csv
,.json
vagy .txt
kiterjesztésekkel. A karakterkódolási problémák elkerülése érdekében érdemes UTF-8 vagy UTF-16-os kódolást alkalmazni, mivel ezek tartalmazzák a magyar nyelv ékezetes karaktereit is.11
Arra is van lehetőség, hogy az elemezni kívánt korpuszt papíron keletkezett, majd szkennelt és szükség szerint optikai karakterfelismerés (Optical Character Recognition – OCR) segítségével feldolgozott szövegekből építsük fel. Mivel azonban ezeket a feladatokat nem R-ben végezzük, ezekről itt nem szólunk bővebben. Az így beszerzett és .txt
vagy .csv
fájllá alakított szövegekből való korpuszépítés a következő lépésekben megegyezik a weboldalakról gyűjtött szövegekével.
4.2 Szövegelőkészítés
Az elemzéshez vezető következő lépés a szövegelőkészítés, amit a szöveg tisztításával kell kezdenünk. A szövegtisztításnál mindig járjunk el körültekintően és az egyes lépéseket a kutatási kérdésünknek megfelelően tervezzük meg, a folyamat során pedig időnként végezzünk ellenőrzést, ezzel elkerülhetjük a kutatásunkhoz szükséges információk elvesztését.
Miután az elemezni kívánt szövegeinket beszereztük, majd a Az adatok importálása alfejezetben leírtak szerint importáltuk, következhetnek az alapvető előfeldolgozási lépések, ezek közé tartozik például a scrapelés során a korpuszba került html címkék, számok és egyéb zajok (például a speciális karakterek, írásjelek) eltávolítása, valamint a kisbetűsítés, a tokenizálás, a szótövezés és a tiltólistás szavak eltávolítása, azaz stopszavazás.
A stringr
csomag segítségével először eltávolíthatjuk a felesleges html
címkéket a korpuszból.12 Ehhez először létrehozzuk a text1
nevű objektumot, ami egy karaktervektorból áll.
text1 <- c("MTA TK", "<font size='6'> Political and Legal Text Mining and Artificial Intelligence Laboratory (poltextLAB)")
text1
#> [1] "MTA TK"
#> [2] "<font size='6'> Political and Legal Text Mining and Artificial Intelligence Laboratory (poltextLAB)"
Majd a str_replace_all()
függvény segítségével eltávolítjuk két html címke közötti szövegrészt. Ehhez a függvény argumentumában létrehozunk egy regex kifejezést, aminek segítségével a függvény minden < >
közötti szövegrészt üres karakterekre cserél. Ezután a str_to_lower()
mindent kisbetűvé konvertál, majd a str_trim()
eltávolítja a szóközöket a karakterláncok elejéről és végéről.
text1 %>%
stringr::str_replace_all(pattern = "<.*?>", replacement = "") %>%
stringr::str_to_lower() %>%
stringr::str_trim()
#> [1] "mta tk"
#> [2] "political and legal text mining and artificial intelligence laboratory (poltextlab)"
4.2.1 Tokenizálás, szótövezés, kisbetűsítés és a tiltólistás szavak eltávolítása
Az előkészítés következő lépésében tokenizáljuk, azaz egységeire bontjuk az elemezni kívánt szöveget, így a tokenek az egyes szavakat vagy kifejezéseket fogják jelölni. Ennek eredményeként kapjuk meg az n-gramokat, amik a vizsgált egységek (számok, betűk, szavak, kifejezések) n-elemű sorozatát alkotják.
A következőkben a „Példa az előkészítésre” mondatot bontjuk először tokenekre a tokens()
függvénnyel, majd a tokeneket a tokens_tolower()
segítségével kisbetűsítjük, a tokens_wordstem()
függvénnyel pedig szótövezzük. Végezetül a quanteda
csomagban található magyar nyelvű stopszótár segítségével, elvégezzük a tiltólistás szavak eltávolítását. Ehhez először létrehozzuk az sw
elnevezésű karaktervektort a magyar stopszavakból. A head()
függvény segítségével belenézhetünk a szótárba, és a console-ra kiírathatjuk a szótár első hat szavát. Végül a tokens_remove()
segítségével eltávolítjuk a stopszavakat.
text <- "Példa az elokészítésre"
toks <- quanteda::tokens(text)
toks <- quanteda::tokens_tolower(toks)
toks <- quanteda::tokens_wordstem(toks)
toks
#> Tokens consisting of 1 document.
#> text1 :
#> [1] "példa" "az" "elokészítésr"
sw <- quanteda::stopwords("hungarian")
head(sw)
#> [1] "a" "ahogy" "ahol" "aki" "akik" "akkor"
quanteda::tokens_remove(toks, sw)
#> Tokens consisting of 1 document.
#> text1 :
#> [1] "példa" "elokészítésr"
Ezt követi a szótövezés (stemmelés) lépése, melynek során az alkalmazott szótövező algoritmus egyszerűen levágja a szavak összes toldalékát, a képzőket, a jelzőket és a ragokat. Szótövezés helyett alkalmazhatunk szótári alakra hozást is (lemmatizálás). A két eljárás közötti különbség abban rejlik, hogy a szótövezés során csupán eltávolítjuk a szavak toldalékként azonosított végződéseit, hogy ugyanannak a szónak különböző megjelenési formáit közös törzsre redukáljuk, míg a lemmatizálás esetében rögtön az értelmes, szótári formát kapjuk vissza. A két módszer közötti választás a kutatási kérdés alapján meghozott kutatói döntésen alapul (Grimmer and Stewart 2013).
Az alábbi példában egyetlen szó különböző alakjainak szótári alakra hozásával szemléltetjük a lemmatizálás működését. Ehhez először a text1
nevű objektumban tároljuk a szótári alakra hozni kívánt szöveget, majd tokenizáljuk és eltávolítjuk a központozást. Ezután definiáljuk a megfelelő szótövet és azt, hogy mely szavak alakjait szeretnénk erre a szótőre egységesíteni, majd a rep()
függvény segítségével a korábban zárójelben megadott kifejeztéseket az “elokeszites” lemmával helyettesítjük, azaz a korábban definiált szólakokat az általunk megadott szótári alakkal helyettesítjük. Hosszabb szövegek lemmatizálásához előre létrehozott szótárakat használhatunk, ilyen például a WordNet
, ami magyar nyelven is elérhető.13
text1 <- "Példa az előkészítésre. Az előkészítést a szövetisztítással kell megkezdenünk. Az előkészített korpuszon elemzést végzünk"
toks1 <- tokens(text1, remove_punct = TRUE)
elokeszites <- c("előkészítésre", "előkészítést", "előkészített")
lemma <- rep("előkészítés", length(elokeszites))
toks1 <- quanteda::tokens_replace(toks1, elokeszites, lemma, valuetype = "fixed")
toks1
#> Tokens consisting of 1 document.
#> text1 :
#> [1] "Példa" "az" "előkészítés" "Az" "előkészítés" "a"
#> [7] "szövetisztítással" "kell" "megkezdenünk" "Az" "előkészítés" "korpuszon"
#> [ ... and 2 more ]
A fenti text1
objektumban tárolt szöveg szótövezését az alábbiak szerint tudjuk elvégezni. Megvizsgálva az előkészítés különböző alakjainak lemmatizált és stemmelt változatát jól láthatjuk a két módszer közötti különbséget.
text1 <- "Példa az előkészítésre. Az előkészítést a szövetisztítással kell megkezdenünk. Az előkészített korpuszon elemzést végzünk"
toks2 <- tokens(text1, remove_punct = TRUE)
toks2 <- tokens_wordstem(toks2)
toks2
#> Tokens consisting of 1 document.
#> text1 :
#> [1] "Példa" "az" "előkészítésr" "Az" "előkészítést" "a" "szövetisztításs"
#> [8] "kell" "megkezdenünk" "Az" "előkészített" "korpuszon"
#> [ ... and 2 more ]
4.2.2 Dokumentum kifejezés mátrix (dtm, dfm)
A szövegbányászati elemzések nagy részéhez szükségünk van arra, hogy a szövegeinkből dokumentum kifejezés mátrix-ot (Document Term Matrix – dtm vagy Document Feature Matrix – dfm) hozzunk létre.14 Ezzel a lépéssel alakítjuk a szövegeinket számokká, ami lehetővé teszi, hogy utána különböző statisztikai műveleteket végezzünk velük.
A dokumentum kifejezés mátrix minden sora egy dokumentum, minden oszlopa egy kifejezés, az oszlopokban szereplő változók pedig megmutatják az egyes kifejezések számát az egyes dokumentumokban. A legtöbb dokumentum kifejezés mátrix ritka mátrix, mivel a legtöbb dokumentum és kifejezés párosítása nem történik meg: a kifejezések nagy része csak néhány dokumentumban szerepel, ezek értéke nulla lesz.
Az alábbi példában három, egy-egy mondatos dokumentumon szemléltetjük a fentieket. A korábban megismert módon előkészítjük, azaz kisbetűsítjük, szótövezzük a dokumentumokat, eltávolítjuk a tiltólistás szavakat, majd létrehozzuk belőlük a dokumentum kifejezés mátrixot.15
text <- c(
d1 = "Ez egy példa az elofeldolgozásra",
d2 = "Egy másik lehetséges példa",
d3 = "Ez pedig egy harmadik példa"
)
dfm <- text %>%
tokens %>%
tokens_remove(pattern = stopwords("hungarian")) %>%
tokens_tolower() %>%
tokens_wordstem(language = "hungarian") %>%
dfm()
dfm
#> Document-feature matrix of: 3 documents, 4 features (50.00% sparse) and 0 docvars.
#> features
#> docs péld elofeldolgozás lehetséges harmad
#> d1 1 1 0 0
#> d2 1 0 1 0
#> d3 1 0 0 1
4.2.3 Súlyozás
A dokumentum kifejezés mátrix lehet egy egyszerű bináris mátrix, ami csak azt az információt tartalmazza, hogy egy adott szó előfordul-e egy adott dokumentumban. Míg az egyszerű bináris mátrixban ugyanakkora súlya van egy szónak ha egyszer és ha tízszer szerepel, készíthetünk olyan mátrixot is, ahol egy szónak annál nagyobb a súlya egy dokumentumban, minél többször fordul elő. A szógyakoriság (term frequency – TF) szerint súlyozott mátrixnál azt is figyelembe vesszük, hogy az adott szó hány dokumentumban szerepel. Minél több dokumentumban szerepel egy szó, annál kisebb a jelentősége. Ilyen szavak például a névelők, amelyek sok dokumentumban előfordulnak ugyan, de nem sok tartalmi jelentőséggel bírnak. Két szó közül általában az a fontosabb, amelyik koncentráltan, kevés dokumentumban, de azokon belül nagy gyakorisággal fordul elő. A dokumentum gyakorisági érték (document frequency – DF) egy szó gyakoriságát jellemzi egy korpuszon belül. A súlyozási sémákban általában a dokumentum gyakorisági érték inverzével számolnak (inverse document frequency - IDF), ez a leggyakrabban használt TF-IDF súlyozás (term frequency & inverse document frequency - TF-IDF). Az így súlyozott TF mátrix egy-egy cellájában található érték azt mutatja, hogy egy adott szónak mekkora a jelentősége egy adott dokumentumban. A TF-IDF súlyozás értéke tehát magas azon szavak esetén, amelyek az adott dokumentumban gyakran fordulnak elő, míg a teljes korpuszban ritkán; alacsonyabb azon szavak esetén, amelyek az adott dokumentumban ritkábban, vagy a korpuszban gyakrabban fordulnak elő; és kicsi azon szavaknál, amelyek a korpusz lényegében összes dokumentumában előfordulnak (Tikk 2007, 33–37 o.)
Az alábbiakban az 1999-es törvényszövegeken szemléltetjük, hogy egy 125 dokumentumból létrehozott mátrix segítségével milyen alapvető statisztikai műveleteket végezhetünk.16
A HunMineR
csomagból tudjuk importálni a törvényeket.
Majd az importált adatokból létrehozzuk a korpuszt lawtext_corpus
néven. Ezt követi a dokumentum kifejezés mátrix kialakítása (mivel a quanteda
csomaggal dolgozunk, dfm
mátrixot hozunk létre), és ezzel egy lépésben elvégezzük az alapvető szövegtisztító lépéseket is.
lawtext_corpus <- quanteda::corpus(lawtext_df)
lawtext_dfm <- lawtext_corpus %>%
tokens(
remove_punct = TRUE,
remove_symbols = TRUE,
remove_numbers = TRUE
) %>%
tokens_tolower() %>%
tokens_remove(pattern = stopwords("hungarian")) %>%
tokens_wordstem(language = "hungarian") %>%
dfm()
A topfeatures()
függvény segítségével megnézhetjük a mátrix leggyakoribb szavait, a függvény argumentumában megadva a dokumentum kifejezés mátrix nevét és a kívánt kifejezésszámot.
quanteda::topfeatures(lawtext_dfm, 15)
#> the bekezdés of áll szerződő rendelkezés törvény to hely an személy
#> 7942 5877 5666 4267 3620 3403 3312 3290 3114 3068 3048
#> év kiadás b ha
#> 2975 2884 2831 2794
Mivel látható, hogy a szövegekben sok angol kifejezés is volt egy következő lépcsőben az angol stopszavakat is eltávolítjuk.
Ezután megnézzük a leggyakoribb 15 kifejezést.
topfeatures(lawtext_dfm_2, 15)
#> bekezdés áll szerződő rendelkezés törvény hely személy év kiadás b
#> 5877 4267 3620 3403 3312 3114 3048 2975 2884 2831
#> ha következő költségvetés működés eset
#> 2794 2398 2376 2325 2070
A következő lépés, hogy TF-IDF súlyozású statisztikát készítünk, a dokumentum kifejezés mátrix alapján. Ehhez először létrehozzuk a lawtext_tfidf
nevű objektumot, majd a textstat_frequency()
függvény segítségével kilistázzuk annak első 10 elemét.
lawtext_tfidf <- quanteda::dfm_tfidf(lawtext_dfm_2)
quanteda.textstats::textstat_frequency(lawtext_tfidf, force = TRUE, n = 10)
#> feature frequency rank docfreq group
#> 1 felhalmozás 1452 1 7 all
#> 2 szerződő 1349 2 53 all
#> 3 shall 1291 3 14 all
#> 4 kiadás 1280 4 45 all
#> 5 költségvetés 1101 5 43 all
#> 6 támogatás 1035 6 38 all
#> 7 beruházás 942 7 22 all
#> 8 contracting 894 8 7 all
#> 9 articl 884 9 14 all
#> 10 működés 741 10 60 all
A folyamatról bővebb információ található például az alábbi oldalakon: https://cran.r-project.org/web/packages/rvest/rvest.pdf, https://rvest.tidyverse.org.↩︎
A
quanteda
csomagnak több kiegészítő csomagja van, amelyeknek a tartalmára a nevük utal. A könyvben aquanteda.textmodels
,quanteda.textplots
,quanteda.textstats
csomagokat használjuk még, amelyek statisztikai és vizualizaciós függvényeket tartalmaznak.↩︎A VPN (Virtual Private Network, azaz a virtuális magánhálózat) azt teszi lehetővé, hogy a felhasználók egy megosztott vagy nyilvános hálózaton keresztül úgy küldjenek és fogadjanak adatokat, mintha számítógépeik közvetlenül kapcsolódnának a helyi hálózathoz.↩︎
A karakterkódolással kapcsolatosan további hasznos információk találhatóak az alábbi oldalon: http://www.cs.bme.hu/~egmont/utf8.↩︎
A
stringr
csomag jól használható eszköztárat kínál a különböző karakterláncokkezeléséhez. Részletesebb leírása megtalálható: https://stringr.tidyverse.org/. A karakterláncokról bővebben: https://r4ds.had.co.nz/strings.html↩︎WordNet
: https://github.com/mmihaltz/huwn. A magyar nyelvű szövegek lemmatizálását elvégezhetjük a szövegek R-be való beolvasása előtt is amagyarlanc
nyelvi elemző segítségével, melyről a Természetes-nyelv feldolgozás (NLP) és névelemfelismerés című fejezetben szólunk részletesebben.↩︎A két mátrix csak a nevében különbözik, tartalmilag nem. A használni kívánt csomag leírásában mindig megtalálható, hogy dtm vagy dfm mátrix segítségével dolgozik-e. Mivel a könyvben általunk használt quanteda csomag dfm mátrixot használ, mi is ezt használjuk.↩︎
Bár a lenti mátrixok, amelyekben csak 0 és 1 szerepel, a bináris mátrix látszatát keltik, de valójában nem azok, ha egy-egy kifejezésből több is szerepelne a mondatban, a mátrixban 2, 3, 4 stb. szám lenne.↩︎
Az itt használt kódok az alábbiakon alapulnak: https://rdrr.io/cran/quanteda/man/dfm_weight.html, https://rdrr.io/cran/quanteda/man/dfm_tfidf.html. A példaként használt korpusz a Hungarian Comparative Agendas Project keretében készült adatbázis része: https://cap.tk.hu/torveny↩︎