EinfĂĽhrung in Natural Language Processing mit NLP++

Wer möchte nicht gerne das Wissen täglich neu entstehender Texte nutzen? Mit den Methoden von Natural Language Processing und der speziell für die Sprachanalyse entwickelten Programmiersprache NLP++ ist es möglich, Texte zu strukturieren, zu analysieren und das gesuchte Wissen nach den gewünschten Kriterien zu kategorisieren.

In Pocket speichern vorlesen Druckansicht 1 Kommentar lesen
Lesezeit: 14 Min.
Von
  • Dominik Holenstein
Inhaltsverzeichnis

Wer möchte nicht gerne das Wissen täglich neu entstehender Texte nutzen? Mit den Methoden von Natural Language Processing und der speziell für die Sprachanalyse entwickelten Programmiersprache NLP++ ist es möglich, Texte zu strukturieren, zu analysieren und das gesuchte Wissen nach den gewünschten Kriterien zu kategorisieren.

Da die menschliche Sprache sowohl in geschriebener als auch in gesprochener Form unstrukturiert ist und sich ständig verändert, ist ihre Analyse eine Herausforderung, aber nicht unmöglich. Neben der Analyse und Extrahierung von Wissen wird auch zunehmend öfter der umgekehrte Weg gegangen: den der Textproduktion. Aus halb- und vollstrukturierten Daten will man Texte generieren. Beides, die Textanalyse und die Textproduktion, kann man mit den Methoden des Natural Language Processing (NLP; im Deutschen oft mit Computerlinguistik übersetzt) realisieren.

Dass NLP ein zeitgemäßes Thema ist, beweisen zum Beispiel Publikationen aus dem Heise Zeitschriften Verlag in den letzten Monaten [1], [2], [3].

Es gibt immer mehr APIs und Tools für gängige Programmiersprachen, um das Analysieren und Strukturieren von Texten zu ermöglichen. Eine aktuelle Übersicht bietet diese Liste. Oft benutzen die damit einhergehenden Bibliotheken statistische Methoden und Algorithmen aus dem Machine Learning. Entwickler können aus verschiedenen Modulen wie Sentiment Analysis, POS-Tagger und Information Extraction die passenden Funktionen auswählen und die Texte damit analysieren.

Diese Systeme sind Blackboxen, das heißt, man weiß bei proprietären Systemen nicht genau, wie sie intern funktionieren. Anpassungen nach Kundenwünschen sind möglich, können aber zeitlich und technisch aufwendig sein. Bei Systemen wie dem Natural Language Toolkit oder bei Stanford CoreNLP hingegen ist die Transparenz durch die Open-Source-Lizenzen vorhanden. Wer schnell mit seiner Alltagsprogrammiersprache zum Ziel kommen will oder muss, macht sicherlich nichts falsch, wenn man sich für eine dieser Blackbox-Angebote entscheidet.

VisualText ist eine IDE fĂĽr das Entwickeln von Text-Analyzern, und NLP++ wurde speziell fĂĽr das Analysieren von Sprache entwickelt und basiert auf dem Glasbox-Konzept. Hier bekommt man keine vorgefertigte API oder Module geliefert, sondern entwickelt den Text-Analyzer von Grund auf und themenbezogen selbst. Das hat Vor- und Nachteile. Eine HĂĽrde ist sicherlich, dass man mit NLP++ eine neue Programmiersprache lernen muss. Dazu liefert die Online-Hilfe von VisualText viel Material und ausfĂĽhrliche Tutorials.

Im Gegensatz zu anderen bekannten Programmiersprachen, die fĂĽr den universellen Einsatz gedacht sind, ist NLP++ eine auf C basierende DSL (Domain Specific Language) fĂĽr das Natural Language Processing. Die Erfahrung der letzten zehn Jahre zeigt, dass sich auch Nichtprogrammierer mit der Sprache schnell zurechtfinden. Wer schon Programmiererfahrung hat (egal in welcher Sprache), merkt schnell, dass sich die Entwickler Amnon Meyers und David C. Hilsters viele Gedanken bei der Implementierung mit dem Fokus auf dem Analysieren von Sprache gemacht haben.

Sie haben nicht den statistischen, sondern den auf Regeln basierenden Ansatz gewählt. Der Vorteil liegt darin, dass Programmierer auf Basis der erkannten Regeln und Muster einen Text-Analyzer genau auf die Bedürfnisse des Kunden hin entwickeln können. Sie können das strikt nach linguistischen Kriterien durchführen (Morphologie, Syntax, Semantik usw.), aber auch die Texte nach den eigenen Musterdefinitionen analysieren und aufbereiten. Mit dem Glasbox- und dem auf Regeln basierenden Ansatz erreicht man, dass ein Analyzer sehr genau sowie transparent ist und sich ständig weiterentwickeln lässt.

Zuerst wird das Resultat definiert, das der Text-Analyzer liefern soll. Im folgenden Beispiel geht es darum, aus täglichen Meldungen der Agentur AWP International Informationen zum Rohölpreis zu extrahieren. Ist der Preis gestiegen oder gefallen? Um wie viel? Welche Rohölsorte (US-WTI, Nordsee Brent, Opec)? Zudem sollen die Preisinformationen (die Änderung und der neue Preis pro Barrel) als Float-Werte gespeichert werden, um sie für spätere Berechnungen verwenden zu können. In einer späteren Version sollen die Gründe für die Preisänderungen gesammelt und strukturiert gespeichert werden.

Danach entwickelt der Programmierer ein Prototyp auf Basis der Inputtexte. Läuft er zufrieden stellend, fügt er einen weiteren Text hinzu und prüft ihn mit dem Prototypen. Dabei wird kaum ein befriedigendes Resultat herauskommen. Der Prototyp wird solange angepasst, bis er auch mit dem zweiten Text korrekt funktioniert.

Die Programmierung sollte so generisch wie möglich sein. Dieses Vorgehen erfolgt bei jedem weiteren Inputtext. FĂĽr einen gut trainierten Text-Analyzer brauchen Entwickler erfahrungsgemäß 40 bis 50 Beispieltexte. Das hängt auch davon ab, wie ähnlich oder verschieden die Eingabetexte strukturell und inhaltlich aufgebaut sind. Bei stark standardisierten Texten wie den täglichen Agenturmeldungen zum Ă–lmarkt braucht man weniger Trainingstexte als fĂĽr die Analyse von E-Mails.

Dieses Beispiel ist dem Rohoelnews_Analyzer_v0.1 entnommen. In dem Satz: "Rohöl der US-Sorte West Texas Intermediate (WTI) verteuerte sich hingegen um 45 Cent auf 94,34 Dollar." sollen die Ă–lsorte, deren Preisänderung und -richtung (höher/tiefer/gleich), der neue Preis und die Währung erkannt sowie die Geldbeträge fĂĽr weitere Berechnungen in Float-Werte umgewandelt und in Variablen gespeichert werden. Die folgende Abbildung zeigt den Output fĂĽr den Beispielsatz.

Das Resultat fĂĽr den Beispielsatz nach der Analyse (Abb. 1).

Den NLP++-Code schreibt man in sogenannten Passfiles (nachfolgend auch "Pass" oder "Passes" genannt), die wiederum in VisualText im Ana-Tab (AbkĂĽrzung fĂĽr "Analysis Tab") gespeichert und strukturiert werden. Ein Pass ist vergleichbar mit einer Code-Datei bei anderen Programmiersprachen und verarbeitet eine oder mehrere vom Entwickler programmierte Aufgaben.

Der Ana-Tab mit den Passfiles des Rohölnews-Analyzers (Auszug) (Abb. 2)

Ein Passfile kann bis zu fünf Bereiche enthalten: Declare, Code, Context, Minipass und Grammar. Ersteres umfasst die benutzerdefinierten Funktionen, die dann im Analyzer zur Verfügung stehen. Der Code-Bereich enthält NLP++-Code, der unabhängig von der gefundenen Regel ausgeführt wird. In der Context-Region definiert man den Knoten innerhalb des Parse Tree, auf den die Regeln geprüft werden sollen. Die Minipass-Region erlaubt es, rekursive Algorithmen zu implementieren. In der Grammar-Region werden schließlich die Regeln und Muster für die Texterkennung definiert. Pro Passfile kann der Entwickler mehrere Grammar-Regionen implementieren (Abb. 3). Keine dieser Bereiche ist zwingend – ein Passfile kann auch nur eine Code- oder eine Grammar-Region enthalten.

Die häufigsten Regionen innerhalb eines Passfile (ohne Declare und Minipass) (Abb. 3)

Nach dem Start des Analyzers mit Run arbeitet er jedes Passfile der Reihe nach von oben nach unten ab. Die Kunst ist es, die Passfiles in der richtigen Reihenfolge zu organisieren. Ein Passfile lässt sich jederzeit an eine andere Position verschieben. Das kann das Resultat des Analyzers im Guten wie im Schlechten massiv beeinflussen.

Nach jedem Passfile wird der Parse Tree neu erstellt. Diese Multipass-Strategie führt dazu, dass man mit dem einen Passfile die Grundlage für das nächste legen kann. Vorangehende können die nachfolgenden Passes mit zusätzlichen Informationen füttern oder nicht benötigte Textteile entfernen.

Das erste Passfile in jedem Analyzer ist der Tokenize-Pass. Das ist ein System-Pass, der sich nicht verändern lässt. Er liest den Inputtext und erstellt einen Parse Tree mit nur einer Ebene. AuĂźerdem erstellt er Tokens fĂĽr Wörter (alpha), Zahlen (num), Leerzeichen (white) und Satzzeichen (punct). Linguistisch gesehen ist das die unterste Ebene der Analyse. Der String "94,34 Dollar" aus dem Beispielsatz wird ĂĽber mehrere Passfiles schrittweise zum Konzept "New Price = 94.34" entwickelt. Zuerst wird im Join-Pass die Währung extrahiert

Das Konzept _usDollar erscheint im Parse Tree als Knoten, falls die Regel rechts vom Regelpfeil (<-) passt. Das Schema fĂĽr die Regeln oder die Mustererkennung ist:

@RULES
_S <- _A eintext _B @@

_S ist der Knoten, den der Entwickler im Parse Tree erstellt, wenn die Regel rechts vom Regelpfeil (<-) passt. Hier steht der Ausdruck mit den Elementen, für deren Muster er den passenden Text sucht. _A und _B sind Knoten oder Nonliterale. Ihre Namen beginnen immer mit einem Unterstrich. eintext ist ein Literal oder ein terminaler Knoten und repräsentiert Wörter, Nummern und Satzzeichen.

Die Währung wird im Join-Pass über die verschiedenen Schreibweisen von "Dollar" festgestellt. Hier handelt es sich rechts vom Regelpfeil um Literale, also um Strings, die genauso im Inputtext stehen. Die Regeln innerhalb des Grammar-Bereichs werden wie die Passfiles im Ana Tab der Reihe nach von oben nach unten abgearbeitet. Sobald eine Regel passt, muss man die nachfolgenden Regeln nicht mehr beachten. Daher sind die Regeln nach der Länge der Muster zu ordnen, wobei die längste am Anfang steht. Wäre die Regel

_usDollar <- US \- Dollar @@ 

aus der Abbildung 4 oben an dritter Stelle, dann passte fĂĽr den String US-Dollar zuerst die Regel

_usDollar <- Dollar @@

und würde so nicht korrekt erkannt. Wie erwähnt ist die Reihenfolge der Regeln innerhalb eines Passfiles wichtig. Der Parse Tree für Dollar aus dem Beispielsatz nach dem Join-Pass ist in Abbildung 5 abgebildet.

Der Parse Tree fĂĽr das Literal "Dollar" nach dem Join-Pass (Abb. 5)

Der nächste Schritt fügt den String 94,34 dem Knoten _moneyValue hinzu.

Die Regel für Geldbeträge wie "94,34" ... (Abb. 6)

... und der entsprechende Parse Tree (Abb. 7).

NLP++ bietet vordefinierte Regelelemente unter anderem für das Erkennen von Wörtern (_xALPHA), Zahlen (_xNUM), Leerzeichen (_xWHITE) und Satzzeichen (_xPUNCT). _xWILD ist eine Wildcard
fĂĽr jede Art von erkannten Zeichen.

Die Regel fĂĽr _moneyValue passt, wenn im Text zuerst eine Zahl, dann ein Komma und danach wieder eine Zahl gefunden wird. Die Modifikatoren [base] und [s] fĂĽhren dazu, dass sich innerhalb eines Satzes mehrere Muster dieser Art erkennen lassen. Interessant ist dieser Teil der Regel:

_xWILD [ s one matches=( \, )]

Er legt fest, dass zwingend ein Komma zwischen zwei Zahlen stehen muss. NLP++ bietet diesbezüglich viele Einstellungsmöglichkeiten.

Nun steht das Konzept _moneyValue, aber noch fehlt der Float-Wert. Das wird im money-Pass erledigt.

Der money-Pass mit Variablen .... (Abb. 8)

... und der resultierende Parse Tree fĂĽr "94,34 Dollar" (Abb. 9).
Mehr Infos

Variablentypen in NLP++

  • L(): Lokale Variablen, nur lokal verfĂĽgbar, im Beispiel innerhalb der @POST-Region.
  • G(): Globale Variable, sichtbar im Passfile, wo sie definiert wurde, und fĂĽr alle folgenden Passfiles.
  • S(): Suggested-Variable, wird dem Knoten links der Regel hinzugefĂĽgt.
  • N(): Node-Variable, die enthält den Text der gefundenen Regel.
  • X(): KonteXt-Variable, sie dient dazu, Variablen ĂĽber die Parse-Tree-Hierarchie weiterzureiche.

Passt eine Regel, wird danach, sofern vorhanden, der Code in der @POST-Region ausgefĂĽhrt. In NLP++ gibt es fĂĽnf Variablentypen, wovon diese Regel vier verwendet (siehe "Variablentypen in NLP++").

Mit N("$text",n) kann man auf ein Element an der Position n in der gefundenen Regel zugreifen. In diesem Beispiel wird mit N("$text",1) der String 94,34 ausgelesen. Die Funktion strsubst() ersetzt das Komma mit einem Punkt. Den neuen Ausdruck 94.34 weist man der lokalen Variablen L("tmp") zu. Im nächsten Schritt wird der String 94.34 in den Float-Wert "94.34" umgewandelt und dem Knoten _moneyValUSDandCents die Variable S("moneyValue") zugewiesen.

Jetzt fehlt noch die Semantik. Um was für einen Wert handelt es sich? Es ist der neue Preis für einen Barrel US-Rohöl. Im Beispiel-Analyzer überspringt man nun ein paar Passfiles, und im zweitletzten Passfile conceptNewPrice wird innerhalb des Knotens _changeTo aus dem untergeordneten Knoten _moneyValueUSD der Wert 94.34 der Kontextvariablen X("newPrice",3) zugewiesen. Die 3 steht für die dritte Ebene im Parse Tree (_sentence).

Das Passfile, das "94.34" als neuen Preis definiert und dieses Konzept dem Sentence-Knoten zuweist (Abb. 10)

Rollt man nun mit dem Maus-Cursor im Parse Tree über den Beispielsatz, öffnet sich das gelbe Fenster wie in Abbildung 11.

Der Wert "94.34" ist nun dem Beispielsatz mit der Variablen newPrice zugeordnet. Somit hat der String "94,34 Dollar" die gewĂĽnschte Bedeutung erhalten (Abb. 11).

NLP++ und die Multipass-Strategie von VisualText bilden ein mächtiges Team, um Texte zu analysieren. Die gezeigten Beispiele geben hier nur einen ersten und oberflächlichen Eindruck. Die Entwicklung eines Analyzers ist keine exakte Wissenschaft, sondern intuitiv und kreativ. Ein gutes Verständnis für NLP++ bekommen Interessierte beim Durcharbeiten der sechs Tutorien aus der VisualText-Hilfe. Fortgeschrittene können hier den
Corporate-Analyzer herunterladen. Und hier steht den Lesern der gezeigte Rohölnews-Analyzer zur Verfügung. Er wird weiter entwickelt und laufend dokumentiert.

Die VisualText IDE mit NLP++ lässt sich fĂĽr nichtkommerzielle Projekte umsonst nutzen. Funktional gibt es keine Einschränkungen gegenĂĽber einer kommerziellen Lizenz. Letzten Sommer wurde auĂźerdem die NLPCloud veröffentlicht. Es handelt sich um die Online-Version der VisualText IDE inklusive NLP++, die derzeit circa 80 Prozent der Funktionen der Desktop-Version bietet.

Dominik Holenstein
arbeitet als CRM- und Reporting-Analyst bei 3M in der Schweiz. In der Freizeit liest und programmiert er (NLP++, MATLAB, Julia, kdb+/q und VBA) und ist NLP++-Community-Manager auf freiwilliger Basis.

  1. Frank Puscher; Blechkollegen – Journalistische Texte mit Software erstellen; iX 8/2014, S. 36
  2. Roboter-Journalismus: Associated Press automatisiert Meldungen zu Geschäftsberichten; heise online, 01.07.2014
  3. Hans Holger Rath; Buchstabensuppe – Kundenkommentare verstehen und auswerten; iX 6/2014, S. 38

Die Definitionen wurden von Wikipedia entnommen ĂĽbersetzt, leicht modifiziert und gekĂĽrzt.

  • Information Extraction: Automatisierte Extrahierung strukturierter Informationen von unstrukturierten und halbstrukturierten Texten.
  • Information Retrieval: Abfrage einer gesuchten Information aus einer Sammlung von Text- oder Datenquellen.
  • Machine Learning: ein Teilgebiet der Informatik und Statistik. Es geht um den Aufbau und das Verstehen von Systemen, die von Daten lernen, ohne nur explizit programmierte Instruktionen zu nutzen.
  • Natural Language Processing: Schnittstelle zwischen Linguistik und Informatik. Mit NLP untersucht man, wie sich natĂĽrliche Sprache (geschrieben und gesprochen) mit Algorithmen verarbeiten lässt.
  • POS: In der Grammatik ist eine Wortart (Part of Speech) eine linguistische Kategorie von lexikalischen Einheiten, die durch syntaktische oder morphologische Regeln definiert werden. Wortarten sind zum Beispiel Nomen, Adjektiv und Adverb.
  • Sentiment Analysis, auch Opinion Mining genannt: Durch die Anwendung von Natural Language Processing, Textanalyse und Computerlinguistik werden subjektive Informationen aus dem Quellmaterial (z. B. Twitter, Facebook und CRM-Systemen) identifiziert und extrahiert.
  • Morphologie: Struktur der Wörter.
  • Syntax: ein Teilgebiet der Grammatik natĂĽrlicher Sprachen, das den Aufbau von Sätzen aus elementaren Bausteinen (Wörtern bzw. Wortgruppen) behandelt, auch Satzlehre genannt.
  • Semantik: Bedeutung und Wissen.
  • Text Analysis und Text Analytics: Oft werden beide Begriffe synonym verwendet, was nicht ganz korrekt ist. Beides sind Teilgebiete des Text Mining. Eine Analyse untersucht die Struktur, die Zusammensetzung, den Aufbau, die Bedeutung und liefert Erkenntnisse und Zusammenhänge. Die Analyse ist heuristisch, informal und qualitativ. Die Analytik dagegen ist algorithmisch und wendet systematisch numerische und statistische Methoden an, die quantitative Informationen in Form von Indikatoren, Tabellen oder Visualisierungen liefern. Die Analytik ist formal und reproduzierbar. (Quelle: Seth Grimes; Naming & Classifying: Text Analysis Vs. Text Analytics)

(ane)