Come creare un'app per l'elaborazione del linguaggio naturale

Pubblicato: 2022-03-11

L'elaborazione del linguaggio naturale, una tecnologia che consente alle applicazioni software di elaborare il linguaggio umano, è diventata abbastanza onnipresente negli ultimi anni.

La ricerca di Google è sempre più in grado di rispondere a domande dal suono naturale, Siri di Apple è in grado di comprendere un'ampia varietà di domande e sempre più aziende utilizzano (ragionevolmente) chat intelligenti e robot telefonici per comunicare con i clienti. Ma come funziona davvero questo software apparentemente “intelligente”?

In questo articolo, imparerai a conoscere la tecnologia che fa funzionare queste applicazioni e imparerai come sviluppare il tuo software di elaborazione del linguaggio naturale.

L'articolo ti guiderà attraverso il processo di esempio di creazione di un analizzatore di rilevanza delle notizie. Immagina di avere un portafoglio azionario e desideri che un'app esegua automaticamente la scansione attraverso i siti Web di notizie popolari e identifichi gli articoli rilevanti per il tuo portafoglio. Ad esempio, se il tuo portafoglio azionario include società come Microsoft, BlackStone e Luxottica, vorresti vedere articoli che menzionano queste tre società.

Introduzione alla libreria NLP di Stanford

Le app di elaborazione del linguaggio naturale, come qualsiasi altra app di apprendimento automatico, sono basate su una serie di algoritmi relativamente piccoli, semplici e intuitivi che funzionano in tandem. Spesso ha senso utilizzare una libreria esterna in cui tutti questi algoritmi sono già implementati e integrati.

Per il nostro esempio, utilizzeremo la libreria Stanford NLP, una potente libreria di elaborazione del linguaggio naturale basata su Java che viene fornita con il supporto per molti linguaggi.

Un particolare algoritmo di questa libreria che ci interessa è il tagger part-of-speech (POS). Un tagger POS viene utilizzato per assegnare automaticamente parti del discorso a ogni parola in un pezzo di testo. Questo tagger POS classifica le parole nel testo in base a caratteristiche lessicali e le analizza in relazione ad altre parole che le circondano.

La meccanica esatta dell'algoritmo tagger POS va oltre lo scopo di questo articolo, ma puoi saperne di più qui.

Per iniziare, creeremo un nuovo progetto Java (puoi usare il tuo IDE preferito) e aggiungeremo la libreria Stanford NLP all'elenco delle dipendenze. Se stai usando Maven, aggiungilo semplicemente al tuo file pom.xml :

 <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>

Poiché l'app dovrà estrarre automaticamente il contenuto di un articolo da una pagina Web, dovrai specificare anche le due dipendenze seguenti:

 <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>

Con queste dipendenze aggiunte, sei pronto per andare avanti.

Articoli per la raschiatura e la pulizia

La prima parte del nostro analizzatore riguarderà il recupero degli articoli e l'estrazione del loro contenuto dalle pagine web.

Quando si recuperano articoli da fonti di notizie, le pagine sono generalmente piene di informazioni estranee (video incorporati, collegamenti in uscita, video, pubblicità, ecc.) Che sono irrilevanti per l'articolo stesso. È qui che entra in gioco Boilerpipe.

Boilerpipe è un algoritmo estremamente robusto ed efficiente per rimuovere il "clutter" che identifica il contenuto principale di un articolo di notizie analizzando diversi blocchi di contenuto utilizzando caratteristiche come la lunghezza di una frase media, i tipi di tag utilizzati nei blocchi di contenuto e la densità dei collegamenti. L'algoritmo boilerpipe ha dimostrato di essere competitivo con altri algoritmi molto più costosi dal punto di vista computazionale, come quelli basati sulla visione artificiale. Puoi saperne di più sul sito del progetto.

La libreria Boilerpipe viene fornita con il supporto integrato per lo scraping delle pagine Web. Può recuperare l'HTML dal Web, estrarre il testo dall'HTML e pulire il testo estratto. Puoi definire una funzione, extractFromURL , che prenderà un URL e utilizzerà Boilerpipe per restituire il testo più rilevante come stringa utilizzando ArticleExtractor per questa attività:

 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); } }

La libreria Boilerpipe fornisce diversi estrattori basati sull'algoritmo boilerpipe, con ArticleExtractor specificamente ottimizzato per articoli di notizie in formato HTML. ArticleExtractor si concentra specificamente sui tag HTML utilizzati in ogni blocco di contenuto e sulla densità dei link in uscita. Questo è più adatto al nostro compito rispetto al più veloce ma più semplice DefaultExtractor .

Le funzioni integrate si prendono cura di tutto per noi:

  • HTMLFetcher.fetch ottiene il documento HTML
  • getTextDocument estrae il documento di testo
  • CommonExtractors.ARTICLE_EXTRACTOR.getText estrae il testo rilevante dall'articolo utilizzando l'algoritmo boilerpipe

Ora puoi provarlo con un articolo di esempio sulle fusioni dei giganti dell'ottica Essilor e Luxottica, che puoi trovare qui. Puoi inviare questo URL alla funzione e vedere cosa esce.

Aggiungi il seguente codice alla tua funzione principale:

 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); } }

Dovresti vedere nel tuo output nel corpo principale dell'articolo, senza annunci, tag HTML e link in uscita. Ecco lo snippet iniziale da quello che ho ottenuto quando ho eseguito questo:

 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...

E questo è davvero il corpo principale dell'articolo. Difficile immaginare che questo sia molto più semplice da implementare.

Tagging di parti del discorso

Ora che hai estratto con successo il corpo dell'articolo principale, puoi lavorare per determinare se l'articolo menziona aziende di interesse per l'utente.

Potresti essere tentato di eseguire semplicemente una ricerca di una stringa o di un'espressione regolare, ma questo approccio presenta diversi svantaggi.

Prima di tutto, una ricerca di stringhe può essere soggetta a falsi positivi. Ad esempio, un articolo che menziona Microsoft Excel può essere contrassegnato come menzionato da Microsoft.

In secondo luogo, a seconda della costruzione dell'espressione regolare, una ricerca di un'espressione regolare può portare a falsi negativi. Ad esempio, un articolo che contiene la frase "Gli utili trimestrali di Luxottica hanno superato le aspettative" potrebbe non essere rilevato da una ricerca di espressioni regolari che cerca "Luxottica" circondato da spazi bianchi.

Infine, se sei interessato a un gran numero di aziende e stai elaborando un gran numero di articoli, la ricerca nell'intero corpo del testo per ogni azienda nel portafoglio dell'utente può rivelarsi estremamente dispendiosa in termini di tempo, con prestazioni inaccettabili.

La libreria CoreNLP di Stanford ha molte potenti funzionalità e fornisce un modo per risolvere tutti e tre questi problemi.

Per il nostro analizzatore, utilizzeremo il tagger Parts-of-Speech (POS). In particolare, possiamo utilizzare il tagger POS per trovare tutti i nomi propri nell'articolo e confrontarli con il nostro portafoglio di titoli interessanti.

Incorporando la tecnologia NLP, non solo miglioriamo l'accuratezza del nostro tagger e riduciamo al minimo i falsi positivi e negativi menzionati sopra, ma minimizziamo anche drasticamente la quantità di testo che dobbiamo confrontare con il nostro portafoglio di azioni, poiché i nomi propri comprendono solo un piccolo sottoinsieme del testo integrale dell'articolo.

Pre-elaborando il nostro portfolio in una struttura di dati con un basso costo di query di appartenenza, possiamo ridurre drasticamente il tempo necessario per analizzare un articolo.

Stanford CoreNLP fornisce un tagger molto conveniente chiamato MaxentTagger che può fornire POS Tagging in poche righe di codice.

Ecco una semplice implementazione:

 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); }

La funzione tagger, tagPos , prende una stringa come input e restituisce una stringa che contiene le parole nella stringa originale insieme alla parte corrispondente del discorso. Nella tua funzione principale, istanzia un PortfolioNewsAnalyzer e inserisci l'output dello scraper nella funzione tagger e dovresti vedere qualcosa del genere:

 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...

Elaborazione dell'output con tag in un set

Finora, abbiamo creato funzioni per scaricare, pulire e taggare un articolo di notizie. Ma dobbiamo ancora determinare se l'articolo menziona una delle società di interesse per l'utente.

Per fare ciò, dobbiamo raccogliere tutti i nomi propri e verificare se le azioni del nostro portafoglio sono incluse in quei nomi propri.

Per trovare tutti i nomi propri, vorremo prima dividere l'output della stringa contrassegnata in token (usando gli spazi come delimitatori), quindi dividere ciascuno dei token sul carattere di sottolineatura ( _ ) e controllare se la parte del discorso è un nome proprio .

Una volta che abbiamo tutti i nomi propri, vorremo memorizzarli in una struttura di dati che è meglio ottimizzata per il nostro scopo. Per il nostro esempio, useremo un HashSet . In cambio del non consentire voci duplicate e non tenere traccia dell'ordine delle voci, HashSet consente query di appartenenza molto veloci. Poiché siamo interessati solo a richiedere l'adesione, HashSet è perfetto per i nostri scopi.

Di seguito è riportata la funzione che implementa la divisione e la memorizzazione dei nomi propri. Inserisci questa funzione nella tua classe PortfolioNewsAnalyzer :

 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; }

Tuttavia, c'è un problema con questa implementazione. Se il nome di un'azienda è composto da più parole (ad esempio, Carl Zeiss nell'esempio Luxottica), questa implementazione non sarà in grado di catturarlo. Nell'esempio di Carl Zeiss, "Carl" e "Zeiss" verranno inseriti nel set separatamente, e quindi non conterranno mai la singola stringa "Carl Zeiss".

Per risolvere questo problema, possiamo raccogliere tutti i nomi propri consecutivi e unirli con spazi. Ecco l'implementazione aggiornata che realizza questo:

 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; }

Ora la funzione dovrebbe restituire un insieme con i nomi propri individuali ei nomi propri consecutivi (cioè uniti da spazi). Se stampi propNounSet , dovresti vedere qualcosa di simile al seguente:

 [... 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, ...]

Confrontando il Portfolio con il PropNouns Set

Abbiamo quasi finito!

Nelle sezioni precedenti, abbiamo creato uno scraper in grado di scaricare ed estrarre il corpo di un articolo, un tagger in grado di analizzare il corpo dell'articolo e identificare i nomi propri e un processore che prende l'output con tag e raccoglie i nomi propri in un HashSet . Ora non resta che prendere l' HashSet e confrontarlo con l'elenco delle aziende che ci interessano.

L'implementazione è molto semplice. Aggiungi il seguente codice nella tua classe PortfolioNewsAnalyzer :

 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); }

Mettere tutto insieme

Ora possiamo eseguire l'intera applicazione: raschiatura, pulizia, etichettatura, raccolta e confronto. Ecco la funzione che attraversa l'intera applicazione. Aggiungi questa funzione alla tua classe PortfolioNewsAnalyzer :

 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); }

Finalmente possiamo usare l'app!

Ecco un esempio che utilizza lo stesso articolo di cui sopra e Luxottica come società in portafoglio:

 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"); } }

Esegui questo e l'app dovrebbe stampare "L'articolo menziona le società in portafoglio".

Cambia la società in portafoglio da Luxottica a una società non menzionata nell'articolo (come "Microsoft") e l'app dovrebbe stampare "L'articolo non menziona le società in portafoglio".

La creazione di un'app NLP non deve essere difficile

In questo articolo, abbiamo esaminato il processo di creazione di un'applicazione che scarica un articolo da un URL, lo pulisce utilizzando Boilerpipe, lo elabora utilizzando Stanford NLP e controlla se l'articolo contiene riferimenti specifici di interesse (nel nostro caso, le aziende nel nostro portafoglio). Come dimostrato, sfruttare questa gamma di tecnologie rende quello che altrimenti sarebbe un compito arduo in un compito relativamente semplice.

Spero che questo articolo ti abbia introdotto a concetti e tecniche utili nell'elaborazione del linguaggio naturale e che ti abbia ispirato a scrivere le tue applicazioni in linguaggio naturale.

[Nota: puoi trovare una copia del codice a cui si fa riferimento in questo articolo qui.]