Spracherkennung

Wenn wir irgendetwas mit Texten machen wollen, ist es fast immer notwendig zu wissen, in welcher Sprache die texte geschrieben sind. Egal ob wir eine Rechtschreibkorrektur machen, einen Suchindex aufbauen oder Personennamen im Text finden wollen, die Verarbeitung der Texte ist sprachspezifisch. Manchmal gibt es Metadaten zu Texten, in denen die Sprache genannt wird. Diese sind aber oft nicht korrekt und meistens haben wir übehaupt keine Sprachangaben für die Texte, mit denen wir etwas machen wollen. In vielen Fällen ist es aber einfach, die Sprache des Textes automatisch zu erkennen.

Problemdefinition

Was ist die genaue Aufgabe?

Anzahl der Sprachen

Wenn wir aus zwei möglichen Sprachen wählen müssen, ist die Aufgabe einfacher als wenn wir aus 3000 Sprachen wählen müssen. In vielen praktischen Fällen muss man tatsächlich nur aus einigen Sprachen wählen. Wenn wir z.B. Social Media zu einem Deutschen Thema oder von einer deutschen Seite aus sammeln, können wir davon ausgehen, dass 95% aller Berichte deutsch oder englisch sind. Das gleiche gilt z.B. auch wenn wir Abstracts im Katalog einer deutschen Bibliothek analysieren.

Im folgenden werden wir versuchen aus einer kleinen Anzahl West-Europäischer Sprachen zu wählen.

Textkodierung

Im schlimmsten Fall haben wir eine Datei von der wir nicht mal wissen, wie die Zeichen kodiert sind. Wir lesen also Bits und wissen nicht, ob wir die Bits nach der ASCII-, UTF8-, Latin2-Tabelle usw. interpretieren müssen. Wir müssen jetzt sowohl die Sprache und die Kodierung erraten. Im folgenden vereinfachen wir das Problem, und gehen davon aus, dass alle Text, die wir bekommen in UTF8 kodiert sind.

Verfahren

Ein einfaches Verfahren, dass man sich vorstellen könnte wäre folgendes: Wir schauen in einem Text, wie viele häufig vorkommende deutsche Wörter wir finden. Hierzu muss der Text aber erstens lang genug sein, um eine zuverlässige Aussagen über die Sprache machen zu können, da manche Wörter in verschiedenen Sprachen vorkommen. Eine in vielen Internetforen häufig vorgeschlagene Variante ist die, dass man Stopwörter (Stopwörter sind sehr häufig vorkommende Wörter ohne eigene Bedeutung, wie Artikel, Pronomina, usw.) zählt, das Stopwörter häufig vorkommen und Stopwortlisten für vielen Sprachen leicht erhältlich sind. Für kürzere Text funktioniert dieser Ansatz leider nicht. Wörter wie de und en kommen sowohl in niederländischen wir auch in französischen Stopwortlisten vor. Solche Doppelungen gibt es oft, das Stopwörter meist ein- oder zweisiblige Wörter sind, und die Zahl der möglichen einsilbigen Wörter natürlich nicht unbegrenzt ist. Ein Buchtitel wie

De kleine prins en de grote drakejacht

könnte nach dem Stopwortverfahren genau so gut niederländisch als französisch sein, während es für jemand, der beide Sprachen einigermaßen kennt, sofort klar ist, das es sich beim Beispiel um eine niederlänische Phrase handelt.

Bessere Ergebnisse bekommen wir, wenn wir uns nicht auf Wörter konzentrieren, sondern uns die Verteilung von Buchstaben anschauen. Im Englischen gibt es häufig einen y im Deutschen finden wir viele Buchstaben mit Umlauten usw. Noch besser funktionieren kombinationen von 2 und 3 Buchstaben, sogenannt Bi- und Trigramme. Wir können sogar einzelne Buchstaben, Bi- und Trigramme kombinieren. Dieser Ansatz ist beschrieben im folgenden Paper:

Cavnar, W.B., Trenkle, J.M.: N-gram-based text categorization. In: Proceedings of SDAIR-94, 3rd Annual Symposium on Document Analysis and Information Retrieval. pp. 161-175 (1994) http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.21.3248&rep=rep1&type=pdf

Extrahieren der n-Gramme

Folgende Funktion extrahiert n-Gramme aus einem String:

In [1]:
def ngram(string,n):
    liste = []
    if n < len(string):
        for p in range(len(string) - n + 1) :
            tg = string[p:p+n]
            liste.append(tg)
    return liste

Wir testen die Funktion:

In [2]:
text = "Die Verfasserin unternimmt es in diesem Buche, die Geschichte des Kautschuks in Menschenschicksalen zu erzählen."
trigramme = ngram(text,3)
print(trigramme)
['Die', 'ie ', 'e V', ' Ve', 'Ver', 'erf', 'rfa', 'fas', 'ass', 'sse', 'ser', 'eri', 'rin', 'in ', 'n u', ' un', 'unt', 'nte', 'ter', 'ern', 'rni', 'nim', 'imm', 'mmt', 'mt ', 't e', ' es', 'es ', 's i', ' in', 'in ', 'n d', ' di', 'die', 'ies', 'ese', 'sem', 'em ', 'm B', ' Bu', 'Buc', 'uch', 'che', 'he,', 'e, ', ', d', ' di', 'die', 'ie ', 'e G', ' Ge', 'Ges', 'esc', 'sch', 'chi', 'hic', 'ich', 'cht', 'hte', 'te ', 'e d', ' de', 'des', 'es ', 's K', ' Ka', 'Kau', 'aut', 'uts', 'tsc', 'sch', 'chu', 'huk', 'uks', 'ks ', 's i', ' in', 'in ', 'n M', ' Me', 'Men', 'ens', 'nsc', 'sch', 'che', 'hen', 'ens', 'nsc', 'sch', 'chi', 'hic', 'ick', 'cks', 'ksa', 'sal', 'ale', 'len', 'en ', 'n z', ' zu', 'zu ', 'u e', ' er', 'erz', 'rzä', 'zäh', 'ähl', 'hle', 'len', 'en.']

Laut Cavnar e.a. bekommen wir die beste Ergebnisse, wenn wir Mono-, Bi- und Trigramme im Kombination nutzen. Wir schreiben also eine Funktion, die alle n-Gramme, für $1 \leq n < 4$ extrahiert:

In [3]:
def xgram(string):
    liste = []
    
    for n in range(1,4) :
        liste = liste + ngramn(string.lower(),n)
    return liste

Etwas pythonischer können wir die Funktion auch schreiben als:

In [4]:
def xgram(string):
    return [w for n in range(1,4) for w in ngram(string.lower(),n)]
In [5]:
xgramme = xgram(text)
print(xgramme)
['d', 'i', 'e', ' ', 'v', 'e', 'r', 'f', 'a', 's', 's', 'e', 'r', 'i', 'n', ' ', 'u', 'n', 't', 'e', 'r', 'n', 'i', 'm', 'm', 't', ' ', 'e', 's', ' ', 'i', 'n', ' ', 'd', 'i', 'e', 's', 'e', 'm', ' ', 'b', 'u', 'c', 'h', 'e', ',', ' ', 'd', 'i', 'e', ' ', 'g', 'e', 's', 'c', 'h', 'i', 'c', 'h', 't', 'e', ' ', 'd', 'e', 's', ' ', 'k', 'a', 'u', 't', 's', 'c', 'h', 'u', 'k', 's', ' ', 'i', 'n', ' ', 'm', 'e', 'n', 's', 'c', 'h', 'e', 'n', 's', 'c', 'h', 'i', 'c', 'k', 's', 'a', 'l', 'e', 'n', ' ', 'z', 'u', ' ', 'e', 'r', 'z', 'ä', 'h', 'l', 'e', 'n', '.', 'di', 'ie', 'e ', ' v', 've', 'er', 'rf', 'fa', 'as', 'ss', 'se', 'er', 'ri', 'in', 'n ', ' u', 'un', 'nt', 'te', 'er', 'rn', 'ni', 'im', 'mm', 'mt', 't ', ' e', 'es', 's ', ' i', 'in', 'n ', ' d', 'di', 'ie', 'es', 'se', 'em', 'm ', ' b', 'bu', 'uc', 'ch', 'he', 'e,', ', ', ' d', 'di', 'ie', 'e ', ' g', 'ge', 'es', 'sc', 'ch', 'hi', 'ic', 'ch', 'ht', 'te', 'e ', ' d', 'de', 'es', 's ', ' k', 'ka', 'au', 'ut', 'ts', 'sc', 'ch', 'hu', 'uk', 'ks', 's ', ' i', 'in', 'n ', ' m', 'me', 'en', 'ns', 'sc', 'ch', 'he', 'en', 'ns', 'sc', 'ch', 'hi', 'ic', 'ck', 'ks', 'sa', 'al', 'le', 'en', 'n ', ' z', 'zu', 'u ', ' e', 'er', 'rz', 'zä', 'äh', 'hl', 'le', 'en', 'n.', 'die', 'ie ', 'e v', ' ve', 'ver', 'erf', 'rfa', 'fas', 'ass', 'sse', 'ser', 'eri', 'rin', 'in ', 'n u', ' un', 'unt', 'nte', 'ter', 'ern', 'rni', 'nim', 'imm', 'mmt', 'mt ', 't e', ' es', 'es ', 's i', ' in', 'in ', 'n d', ' di', 'die', 'ies', 'ese', 'sem', 'em ', 'm b', ' bu', 'buc', 'uch', 'che', 'he,', 'e, ', ', d', ' di', 'die', 'ie ', 'e g', ' ge', 'ges', 'esc', 'sch', 'chi', 'hic', 'ich', 'cht', 'hte', 'te ', 'e d', ' de', 'des', 'es ', 's k', ' ka', 'kau', 'aut', 'uts', 'tsc', 'sch', 'chu', 'huk', 'uks', 'ks ', 's i', ' in', 'in ', 'n m', ' me', 'men', 'ens', 'nsc', 'sch', 'che', 'hen', 'ens', 'nsc', 'sch', 'chi', 'hic', 'ick', 'cks', 'ksa', 'sal', 'ale', 'len', 'en ', 'n z', ' zu', 'zu ', 'u e', ' er', 'erz', 'rzä', 'zäh', 'ähl', 'hle', 'len', 'en.']

Sprachmodelle

Ein Modell für eine Sprache ist jetzt eine Menge solcher n-Gramme, mit dazu jeweils die Wahrscheinlichkeit, dass diese n-Gramme vorkommen. Ein Modell ist in Python also ein Dictionary.

In [6]:
def buildmodel(text):
    model = {}

    xgramme = xgram(text)
    nr_of_ngs = len(xgramme)

    for w in xgramme:
        f = 1 + model.get(w,0)
        model[w] = f
    
    for w in model:
        model[w] = float(model[w]) / float(nr_of_ngs)

    return model

Wir testen die Funktion und lassen uns das Ergebnis anzeigen:

In [7]:
model = buildmodel(text)
print(model)
{'d': 0.012012012012012012, 'i': 0.02702702702702703, 'e': 0.05105105105105105, ' ': 0.042042042042042045, 'v': 0.003003003003003003, 'r': 0.012012012012012012, 'f': 0.003003003003003003, 'a': 0.009009009009009009, 's': 0.03303303303303303, 'n': 0.02702702702702703, 'u': 0.015015015015015015, 't': 0.012012012012012012, 'm': 0.012012012012012012, 'b': 0.003003003003003003, 'c': 0.021021021021021023, 'h': 0.021021021021021023, ',': 0.003003003003003003, 'g': 0.003003003003003003, 'k': 0.009009009009009009, 'l': 0.006006006006006006, 'z': 0.006006006006006006, 'ä': 0.003003003003003003, '.': 0.003003003003003003, 'di': 0.009009009009009009, 'ie': 0.009009009009009009, 'e ': 0.009009009009009009, ' v': 0.003003003003003003, 've': 0.003003003003003003, 'er': 0.012012012012012012, 'rf': 0.003003003003003003, 'fa': 0.003003003003003003, 'as': 0.003003003003003003, 'ss': 0.003003003003003003, 'se': 0.006006006006006006, 'ri': 0.003003003003003003, 'in': 0.009009009009009009, 'n ': 0.012012012012012012, ' u': 0.003003003003003003, 'un': 0.003003003003003003, 'nt': 0.003003003003003003, 'te': 0.006006006006006006, 'rn': 0.003003003003003003, 'ni': 0.003003003003003003, 'im': 0.003003003003003003, 'mm': 0.003003003003003003, 'mt': 0.003003003003003003, 't ': 0.003003003003003003, ' e': 0.006006006006006006, 'es': 0.012012012012012012, 's ': 0.009009009009009009, ' i': 0.006006006006006006, ' d': 0.009009009009009009, 'em': 0.003003003003003003, 'm ': 0.003003003003003003, ' b': 0.003003003003003003, 'bu': 0.003003003003003003, 'uc': 0.003003003003003003, 'ch': 0.018018018018018018, 'he': 0.006006006006006006, 'e,': 0.003003003003003003, ', ': 0.003003003003003003, ' g': 0.003003003003003003, 'ge': 0.003003003003003003, 'sc': 0.012012012012012012, 'hi': 0.006006006006006006, 'ic': 0.006006006006006006, 'ht': 0.003003003003003003, 'de': 0.003003003003003003, ' k': 0.003003003003003003, 'ka': 0.003003003003003003, 'au': 0.003003003003003003, 'ut': 0.003003003003003003, 'ts': 0.003003003003003003, 'hu': 0.003003003003003003, 'uk': 0.003003003003003003, 'ks': 0.006006006006006006, ' m': 0.003003003003003003, 'me': 0.003003003003003003, 'en': 0.012012012012012012, 'ns': 0.006006006006006006, 'ck': 0.003003003003003003, 'sa': 0.003003003003003003, 'al': 0.003003003003003003, 'le': 0.006006006006006006, ' z': 0.003003003003003003, 'zu': 0.003003003003003003, 'u ': 0.003003003003003003, 'rz': 0.003003003003003003, 'zä': 0.003003003003003003, 'äh': 0.003003003003003003, 'hl': 0.003003003003003003, 'n.': 0.003003003003003003, 'die': 0.009009009009009009, 'ie ': 0.006006006006006006, 'e v': 0.003003003003003003, ' ve': 0.003003003003003003, 'ver': 0.003003003003003003, 'erf': 0.003003003003003003, 'rfa': 0.003003003003003003, 'fas': 0.003003003003003003, 'ass': 0.003003003003003003, 'sse': 0.003003003003003003, 'ser': 0.003003003003003003, 'eri': 0.003003003003003003, 'rin': 0.003003003003003003, 'in ': 0.009009009009009009, 'n u': 0.003003003003003003, ' un': 0.003003003003003003, 'unt': 0.003003003003003003, 'nte': 0.003003003003003003, 'ter': 0.003003003003003003, 'ern': 0.003003003003003003, 'rni': 0.003003003003003003, 'nim': 0.003003003003003003, 'imm': 0.003003003003003003, 'mmt': 0.003003003003003003, 'mt ': 0.003003003003003003, 't e': 0.003003003003003003, ' es': 0.003003003003003003, 'es ': 0.006006006006006006, 's i': 0.006006006006006006, ' in': 0.006006006006006006, 'n d': 0.003003003003003003, ' di': 0.006006006006006006, 'ies': 0.003003003003003003, 'ese': 0.003003003003003003, 'sem': 0.003003003003003003, 'em ': 0.003003003003003003, 'm b': 0.003003003003003003, ' bu': 0.003003003003003003, 'buc': 0.003003003003003003, 'uch': 0.003003003003003003, 'che': 0.006006006006006006, 'he,': 0.003003003003003003, 'e, ': 0.003003003003003003, ', d': 0.003003003003003003, 'e g': 0.003003003003003003, ' ge': 0.003003003003003003, 'ges': 0.003003003003003003, 'esc': 0.003003003003003003, 'sch': 0.012012012012012012, 'chi': 0.006006006006006006, 'hic': 0.006006006006006006, 'ich': 0.003003003003003003, 'cht': 0.003003003003003003, 'hte': 0.003003003003003003, 'te ': 0.003003003003003003, 'e d': 0.003003003003003003, ' de': 0.003003003003003003, 'des': 0.003003003003003003, 's k': 0.003003003003003003, ' ka': 0.003003003003003003, 'kau': 0.003003003003003003, 'aut': 0.003003003003003003, 'uts': 0.003003003003003003, 'tsc': 0.003003003003003003, 'chu': 0.003003003003003003, 'huk': 0.003003003003003003, 'uks': 0.003003003003003003, 'ks ': 0.003003003003003003, 'n m': 0.003003003003003003, ' me': 0.003003003003003003, 'men': 0.003003003003003003, 'ens': 0.006006006006006006, 'nsc': 0.006006006006006006, 'hen': 0.003003003003003003, 'ick': 0.003003003003003003, 'cks': 0.003003003003003003, 'ksa': 0.003003003003003003, 'sal': 0.003003003003003003, 'ale': 0.003003003003003003, 'len': 0.006006006006006006, 'en ': 0.003003003003003003, 'n z': 0.003003003003003003, ' zu': 0.003003003003003003, 'zu ': 0.003003003003003003, 'u e': 0.003003003003003003, ' er': 0.003003003003003003, 'erz': 0.003003003003003003, 'rzä': 0.003003003003003003, 'zäh': 0.003003003003003003, 'ähl': 0.003003003003003003, 'hle': 0.003003003003003003, 'en.': 0.003003003003003003}

Alternativ können wir auch eine Bibliothek nutzen:

In [8]:
import collections

def buildmodel(text):
    model = collections.Counter(xgram(text))  
    nr_of_ngs = sum(model.values())

    for w in model:
        model[w] = float(model[w]) / float(nr_of_ngs)

    return model
In [9]:
model = buildmodel(text)
print(model)
Counter({'e': 0.05105105105105105, ' ': 0.042042042042042045, 's': 0.03303303303303303, 'i': 0.02702702702702703, 'n': 0.02702702702702703, 'c': 0.021021021021021023, 'h': 0.021021021021021023, 'ch': 0.018018018018018018, 'u': 0.015015015015015015, 'd': 0.012012012012012012, 'r': 0.012012012012012012, 't': 0.012012012012012012, 'm': 0.012012012012012012, 'er': 0.012012012012012012, 'n ': 0.012012012012012012, 'es': 0.012012012012012012, 'sc': 0.012012012012012012, 'en': 0.012012012012012012, 'sch': 0.012012012012012012, 'a': 0.009009009009009009, 'k': 0.009009009009009009, 'di': 0.009009009009009009, 'ie': 0.009009009009009009, 'e ': 0.009009009009009009, 'in': 0.009009009009009009, 's ': 0.009009009009009009, ' d': 0.009009009009009009, 'die': 0.009009009009009009, 'in ': 0.009009009009009009, 'l': 0.006006006006006006, 'z': 0.006006006006006006, 'se': 0.006006006006006006, 'te': 0.006006006006006006, ' e': 0.006006006006006006, ' i': 0.006006006006006006, 'he': 0.006006006006006006, 'hi': 0.006006006006006006, 'ic': 0.006006006006006006, 'ks': 0.006006006006006006, 'ns': 0.006006006006006006, 'le': 0.006006006006006006, 'ie ': 0.006006006006006006, 'es ': 0.006006006006006006, 's i': 0.006006006006006006, ' in': 0.006006006006006006, ' di': 0.006006006006006006, 'che': 0.006006006006006006, 'chi': 0.006006006006006006, 'hic': 0.006006006006006006, 'ens': 0.006006006006006006, 'nsc': 0.006006006006006006, 'len': 0.006006006006006006, 'v': 0.003003003003003003, 'f': 0.003003003003003003, 'b': 0.003003003003003003, ',': 0.003003003003003003, 'g': 0.003003003003003003, 'ä': 0.003003003003003003, '.': 0.003003003003003003, ' v': 0.003003003003003003, 've': 0.003003003003003003, 'rf': 0.003003003003003003, 'fa': 0.003003003003003003, 'as': 0.003003003003003003, 'ss': 0.003003003003003003, 'ri': 0.003003003003003003, ' u': 0.003003003003003003, 'un': 0.003003003003003003, 'nt': 0.003003003003003003, 'rn': 0.003003003003003003, 'ni': 0.003003003003003003, 'im': 0.003003003003003003, 'mm': 0.003003003003003003, 'mt': 0.003003003003003003, 't ': 0.003003003003003003, 'em': 0.003003003003003003, 'm ': 0.003003003003003003, ' b': 0.003003003003003003, 'bu': 0.003003003003003003, 'uc': 0.003003003003003003, 'e,': 0.003003003003003003, ', ': 0.003003003003003003, ' g': 0.003003003003003003, 'ge': 0.003003003003003003, 'ht': 0.003003003003003003, 'de': 0.003003003003003003, ' k': 0.003003003003003003, 'ka': 0.003003003003003003, 'au': 0.003003003003003003, 'ut': 0.003003003003003003, 'ts': 0.003003003003003003, 'hu': 0.003003003003003003, 'uk': 0.003003003003003003, ' m': 0.003003003003003003, 'me': 0.003003003003003003, 'ck': 0.003003003003003003, 'sa': 0.003003003003003003, 'al': 0.003003003003003003, ' z': 0.003003003003003003, 'zu': 0.003003003003003003, 'u ': 0.003003003003003003, 'rz': 0.003003003003003003, 'zä': 0.003003003003003003, 'äh': 0.003003003003003003, 'hl': 0.003003003003003003, 'n.': 0.003003003003003003, 'e v': 0.003003003003003003, ' ve': 0.003003003003003003, 'ver': 0.003003003003003003, 'erf': 0.003003003003003003, 'rfa': 0.003003003003003003, 'fas': 0.003003003003003003, 'ass': 0.003003003003003003, 'sse': 0.003003003003003003, 'ser': 0.003003003003003003, 'eri': 0.003003003003003003, 'rin': 0.003003003003003003, 'n u': 0.003003003003003003, ' un': 0.003003003003003003, 'unt': 0.003003003003003003, 'nte': 0.003003003003003003, 'ter': 0.003003003003003003, 'ern': 0.003003003003003003, 'rni': 0.003003003003003003, 'nim': 0.003003003003003003, 'imm': 0.003003003003003003, 'mmt': 0.003003003003003003, 'mt ': 0.003003003003003003, 't e': 0.003003003003003003, ' es': 0.003003003003003003, 'n d': 0.003003003003003003, 'ies': 0.003003003003003003, 'ese': 0.003003003003003003, 'sem': 0.003003003003003003, 'em ': 0.003003003003003003, 'm b': 0.003003003003003003, ' bu': 0.003003003003003003, 'buc': 0.003003003003003003, 'uch': 0.003003003003003003, 'he,': 0.003003003003003003, 'e, ': 0.003003003003003003, ', d': 0.003003003003003003, 'e g': 0.003003003003003003, ' ge': 0.003003003003003003, 'ges': 0.003003003003003003, 'esc': 0.003003003003003003, 'ich': 0.003003003003003003, 'cht': 0.003003003003003003, 'hte': 0.003003003003003003, 'te ': 0.003003003003003003, 'e d': 0.003003003003003003, ' de': 0.003003003003003003, 'des': 0.003003003003003003, 's k': 0.003003003003003003, ' ka': 0.003003003003003003, 'kau': 0.003003003003003003, 'aut': 0.003003003003003003, 'uts': 0.003003003003003003, 'tsc': 0.003003003003003003, 'chu': 0.003003003003003003, 'huk': 0.003003003003003003, 'uks': 0.003003003003003003, 'ks ': 0.003003003003003003, 'n m': 0.003003003003003003, ' me': 0.003003003003003003, 'men': 0.003003003003003003, 'hen': 0.003003003003003003, 'ick': 0.003003003003003003, 'cks': 0.003003003003003003, 'ksa': 0.003003003003003003, 'sal': 0.003003003003003003, 'ale': 0.003003003003003003, 'en ': 0.003003003003003003, 'n z': 0.003003003003003003, ' zu': 0.003003003003003003, 'zu ': 0.003003003003003003, 'u e': 0.003003003003003003, ' er': 0.003003003003003003, 'erz': 0.003003003003003003, 'rzä': 0.003003003003003003, 'zäh': 0.003003003003003003, 'ähl': 0.003003003003003003, 'hle': 0.003003003003003003, 'en.': 0.003003003003003003})

Wenn wir jetzt ausreichend Text haben, bekommen wir typische Werte für eine Sprache. Die Werte in einem unbekannten Text können wir hiermit vergleichen.

Im NLTK-Paket ist die Erklärung der Menschenrechte in 300 Sprachen enthalten. Hiermit können wir jetzt mal einige Sprachmodelle bauen.

Wenn Sie das Paket nltk zum ersten mal nutzen, oder bisher keine Corpora benutzt haben, bekommen Sie vielleicht eine Fehlermeldung, die darauf zurückgeführt werden kann, dass nicht alle Ressourcen aus dem Paket installiert sind. Sie können in dem Fall einmalig den folgenden Code ausführen. Es erscheint jetzt ein Fenster, in dem Sie auswählen können, welche Ressourcen Sie herunterladen möchte. Die Option 'Book' ist für uns ausreichend.

In [10]:
# Ggf. auskommentieren und einmalig ausführen
#
#import nltk
#
#nltk.download()
In [11]:
from nltk.corpus import udhr

#print udhr.fileids()

languages = ['english','german','dutch','french','italian','spanish']

english_udhr = udhr.raw('English-Latin1')
german_udhr = udhr.raw('German_Deutsch-Latin1')
dutch_udhr = udhr.raw('Dutch_Nederlands-Latin1')
french_udhr = udhr.raw('French_Francais-Latin1')
italian_udhr = udhr.raw('Italian_Italiano-Latin1')
spanish_udhr = udhr.raw('Spanish_Espanol-Latin1')

texts = {'english':english_udhr,'german':german_udhr,'dutch':dutch_udhr,'french':french_udhr,'italian':italian_udhr,'spanish':spanish_udhr}
models = {lang:buildmodel(texts[lang]) for lang in languages}

Die Texte sind sehr kurz und entalten nur ein Bruchteil der Wörter, die in einer Sprache vorkommen. Fürs Deutsche haben wir gerade mal 10 000 Zeichen:

In [12]:
print(len(german_udhr))
9999

Sprache Bestimmen

Wir müssen jetzt die n-Gram Frequenzen eiens Textes mit den Frequenzen der Modelle vergleichen. Um die Modelle zu vergleichen berechnen wir den Cosinus: $$ cos(a,b) = \frac{\sum_i a_i b_i}{\sqrt{\sum_i a_i^2}\sqrt{\sum_i b_i^2}} $$

In [13]:
import math

def cosinus(a,b):
    return sum([a[k]*b[k] for k in a if k in b]) / (math.sqrt(sum([a[k]**2 for k in a])) * math.sqrt(sum([b[k]**2 for k in b])))
In [14]:
print(text)
textmodel = buildmodel(text)
for m in models:
    print(m, cosinus(models[m],textmodel))
Die Verfasserin unternimmt es in diesem Buche, die Geschichte des Kautschuks in Menschenschicksalen zu erzählen.
english 0.7411160989023053
german 0.8522092567280237
dutch 0.7916873428505355
french 0.7659701598838251
italian 0.7148098723418794
spanish 0.7444444064683035

Wir sehen, dass es jetzt schon funktioniert! Zuverlässiger wird es natürlich, wenn wir längere Texte und mehr Genres nehmen um die Sprachmodelle zu bauen.

Ein wenig Kosmetik

Wir brauchen schließlich eine Funktion, die einfach sagt, in welcher Sprache ein Text geschrieben ist.

In [15]:
def guess_language(text):
    textmodel = buildmodel(text)
    lang = "english"
    best = 0
    for m in models:
        c = cosinus(models[m],textmodel)
        if c > best:
            best = c
            lang = m
    return lang
In [16]:
t = "Wie zijn leven voltooid vindt en met een consulent in gesprek gaat over zelfdoding, stelt zelfeuthanasie vaak uit of ziet ervan af"  
print(guess_language(t))

t = u"L’ancien candidat écologiste à la primaire de la gauche s’était engagé à soutenir le vainqueur de ce scrutin à la fin janvier, en l’occurrence Benoît Hamon."  
print(guess_language(t))


t = "Una capra al posto del giardiniere"
print(guess_language(t))
dutch
french
spanish

Das letzte Ergebnis ist falsch! Italienisch wäre richtig. Der Text ist zu kurz und das Modell basiert auf zu wenig Wörtern.

Schließlich wollen wir die Modelle nicht jedes mal neu berechnen. Wir speichern die Modelle daher in eine Datei.

In [17]:
import pickle

pickle.dump(models, open('langidmodels.p', 'wb'))

Wir können die Modelle jetzt auch wieder einlesen:

In [ ]:
 
In [18]:
models = pickle.load(open('langidmodels.p', 'rb'))

Übungen

  1. Testen Sie den Classifier für verschiedene kurze und längere Texte
  2. Fügen Sie weitere Sprachen (auch Sprachen, die den bereits vorhandenen Sprachen sehr ähnlich sind, wie Luxemburgisch oder Katelanisch) hinzu, und testen Sie erneut.
  3. Suchen Sie größere Textcorpora zum Trainieren. Hier finden Sie eine Liste mit Corpora, die in NLTK verfügbar sind: http://www.nltk.org/book/ch02.html Als deutsches Corpus können Sie das Tiger-Corpus nutzen. Das Corpus kann von dieser Seite heruntergeladen werden: http://www.ims.uni-stuttgart.de/forschung/ressourcen/korpora/tiger.html

Sie können dieses Corpus wie folgt einlesen:

In [19]:
import nltk

root = 'Corpora'
fileid = 'tiger.16012013.conll09'
columntypes = ['ignore','words','ignore','ignore','pos']

tiger_corpus = nltk.corpus.ConllCorpusReader(root,fileid,columntypes,encoding='utf8')

Sie können eine Liste von Wörtern bekommen und müssen jetzt entweder hieraus einen Text basteln, oder das Modell aus einer Liste von Wörtern aufbauen. Es ist dann sinnvoll jedes Wort mit einem Wortanfang- und Wortendesymbol zu erweitern. Aus Diktator wird also #Diktator#. Sie haben dann auch Bigramme, wie #D und r#. Daraus sieht man, dass ein Deutsches Wort mit D anfangen und auf r enden kann.

In [20]:
text_german = tiger_corpus.words()
print(len(text_german))
print(text_german[:10])
888238
['``', 'Ross', 'Perot', 'wäre', 'vielleicht', 'ein', 'prächtiger', 'Diktator', "''", 'Konzernchefs']