So erstellen Sie eine Anwendung zur Verarbeitung natürlicher Sprache
Veröffentlicht: 2022-03-11Natural Language Processing – eine Technologie, die es Softwareanwendungen ermöglicht, menschliche Sprache zu verarbeiten – ist in den letzten Jahren ziemlich allgegenwärtig geworden.
Die Google-Suche ist zunehmend in der Lage, natürlich klingende Fragen zu beantworten, Apples Siri ist in der Lage, eine Vielzahl von Fragen zu verstehen, und immer mehr Unternehmen verwenden (halbwegs) intelligente Chat- und Telefon-Bots, um mit Kunden zu kommunizieren. Aber wie funktioniert diese scheinbar „intelligente“ Software wirklich?
In diesem Artikel erfahren Sie mehr über die Technologie, die diese Anwendungen zum Laufen bringt, und Sie erfahren, wie Sie Ihre eigene Software zur Verarbeitung natürlicher Sprache entwickeln können.
Der Artikel führt Sie durch den beispielhaften Prozess zum Erstellen eines Nachrichtenrelevanz-Analysators. Stellen Sie sich vor, Sie haben ein Aktienportfolio und möchten, dass eine App automatisch beliebte Nachrichten-Websites durchsucht und Artikel identifiziert, die für Ihr Portfolio relevant sind. Wenn Ihr Aktienportfolio beispielsweise Unternehmen wie Microsoft, BlackStone und Luxottica umfasst, möchten Sie Artikel sehen, die diese drei Unternehmen erwähnen.
Erste Schritte mit der Stanford NLP Library
Apps zur Verarbeitung natürlicher Sprache basieren wie alle anderen Apps für maschinelles Lernen auf einer Reihe relativ kleiner, einfacher, intuitiver Algorithmen, die zusammenarbeiten. Oft ist es sinnvoll, eine externe Bibliothek zu verwenden, in der alle diese Algorithmen bereits implementiert und integriert sind.
Für unser Beispiel verwenden wir die Stanford NLP-Bibliothek, eine leistungsstarke Java-basierte Bibliothek zur Verarbeitung natürlicher Sprache, die viele Sprachen unterstützt.
Ein bestimmter Algorithmus aus dieser Bibliothek, an dem wir interessiert sind, ist der Part-of-Speech (POS)-Tagger. Ein POS-Tagger wird verwendet, um jedem Wort in einem Text automatisch Wortarten zuzuordnen. Dieser POS-Tagger klassifiziert Wörter im Text basierend auf lexikalischen Merkmalen und analysiert sie in Bezug auf andere Wörter in ihrer Umgebung.
Die genaue Funktionsweise des POS-Tagger-Algorithmus würde den Rahmen dieses Artikels sprengen, aber Sie können hier mehr darüber erfahren.
Zunächst erstellen wir ein neues Java-Projekt (Sie können Ihre bevorzugte IDE verwenden) und fügen die Stanford NLP-Bibliothek zur Liste der Abhängigkeiten hinzu. Wenn Sie Maven verwenden, fügen Sie es einfach Ihrer pom.xml
-Datei hinzu:
<dependency> <groupId>edu.stanford.nlp</groupId> <artifactId>stanford-corenlp</artifactId> <version>3.6.0</version> </dependency> <dependency> <groupId>edu.stanford.nlp</groupId> <artifactId>stanford-corenlp</artifactId> <version>3.6.0</version> <classifier>models</classifier> </dependency>
Da die App den Inhalt eines Artikels automatisch von einer Webseite extrahieren muss, müssen Sie auch die folgenden zwei Abhängigkeiten angeben:
<dependency> <groupId>de.l3s.boilerpipe</groupId> <artifactId>boilerpipe</artifactId> <version>1.1.0</version> </dependency>
<dependency> <groupId>net.sourceforge.nekohtml</groupId> <artifactId>nekohtml</artifactId> <version>1.9.22</version> </dependency>
Wenn diese Abhängigkeiten hinzugefügt sind, können Sie fortfahren.
Kratz- und Reinigungsartikel
Der erste Teil unseres Analysators umfasst das Abrufen von Artikeln und das Extrahieren ihres Inhalts von Webseiten.
Beim Abrufen von Artikeln aus Nachrichtenquellen sind die Seiten normalerweise mit irrelevanten Informationen (eingebettete Videos, ausgehende Links, Videos, Anzeigen usw.) übersät, die für den Artikel selbst irrelevant sind. Hier kommt Boilerpipe ins Spiel.
Boilerpipe ist ein äußerst robuster und effizienter Algorithmus zum Entfernen von „Unordnung“, der den Hauptinhalt eines Nachrichtenartikels identifiziert, indem verschiedene Inhaltsblöcke anhand von Merkmalen wie der Länge eines durchschnittlichen Satzes, Arten von Tags, die in Inhaltsblöcken verwendet werden, und der Dichte von Links analysiert werden. Der Boilerpipe-Algorithmus hat sich als konkurrenzfähig gegenüber anderen, viel rechenintensiveren Algorithmen erwiesen, beispielsweise solchen, die auf maschinellem Sehen basieren. Auf der Projektseite können Sie mehr erfahren.
Die Boilerpipe-Bibliothek verfügt über eine integrierte Unterstützung zum Scrapen von Webseiten. Es kann HTML aus dem Web abrufen, Text aus HTML extrahieren und den extrahierten Text bereinigen. Sie können eine Funktion, extractFromURL
, definieren, die eine URL nimmt und Boilerpipe verwendet, um den relevantesten Text als Zeichenfolge zurückzugeben, indem ArticleExtractor
für diese Aufgabe verwenden:
import java.net.URL; import de.l3s.boilerpipe.document.TextDocument; import de.l3s.boilerpipe.extractors.CommonExtractors; import de.l3s.boilerpipe.sax.BoilerpipeSAXInput; import de.l3s.boilerpipe.sax.HTMLDocument; import de.l3s.boilerpipe.sax.HTMLFetcher; public class BoilerPipeExtractor { public static String extractFromUrl(String userUrl) throws java.io.IOException, org.xml.sax.SAXException, de.l3s.boilerpipe.BoilerpipeProcessingException { final HTMLDocument htmlDoc = HTMLFetcher.fetch(new URL(userUrl)); final TextDocument doc = new BoilerpipeSAXInput(htmlDoc.toInputSource()).getTextDocument(); return CommonExtractors.ARTICLE_EXTRACTOR.getText(doc); } }
Die Boilerpipe-Bibliothek bietet verschiedene Extraktoren auf Basis des Boilerpipe-Algorithmus, wobei ArticleExtractor
speziell für HTML-formatierte Nachrichtenartikel optimiert ist. ArticleExtractor
konzentriert sich speziell auf HTML-Tags, die in jedem Inhaltsblock und der ausgehenden Linkdichte verwendet werden. Dies ist für unsere Aufgabe besser geeignet als der schnellere, aber einfachere DefaultExtractor
.
Die eingebauten Funktionen erledigen alles für uns:
-
HTMLFetcher.fetch
das HTML-Dokument ab -
getTextDocument
extrahiert das Textdokument -
CommonExtractors.ARTICLE_EXTRACTOR.getText
extrahiert mithilfe des Boilerpipe-Algorithmus den relevanten Text aus dem Artikel
Jetzt können Sie es mit einem Beispielartikel zu den Fusionen der Optikgiganten Essilor und Luxottica ausprobieren, den Sie hier finden. Sie können diese URL der Funktion zuführen und sehen, was dabei herauskommt.
Fügen Sie Ihrer Hauptfunktion den folgenden Code hinzu:
public class App { public static void main( String[] args ) throws java.io.IOException, org.xml.sax.SAXException, de.l3s.boilerpipe.BoilerpipeProcessingException { String urlString = "http://www.reuters.com/article/us-essilor-ma-luxottica-group-idUSKBN14Z110"; String text = BoilerPipeExtractor.extractFromUrl(urlString); System.out.println(text); } }
Sie sollten in Ihrer Ausgabe im Hauptteil des Artikels ohne Anzeigen, HTML-Tags und ausgehende Links sehen. Hier ist der Anfangsausschnitt von dem, was ich bekam, als ich dies ausführte:
MILAN/PARIS Italy's Luxottica (LUX.MI) and France's Essilor (ESSI.PA) have agreed a 46 billion euro ($49 billion) merger to create a global eyewear powerhouse with annual revenue of more than 15 billion euros. The all-share deal is one of Europe's largest cross-border tie-ups and brings together Luxottica, the world's top spectacles maker with brands such as Ray-Ban and Oakley, with leading lens manufacturer Essilor. "Finally ... two products which are naturally complementary -- namely frames and lenses -- will be designed, manufactured and distributed under the same roof," Luxottica's 81-year-old founder Leonardo Del Vecchio said in a statement on Monday. Shares in Luxottica were up by 8.6 percent at 53.80 euros by 1405 GMT (9:05 am ET), with Essilor up 12.2 percent at 114.60 euros. The merger between the top players in the 95 billion eyewear market is aimed at helping the businesses to take full advantage of expected strong demand for prescription spectacles and sunglasses due to an aging global population and increasing awareness about eye care. Jefferies analysts estimate that the market is growing at between...
Und das ist tatsächlich der Hauptartikelkörper des Artikels. Schwer vorstellbar, dass dies viel einfacher zu implementieren ist.
Wortarten markieren
Nachdem Sie den Haupttext des Artikels erfolgreich extrahiert haben, können Sie daran arbeiten, festzustellen, ob der Artikel Unternehmen erwähnt, die für den Benutzer von Interesse sind.
Sie könnten versucht sein, einfach eine Suche nach Zeichenfolgen oder regulären Ausdrücken durchzuführen, aber dieser Ansatz hat mehrere Nachteile.
Zunächst einmal kann eine Zeichenfolgensuche anfällig für Fehlalarme sein. Ein Artikel, in dem Microsoft Excel erwähnt wird, kann beispielsweise mit der Erwähnung von Microsoft gekennzeichnet sein.
Zweitens kann eine Suche nach regulären Ausdrücken je nach Konstruktion des regulären Ausdrucks zu falsch negativen Ergebnissen führen. Beispielsweise kann ein Artikel, der den Ausdruck „Luxotticas Quartalsgewinne übertrafen die Erwartungen“ enthält, von einer Suche mit regulären Ausdrücken übersehen werden, die nach „Luxottica“ sucht, das von Leerzeichen umgeben ist.
Wenn Sie schließlich an einer großen Anzahl von Unternehmen interessiert sind und eine große Anzahl von Artikeln bearbeiten, kann sich das Durchsuchen des gesamten Textkörpers für jedes Unternehmen im Portfolio des Benutzers als äußerst zeitaufwändig erweisen und zu einer inakzeptablen Leistung führen.
Die CoreNLP-Bibliothek von Stanford verfügt über viele leistungsstarke Funktionen und bietet eine Möglichkeit, alle drei dieser Probleme zu lösen.
Für unseren Analysator verwenden wir den Parts-of-Speech (POS)-Tagger. Insbesondere können wir mit dem POS-Tagger alle Eigennamen im Artikel finden und mit unserem Portfolio an interessanten Aktien vergleichen.

Durch die Integration der NLP-Technologie verbessern wir nicht nur die Genauigkeit unseres Taggers und minimieren die oben erwähnten falsch positiven und negativen Ergebnisse, sondern wir minimieren auch die Textmenge, die wir für den Vergleich mit unserem Aktienportfolio benötigen, drastisch, da Eigennamen nur eine kleine Teilmenge ausmachen des vollständigen Artikeltextes.
Durch die Vorverarbeitung unseres Portfolios in eine Datenstruktur mit geringen Kosten für Mitgliederabfragen können wir die für die Analyse eines Artikels benötigte Zeit drastisch reduzieren.
Stanford CoreNLP bietet einen sehr praktischen Tagger namens MaxentTagger, der POS-Tagging in nur wenigen Codezeilen bereitstellen kann.
Hier ist eine einfache Implementierung:
public class PortfolioNewsAnalyzer { private HashSet<String> portfolio; private static final String modelPath = "edu\\stanford\\nlp\\models\\pos-tagger\\english-left3words\\english-left3words-distsim.tagger"; private MaxentTagger tagger; public PortfolioNewsAnalyzer() { tagger = new MaxentTagger(modelPath); } public String tagPos(String input) { return tagger.tagString(input); }
Die Tagger-Funktion tagPos
nimmt eine Zeichenfolge als Eingabe und gibt eine Zeichenfolge aus, die die Wörter der ursprünglichen Zeichenfolge zusammen mit der entsprechenden Wortart enthält. Instanziieren Sie in Ihrer Hauptfunktion einen PortfolioNewsAnalyzer
und speisen Sie die Ausgabe des Scrapers in die Tagger-Funktion ein, und Sie sollten so etwas sehen:
MILAN/PARIS_NN Italy_NNP 's_POS Luxottica_NNP -LRB-_-LRB- LUX.MI_NNP -RRB-_-RRB- and_CC France_NNP 's_POS Essilor_NNP -LRB-_-LRB- ESSI.PA_NNP -RRB-_-RRB- have_VBP agreed_VBN a_DT 46_CD billion_CD euro_NN -LRB-_-LRB- $_$ 49_CD billion_CD -RRB-_-RRB- merger_NN to_TO create_VB a_DT global_JJ eyewear_NN powerhouse_NN with_IN annual_JJ revenue_NN of_IN more_JJR than_IN 15_CD billion_CD euros_NNS ._. The_DT all-share_JJ deal_NN is_VBZ one_CD of_IN Europe_NNP 's_POS largest_JJS cross-border_JJ tie-ups_NNS and_CC brings_VBZ together_RB Luxottica_NNP ,_, the_DT world_NN 's_POS top_JJ spectacles_NNS maker_NN with_IN brands_NNS such_JJ as_IN Ray-Ban_NNP and_CC Oakley_NNP ,_, with_IN leading_VBG lens_NN manufacturer_NN Essilor_NNP ._. ``_`` Finally_RB ..._: two_CD products_NNS which_WDT are_VBP naturally_RB complementary_JJ --_: namely_RB frames_NNS and_CC lenses_NNS --_: will_MD be_VB designed_VBN ,_, manufactured_VBN and_CC distributed_VBN under_IN the_DT same_JJ roof_NN ,_, ''_'' Luxottica_NNP 's_POS 81-year-old_JJ founder_NN Leonardo_NNP Del_NNP Vecchio_NNP said_VBD in_IN a_DT statement_NN on_IN Monday_NNP ._. Shares_NNS in_IN Luxottica_NNP were_VBD up_RB by_IN 8.6_CD percent_NN at_IN 53.80_CD euros_NNS by_IN 1405_CD GMT_NNP -LRB-_-LRB- 9:05_CD am_NN ET_NNP -RRB-_-RRB- ,_, with_IN Essilor_NNP up_IN 12.2_CD percent_NN at_IN 114.60_CD euros_NNS ._. The_DT merger_NN between_IN the_DT top_JJ players_NNS in_IN the_DT 95_CD billion_CD eyewear_NN market_NN is_VBZ aimed_VBN at_IN helping_VBG the_DT businesses_NNS to_TO take_VB full_JJ advantage_NN of_IN expected_VBN strong_JJ demand_NN for_IN prescription_NN spectacles_NNS and_CC sunglasses_NNS due_JJ to_TO an_DT aging_NN global_JJ population_NN and_CC increasing_VBG awareness_NN about_IN...
Verarbeiten der getaggten Ausgabe in einen Satz
Bisher haben wir Funktionen zum Herunterladen, Bereinigen und Markieren eines Nachrichtenartikels entwickelt. Wir müssen jedoch noch feststellen, ob der Artikel eines der für den Benutzer interessanten Unternehmen erwähnt.
Dazu müssen wir alle Eigennamen sammeln und prüfen, ob Aktien aus unserem Portfolio in diesen Eigennamen enthalten sind.
Um alle Eigennamen zu finden, wollen wir zuerst die getaggte Zeichenfolgenausgabe in Token aufteilen (mit Leerzeichen als Trennzeichen), dann jedes der Token auf dem Unterstrich ( _
) aufteilen und prüfen, ob die Wortart ein Eigenname ist .
Sobald wir alle Eigennamen haben, möchten wir sie in einer Datenstruktur speichern, die für unseren Zweck besser optimiert ist. Für unser Beispiel verwenden wir ein HashSet
. Als Gegenleistung dafür, dass doppelte Einträge nicht zugelassen werden und die Reihenfolge der Einträge nicht verfolgt wird, ermöglicht HashSet
sehr schnelle Mitgliedschaftsabfragen. Da wir nur daran interessiert sind, die Mitgliedschaft abzufragen, ist das HashSet
perfekt für unsere Zwecke.
Unten ist die Funktion, die das Aufteilen und Speichern von Eigennamen implementiert. Platzieren Sie diese Funktion in Ihrer PortfolioNewsAnalyzer
-Klasse:
public static HashSet<String> extractProperNouns(String taggedOutput) { HashSet<String> propNounSet = new HashSet<String>(); String[] split = taggedOutput.split(" "); for (String token: split ){ String[] splitTokens = token.split("_"); if(splitTokesn[1].equals("NNP")){ propNounSet.add(splitTokens[0]); } } return propNounSet; }
Es gibt jedoch ein Problem mit dieser Implementierung. Wenn der Name eines Unternehmens aus mehreren Wörtern besteht (z. B. Carl Zeiss im Luxottica-Beispiel), kann diese Implementierung ihn nicht erfassen. Im Beispiel von Carl Zeiss werden „Carl“ und „Zeiss“ separat in den Satz eingefügt und enthalten daher niemals die einzelne Zeichenkette „Carl Zeiss“.
Um dieses Problem zu lösen, können wir alle aufeinanderfolgenden Eigennamen sammeln und mit Leerzeichen verbinden. Hier ist die aktualisierte Implementierung, die dies erreicht:
public static HashSet<String> extractProperNouns(String taggedOutput) { HashSet<String> propNounSet = new HashSet<String>(); String[] split = taggedOutput.split(" "); List<String> propNounList = new ArrayList<String>(); for (String token: split ){ String[] splitTokens = token.split("_"); if(splitTokens[1].equals("NNP")){ propNounList.add(splitTokens[0]); } else { if (!propNounList.isEmpty()) { propNounSet.add(StringUtils.join(propNounList, " ")); propNounList.clear(); } } } if (!propNounList.isEmpty()) { propNounSet.add(StringUtils.join(propNounList, " ")); propNounList.clear(); } return propNounSet; }
Nun soll die Funktion eine Menge mit den einzelnen Eigennamen und den aufeinanderfolgenden Eigennamen (dh durch Leerzeichen verbunden) zurückgeben. Wenn Sie das propNounSet
drucken, sollten Sie etwa Folgendes sehen:
[... Monday, Gianluca Semeraro, David Goodman, Delfin, North America, Luxottica, Latin America, Rossi/File Photo, Rome, Safilo Group, SFLG.MI, Friday, Valentina Za, Del Vecchio, CEO Hubert Sagnieres, Oakley, Sagnieres, Jefferies, Ray Ban, ...]
Vergleich des Portfolios mit dem PropNouns-Set
Wir sind fast fertig!
In den vorherigen Abschnitten haben wir einen Scraper gebaut, der den Hauptteil eines Artikels herunterladen und extrahieren kann, einen Tagger, der den Hauptteil des Artikels parsen und Eigennamen identifizieren kann, und einen Prozessor, der die gekennzeichnete Ausgabe nimmt und die Eigennamen in einem HashSet
sammelt. Jetzt müssen wir nur noch das HashSet
nehmen und es mit der Liste der Unternehmen vergleichen, die uns interessieren.
Die Implementierung ist sehr einfach. Fügen Sie Ihrer PortfolioNewsAnalyzer
-Klasse den folgenden Code hinzu:
private HashSet<String> portfolio; public PortfolioNewsAnalyzer() { portfolio = new HashSet<String>(); } public void addPortfolioCompany(String company) { portfolio.add(company); } public boolean arePortfolioCompaniesMentioned(HashSet<String> articleProperNouns){ return !Collections.disjoint(articleProperNouns, portfolio); }
Alles zusammenfügen
Jetzt können wir die gesamte Anwendung ausführen – das Scraping, Säubern, Markieren, Sammeln und Vergleichen. Hier ist die Funktion, die sich durch die gesamte Anwendung zieht. Fügen Sie diese Funktion zu Ihrer PortfolioNewsAnalyzer
-Klasse hinzu:
public boolean analyzeArticle(String urlString) throws IOException, SAXException, BoilerpipeProcessingException { String articleText = extractFromUrl(urlString); String tagged = tagPos(articleText); HashSet<String> properNounsSet = extractProperNouns(tagged); return arePortfolioCompaniesMentioned(properNounsSet); }
Endlich können wir die App nutzen!
Hier ist ein Beispiel mit demselben Artikel wie oben und Luxottica als Portfoliounternehmen:
public static void main( String[] args ) throws IOException, SAXException, BoilerpipeProcessingException { PortfolioNewsAnalyzer analyzer = new PortfolioNewsAnalyzer(); analyzer.addPortfolioCompany("Luxottica"); boolean mentioned = analyzer.analyzeArticle("http://www.reuters.com/article/us-essilor-ma-luxottica-group-idUSKBN14Z110"); if (mentioned) { System.out.println("Article mentions portfolio companies"); } else { System.out.println("Article does not mention portfolio companies"); } }
Führen Sie dies aus, und die App sollte „Artikel erwähnt Portfoliounternehmen“ drucken.
Ändern Sie das Portfoliounternehmen von Luxottica in ein Unternehmen, das im Artikel nicht erwähnt wird (z. B. „Microsoft“), und die App sollte „Artikel erwähnt keine Portfoliounternehmen“ drucken.
Das Erstellen einer NLP-App muss nicht schwer sein
In diesem Artikel haben wir den Prozess zum Erstellen einer Anwendung durchlaufen, die einen Artikel von einer URL herunterlädt, ihn mit Boilerpipe bereinigt, mit Stanford NLP verarbeitet und prüft, ob der Artikel bestimmte interessante Referenzen enthält (in unserem Fall Unternehmen in unserem Portfolio). Wie gezeigt, macht die Nutzung dieser Reihe von Technologien eine ansonsten entmutigende Aufgabe zu einer relativ unkomplizierten Aufgabe.
Ich hoffe, dieser Artikel hat Ihnen nützliche Konzepte und Techniken der Verarbeitung natürlicher Sprache vorgestellt und Sie dazu inspiriert, eigene Anwendungen für natürliche Sprache zu schreiben.
[Hinweis: Eine Kopie des Codes, auf den in diesem Artikel verwiesen wird, finden Sie hier.]