Ebben a bejegyzésben átnézzük a legalapvetőbb számítógépes nyelvészeti (Natural Language Processing, NLP) eljárást, az úgynevezett szózsákmodellt (bag-of-words).
Kezdjük talán azzal, mi is a számítógépes nyelvészet! Mivel korábban erről nem volt szó a blogon. Lényegében minden olyan számítógépes tevékenység, ami természetes nyelvek feldolgozásával foglalkozik.
Az NLP azt szeretné elérni, hogy feldolgozhassunk természetes szövegeket. Például szeretnénk megtudni, hogy miről szól a szöveg, két szöveg hasonló-e egymásra, mi lesz a következő szó, amit a felhasználó begépel etc. Ezek a kérdések elég összetettek, és csak több lépésben válaszolhatók meg. A szózságmodell[1] az egyik ilyen lépés lesz, nem maga a válasz.
Az alapvető problémánk természetes nyelvek feldolgozásánál: hogy miképpen ültessük át olyan formátumra, ami a számítógépnek is értelmezhető és lehetővé teszi, hogy matematikai formában felírjuk a problémánkat. Erre a kérdésre válaszol a bag-of-words.
Maga az ötlet nagyon egyszerű: készítsünk egy szótárt, amiben minden egyes ismert szó benne van. A feldolgozandó szövegekben pedig számoljuk meg, hogy a szótár szavai hányszor fordulnak elő. A fenti szótárt felfoghatjuk úgy is, mint egy dimenzióteret, és a szövegben előforduló szavak pedig az szöveg adott dimenzióban felvett értékei. Elméletileg ez lehetővé teszi számunkra, hogy mérjük a szövegek közötti távolságot.
Nézzünk egy példát! Legyen két szövegünk:
- „János szeret moziba járni. Éva is szeret moziba járni.”
- „Éva kirándulni is szeret.”
Mi lesz a szótárunk? Mi lesz ennek a szózsák modellje:
Szótár | 1. szöveg | 2. szöveg |
---|---|---|
János | 1 | 0 |
szeret | 2 | 1 |
moziba | 2 | 0 |
járni | 2 | 0 |
Éva | 1 | 1 |
kirándulni | 0 | 1 |
is | 1 | 1 |
Táblázat: Szózsákmodell
Szózsákmodell
Most, hogy tudjuk, hogy néz ki a modell, készítsük el Pythonban:
szovegek = [
"János szeret moziba járni. Éva is szeret moziba járni.",
"Éva kirándulni is szeret."
]
# minimális szöveg előkészítés
for szidx in range(len(szovegek)):
# különleges karakterek
pattern = r"[{}]".format("(),.;:\-%\"")
szovegek[szidx] = re.sub(pattern, "", szovegek[szidx])
# szózsákmodell készítése
from sklearn.feature_extraction.text import CountVectorizer
BoW_Vector = CountVectorizer(min_df = 0., max_df = 1.)
BoW_Matrix = BoW_Vector.fit_transform(szovegek)
# a szózsákmodell a blog bejegyzésnek megfelelően formázva
features = BoW_Vector.get_feature_names()
import pandas as pd
BoW_df = pd.DataFrame(BoW_Matrix.toarray(), columns = features)
print(BoW_df.T)
Ami az elvárt eredményt nyomtatja ki:
0 1
is 1 1
jános 1 0
járni 2 0
kirándulni 0 1
moziba 2 0
szeret 2 1
éva 1 1
Lényegében ennyi a szórósan vett szózsákmodell.
Most nézzünk meg néhány kérdést és problémát ezzel a modellel kapcsolatban.
Távolságmérés
Mint fentebb említettem, az alapvető cél az NLP-ben, hogy matematikailag tudjuk mérni a szövegeket és megfogalmazni a kérdéseinket. A fenti két szöveg az „Éva” és az „is” dimenzióban hasonlít a legjobban egymásra, ahol mindkettő értéke 1. A legnagyobb távolságuk pedig a „moziba” és a „járni” dimenzióban van. Ez eddig egyszerű, minden egyes tengely mentén látjuk, hogy mekkor a két szöveg távolsága. De hogyan lehetne ezt egyetlen számban kifejezni?
Két vektor közötti távolságot több módon lehet mérni. A két legelterjedtebb az un. Cosine hasonlóság és az euklideszi távolság. A következő ábra szemléti, hogy mit is jelentenek ezek:

Az ábrán az A és B pont közti távolságot mérjük. Látható. hogy az euklideszi távolság, E-vel jelölve, a két pontot összekötő egyenes hossza.
A Cosine hasonlóság a fenti ábrán -val van jelölve. Lényegében ez nem más, mint a két vektor által bezárt szög. Pontosabban ennek a szögnek a koszinusza. Miért koszinusz? Ez dinamikusabban változik 0-hoz közel, mint egy lineáris függvény. Vagyis segít abban, hogy kisebb különbségeket is észrevegyünk amikor a vektorok amúgy nagyon hasonlóak egymáshoz.
A konkrét számítás így néz ki:
Ahol:
— a Cosine hasonlóság
— az A vektor
— a B vektor
— az A vektor hossza
— a B vektor hossza
— a dimenziók száma, esetünkben a szótár szavainak száma
— az A vektor értéke az i dimenzióban, esetünkben az egyes szavak száma a szövegben
— a B vektor értéke az i dimenzióban
Rendben, akkor számoljuk ki:
import numpy as np
# János szeret moziba járni. Éva is szeret moziba járni.
elsoszoveg = np.array([1,2,2,2,1,0,1])
# Éva kirándulni is szeret.
masodikszoveg = np.array([0,1,0,0,1,1,1])
# euklideszi távolság
dist = np.linalg.norm(elsoszoveg -masodikszoveg )
# cosine hasonlóság
cos_sim = np.dot(elsoszoveg , masodikszoveg )/ \
( \
np.linalg.norm(elsoszoveg )* \
np.linalg.norm(masodikszoveg ) \
)
Ami a fenti példában kb. 0,516 lesz. Mit is jelent ez a szám? Ha a két szöveg teljesen megegyezik, akkor az általuk bezárt szög 0°, aminek a koszinusza 1. Tehát 1 a maximális értéke a Cosine hasonlóságnak. Hasonló logika alapján, a legnagyobb távolság akkor van, ha a vektorok 90° szöget zárnak be, amikor is 0 a Cosine hasonlóság. A mi esetünkben körülbelül 60° ez a szög, ami 0,5.
Ez a 60° magában nem mond semmit. Viszont ha van egy harmadik szövegünk, akkor már tudjuk mérni, hogy melyik két szöveg hasonlít egymásra a legjobban. Tegyük ezt! Az új, 3. szövegünk legyen:
- „Éva is szeret moziba járni.”
Ez azért érdekes, mert, ugye, ez az 1. szöveg egy része, ugyanakkor szerepelnek benne „Éva”, „szeret” és „is” szavak, ami miatt a szavai 3/4 részben[2] megegyeznek a 2. szöveggel.
Az új szöveg vektorformában:
Szótár | 3. szöveg |
---|---|
János | 0 |
szeret | 1 |
moziba | 1 |
járni | 1 |
Éva | 1 |
kirándulni | 0 |
is | 1 |
Táblázat: 3. szöveg szózsákmodellje
Ha elvégezzük a fenti számításokat, akkor a következő eredményt fogjuk kapni a Cosine hasonlóságra:
1. szöveg | 2. szöveg | 3. szöveg | |
---|---|---|---|
1. szöveg | 1 | 0,5163977794943222 | 0,9237604307034011 |
2. szöveg | 1 | 0,6708203932499369 | |
3.szöveg | 1 |
Táblázat: Példa szövegek Cosine hasonlósága
Amint az fentebb látható, a Cosine hasonlóság alapján az 1. és a 3. szöveg hasonlít legjobban egymásra. Ez nagyjából várható volt, mivel a 3. az 1. része.
Most nézzük az euklideszi távolságokat:
1. szöveg | 2. szöveg | 3. szöveg | |
---|---|---|---|
1. szöveg | 0 | 3.3166247903554 | 2.0 |
2. szöveg | 0 | 1.7320508075688772 | |
3. szöveg | 0 |
Táblázat: példaszövegek euklideszi távolsága
Hoppá! Itt viszont a 2. és a 3. szöveg között a legkisebb a távolság, tehát ezek hasonítanak a leginkább egymásra. Ez várható volt olyan szempontból, hogy a két szöveg szókincse 75%-ban megegyezik.
A fenti különbség szemlélteti, hogy miért szoktak az NLP területén inkább a Cosine hasonlóságot számolni. Természetes szövegek feldolgozásánál jobb ötletnek vélhető nem csupán a szókincset figyelembe venni a hasonlóság számítása esetén, hanem a szövegek hosszát is. Ez az, amit a Cosine hasonlóság megtesz az euklideszi távolság számítással ellentétben.
Talán egy kicsit zavaró lehet a fentiekben, hogy míg az euklideszi távolságnál a kisebb szám jelenti azt, hogy a vektorok közelebb vannak egymáshoz, addig a Cosine hasonlóságnál a nagyobb. Ezért néha Cosine távolságot is szoktak használni, ami . Pythonban ha scikit-et használunk, akkor ezeket a következő módon számolhatjuk:
szovegek = np.array([[1,2,2,2,1,0,1],[0,1,0,0,1,1,1]])
# Cosine távolság
from sklearn.metrics import pairwise_distances
cosine_tavolsag = pairwise_distances(szovegek, metric = "cosine",n_jobs = -1 )
# Cosine hasonlóság
from sklearn.metrics.pairwise import cosine_similarity
cosine_hasonlosag = cosine_similarity(szovegek)
# Ellenőrzés
print(cosine_tavolsag+cosine_hasonlosag)
# A legnagyobb távolság indexe
indices = np.where(cosine_tavolsag == cosine_tavolsag.max())
for index in indices:
print("A legnagyobb távolság indexe: ", index)
Problémák a szózsákmodellel
Ez eddig mind szép és jó, de van itt néhány probléma.
Először is a szórendet nem vesszük figyelembe. Ez a magyar nyelvben talán nem akkora probléma, mivel nálunk nincs kötött szórend, de több más nyelv esetében, például az angolban annál inkább jelentős a szórend.
Másodszor, ahogy egyre több szövegünk van, a szótárunk egyre nagyobb, ezzel párhuzamosan az egyes szövegek vektorjaiban egyre kisebb a hasznos információ aránya. Képzeljük el, mi történik, ha a fenti szótárt kibővítjük száz szóval. Az 1. szövegben ebben az esetben is csak hat egyedi szó lesz. De a vektorterünk 107 dimenziós lesz, vagyis 101 darab 0 lesz az 1. szöveget ábrázoló vektorban. Ennek a megnövekedett vektortérnek a kezelése pedig sokkal nagyobb erőforrást igényel.
Harmadszor, mi van a hasonló jelentésű szavakkal? Például ha „szeret” helyett „kedvel” szerepel. A modell szerint ez a kettő nem hasonlít jobban egymásra mint a „szeret” és „utál” szavak. Ez a probléma a ragozó nyelveket, mint amilyen a magyar, hatványozottan érinti. A „moziban” és a „moziból” szoros rokonságban vannak. Sajnos ezt a szózsákmodell nem képes megragadni.
A fenti problémákra válaszként több irányba próbálták továbbfejleszteni a szózsákmodellt. Ezek közül ma csak egyet fogunk megnézni: az n-gram modellt. Miért? Mert ez közvetlen leszármazottja a szózsákmodellnek.
n-gram változat
Az első probléma megoldására született a szózsákmodell ezen változata. Az ötlet nagyjából annyi, hogy amennyiben a szórendet nem akarjuk elveszteni, akkor ne csupán az egyes szavak legyenek a szótárban, hanem a szókapcsolatok is. Az n netű azt jelenti, hogy menyi szót kapcsoljunk össze. Például egy 2-gram szózsákmodell esetén két szó hosszasságúak az egységek.
Maradva a fenti példánál ebben az esetben a 2-gram szótár így fog alakulni:
Szótár | 1. szöveg | 2. szöveg | 3. szöveg |
---|---|---|---|
János | 1 | 0 | 0 |
szeret | 2 | 1 | 1 |
János szeret | 1 | 0 | 0 |
moziba | 2 | 0 | 1 |
szeret moziba | 2 | 0 | 1 |
járni | 2 | 0 | 1 |
moziba járni | 2 | 0 | 1 |
Éva | 1 | 1 | 1 |
is | 1 | 1 | 1 |
Éva is | 1 | 0 | 1 |
is szeret | 1 | 1 | 1 |
kirándulni | 0 | 1 | 0 |
Éva kirándulni | 0 | 1 | 0 |
kirándulni is | 0 | 1 | 0 |
Táblázat: 2-gram Szózsákmodell
Rendben. Feltételezem, hogy mindenki látja az alapötletet. Vajon a problémákat is? Először is, döntsük el, hogy mennyi legyen az „n”. Másodszor lássuk be, hogy a szavak nem csupán a közvetlen szomszédjukra hathatnak. Harmadszor pedig a fentebb már említett második problémát nem oldottuk meg, mert a hasznos információ arányát túlzottan csökkentettük.
Végezetül
A szózsákmodell nagyjelentőségű volt az 50-es években, amikor kidolgozták, de mára nyilvánvalóvá váltak a korlátai. A jövőbeli bejegyzésekben majd igyekszem bemutatni, hogy milyen alternatíváink vannak napjainkban.
Irodalom
- Jason Brownlee: A Gentle Introduction to the Bag-of-Words Model
- Selva Prabhakaran: Cosine Similarity – Understanding the math and how it works (with python codes)
“Szózsákmodell (Bag-of-words)” bejegyzéshez egy hozzászólás