Python-Mengen: Umfangreiches Beispiel¶
Einfache Computerlinguistik mit Python¶
Dieses Kapitel beschäftigt sich mit natürlicher Sprache und Literatur. Es ist auch ein Beispiel von einfacher statistischer Computerlinguistik. Aber in erster Linie geht es darum ein umfangreiches Beispiel und einen interessanten Anwendungsfall für Python-Mengen bieten. Neulinge in Python denken oft, dass Mengen nur ein Spielzeug für Mathematiker sind und dass es keinen wirklichen Anwendungsfall in der Programmierung gibt. Das Gegenteil ist der Fall. Es gibt mehrere Anwendungsfälle für die Mengen also den Datentyp 'set' in der Python-Programmierung. Sie werden z. B. verwendet, um Doubletten - mehrfach vorkommende Elemente - in einer Liste zu beseitigen, d. h. um eine Liste eindeutig zu machen.
Im folgenden Beispiel werden wir den Datentyp 'set' verwenden, um die verschiedenen Wörter zu bestimmen, die in einem Roman vorkommen. Unser Anwendungsfall dreht sich um einen Roman, der von vielen gelobt und von einigen enthusiastisch als der beste Roman in englischer Sprache des 20. Jahrhunderts betrachtet wird, aber auch als der am schwersten zu lesende angesehen wird. Wir sprechen über den Roman "Ulysses" von James Joyce. Wir werden nicht über die Schönheit der Sprache oder den Sprachstil sprechen. Wir werden den Roman untersuchen, indem wir uns die im Roman verwendeten Wörter genau ansehen. Unser Ansatz wird rein statistisch sein. Die Behauptung ist, dass James Joyce in seinem Roman mehr Wörter verwendet als jeder andere Autor. Tatsächlich ist sein Wortschatz größer als der aller anderen Autoren, vielleicht sogar als der von Shakespeare.
Außer dem Roman "Ulysses" werden wir noch die folgenden Romane verwenden "Sons and Lovers" von D.H. Lawrence, "The Way of All Flesh" von Samuel Butler, "Robinson Crusoe" von Daniel Defoe, "To the Lighthouse" von Virginia Woolf, "Moby Dick" von Herman Melville and the Short Story "Metamorphosis" von Franz Kafka.
Wörter in einem Text¶
Um alle Wörter aus dem Roman "Ulysses" herauszuschneiden, können wir die Funktion findall aus dem Modul "re" verwenden:
import re
# uns ist die Groß- und Kleinschreibung egal und wir verwenden Kleinbuchstaben:
ulysses_txt = open("books/james_joyce_ulysses.txt").read().lower()
words = re.findall(r"\b[\w-]+\b", ulysses_txt)
print(f"Der Roman Ulysses enthält {len(words)} Wörter.")
Diese Zahl ergibt sich aus der Summe aller Wörter sowie der vielen Wörter, die mehrfach vorkommen:
for word in ["the", "while", "good", "bad", "ireland", "irish"]:
outstr = f"Das Wort '{word}' kommt {words.count(word)}"
outstr += f" Mal in dem Roman vor!"
print(outstr)
272452 Wörter sind sicherlich eine enorme Anzahl von Wörtern für einen Roman, aber auf der anderen Seite gibt es viele Romane mit noch mehr Wörtern. Interessanter und aussagekräftiger über die Qualität eines Romans ist die Anzahl der verschiedenen Wörter. Das ist der Moment, in dem wir endlich "Set" brauchen. Wir werden die Wortliste "words" in eine Menge verwandeln. Wenden wir "len" auf diese Menge an, erhalten wir die Anzahl der verschiedenen Wörter:
diff_words = set(words)
print(f"'Ulysses' enthält {len(diff_words)} verschiedene Wörter!")
Das ist in der Tat eine beeindruckende Zahl. Das sieht man umso besser, wenn man es mit den Worthäufigkeiten der anderen unten stehenden Romane vergleicht:
novels = ['sons_and_lovers_lawrence.txt',
'metamorphosis_kafka.txt',
'the_way_of_all_flash_butler.txt',
'robinson_crusoe_defoe.txt',
'to_the_lighthouse_woolf.txt',
'james_joyce_ulysses.txt',
'moby_dick_melville.txt']
print(f"{'Name des Romans':38s}: Anz. verschiedene Wörter")
for novel in novels:
txt = open("books/" + novel).read().lower()
words = re.findall(r"\b[\w-]+\b", txt)
diff_words = set(words)
n = len(diff_words)
print(f"{novel[:-4]:38s}: {n:5d}")
Besondere Wörter in Ulysses¶
In dem folgenden kleinen Python-Programm werden wir alle Wörter, die in den anderen Romanen vorkommen, von "Ulysses" subtrahieren. Es ist erstaunlich, wie viele Wörter von James Joyce verwendet werden, die keiner der anderen Autoren verwendet:
words_in_novel = {}
for novel in novels:
txt = open("books/" + novel).read().lower()
words = re.findall(r"\b[\w-]+\b", txt)
words_in_novel[novel] = words
words_only_in_ulysses = set(words_in_novel['james_joyce_ulysses.txt'])
novels.remove('james_joyce_ulysses.txt')
for novel in novels:
words_only_in_ulysses -= set(words_in_novel[novel])
with open("books/words_only_in_ulysses.txt", "w") as fh:
txt = " ".join(words_only_in_ulysses)
fh.write(txt)
print(len(words_only_in_ulysses))
Übrigens hat amerikanische Kinderbuchautor Dr. Seuss ein Buch mit nur 50 verschiedenen Wörtern geschrieben: "Green Eggs and Ham"
Die Datei mit den Wörtern, die nur in Ulysses vorkommen enthält seltsame oder selten verwendete Wörter wie:
huntingcrop tramtrack pappin kithogue pennyweight undergarments scission nagyaságos wheedling begad dogwhip hawthornden turnbull calumet covey repudiated pendennis waistcoatpocket nostrum
Gemeinsame Wörter¶
Es ist auch möglich, die Wörter zu finden, die in jedem Buch vorkommen. Dazu benötigen wir die Schnittmenge:
# Wir starten mit den Wörtern in ulysses
common_words = set(words_in_novel['james_joyce_ulysses.txt'])
for novel in novels:
common_words &= set(words_in_novel[novel])
print(len(common_words))
Richtig machen¶
Bei den vorherigen Berechnungen ist uns ein kleiner Fehler unterlaufen. Wenn Sie sich die Texte ansehen, werden Sie feststellen, dass sie einen Kopf- und Fußteil haben, der von Project Gutenberg hinzugefügt wurde und nicht zu den Texten gehört. Die Texte sind zwischen den Zeilen positioniert:
START OF THE PROJECT GUTENBERG EBOOK THE WAY OF ALL FLESH
und
END OF THE PROJECT GUTENBERG EBOOK THE WAY OF ALL FLESH
oder
START OF THIS PROJECT GUTENBERG EBOOK ULYSSES
und
END OF THIS PROJECT GUTENBERG EBOOK ULYSSES
Die Funktion read_text nimmt sich dieses Problemes an, d.h. sie schneidet die Textblöcke aus:
def read_text(fname):
beg_e = re.compile(r"\*\*\* ?start of (this|the) project gutenberg ebook[^*]*\*\*\*")
end_e = re.compile(r"\*\*\* ?end of (this|the) project gutenberg ebook[^*]*\*\*\*")
txt = open("books/" + fname).read().lower()
beg = beg_e.search(txt).end()
end = end_e.search(txt).start()
return txt[beg:end]
words_in_novel = {}
for novel in novels + ['james_joyce_ulysses.txt']:
txt = read_text(novel)
words = re.findall(r"\b[\w-]+\b", txt)
words_in_novel[novel] = words
words_in_ulysses = set(words_in_novel['james_joyce_ulysses.txt'])
for novel in novels:
words_in_ulysses -= set(words_in_novel[novel])
with open("books/words_in_ulysses.txt", "w") as fh:
txt = " ".join(words_in_ulysses)
fh.write(txt)
print(len(words_in_ulysses))
# we start with the words in ulysses
common_words = set(words_in_novel['james_joyce_ulysses.txt'])
for novel in novels:
common_words &= set(words_in_novel[novel])
print(len(common_words))
Die Wörter der Menge "common_words" sind Wörter, die zu den am häufigsten verwendeten Wörtern der englischen Sprache gehören. Schauen wir uns 30 beliebige Wörter aus dieser Menge an:
counter = 0
for word in common_words:
print(word, end=", ")
counter += 1
if counter == 30:
break