Natural Language Processing mit Python

In den nächsten zwei Sitzungen geht es um NLP mit Python. Da hierbei auch die jeweiligen Konzepte aus der Linguistik eingeführt werden müssen, werden wir nur einige wenige Grundbegriffe ansprechen.

Sie werden dabei lernen, wie man mit zwei Bibliotheken diese Aufgaben erledigt, um dann in den Hausaufgaben mit weiteren Bibliotheken zu arbeiten.

  1. Textblob
  2. Spacy

In [2]:
text = """Als der Abend herbeikam und die Freunde in einer weitumherschauenden Laube saßen, trat eine ansehnliche Figur auf die Schwelle, welche unser Freund sogleich für den Barbier von heute früh erkannte. Auf einen tiefen, stummen Bückling des Mannes erwiderte Lenardo: Ihr kommt, wie immer, sehr gelegen und werdet nicht säumen, uns mit Eurem Talent zu erfreuen. — Ich kann Ihnen wohl, fuhr er zu Wilhelmen gewendet fort, Einiges von der Gesellschaft erzählen, deren Band zu sein ich mich rühmen darf. Niemand tritt in unsern Kreis, als wer gewisse Talente aufzuweisen hat, die zum Nutzen oder Vergnügen einer jeden Gesellschaft dienen würden. Dieser Mann ist ein derber Wundarzt, der in bedenklichen Fällen, wo Entschluß und körperliche Kraft gefordert wird, seinem Meister trefflich an der Seite zu stehen bereit ist. Was er als Bartkünstler leistet, davon können Sie ihm selbst ein Zeugniß geben. Hiedurch ist er uns eben so nöthig als willkommen. Da nun aber diese Beschäftigung gewöhnlich eine große und oft lästige Geschwätzigkeit mit sich führt, so hat er sich zu eigner Bildung eine Bedingung gefallen lassen, wie denn Jeder, der unter uns leben will, sich von einer gewissen Seite bedingen muß, wenn ihm nach anderen Seiten hin die größere Freiheit gewährt ist. Dieser also hat nun auf die Sprache Verzicht gethan, insofern etwas Gewöhnliches oder Zufälliges durch sie ausgedrückt wird; daraus aber hat sich ihm ein anderes Redetalent entwickelt, welches absichtlich, klug und erfreulich wirkt, die Gabe des Erzählens nämlich. Sein Leben ist reich an wunderlichen Erfahrungen, die er sonst zu ungelegener Zeit schwätzend zersplitterte, nun aber durch Schweigen genöthigt im stillen Sinne wiederholt und ordnet. Hiermit verbindet sich denn die Einbildungskraft und verleiht dem Geschehenen Leben und Bewegung. Mit besonderer Kunst und Geschicklichkeit weiß er wahrhafte Märchen und märchenhafte Geschichten zu erzählen, wodurch er oft zur schicklichen Stunde uns gar sehr ergötzt, wenn ihm die Zunge durch mich gelös't wird; wie ich denn gegenwärtig thue, und ihm zugleich das Lob ertheile, daß er sich in geraumer Zeit, seitdem ich ihn kenne, noch niemals wiederholt hat. Nun hoff' ich, daß er auch diesmal, unserm theuren Gast zu Lieb' und Ehren, sich besonders hervorthun werde.
Ueber das Gesicht des Rothmantels verbreitete sich eine geistreiche Heiterkeit, und er fing ungesäumt folgendermaßen zu sprechen an:
Hochverehrte Herren! da mir bekannt ist, daß Sie vorläufige Reden und Einleitungen nicht besonders lieben, so will ich ohne weiteres versichern, daß ich diesmal vorzüglich gut zu bestehen hoffe. Von mir sind zwar schon gar manche wahrhafte Geschichten zu hoher und allseitiger Zufriedenheit ausgegangen, heute aber darf ich sagen, daß ich eine zu erzählen habe, welche die bisherigen weit übertrifft, und die, wiewohl sie mir schon vor einigen Jahren begegnet ist, mich noch immer in der Erinnerung unruhig macht, ja sogar eine endliche Entwicklung hoffen läßt. Sie möchte schwerlich ihres Gleichen finden.
"""

Textblob installieren:

Text in Sätze zerlegen

1. Textblob:


In [3]:
from textblob_de import TextBlobDE as TextBlob
from textblob_de import PatternParser

doc = TextBlob(text)
print("Number of sentences: ", len(doc.sentences))
print("Length of sentences in characters: ")
for s in doc.sentences:
    print(len(s), end=" - ")


Number of sentences:  17
Length of sentences in characters: 
197 - 158 - 138 - 141 - 175 - 79 - 50 - 319 - 264 - 183 - 97 - 362 - 108 - 153 - 173 - 366 - 44 - 

Achtung: Mit doc.sentences iterieren wir über die Sätze im Text. Aber der Satz ist kein String, sondern ein besonderes Objekt:


In [4]:
type(s)


Out[4]:
textblob_de.blob.Sentence

In [5]:
Das gilt auch schon für unser Dokument-Objekt doc:


  File "<ipython-input-5-34440789d4f3>", line 1
    Das gilt auch schon für unser Dokument-Objekt doc:
           ^
SyntaxError: invalid syntax

In [6]:
type(doc)


Out[6]:
textblob_de.blob.TextBlobDE

Das Gute daran, ist, dass wir - wie oben - über dieses Objekt iterieren können:

for s in doc.sentences

Aber genau genommen iterieren wir ja nicht über das 'doc'-Objekt, sondern über die Daten einer bestimmten Sicht, die wir mit dem Attribut 'sentences' aktivieren. Wir können auch andere Sichten aktivieren, z.B. Worte:

for w in doc.words

In [7]:
doc.words[:20]


Out[7]:
WordList(['Als', 'der', 'Abend', 'herbeikam', 'und', 'die', 'Freunde', 'in', 'einer', 'weitumherschauenden', 'Laube', 'saßen', 'trat', 'eine', 'ansehnliche', 'Figur', 'auf', 'die', 'Schwelle', 'welche'])

In [8]:
w = doc.words[0]

In [9]:
type(w)


Out[9]:
textblob_de.blob.Word

Vielleicht sollten wir erst einmal erläutern, warum es nicht ganz einfach ist, einen Text in Sätze zu zerlegen. Zuuerst könnte man denken, dass man das mit einigen sehr einfachen Regeln erledigen kann, aber wie ein Blick auf das nächste Beispiel zeigt, ist das nicht so einfach:


In [10]:
text_2 = """Johann Wolfgang Goethe wurde, glaube ich, am 28.8.1749 geboren. Es könnte auch am 20.8. sein. Ich muss zugeben: Genau weiß ich das nicht."""
text_3 = """Die heutige Agenda ist kurz. 1. Die Frage nach dem Anfang. 2. Ende. Viel Spaß!"""

In [11]:
doc = TextBlob(text_2)
list(doc.sentences)


Out[11]:
[Sentence("Johann Wolfgang Goethe wurde, glaube ich, am 28.8.1749 geboren."),
 Sentence("Es könnte auch am 20.8. sein."),
 Sentence("Ich muss zugeben: Genau weiß ich das nicht.")]

In [12]:
doc = TextBlob(text_3)
list(doc.sentences)


Out[12]:
[Sentence("Die heutige Agenda ist kurz."),
 Sentence("1."),
 Sentence("Die Frage nach dem Anfang."),
 Sentence("2."),
 Sentence("Ende."),
 Sentence("Viel Spaß!")]

In [13]:
blob = TextBlob("Das ist ein schönes Auto.", parser=PatternParser(pprint=True, lemmata=True))
blob.parse()


          WORD   TAG    CHUNK   ROLE   ID     PNP    LEMMA   
                                                             
           Das   DT     -       -      -      -      das     
           ist   VB     VP      -      -      -      sein    
           ein   DT     NP      -      -      -      ein     
       schönes   JJ     NP ^    -      -      -      schön   
          Auto   NN     NP ^    -      -      -      auto    
             .   .      -       -      -      -      .       

In [14]:
doc.sentences[0].words


Out[14]:
WordList(['Die', 'heutige', 'Agenda', 'ist', 'kurz'])

2. Spacy


In [15]:
import spacy
nlp = spacy.load('de')
doc = nlp(text_2)
for s in doc.sents:
    print(s)


Johann Wolfgang Goethe wurde, glaube ich, am 28.8.1749 geboren.
Es könnte auch am 20.8. sein.
Ich muss zugeben:
Genau weiß ich das nicht.

In [16]:
doc = nlp(text_3)
for s in doc.sents:
    print(s)


Die heutige Agenda ist kurz.
1
. Die Frage nach dem Anfang.
2. Ende.
Viel Spaß!

Im folgenden werden wir nur mit Spacy weiterarbeiten. Für Spacy spricht, dass es recht neu ist, eine ganze Reihe von Sprachen unterstützt, ein modernes Python-Interface mit einer wohlüberlegten API hat, vergleichsweise neue Aspekte der Sprachtechnologie, z.B. Word Embeddings, unterstützt und dass Deutsch zu den gut unterstützten Sprachen zählt. Gegen Spacy spricht, dass es von einer privaten Firma entwickelt wird, allerdings wird das dadurch gemildert, dass spacy selbst auf github unter einer sehr freizügigen MIT-Lizenz verfügbar ist.


In [17]:
print(spacy.__version__)


2.0.11

Tokenisieren

Spacy


In [18]:
import spacy
doc = nlp(text_2)
for token in doc: 
    print(token.text, end="< | >")


Johann< | >Wolfgang< | >Goethe< | >wurde< | >,< | >glaube< | >ich< | >,< | >am< | >28.8.1749< | >geboren< | >.< | >Es< | >könnte< | >auch< | >am< | >20.8< | >.< | >sein< | >.< | >Ich< | >muss< | >zugeben< | >:< | >Genau< | >weiß< | >ich< | >das< | >nicht< | >.< | >

In [19]:
doc = nlp(text_3)
a = [print(token.text, end="< | >") for token in doc]


Die< | >heutige< | >Agenda< | >ist< | >kurz< | >.< | >1< | >.< | >Die< | >Frage< | >nach< | >dem< | >Anfang< | >.< | >2< | >.< | >Ende< | >.< | >Viel< | >Spaß< | >!< | >

In [20]:
doc = nlp(text_2)
print("{:<15}{:<15}{:<15}".format("TOKEN", "LEMMA", "POS-Tag"))
for token in doc:
    print("{:15}{:15}{:15}".format(token.text, token.lemma_, token.pos_ ))


TOKEN          LEMMA          POS-Tag        
Johann         Johann         PROPN          
Wolfgang       Wolfgang       PROPN          
Goethe         Goethe         PROPN          
wurde          werden         AUX            
,              ,              PUNCT          
glaube         glauben        VERB           
ich            ich            PRON           
,              ,              PUNCT          
am             am             ADP            
28.8.1749      28.8.1749      NOUN           
geboren        gebären        VERB           
.              .              PUNCT          
Es             Es             PRON           
könnte         können         VERB           
auch           auch           ADV            
am             am             ADP            
20.8           20.8           NOUN           
.              .              PUNCT          
sein           mein           AUX            
.              .              PUNCT          
Ich            Ich            PRON           
muss           muss           ADJ            
zugeben        zugeben        VERB           
:              :              PUNCT          
Genau          Genau          ADJ            
weiß           weiß           VERB           
ich            ich            PRON           
das            der            PRON           
nicht          nicht          PART           
.              .              PUNCT          

In [21]:
doc = nlp("Diese Auskünfte muss ich dir nicht geben.")
[token.lemma_ for token in doc]


Out[21]:
['Diese', 'Auskunft', 'muss', 'ich', 'sich', 'nicht', 'geben', '.']

In [24]:
from spacy_iwnlp import spaCyIWNLP
iwnlp = spaCyIWNLP(lemmatizer_path=r'\mydata\Dropbox\uni\progrs\spacy-iwnlp\IWNLP.Lemmatizer_20170501.json')
nlp.add_pipe(iwnlp)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-24-6bda69b5c935> in <module>()
      1 from spacy_iwnlp import spaCyIWNLP
      2 iwnlp = spaCyIWNLP(lemmatizer_path=r'\mydata\Dropbox\uni\progrs\spacy-iwnlp\IWNLP.Lemmatizer_20170501.json')
----> 3 nlp.add_pipe(iwnlp)
      4 doc = nlp("Diese Auskünfte muss ich dir nicht geben.")
      5 for token in doc:

C:\conda\lib\site-packages\spacy\language.py in add_pipe(self, component, name, before, after, first, last)
    269                 name = repr(component)
    270         if name in self.pipe_names:
--> 271             raise ValueError(Errors.E007.format(name=name, opts=self.pipe_names))
    272         if sum([bool(before), bool(after), bool(first), bool(last)]) >= 2:
    273             raise ValueError(Errors.E006)

ValueError: [E007] 'spaCyIWNLP' already exists in pipeline. Existing names: ['tagger', 'parser', 'ner', 'spaCyIWNLP']

In [29]:
import spacy
from spacy_iwnlp import spaCyIWNLP
nlp = spacy.load('de')
iwnlp = spaCyIWNLP(lemmatizer_path=r'\mydata\Dropbox\uni\progrs\spacy-iwnlp\IWNLP.Lemmatizer_20170501.json')
nlp.add_pipe(iwnlp)
doc = nlp('Wir mögen Fußballspiele mit ausgedehnten Verlängerungen.')
for token in doc:
    print('POS: {}\tIWNLP:{}'.format(token.pos_, token._.iwnlp_lemmas))    
l


POS: PRON	IWNLP:None
POS: VERB	IWNLP:['mögen']
POS: NOUN	IWNLP:['Fußballspiel']
POS: ADP	IWNLP:None
POS: ADJ	IWNLP:['ausgedehnt']
POS: NOUN	IWNLP:['Verlängerung']
POS: PUNCT	IWNLP:None
Out[29]:
[None, ['Auskunft'], ['müssen'], None, None, None, ['geben'], None]

In [28]:
doc = nlp('Wir mögen Fußballspiele mit ausgedehnten Verlängerungen.')
for token in doc:
    print('POS: {}\tIWNLP:{}'.format(token.pos_, token._.iwnlp_lemmas))


POS: PRON	IWNLP:None
POS: VERB	IWNLP:['mögen']
POS: NOUN	IWNLP:['Fußballspiel']
POS: ADP	IWNLP:None
POS: ADJ	IWNLP:['ausgedehnt']
POS: NOUN	IWNLP:['Verlängerung']
POS: PUNCT	IWNLP:None

In [ ]:


In [ ]:


In [90]:
from spacy import displacy
text_4 = "Am Anfang war das Wort, das aber bald durch blutige Taten ersetzt wurde."
doc = nlp(text_4)
displacy.render(doc, style='dep', jupyter=True)


C:\conda\lib\runpy.py:193: DeprecationWarning: Positional arguments to Doc.merge are deprecated. Instead, use the keyword arguments, for example tag=, lemma= or ent_type=.
  "__main__", mod_spec)
C:\conda\lib\runpy.py:193: DeprecationWarning: Positional arguments to Doc.merge are deprecated. Instead, use the keyword arguments, for example tag=, lemma= or ent_type=.
  "__main__", mod_spec)
Am ADP Anfang NOUN war AUX das DET Wort, NOUN das PRON aber ADV bald ADV durch ADP blutige ADJ Taten NOUN ersetzt VERB wurde. AUX mo nk nk sb sb mo mo mo nk nk oc rc

In [ ]:

Part-of-Speech-Tagging

Named Entity Recognition


In [100]:
text_5 = """Früher hat man über Johann Wolfang von Goethe gesprochen, weil er den 'Faust' geschrieben hat, oder über Mozart, 
weil der die Zauberflöte komponiert hat. Heute dagegen redet man über Samsung, weil das neue Samsung Note4 erschienen ist, 
oder über den neuen BMW. Gut, über Steve Jobs hat man noch so geredet, als wäre er ein neuer Mozart der Technologie. 
In den USA weiß man kaum noch wer Shakespeare ist, und in Berlin benimmt man sich schon so, also könnte man mit 
1 Mio. € einen Goethe kaufen."""
#text_5 = text_5.replace("\n", "") #new lines irritate the parser
doc = nlp(text_5)
for ent in doc.ents:
    print(ent.text, ent.start_char, ent.end_char, ent.label_)


Johann Wolfang von Goethe 20 45 PER
Mozart 105 111 PER

 113 114 PER
Zauberflöte 127 138 MISC
Samsung 184 191 MISC
Samsung Note4 207 220 MISC

 237 238 MISC
BMW 258 261 ORG
Steve Jobs 273 283 PER

 355 356 ORG
den USA 359 366 LOC
Shakespeare 390 401 PER
Berlin 414 420 LOC

1 Mio. 468 475 ORG

In [ ]:


In [ ]: