Comment créer une application de traitement du langage naturel
Publié: 2022-03-11Le traitement du langage naturel, une technologie qui permet aux applications logicielles de traiter le langage humain, est devenu assez omniprésent au cours des dernières années.
La recherche Google est de plus en plus capable de répondre à des questions qui semblent naturelles, Siri d'Apple est capable de comprendre une grande variété de questions, et de plus en plus d'entreprises utilisent (raisonnablement) des robots de chat et de téléphone intelligents pour communiquer avec les clients. Mais comment fonctionne vraiment ce logiciel apparemment «intelligent»?
Dans cet article, vous découvrirez la technologie qui fait fonctionner ces applications et vous apprendrez à développer votre propre logiciel de traitement du langage naturel.
L'article vous guidera à travers l'exemple de processus de création d'un analyseur de pertinence des actualités. Imaginez que vous ayez un portefeuille d'actions et que vous souhaitiez qu'une application parcoure automatiquement les sites Web d'actualités populaires et identifie les articles pertinents pour votre portefeuille. Par exemple, si votre portefeuille d'actions comprend des sociétés telles que Microsoft, BlackStone et Luxottica, vous souhaiterez voir des articles mentionnant ces trois sociétés.
Premiers pas avec la bibliothèque Stanford NLP
Les applications de traitement du langage naturel, comme toutes les autres applications d'apprentissage automatique, reposent sur un certain nombre d'algorithmes relativement petits, simples et intuitifs fonctionnant en tandem. Il est souvent judicieux d'utiliser une bibliothèque externe où tous ces algorithmes sont déjà implémentés et intégrés.
Pour notre exemple, nous utiliserons la bibliothèque Stanford NLP, une puissante bibliothèque de traitement du langage naturel basée sur Java qui prend en charge de nombreuses langues.
Un algorithme particulier de cette bibliothèque qui nous intéresse est le marqueur de partie du discours (POS). Un étiqueteur POS est utilisé pour attribuer automatiquement des parties du discours à chaque mot d'un morceau de texte. Ce marqueur POS classe les mots dans le texte en fonction de caractéristiques lexicales et les analyse par rapport aux autres mots qui les entourent.
La mécanique exacte de l'algorithme de tagger POS dépasse le cadre de cet article, mais vous pouvez en savoir plus ici.
Pour commencer, nous allons créer un nouveau projet Java (vous pouvez utiliser votre IDE préféré) et ajouter la bibliothèque Stanford NLP à la liste des dépendances. Si vous utilisez Maven, ajoutez-le simplement à votre fichier 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>
Étant donné que l'application devra extraire automatiquement le contenu d'un article d'une page Web, vous devrez également spécifier les deux dépendances suivantes :
<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>
Avec ces dépendances ajoutées, vous êtes prêt à aller de l'avant.
Articles de grattage et de nettoyage
La première partie de notre analyseur consistera à récupérer des articles et à extraire leur contenu des pages web.
Lors de la récupération d'articles à partir de sources d'actualités, les pages sont généralement truffées d'informations superflues (vidéos intégrées, liens sortants, vidéos, publicités, etc.) qui ne sont pas pertinentes pour l'article lui-même. C'est là que Boilerpipe entre en jeu.
Boilerpipe est un algorithme extrêmement robuste et efficace pour supprimer le « fouillis » qui identifie le contenu principal d'un article de presse en analysant différents blocs de contenu à l'aide de fonctionnalités telles que la longueur d'une phrase moyenne, les types de balises utilisées dans les blocs de contenu et la densité des liens. L'algorithme de chaudière s'est avéré compétitif avec d'autres algorithmes beaucoup plus coûteux en calcul, tels que ceux basés sur la vision artificielle. Vous pouvez en savoir plus sur son site de projet.
La bibliothèque Boilerpipe est livrée avec un support intégré pour le grattage des pages Web. Il peut récupérer le code HTML à partir du Web, extraire du texte à partir de HTML et nettoyer le texte extrait. Vous pouvez définir une fonction, extractFromURL
, qui prendra une URL et utilisera Boilerpipe pour renvoyer le texte le plus pertinent sous forme de chaîne en utilisant ArticleExtractor
pour cette tâche :
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 bibliothèque Boilerpipe fournit différents extracteurs basés sur l'algorithme Boilerpipe, ArticleExtractor
étant spécifiquement optimisé pour les articles de presse au format HTML. ArticleExtractor
se concentre spécifiquement sur les balises HTML utilisées dans chaque bloc de contenu et la densité des liens sortants. Ceci est mieux adapté à notre tâche que le DefaultExtractor
plus rapide mais plus simple.
Les fonctions intégrées s'occupent de tout pour nous :
-
HTMLFetcher.fetch
obtient le document HTML -
getTextDocument
extrait le document texte -
CommonExtractors.ARTICLE_EXTRACTOR.getText
extrait le texte pertinent de l'article à l'aide de l'algorithme de chaudière
Vous pouvez maintenant l'essayer avec un exemple d'article concernant les fusions des géants de l'optique Essilor et Luxottica, que vous pouvez trouver ici. Vous pouvez transmettre cette URL à la fonction et voir ce qui en ressort.
Ajoutez le code suivant à votre fonction 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); } }
Vous devriez voir dans votre sortie dans le corps principal de l'article, sans les publicités, les balises HTML et les liens sortants. Voici l'extrait de début de ce que j'ai obtenu lorsque j'ai exécuté ceci:
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...
Et c'est bien le corps de l'article principal de l'article. Difficile d'imaginer que cela soit beaucoup plus simple à mettre en œuvre.
Balisage des parties du discours
Maintenant que vous avez réussi à extraire le corps principal de l'article, vous pouvez déterminer si l'article mentionne des entreprises qui intéressent l'utilisateur.
Vous pourriez être tenté d'effectuer simplement une recherche de chaîne ou d'expression régulière, mais cette approche présente plusieurs inconvénients.
Tout d'abord, une recherche de chaîne peut être sujette à des faux positifs. Un article qui mentionne Microsoft Excel peut être marqué comme mentionnant Microsoft, par exemple.
Deuxièmement, selon la construction de l'expression régulière, une recherche d'expression régulière peut conduire à des faux négatifs. Par exemple, un article qui contient l'expression « les bénéfices trimestriels de Luxottica ont dépassé les attentes » peut être manqué par une recherche d'expression régulière qui recherche « Luxottica » entouré d'espaces blancs.
Enfin, si vous vous intéressez à un grand nombre d'entreprises et que vous traitez un grand nombre d'articles, la recherche dans tout le corps du texte pour chaque entreprise du portefeuille de l'utilisateur peut s'avérer extrêmement chronophage, avec des performances inacceptables.
La bibliothèque CoreNLP de Stanford possède de nombreuses fonctionnalités puissantes et offre un moyen de résoudre ces trois problèmes.
Pour notre analyseur, nous utiliserons le tagger Parts-of-Speech (POS). En particulier, nous pouvons utiliser le tagger POS pour trouver tous les noms propres dans l'article et les comparer à notre portefeuille d'actions intéressantes.

En incorporant la technologie NLP, nous améliorons non seulement la précision de notre tagger et minimisons les faux positifs et négatifs mentionnés ci-dessus, mais nous minimisons également considérablement la quantité de texte que nous devons comparer à notre portefeuille d'actions, car les noms propres ne comprennent qu'un petit sous-ensemble. du texte intégral de l'article.
En pré-traitant notre portefeuille dans une structure de données qui a un faible coût de requête d'adhésion, nous pouvons réduire considérablement le temps nécessaire pour analyser un article.
Stanford CoreNLP fournit un étiqueteur très pratique appelé MaxentTagger qui peut fournir un étiquetage POS en seulement quelques lignes de code.
Voici une implémentation simple :
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 fonction tagger, tagPos
, prend une chaîne en entrée et génère une chaîne qui contient les mots de la chaîne d'origine avec la partie correspondante du discours. Dans votre fonction principale, instanciez un PortfolioNewsAnalyzer
et alimentez la sortie du scraper dans la fonction tagger et vous devriez voir quelque chose comme ceci :
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...
Traitement de la sortie balisée en un ensemble
Jusqu'à présent, nous avons créé des fonctions pour télécharger, nettoyer et taguer un article d'actualité. Mais nous devons encore déterminer si l'article mentionne l'une des entreprises qui intéressent l'utilisateur.
Pour ce faire, nous devons collecter tous les noms propres et vérifier si les actions de notre portefeuille sont incluses dans ces noms propres.
Pour trouver tous les noms propres, nous voudrons d'abord diviser la sortie de la chaîne balisée en jetons (en utilisant des espaces comme délimiteurs), puis diviser chacun des jetons sur le trait de soulignement ( _
) et vérifier si la partie du discours est un nom propre .
Une fois que nous aurons tous les noms propres, nous voudrons les stocker dans une structure de données mieux optimisée pour notre objectif. Pour notre exemple, nous utiliserons un HashSet
. En échange de l'interdiction des entrées en double et du non suivi de l'ordre des entrées, HashSet
permet des requêtes d'adhésion très rapides. Étant donné que nous ne sommes intéressés que par les requêtes d'adhésion, le HashSet
est parfait pour nos besoins.
Vous trouverez ci-dessous la fonction qui implémente la division et le stockage des noms propres. Placez cette fonction dans votre 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; }
Il y a cependant un problème avec cette implémentation. Si le nom d'une société se compose de plusieurs mots (par exemple, Carl Zeiss dans l'exemple de Luxottica), cette implémentation ne pourra pas l'attraper. Dans l'exemple de Carl Zeiss, "Carl" et "Zeiss" seront insérés dans l'ensemble séparément, et ne contiendront donc jamais la seule chaîne "Carl Zeiss".
Pour résoudre ce problème, nous pouvons collecter tous les noms propres consécutifs et les joindre avec des espaces. Voici l'implémentation mise à jour qui accomplit cela :
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; }
Maintenant, la fonction doit retourner un ensemble avec les noms propres individuels et les noms propres consécutifs (c'est-à-dire, reliés par des espaces). Si vous imprimez le propNounSet
, vous devriez voir quelque chose comme ceci :
[... 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, ...]
Comparaison du portefeuille avec l'ensemble de noms propres
On a presque fini!
Dans les sections précédentes, nous avons construit un scraper qui peut télécharger et extraire le corps d'un article, un tagger qui peut analyser le corps de l'article et identifier les noms propres, et un processeur qui prend la sortie étiquetée et collecte les noms propres dans un HashSet
. Il ne reste plus qu'à prendre le HashSet
et à le comparer avec la liste des entreprises qui nous intéressent.
La mise en oeuvre est très simple. Ajoutez le code suivant dans votre 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); }
Mettre tous ensemble
Nous pouvons désormais exécuter l'intégralité de l'application : le grattage, le nettoyage, le balisage, la collecte et la comparaison. Voici la fonction qui parcourt toute l'application. Ajoutez cette fonction à votre 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); }
Enfin, nous pouvons utiliser l'application!
Voici un exemple utilisant le même article que ci-dessus et Luxottica comme société de portefeuille :
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"); } }
Exécutez ceci, et l'application devrait afficher "L'article mentionne les sociétés du portefeuille".
Changez la société de portefeuille de Luxottica en une société non mentionnée dans l'article (telle que "Microsoft"), et l'application devrait afficher "L'article ne mentionne pas les sociétés de portefeuille".
Construire une application PNL n'a pas besoin d'être difficile
Dans cet article, nous avons parcouru le processus de création d'une application qui télécharge un article à partir d'une URL, le nettoie à l'aide de Boilerpipe, le traite à l'aide de Stanford NLP et vérifie si l'article fait des références spécifiques intéressantes (dans notre cas, les entreprises de notre portefeuille). Comme démontré, tirer parti de cet éventail de technologies transforme ce qui serait autrement une tâche ardue en une tâche relativement simple.
J'espère que cet article vous a présenté des concepts et des techniques utiles dans le traitement du langage naturel et qu'il vous a inspiré pour écrire vos propres applications en langage naturel.
[Remarque : Vous pouvez trouver une copie du code référencé dans cet article ici.]