Word-Embeddings auf Deutsch

In meinem letzten letzten Blog-Eintrag habe ich mir einen sehr einfachen Chatbot angesehen. Dieser hat quasi Schlagworte in Kundenanfragen gefunden und entsprechende vordefinierte Antworten geliefert. Fundament von dem Ansatz war das Bag-of-Words-Modell aus der Computerlinguistik. Das nimmt Sätze und vereinfacht sie radikal. Die Reihenfolge und das quantitative Vorkommen der Wörter geht verloren, weil man mit mathematischen Mengen arbeitet. Das ist nicht weiter wild. Es ist recht mutig, liefert aber in diesem Fall wunderbare Ergebnisse. Kein Stress. Absolute Entspannung.

Tiefe Gedanken beim Studium fremder Quelltexte.

Beim Durcharbeiten hatte ich die ganze Zeit zwei Gedanken…

  1. Wie bringe ich dem englischen Bot Deutsch bei?
  2. Wie kann ich dem Bot mehr Semantik beibringen?

Die erste Frage war sehr einfach zu beantworten. Der Chatbot wird mit einer Spezifikations-Datei konfiguriert. Andere Sprachen sind absolut möglich. Man muss nur diese Datei übersetzen. Das habe ich gemacht. Die Übersetzung ist in meinem GitHub-Repository zu finden. So etwas nennt man „No-Brainer“.

Die zweite Frage ist tricky. Hier liegt viel Spannendes versteckt. Wie gesagt reduziert das Bag-of-Words-Modell den Semantikgehalt radikal. Und fällt für mich somit beinahe aus. Da muss was besseres her. Ich möchte gerne einen Chatbot bauen, der ein größeres Verständnis von der Sprache hat. Und welchen Ansatz nimmt man da? Ja richtig. Word Embeddings. Oder auf Deutsch: Worteinbettungen. Darüber hatte ich schon in einem vergangenen Blog-Eintrag über Mathemagie geschrieben. Ich forsche also grad und versuche den Chatbot mit Word-Embeddings aufzuwerten. Und am besten gleich auf Deutsch.

Vor der Jagd… Wie arbeite ich mit Word-Embeddings?

Technisch sind Word-Embeddings sehr einfach. Mathematisch gesehen haben wir hier eine Abbildung zwischen Zeichenketten und Wortvektoren vorliegen. Wortvektoren bestehen aus vielen Zahlen – manchmal sogar 300 – und tragen eine Semantik mit sich herum. Man kann damit jedes Wort in einen Wortvektor umwandeln und damit rechnen. „König“ minus „Mann“ plus „Frau“ ergibt nach Adam Riese „Königin“. Auch Synonyme und Antonyme lassen sich damit finden. Alles über die Zahlenketten.

Word-Embeddings, also diese Abbildung, kann man sich einfach im Internet runter laden. So die Theorie… Auf der Festplatte abspeichern und benutzen. Praktisch muss man da schon eine Weile suchen. Deutsch scheint ein wenig vernachlässigt. Ich zeige am besten erstmal wie man Word-Embeddings benutzt. Wir tun einfach mal so als hätten wir eine Datei, die alle nötigen Wörter enthält…

Gensim ist eine phänomenale Hilfe.

from gensim.models.keyedvectors import KeyedVectors

So einfach ist das wieder. Import im Python-Style und man kann loslegen. Gensim ist eine Library für unsupervised Learning im Sprachumfeld. Damit kann man unter anderem mit Word-Embeddings arbeiten. „KeyedVectors“ ist schon verheißungsvoll. Damit kann man diese Vektoren laden. Speaking of which… Word-Embeddings kommen meistens in zwei Formaten. Binär kodiert oder von Menschen lesbar. Hier ist eine Methode die beides ausprobiert:

def load_word_vectors():
    print("Loading word vectors from",  word_vectors_model_filename + "...")
    word_vectors_model_size = os.path.getsize(word_vectors_model_filename) / (1024.0 * 1024.0)
    print("File size is {0:.2f}MB".format(word_vectors_model_size))
    print("Be patient! This might take a while...")
    global word_vectors_model
    try:
        print("Attempting to load as vector-format...")
        word_vectors_model = KeyedVectors.load_word2vec_format(word_vectors_model_filename, binary=False)
        print("Success!")
    except:
        print("Failed!")
        try:
            print("Attempting to load as binary-format...")
            word_vectors_model = KeyedVectors.load_word2vec_format(word_vectors_model_filename, binary=True)
        except:
            print("Failed!")
            exit(0)

    print("Success!")

Kern ist hier „load_word2vec_format“. Wenn das Laden in einem Format nicht geht, wird einfach das andere ausprobiert. Danach hat man das Modell in der Hand. Und zwar in Form von „word_vectors_model“. Was kann man damit machen? Was ist die Mathemagie?

Mathemagie mit Word-Embeddings. Es darf wieder gezaubert werden.

Fangen wir mal mit ein wenig Statistik an. Word-Embeddings zu laden geht schnell. Na gut… programmatisch. Zeitlich ist das abhängig von der Größe. Es kann also nicht schaden, die Randdaten zu betrachten. Wie viele Wörter sind enthalten? Wie lang sind die Wortvektoren? Dieser Code sorgt für die Antworten:

print("Vocabulary size", len(list(word_vectors_model.wv.vocab.keys())))
print("Word vector length:", len(word_vectors_model.wv["Mann"]))

Das ist die Ausgabe:

Vocabulary size: 608130
Word vector length: 300

Die Umwandlung beliebiger Worte in ihre Wortvektoren ist Schlüsseltechnologie:

print("Word vector for 'Mann'", word_vectors_model.wv["Mann"])

Wort-Vektoren können recht lang sein:

Word vector for 'Mann' [ 0.1678151  -0.01715856  0.14564128  0.01643708  0.02733266  0.49015957
 -0.19200401 -0.16438557 -0.06258204  0.12464311  0.07775309 -0.17094384
  0.05773254  0.23981114 -0.39820656 -0.01941513 -0.15894736 -0.07663183
  0.22897393 -0.0404997   0.00237953  0.15012988  0.25289607  0.04408217
  0.09215517 -0.26481789 -0.51969105 -0.18874237  0.04324916  0.33723992
  0.03272996  0.1023312   0.07781161 -0.27436259  0.14366916 -0.0647203
 -0.27065626 -0.15682699  0.30538768  0.00759732 -0.20001976  0.18530159
 -0.06843861  0.10367234  0.2666401   0.07758536  0.06988724 -0.18472718
  0.04586994 -0.07091724 -0.01570967 -0.11631202 -0.27903217 -0.22006431
 -0.29344234 -0.16378929 -0.01637682 -0.02759546 -0.14867654 -0.10342148
  0.42888156 -0.14884272 -0.07814548 -0.20129368 -0.08100423  0.44298968
  0.0096443   0.16843927  0.0403233   0.11056637 -0.14966607  0.0775136
 -0.37262949  0.15971391  0.10339827 -0.08182968 -0.14464283  0.09974999
  0.22623466 -0.07307606 -0.06060811 -0.08569473  0.06044295 -0.15012601
  0.19709101  0.10288548  0.03458636 -0.07928915 -0.08164569  0.1086121
 -0.15900649 -0.1528122   0.36258924  0.60345978 -0.26886266  0.27221575
  0.15626612 -0.08666559 -0.08597019  0.02326066  0.21716742 -0.10805785
  0.14552192  0.39275661 -0.2137285   0.17737752 -0.04638224 -0.09086929
  0.17228992  0.45745647  0.09721016  0.22619696  0.15587658 -0.1078452
  0.10545702 -0.1295034   0.15754125  0.20287803 -0.12768655  0.25871921
 -0.36970127 -0.27917591  0.35536325 -0.10449084  0.29919827  0.21061943
  0.02757434 -0.10462383  0.38722783  0.13090241  0.07226188 -0.20390855
 -0.18720752 -0.30094409  0.04955009  0.15689072  0.06089444  0.1221517
  0.29320481  0.06018539 -0.26484498 -0.08885223  0.20251615 -0.08014334
  0.25534236  0.12345839  0.04113655 -0.17464676 -0.02555929 -0.24928689
 -0.00726197 -0.06367166 -0.00622844 -0.16187102  0.06470232  0.00213742
 -0.00922389 -0.03507412  0.15772884  0.1308917  -0.14758556 -0.03714319
 -0.19372585 -0.02303672  0.12259024  0.03133943 -0.1119096   0.4193885
 -0.01616556 -0.10328371 -0.25653988 -0.13578537 -0.04558701  0.14982465
  0.12962459 -0.057076    0.20319712 -0.03295657  0.12831989  0.08225089
  0.27632949 -0.07261054  0.16000392  0.23529905  0.12380063 -0.10244995
 -0.05999006 -0.30248201  0.09638175 -0.16388389 -0.06575203 -0.13759583
  0.03383766  0.03422916  0.11466212  0.17075454 -0.08441102  0.02949743
  0.0929366  -0.14893182  0.31528315 -0.13083901  0.24421875 -0.32063347
 -0.10847533  0.32907581 -0.24505091  0.3328152  -0.02556069  0.23488981
  0.1086755   0.05432398  0.272899    0.04030114  0.24553958 -0.00683912
  0.18527657  0.00568248  0.00947272 -0.03928746 -0.16926947 -0.1675348
 -0.09187805  0.0302988   0.06565314  0.07030207  0.11808337 -0.11149222
 -0.18275204  0.00937724  0.21726315 -0.02559427 -0.10452805  0.04012734
  0.22440714 -0.23658764 -0.08710285 -0.32860765 -0.09435543 -0.10201801
  0.02778483 -0.12212405 -0.15352622 -0.2402246   0.16305342  0.07084775
 -0.34023714  0.06145613  0.36542648 -0.21715111  0.18380807  0.31104729
 -0.06712623 -0.04920201 -0.04121515  0.10949919  0.09336475 -0.05284641
 -0.07185549 -0.1092885   0.12177272  0.04907761  0.16923627  0.13010481
  0.14412749 -0.38434115  0.19248633  0.10296852 -0.06169356 -0.12030595
  0.00750719 -0.02544785 -0.07714268 -0.15853207 -0.1508005  -0.08645581
 -0.14417176 -0.09917884 -0.05555947 -0.2346331  -0.13082129 -0.11645748
 -0.23315814  0.11022738  0.19593142  0.18401168  0.00494276 -0.21829559
  0.29854178 -0.12982871  0.07186505 -0.10553935  0.20121247  0.11509009
  0.06089201  0.21626161  0.29866579  0.00985046 -0.13242571 -0.27146202]

Man kann auch die Ähnlichkeit von Worten ausrechnen. Wie ähnlich sich „Bundeskanzler“ und „Bundeskanzlerin“ sind, ermittelt dieser Code:

print("Similarity:", word_vectors_model.similarity("Bundeskanzler", "Bundeskanzlerin"))

Und das ist das Ergebnis:

Similarity: 0.642387164957

Bei den Ähnlichkeitsrechnungen kann man mehrere Wege gehen. „most_similar“ ist die Standard-Implementierung. „most_similar_cosmul“ ist die Methode von Omer Levy und Yoav Goldberg. Mal ist das eine besser, mal das andere… Was kommt nun raus wenn man „Frau“ und „Bundeskanzler“ addiert und dann „Bundeskanzlerin“ abzieht? Zunächst die große Gleichung:

positive = ["Frau", "Bundeskanzler"]
negative = ["Bundeskanzlerin"]
print("Most similar:", word_vectors_model.most_similar(positive=positive, negative=negative, topn=1)[0])
print("Most similar cosmul:", word_vectors_model.most_similar_cosmul(positive=positive, negative=negative, topn=1)[0])

Und das Ergebnis ist absolut gar nicht überraschend:

Most similar: ('Mann', 0.6585341095924377)
Most similar cosmul: ('Mann', 0.9142228960990906)

Mit Gensim kann man auch aus einer Liste von Worten das finden, das nicht dazu passt. Also jenes welches am weitesten von allen anderen weg ist:

print("Does not match:", word_vectors_model.doesnt_match("Frühstück Bundeskanzler Mittagessen Abendessen".split()))

Und das Ergebnis klarerweise:

Does not match: Bundeskanzler

Weiter im Text. Wir haben die ganze Zeit aus Wörtern Vektoren gemacht. Wie macht man aus Vektoren wieder Wörter? So geht das:

your_word_vector = word_vectors_model.wv["Merkel"]
print("Word from a vector:", word_vectors_model.most_similar(positive=[your_word_vector], topn=1)[0])

Hier können natürlich mehrere rauskommen. Wir nehmen einfach das passendste. Mit dem Ergebnis:

Word from a vector: ('Merkel', 0.9999998807907104)

Schließlich kann man sich auch ähnliche Worte ermitteln lassen. Etwa die Top-10 der „Merkel“-nähsten Begriffe:

most_similar_topten = word_vectors_model.most_similar(positive=["Merkel"], topn=10)
print("Most similar top-10:")
for i, most_similar in enumerate(most_similar_topten):
    print(str(i + 1) + ":", most_similar)

Das Ergebnis, wieder ohne Überraschungen:

Most similar top-10:
1: ('Kanzlerin_Merkel', 0.8945820331573486)
2: ('Merkel_CDU', 0.8921669125556946)
3: ('Bundeskanzlerin', 0.8700551986694336)
4: ('Kanzlerin', 0.8666472434997559)
5: ('Angela_Merkel', 0.8659625053405762)
6: ('Kanzlerin_Angela', 0.8306122422218323)
7: ('Steinbrueck', 0.8213914036750793)
8: ('Bundeskanzlerin_Angela', 0.8004235625267029)
9: ('SPD-Kanzlerkandidat_Steinbrueck', 0.7869447469711304)
10: ('CDU-Chefin', 0.7858077883720398)

Der ganze Quelltext ist in meinem GitHub-Repository zu finden. Viel Spaß beim Schmökern.

Die Existenz von Word-Embeddings fängt immer mit einem Textkorpus an. Ist so!

Wir haben jetzt viele semantische Zusammenhänge über die Mathemagie zu Gesicht bekommen. Wichtig zu wissen ist, dass all diese Zusammenhänge aus einem Textkorpus stammen. Hier wird es wieder spannend. Wir erinnern uns mit Wonne: Ein Textkorpus ist in der Natural Language Processing immer die Grundlage für alle Arbeiten. Bei den Word-Embeddings wird das Korpus benutzt, um die Wordvektoren zu berechnen. Unter der Haube werden Abstände berechnet. Denn in der Wissenschaft der Sprachen heißt es, dass Worte die semantisch zusammenhängen in Texten im Schnitt nah aneinander stehen.

Das heißt für uns simpel: Für jede Sprache, die wir verarbeiten wollen. brauchen wir ein spezielles Word-Embedding. Das sucht man entweder oder man berechnet es selbst. Und das macht man mit einem Korpus. Etwa aus der gesamten finnischen Wikipedia oder aus allen bengalischen Google-News-Einträgen.

Das ist in der Theorie einfach. Man braucht einen Preprocessor, der einen Korpus erschließt. Das kann ein einfacher Web-Crawler sein, der sich alle Texte einer Website saugt. Also abhängig davon, wo der Korpus gespeichert ist. Die aufbereiteten Daten werden dann in einen Word-Embedding-Algorithus gepackt. Nach einer Weile steht dann das Word-Embedding zur Verfügung. Das kann Minuten, aber auch Stunden dauern.

So einfach die Theorie… Downloaden ist eindeutig bevorzugt, weil die Vorverarbeitung teilweise sehr langwierig sein kann. Und meistens haben sich Leute von Fach damit auseinander gesetzt und sind stolz auf ihr reines Word-Embedding.

Jetzt kommt aber leichte miese Stimmung. Stand heute bin ich ein wenig unzufrieden, was die deutsche Sprache betrifft. Ja, es gibt Word-Embeddings zum Download. Darüber brauchen wir nicht streiten. Hier ist die Angebotslandschaft aber massiv überschaubarer als bei unseren englischsprachigen Linguistik-Genossen. Die haben einfach einen Vorsprung.

Word-Embeddings auf Deutsch – Jäger des verlorenen Schatzes.

Nach langer Recherche sind bei mir im Wesentlichen zwei Word-Embeddings auf dem Schreibtisch gelandet:

  • Unsere Freunde von Facebook Research haben damals fastText verzapft. Nicht abwertend gemeint. Die Arbeiten sind ausgezeichnet. Im Angebot sind vortrainierte Wortvektoren in vielen, gängigen Sprachen. Trainiert wurde jeweils auf der nationalen Wikipedia. Da steht ja genug drin. Die Wortvektoren für die deutsche Sprache sind wunderbar. Die Sammlung ist sehr vollständig. Finde ich. Doch leider ist sie nicht ganz so praktisch. Ich habe hier eine Datei, die mehr als 5GB groß ist. Mir sind auch größere untergekommen. Alleine diese per Python in den Arbeitsspeicher zu laden dauert bestimmt eine Viertelstunde. Das ist mir als eingefleischten Mathemagier zu lange. Besonders, wenn ich einfach nur mit ein paar Begriffen rechnen möchte.
  • Andreas Müller hat für seine Bachelor-Thesis Word-Embeddings trainiert. Macht man halt mal. Der Textkorpus bestand jeweils aus einem Ausschnitt der deutschen Wikipedia und deutschen Nachrichten. Das Word-Embedding ist hier „nur“ 700MB groß. Ich konnte recht gut damit arbeiten. Und besonders die Ladezeit ist wundervoll.

Abschließende Bemerkungen.

Word-Embeddings sind richtig genial. Allein die Mathemagie ist berauschend. Aus Wörtern Zahlenreihen zu machen ist schon recht charmant. Dass man damit rechnen kann ist charmanter. Und wie wir wissen, ist das traumhaft für weitere Arbeiten mit Neural Networks.

Jetzt habe ich alles in der Hand, um vielleicht mal den Chatbot aus dem ChatbotsMagazine mit Word-Embeddings nachzurüsten. Das kann ja so schwer nicht sein!

Hinterlasse einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.