31/10/2018 Tech
Handson de quelques tâches courantes en NLP 0
Ce post est un rapide tutoriel de quelques tâches courantes de NLP en Python et plus spécifiquement avec les librairies NLTK et SpaCy.
This post is also available in english.
Introduction et mise en place de l’environnement
Dans ce post, nous allons dérouler quelques exemples de tâches courantes en Natural Language Processing (NLP), en mettant en parallèle deux librairies Python (en) couramment utilisées : NLTK (en) et SpaCy (en). Pour plus d’informations sur ces librairies et le NLP en général, on pourra se référer aux posts précédents : Introduction au NLP Partie I et Partie II.
Pour reproduire ces exemples, il est conseillé d’utiliser un environnement Anaconda (en) en Python 3.5 :
$ conda create --name spacy_nltk_examples python=3.5
$ source activate spacy_nltk_examples
Pour plus de facilité, on pourra travailler dans un notebook Jupyter (en). Il faut donc installer le package jupyter ainsi que spacy et nltk puis lancer le serveur notebook.
$ pip install jupyter==1.0.0 spacy==1.9.0 nltk==3.2.2
$ jupyter notebook
Le notebook s’ouvre alors dans le navigateur et on accède au tableau de bord. Puis on crée un nouveau notebook (en Python 3) :
Pour faire tourner les exemples, il suffit de les coller dans les cellules du notebook.
Puis d’exécuter les cellules.
Pour pouvoir utiliser les modèles SpaCy, il faut d’abord les télécharger. Il est possible de le faire via le notebook :
!python -m spacy download en # modèle anglais
!python -m spacy download fr # modèle français
Chacun des exemples ayant été écrit de manière à pouvoir être exécuté indépendamment, certaines parties (importation de librairie, création de pipeline, création de phrases,…) sont répétées à chaque fois. Cependant, ces répétitions sont inutiles si tous les exemples sont exécutés les uns à la suite des autres.
Les exemples sont aussi compilés dans un notebook Jupyter disponible sur GitHub.
Transformation de texte
Segmentation de texte en phrases
Notre premier exemple concerne un problème qui peut sembler simple en apparence, la segmentation d’un texte en phrases (en).
Avec SpaCy
# On importe la librairie SpaCy et on crée le pipeline SpaCy permettant de traiter le texte français
import spacy
nlp_fr = spacy.load("fr")
# On crée un texte composé de plusieurs phrases
text_fr = "Ceci est 1 première phrase. Puis j'en écris une seconde. pour finir en voilà une troisième sans mettre de majuscule"
# On passe le texte par le pipeline
doc_fr = nlp_fr(text_fr)
# On affiche les phrases
for sent in doc_fr.sents:
print(sent)
Output :
Ceci est 1 première phrase. Puis j'en
écris une seconde.
pour finir en voilà une troisième sans mettre de majuscule
Avec NLTK
# On importe la fonction de segmentation en phrases de NLTK
from nltk.tokenize import sent_tokenize
# On crée un texte composé de plusieurs phrases
text_fr = "Ceci est 1 première phrase. Puis j'en écris une seconde. pour finir en voilà une troisième sans mettre de majuscule"
# On utilise la fonction de segmentation sur notre texte
sentences = sent_tokenize(text_fr, language = 'french')
# On affiche le résultat
for sent in sentences:
print(sent)
Output :
Ceci est 1 première phrase.
Puis j'en écris une seconde.
pour finir en voilà une troisième sans mettre de majuscule
Segmentation de texte en tokens
Il est possible de segmenter un texte de façon plus fine, en tokens (en). Ces tokens peuvent être des mots, des n-grammes, de la ponctuation, des symboles ou des chiffres.
Avec SpaCy
# On importe la librairie SpaCy et on crée le pipeline SpaCy permettant de traiter le texte français
import spacy
nlp_fr = spacy.load("fr")
# On crée une phrase
text_fr = "Les tokens peuvent être des symboles $ ++, des chiffres 7 99, de la ponctuation !? des mots."
# On passe la phrase par le pipeline
doc_fr = nlp_fr(text_fr)
# On récupère et on affiche les tokens
words = [w.text for w in doc_fr]
print(words)
Output :
['Les', 'tokens', 'peuvent', 'être', 'des', 'symboles', '$', '+', '+', ',', 'des', 'chiffres',
'7', '99', ',', 'de', 'la', 'ponctuation', '!', '?', 'des', 'mots', '.']
Avec NLTK
# On importe la fonction de tokenisation en mots de NLTK
from nltk.tokenize import word_tokenize
# On crée une phrase
text_fr = "Les tokens peuvent être des symboles $ ++, des chiffres 7 99, de la ponctuation !? des mots."
# On applique la fonction de tokenization à la phrase et on affiche le résultat
words = word_tokenize(text_fr, language = 'french')
print(words)
Output :
['Les', 'tokens', 'peuvent', 'être', 'des', 'symboles', '$', '++', ',', 'des', 'chiffres',
'7', '99', ',', 'de', 'la', 'ponctuation', '!', '?', 'des', 'mots', '.']
Dans certaines applications de NLP, il est parfois plus significatif d’utiliser un ensemble de tokens successifs plutôt qu’un seul token. Par exemple, si le mot “New-York” est présent dans un texte, une tokenisation classique va le séparer en trois tokens distincts “New”, “-” et “York”, lui faisant ainsi perdre du sens. Pour pallier à ce genre de problème, il est possible de segmenter le texte en n-grammes (n tokens successifs). NLTK contrairement à SpaCy permet de générer des n-grammes à partir d’une liste de tokens.
# On importe les fonctions de tokenisation en mots et de génération des n-grammes de NLTK
from nltk.tokenize import word_tokenize
from nltk.util import ngrams
# On tokenise une phrase
words = word_tokenize("Le NLP avec NLTK, c'est génial!")
# On génère des bi-grammes à partir de notre phrase tokenisée
bigrams=ngrams(words,2)
# On affiche le résultat
print(list(bigrams))
Output :
[('Le', 'NLP'), ('NLP', 'avec'), ('avec', 'NLTK'), ('NLTK', ','), (',', "c'est"),
("c'est", 'génial'), ('génial', '!')]
# On importe les fonctione de tokenisation en mots et de génération des n-grammes de NLTK
from nltk.tokenize import word_tokenize
from nltk.util import ngrams
# On tokenise une phrase
words = word_tokenize("Le NLP avec NLTK, c'est génial!")
# On génère des tri-grammes à partir de notre phrase tokenisée
trigrams=ngrams(words,3)
# On affiche le résultat
print(list(trigrams))
Output :
[('Le', 'NLP', 'avec'), ('NLP', 'avec', 'NLTK'), ('avec', 'NLTK', ','), ('NLTK', ',', "c'est"),
(',', "c'est", 'génial'),("c'est", 'génial', '!')]
Extraction d’informations
Part-Of-Speech-Tagging (POS-Tagging)
Le POS-Tagging consiste à associer un mot à sa classe morphosyntaxique (nom, verbe, adjectif,…).
Avec SpaCy
# On importe la librairie SpaCy et on crée le pipeline SpaCy permettant de traiter le texte français
import spacy
nlp_fr = spacy.load("fr")
# On crée une phrase en français
text_fr = "Je vais au parc avec mon chien"
# On passe la phrase par le pipeline spécifique à sa langue
doc_fr = nlp_fr(text_fr)
# On affiche chaque token et son tag pour la phrase française
for token in doc_fr:
print('Word : {0}, , Tag : {1}' .format(token.text, token.tag_))
Output :
Word : Je, , Tag : PRON
Word : vais, , Tag : AUX
Word : au, , Tag : PRON
Word : parc, , Tag : NOUN
Word : avec, , Tag : ADP
Word : mon, , Tag : DET
Word : chien, , Tag : NOUN
# On importe la librairie SpaCy et on crée le pipeline SpaCy permettant de traiter le texte anglais
import spacy
nlp_en = spacy.load("en")
# On crée une phrase en anglais
text_en = "I go to the park with my dog"
# On passe la phrase par le pipeline spécifique à sa langue
doc_en = nlp_en(text_en)
# On affiche chaque token et son tag pour la phrase anglaise
for token in doc_en:
print('Word : {0}, , Tag : {1}' .format(token.text, token.tag_))
Output :
Word : I, , Tag : PRP
Word : go, , Tag : VBP
Word : to, , Tag : IN
Word : the, , Tag : DT
Word : park, , Tag : NN
Word : with, , Tag : IN
Word : my, , Tag : PRP$
Word : dog, , Tag : NN
La signification des différents tags français et anglais est disponible dans la bibliographie.
Avec NLTK
# On importe les fonctions de tokenisation de mots et de POS-Tagging de NLTK
from nltk.tokenize import word_tokenize
from nltk.tag import pos_tag
# On crée une phrase en anglais
text_en = "I go to the park with my dog"
# On tokenise la phrase
tokens_en = word_tokenize(text_en)
# On tague les tokens
tags_en = pos_tag(tokens_en)
# On affiche le résultat
print (tags_en)
Output :
[('I', 'PRP'), ('go', 'VBP'), ('to', 'TO'), ('the', 'DT'), ('park', 'NN'), ('with', 'IN'),
('my', 'PRP$'), ('dog', 'NN')]
Reconnaissance d’entités nommées
La reconnaissance d’entités nommées (en) (ou Named-Entity Recognition, NER en anglais) permet de reconnaître les mots d’un texte qui correspondent à des concepts catégorisables (noms de personnes, lieux, organisations,…).
Avec SpaCy
SpaCy compte 17 types d’entités reconnues (en) : PERSON, NORP, FACILITY, ORG, GPE, LOC, PRODUCT, EVENT, WORK_OF_ART, LANGUAGE, DATE, TIME, PERCENT, MONEY, QUANTITY, ORDINAL et CARDINAL.
# On importe la librairie SpaCy et on crée le pipeline permettant de traiter le texte anglais
import spacy
nlp_fr = spacy.load("en")
# On crée une phrase
text_en = "Mark Elliot Zuckerberg (born May 14, 1984) is a co-founder of Facebook."
# On passe la phrase par le pipeline
doc_en = nlp_fr(text_en)
# On affiche chaque token et si une entité est reconnue, on affiche le type d’entité
for token in doc_en:
print('Word : {0}, , Entity : {1}' .format(token.text, token.ent_type_))
Output :
Word : Mark, , Entity : PERSON
Word : Elliot, , Entity : PERSON
Word : Zuckerberg, , Entity : PERSON
Word : (, , Entity :
Word : born, , Entity :
Word : May, , Entity : DATE
Word : 14, , Entity : DATE
Word : ,, , Entity : DATE
Word : 1984, , Entity : DATE
Word : ), , Entity :
Word : is, , Entity :
Word : a, , Entity :
Word : co, , Entity :
Word : -, , Entity :
Word : founder, , Entity :
Word : of, , Entity :
Word : Facebook, , Entity : ORG
Word : ., , Entity :
Avec NLTK
Il existe 9 types d’entités dans NLTK : ORGANIZATION, PERSON, LOCATION, DATE, TIME, MONEY, PERCENT, FACILITY et GPE. Plus de détails sur ces catégories sont disponibles dans le paragraphe 5 du chapitre 7 du livre NLTK donné en référence.
# On importe les fonctions de tokenisation de mots, de POS-Tagging et de reconnaissance d'entités de NLTK
from nltk.tokenize import word_tokenize
from nltk.tag import pos_tag
from nltk.chunk import ne_chunk
# On crée une phrase en anglais
text_en = "Mark Elliot Zuckerberg (born May 14, 1984) is a co-founder of Facebook."
# On tokenise la phrase
tokens_en = word_tokenize(text_en)
# On tague les tokens
tags_en = pos_tag(tokens_en)
# On applique la fonction de reconnaissance d’entités
ner_en = ne_chunk(tags_en)
# On affiche le résultat
print (ner_en)
Output :
(S
(PERSON Mark/NNP)
(PERSON Elliot/NNP Zuckerberg/NNP)
(/(
born/VBN
May/NNP
14/CD
,/,
1984/CD
)/)
is/VBZ
a/DT
co-founder/NN
of/IN
(GPE Facebook/NNP)
./.)
NLTK versus SpaCy
Fonctionnement
SpaCy et NLTK ont une façon très différente de traiter un texte. NLTK traite des chaînes de caractères alors que SpaCy a une approche plus orientée objet. En effet, NLTK fournit un ensemble de fonctions permettant d’effectuer une tâche de NLP (pos_tag() pour le POS-Tagging, sent_tokenize() pour la segmentation de phrases, word_tokenize() pour la tokenisation,…). Ces fonctions prennent en général une chaîne de caractères ou une liste de chaînes de caractères en paramètre d’entrée et renvoient également une chaîne ou une liste de chaînes. Il suffit donc à l’utilisateur de déterminer la fonction correspondant à la tâche qu’il veut effectuer puis de lui fournir son texte sous forme de chaîne de caractères.
SpaCy fonctionne différemment. Le texte, sous forme de chaîne de caractères, doit passer par un “language processing pipeline” spécifique à la langue du texte (l’anglais, le français, l’espagnol et l’allemand sont pour l’instant supportées par SpaCy). En sortie du pipeline, on obtient un objet Doc (en) qui est un conteneur permettant d’accéder aux annotations linguistiques. Chaque élément ou ensemble d’éléments d’un Doc est également représenté par un objet possédant des attributs, des méthodes et des propriétés qui permettent d’accéder aux informations linguistiques souhaitées. Cette approche est donc beaucoup plus moderne et “pythonic” que celle utilisée par NLTK.
Retour sur les exemples
Revenons sur les exemples déroulés précédemment en comparant les résultats obtenus par les deux librairies.
- Le premier exemple porte sur la segmentation d’un texte en phrases. SpaCy permet de segmenter un texte écrit en langue anglaise, française, espagnole ou allemande. Pour NLTK, le package nltk.tokenize (en) fournit plusieurs outils pour la segmentation en phrases, notamment la fonction sent_tokenize() qui dispose d’un paramètre “language” supportant la plupart des langues européennes (en). Si l’on revient sur notre exemple, on voit que NLTK segmente bien les trois phrases du texte alors que SpaCy ne sépare pas correctement les deux premières phrases.
- Le deuxième exemple concerne la segmentation d’une phrase en tokens. Comme pour la segmentation en phrase, c’est le package nltk.tokenize qui regroupe les outils de segmentation en tokens pour NLTK et en particulier la fonction word_tokenize() qui dispose du même paramètre “language”. Si l’on regarde notre exemple, on voit que la seule différence notable entre les résultats, est le traitement de “++”. NLTK le considère comme un token à part entière alors que SpaCy sépare les deux symboles “+” pour retourner deux tokens distincts.
- L’exemple suivant porte sur le POS-Tagging. Seule SpaCy propose d’autres langues que l’anglais (français, espagnol et allemand). Pour ce qui est du tagging pour la langue anglaise, les deux librairies s’appuie sur la Penn Treebank (en). Encore une fois, il y a une légère différence de résultat entre les deux librairies pour l’exemple fourni : le mot “to” est tagué “TO” (tag signifiant “to de l’infinitif”) avec NLTK et “IN” (tag signifiant conjonction, conjonction de subordination ou préposition) avec SpaCy. Il semble donc que NLTK se trompe ici.
- Pour le dernier exemple, on s’intéresse à la reconnaissance d’entités nommées. Comme précédemment, seule SpaCy propose une alternative à l’anglais puisqu’il existe un modèle de NER en allemand. Les modèles français et espagnol ne sont pas encore disponibles. Un deuxième avantage pour SpaCy par rapport à NLTK est le nombre de catégories des entités : 17 pour SpaCy contre 9 pour NLTK. La phrase de notre exemple utilise trois entités dont les catégories sont communes aux deux libraries : PERSON, DATE et_GPE_. Si SpaCy reconnaît bien ces trois entités, NLTK ne reconnaît pas l’entité DATE.
D’autres applications non traitées en exemples sont disponibles grâce à ces deux librairies :
- La lemmatisation (en). Elle consiste à prendre la forme canonique d’un mot, son lemme. Pour l’instant, SpaCy possède le lemme des mots uniquement pour son modèle anglais. De même, NLTK n’a qu’un lemmatiser anglais d’implémenté, basé sur Wordnet (en), il s’agit de la fonction WordNetLemmatizer() (en) présente dans le package nltk.stem (en).
- Le stemming (en). Il consiste à prendre la racine du mot. Seule NLTK permet de faire du stemming grâce au package nltk.stem. Ce package propose un stemmer pour la langue arabe (ISRI), deux stemmer pour l’anglais (algorithme Lancaster et algorithme Porter), un stemmer qu’il est possible de customiser grâce à des regex (regexp), un stemmer pour le portugais (RSLP) et un ensemble de stemmer (Snowball) qui couvre plusieurs langues (français, danois, néerlandais, anglais, finlandais, allemand, hongrois, italien, norvégien, portugais, roumain, russe, espagnol et suédois).
- L’analyse de sentiments. L’analyse de sentiments fait partie des fonctions toujours en développement dans SpaCy. En effet, il existe bien un attribut “sentiment” mais celui-ci est pour le moment vide quelque soit le modèle linguistique utilisé. Dans NLTK, on trouve trois packages utiles. Le premier package est nltk.sentiment (en). Il est composé de plusieurs sous-modules :
- nltk.sentiment.vader (en) offre un outil d’analyse de sentiments déjà implémenté appelé VADER (en). La fonction polarity_scores() de ce module permet, pour un texte donné, d’obtenir un score pour chaque polarité (positive, négative et neutre).
- nltk.sentiment.sentiment_analyzer (en). Ce module est un outil permettant d’implémenter et de faciliter les tâches d’analyse de sentiments en utilisant les fonctionnalités et les classifieurs présents dans NLTK. Il propose des fonctions pour l’extraction de features, l’apprentissage et l’évaluation d’un modèle de classification,….
- nltk.sentiment.util (en) rassemble des méthodes utilitaires (conversion et parsing de fichiers, écriture d’une analyse dans un fichier,…).
Les deux autres_packages_ complètent le premier dans la construction de modèles de classification pour l’analyse de sentiments, en fournissant des jeux de données dans nltk.corpus (en) (les corpora de données disponibles dans NLTK sont répertoriés dans le chapitre 2 du livre NLTK donné en référece) et un ensemble de classifieurs dans nltk.classify (en). Parmi les jeux de données pouvant être utilisés, on peut citer subjectivity (il contient 5000 phrases labellisées “subjective” et 5000 phrases labellisées “objective”), twitters_samples (il contient un ensemble de tweets annotés positif/négatif) et movie_reviews (il contient 2000 revues de films annotées positive/négative). Les classifieurs proposés sont ConditionalExponentialClassifier,_ DecisionTreeClassifier_,MaxentClassifier,NaiveBayesClassifier_et WekaClassifier_.
Conclusion
Ces quelques exemples simples ne sont qu’un aperçu de ce qu’il est possible de faire en NLP grâce aux deux librairies présentées. Même si SpaCy semble de plus en plus utilisée, les deux librairies restent complémentaires, chacune offrant des outils que l’autre n’a pas ou qui sont plus développés chez l’une par rapport à l’autre. Si la langue anglaise est privilégiée, on peut espérer que des fonctionnalités se développent prochainement pour les autres langues, notamment dans SpaCy.
Bibliographie
- Livre NLTK : http://www.nltk.org/book/
- Documentation SpaCy : https://spacy.io/docs/
- Tutoriel Jupyter : https://www.datacamp.com/community/tutorials/tutorial-jupyter-notebook#gs.vS8=iks
- Détails des tags français : http://universaldependencies.org/fr/pos/all.html
- Détails des tags anglais : https://www.ling.upenn.edu/courses/Fall_2003/ling001/penn_treebank_pos.html
Crédit images
L’ensemble des illustrations sont sous licence CC0 (en).