Leçon: Introduction au Traitement du langage Naturel (TALN) ou (NLP)¶

Ressources¶

La librairie NLTK¶

Il s'agit d'une librairie de référence, très utilisée pour le TALN, comprenant des ressources pour différentes langues.

pip install --user -U nltk

Attention pour l'utilisation de certains modules il vous faudra télécharger des données supplémentaires, par exemple vous pouvez télécharger le subset d'utilisation la plus courante:

In [ ]:
import nltk
nltk.download('popular')

La librairie Gensim¶

Il s'agit d'une librairie spécialisée dans le topic modeling. Elle propose en autre des implémentations d'embedding connus comme word2vec

Le framework Spacy¶

Il s'agit d'un framework très complet et à la pointe de la recherche en TALN puisqu'il intégre les dernières découvertes comme les transformers. Il est organisé comme un écosystème permettant de créer des chaînes de traitement industrialisables, du prototype à la production.

Installation¶

Le framework Hugging Face¶

Tout comme Spacy, il s'agit d'un framework très complet et haut niveau pour une pratique industrialisable du TALN. Il s'illustre particulièrement dans les modèles pré-entrainés à l'état de l'art qu'ils proposent pour différents types de tâche

Les pré-traitements "standart" spécifiques au texte¶

Comme dans toutes les étapes de traitements, il n'y a pas de recette invariable, les pré-traitements à appliquer dépendront beaucoup des caractéristiques du corpus de document ainsi que de la tâche. Néanmoins, il est courant d'avoir besoin d'appliquer certains traitements standart:

  • gérer la casse
  • gérer les caractères spéciaux : nombres, punctuations, émoticonnes, ...
  • enlever les stopwords
  • tokenisation
  • racinisation / lemmatisation
  • ...

Lorsque l'on souhaite faire de l'apprentissage supervisé, il est parfois nécessaire de labeliser les données en les annontant (pour la classification, la traduction, ...)

Gestion de la casse¶

Deux mots ayant une casse différente seront considérés comme des mots différents dans la plupart des représentations. Si la casse, par ex les majuscules, n'apporte pas d'information supplémentaires, il vaut mieux alors uniformiser la casse:

In [1]:
text = "Linux systems are used by less than 2% of people while 55% of developpers are using Linux. Maybe people should use it more often ;-) "
In [2]:
text = text.lower()
text
Out[2]:
'linux systems are used by less than 2% of people while 55% of developpers are using linux. maybe people should use it more often ;-) '

Dans certaines tâches, comme Named Entity Recognition, conserver les majuscules peut être utile

Gestion des caractères spéciaux¶

Enlever les nombres¶

In [3]:
text = ''.join(word for word in text if not word.isdigit())
text
Out[3]:
'linux systems are used by less than % of people while % of developpers are using linux. maybe people should use it more often ;-) '

Garder les nombres peut s'avérer une information utile dans certains cas (par exemple l'extraction de dates)

Enlever la ponctuation¶

Dans beaucoup de tâches la ponctuation n'apporte pas d'information pertinente pour la tâche. Même en considérant la possibilité de la garder, il faut faire attention au fait que tous les textes ne respectent pas forcement leur format (ex : réseaux sociaux). C'est pourquoi on choisi souvent de la supprimer:

In [4]:
import string
string.punctuation
Out[4]:
'!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
In [5]:
for punctuation in string.punctuation:
    text = text.replace(punctuation, '') 

text
Out[5]:
'linux systems are used by less than  of people while  of developpers are using linux maybe people should use it more often  '

Dans certaines tâches,comme l'analyse de sentiment, les ponctuations particulières comme les ! peuvent être informatives. On peut choisir alors de les garder

Caractères ou motifs particuliers¶

Comme les émoticonnes, certains caractères ou motifs peuvent être informatifs pour une tâche données, par exemple l'analyse de sentiments, ou non informatifs. Il est souvent utiles de pouvoir les extraires pour créer une nouvelle variable ou simplement les supprimer.

On peut utiliser:

  • les expressions régulières : par exemple pour extraires des adresses e-mail
  • des modules particuliers: comme hugging face pour détecter les émoticonnes,
  • ...

Enlever les stopwords¶

Les stopwords sont des mots qui appraissent fréquemment dans les corpus et qui sont généralement peu informatifs (articles; de, le, la, pronoms: elle, lui, ...)

Plusieurs librairies comme NLTK proposent des collections de stopwords dans différentes langues, qui ont été construites par des linguististes spécifiquement pour le traitement automatique du langage

In [6]:
from nltk.corpus import stopwords
stop_words = set(stopwords.words('english')) 
stop_words
Out[6]:
{'a',
 'about',
 'above',
 'after',
 'again',
 'against',
 'ain',
 'all',
 'am',
 'an',
 'and',
 'any',
 'are',
 'aren',
 "aren't",
 'as',
 'at',
 'be',
 'because',
 'been',
 'before',
 'being',
 'below',
 'between',
 'both',
 'but',
 'by',
 'can',
 'couldn',
 "couldn't",
 'd',
 'did',
 'didn',
 "didn't",
 'do',
 'does',
 'doesn',
 "doesn't",
 'doing',
 'don',
 "don't",
 'down',
 'during',
 'each',
 'few',
 'for',
 'from',
 'further',
 'had',
 'hadn',
 "hadn't",
 'has',
 'hasn',
 "hasn't",
 'have',
 'haven',
 "haven't",
 'having',
 'he',
 'her',
 'here',
 'hers',
 'herself',
 'him',
 'himself',
 'his',
 'how',
 'i',
 'if',
 'in',
 'into',
 'is',
 'isn',
 "isn't",
 'it',
 "it's",
 'its',
 'itself',
 'just',
 'll',
 'm',
 'ma',
 'me',
 'mightn',
 "mightn't",
 'more',
 'most',
 'mustn',
 "mustn't",
 'my',
 'myself',
 'needn',
 "needn't",
 'no',
 'nor',
 'not',
 'now',
 'o',
 'of',
 'off',
 'on',
 'once',
 'only',
 'or',
 'other',
 'our',
 'ours',
 'ourselves',
 'out',
 'over',
 'own',
 're',
 's',
 'same',
 'shan',
 "shan't",
 'she',
 "she's",
 'should',
 "should've",
 'shouldn',
 "shouldn't",
 'so',
 'some',
 'such',
 't',
 'than',
 'that',
 "that'll",
 'the',
 'their',
 'theirs',
 'them',
 'themselves',
 'then',
 'there',
 'these',
 'they',
 'this',
 'those',
 'through',
 'to',
 'too',
 'under',
 'until',
 'up',
 've',
 'very',
 'was',
 'wasn',
 "wasn't",
 'we',
 'were',
 'weren',
 "weren't",
 'what',
 'when',
 'where',
 'which',
 'while',
 'who',
 'whom',
 'why',
 'will',
 'with',
 'won',
 "won't",
 'wouldn',
 "wouldn't",
 'y',
 'you',
 "you'd",
 "you'll",
 "you're",
 "you've",
 'your',
 'yours',
 'yourself',
 'yourselves'}

La tokenization¶

Cette opération consiste à segmenter chaque phrase en tokens, un string représentant un élément contenant du sens: par exemple on définit souvent les tokens comme des mots, ou des associations de $n$ mots, les n-grams

In [7]:
from nltk.tokenize import word_tokenize
word_tokens = word_tokenize(text) 
In [8]:
word_tokens
Out[8]:
['linux',
 'systems',
 'are',
 'used',
 'by',
 'less',
 'than',
 'of',
 'people',
 'while',
 'of',
 'developpers',
 'are',
 'using',
 'linux',
 'maybe',
 'people',
 'should',
 'use',
 'it',
 'more',
 'often']

On en profite souvent au passage pour supprimer les stopwords de la liste des tokens:

In [9]:
text = [w for w in word_tokens if not w in stop_words] 
text
Out[9]:
['linux',
 'systems',
 'used',
 'less',
 'people',
 'developpers',
 'using',
 'linux',
 'maybe',
 'people',
 'use',
 'often']

La racinisation ou stemming¶

Ce processus consiste à réduire les tokens à leur racine morphologique: des mots partageant la même racine seront remplacés par leur racine:

Playing, plays, played → play

In [10]:
text
Out[10]:
['linux',
 'systems',
 'used',
 'less',
 'people',
 'developpers',
 'using',
 'linux',
 'maybe',
 'people',
 'use',
 'often']
In [11]:
from nltk.stem.porter import PorterStemmer
stemmer = PorterStemmer()
stemmed = [stemmer.stem(word) for word in text]
stemmed
Out[11]:
['linux',
 'system',
 'use',
 'less',
 'peopl',
 'developp',
 'use',
 'linux',
 'mayb',
 'peopl',
 'use',
 'often']

La lemmatisation¶

Dans le domainee du TALN, la lemmatisation consiste à determiner le lemme d'un mot en se basant sur son sens. Au contraire du stemming, la lemmatisation depend de la nature du mot (que l'on peut determiner par le part of speech tagging) ainsi que du contexte du mot dans la phrase ou par rapport aux autres phases voisines dans le document.

am, are, is → be

cars, car's, car → car

In [12]:
text
Out[12]:
['linux',
 'systems',
 'used',
 'less',
 'people',
 'developpers',
 'using',
 'linux',
 'maybe',
 'people',
 'use',
 'often']
In [16]:
from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
lemmatized = [lemmatizer.lemmatize(word) for word in text]
text = lemmatized
lemmatized
Out[16]:
['linux',
 'system',
 'used',
 'le',
 'people',
 'developpers',
 'using',
 'linux',
 'maybe',
 'people',
 'use',
 'often']

La représentation Bag-of-word¶

Il s'agit d'une représentation très simple des données visant à encoder les tokens, en fonction de leur fréquence d'apparition dans les documents du corpus.

Avantages & Inconvénients¶

Inconvénients

  • Ne prends pas finement en compte les interactions entre les tokens et donc peu d'éléments de contexte
  • L'importance de certains mots fréquents mais peu informatifs tend à produire une représentation biaisée
  • Augmente fortement la dimensionalité du dataset ré-eoncodé

Avantages

  • Simple à comprendre
  • Agnostique du langage étudié

Bien qu'elle soit très simple, il s'agit souvent du point de départ pour de nombreux traitements et analyses dans les chaînes de traitement en TALN !

Vectorisation simple¶

Ce processus consiste à transformer une collection de documents en vecteurs de features numérique. Il est implémenté dans scikit-learn par la classe CountVectorizer

In [21]:
from sklearn.feature_extraction.text import CountVectorizer
corpus = [
    'This is the first document.',
    'This document is the second document.',
    'And this is the third one.',
    'Is this the first document?',
]
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(corpus)
Out[21]:
array(['and', 'document', 'first', 'is', 'one', 'second', 'the', 'third',
       'this'], dtype=object)

X est encodée sous forme de sparse matrix, pour économiser de la RAM:

In [34]:
X
Out[34]:
<4x9 sparse matrix of type '<class 'numpy.int64'>'
	with 21 stored elements in Compressed Sparse Row format>

Attention, On peut repasser X au format numpy array, mais je vous conseille de ne le faire que pour une visualisation temporaire, pas dans votre chaîne de traitement

In [22]:
print(X.toarray())
[[0 1 1 1 0 0 1 0 1]
 [0 2 0 1 0 1 1 0 1]
 [1 0 0 1 1 0 1 1 1]
 [0 1 1 1 0 0 1 0 1]]
In [25]:
import pandas as pd
pd.DataFrame(data = X.toarray(), columns = vectorizer.get_feature_names_out())
Out[25]:
and document first is one second the third this
0 0 1 1 1 0 0 1 0 1
1 0 2 0 1 0 1 1 0 1
2 1 0 0 1 1 0 1 1 1
3 0 1 1 1 0 0 1 0 1

Vectorisation Term Frequency - Inverse Document Frequency (Tf-Idf)¶

Il s'agit d'un variation de la vectorisation simplement basée sur la fréquence d'apparition des tokens dans chaque document. On ajoute en plus dans le calcul un terme de pondération visant à prendre en compte la fréquence des tokens dans le corpus total :

TF-IDF(t,d) = TF(t,d) x IDF(t)

avec $IDF(t) = log(\frac{1+n}{1+df(t)}+1)$

  • n : nombre total de documents
  • 𝑇𝐹(𝑡,𝑑): Frequence du token t dans le document d
  • DF(t) : Frequence du token t dans tous les documents

Les paramètres de vectorisation de CountVectoriser et TfIdfVectorizer¶

Le paramètre ngram_range¶

Vectorisation en considérant des mots (1-grams) comme tokens:

In [37]:
pd.DataFrame(data = X.toarray(), columns = vectorizer.get_feature_names_out())
Out[37]:
and document first is one second the third this
0 0 1 1 1 0 0 1 0 1
1 0 2 0 1 0 1 1 0 1
2 1 0 0 1 1 0 1 1 1
3 0 1 1 1 0 0 1 0 1

Dans cet exemple, on vectorise uniqument des paires de mots, des bi-grams (2-grams):

In [39]:
bigrams = CountVectorizer(analyzer='word', ngram_range=(2, 2))
X_bigrams = bigrams.fit_transform(corpus)
pd.DataFrame(data = X_bigrams.toarray(), columns = bigrams.get_feature_names_out())
Out[39]:
and this document is first document is the is this second document the first the second the third third one this document this is this the
0 0 0 1 1 0 0 1 0 0 0 0 1 0
1 0 1 0 1 0 1 0 1 0 0 1 0 0
2 1 0 0 1 0 0 0 0 1 1 0 1 0
3 0 0 1 0 1 0 1 0 0 0 0 0 1

Le paramètre max_df¶

Il permet d'exclure certains mots trop fréquents du corpus du processus de vectorisation en fonction d'un seuil

In [43]:
vectorizer = CountVectorizer(max_df=0.8)
X = vectorizer.fit_transform(corpus)
pd.DataFrame(data = X.toarray(), columns = vectorizer.get_feature_names_out())
Out[43]:
and document first one second third
0 0 1 1 0 0 0
1 0 2 0 0 1 0
2 1 0 0 1 0 1
3 0 1 1 0 0 0

Le paramètre min_df¶

Il permet d'exclure certains mots trop peu fréquents du corpus du processus de vectorisation en fonction d'un seuil

In [45]:
vectorizer = CountVectorizer(min_df=0.3)
X = vectorizer.fit_transform(corpus)
pd.DataFrame(data = X.toarray(), columns = vectorizer.get_feature_names_out())
Out[45]:
document first is the this
0 1 1 1 1 1
1 2 0 1 1 1
2 0 0 1 1 1
3 1 1 1 1 1

Les words embedding / Distributional Semantic Models¶

On peut voir le word embedding comme une extension de la représentation Bag-of-word

Il s'agit d'appliquer des méthodes d'embedding (plongements) sur un corpus de texte afin de représenter les mots présents dans ce corpus (espace de départ) sous forme d'espace vectoriel multi-dimensionnel (espace d'arrivée).

Le type de plongement est souvent choisi spécifiquement pour faire émerger, dans l'espace d'arrivée, la notion de sématique: pour chaque mot, une partie du contexte sémantique est capturé en prenant en compte les mots voisins dans le calcul

Intérêt¶

Capturer la similarité sémantique¶

L'intérêt principal de ce type de représentation est de capturer la similarité sémantique entre les mots: des mots similaires auront tendance à être représentés par des points proches dans l'espace vectoriel d'arrivée:

Word embedding 2D de genres de livres sur wikipedia d'apres Koehrsen 2018

No description has been provided for this image

Capturer des relations entre les mots¶

Comme chaque mot est représenté par un vecteur, il est possible de faire des opérations arithmetiques sur ces vecteurs ! Les propriétés de l'embedding permettent ainsi de faire apparaître des relations entre les vecteurs de mots, comme capturer des analogies entre les mots

No description has been provided for this image

Dans cet embedding 2D, les vecteurs en rouge semble égaux, c'est à dire qu'on peut définir l'anlogie suivante:
V(Queen) - V(King) = V(Woman) - V(Man)

D'ou on peut déduire : V(Queen) - V(King) + V(Man) = V(Woman)

Quelques méthodes d'embedding "généralistes"¶

  • Neural Network Language model: Il s'agit d'un réseau de neurone simple (souvent à une couche cachée) dans lequel on passe en entrée des mots vectorisés (par exemple par la représentation Bag-of-word) dans lequel la couche cachée joue le rôle d'embedding
No description has been provided for this image
  • Latent Semantic Analysis (LSA) : Cette méthode utilise une décomposition de matrice, la Singular Value Decomposition tronquée (tSVD), sous forme de plongement linéaire, pour fabriquer les vecteurs de base d'un nouvel espace (les composantes)
  • ...

Word2Vec¶

C'est un algorithme très populaire, il est implémenté dans plusieurs frameworks.
Il a été une des premières méthodes à mettre en avant les opérations arithmétiques sur les vecteurs de mots !

Le principe réside dans le fait d'utiliser une astuce dans laquelle on entraine un réseau de neurone (avec une seule couche cachée) sur une fausse tâche inutile.

L'intếret est de d'apprendre un embbeding dense, par la couche cachée qui aura été "forcée" à apprendre une représentation condensée du corpus de texte, en prenant en compte le contexte, représenté par les mots voisins. La représentation ainsi apprise contient des relations sémantiques basées sur les co-occurences des mots dans le dataset.

L'influence des mots voisins est réglée par un hyper-paramètre window size qui définit le nombre de mot voisins avant et après le mot cible qui représenteront le contexte.
Augmenter la taille de window size peut permettre de capturer plus de contexte (au risque d'utiliser des mots voisins non pertinents comme contexte) alors que diminuer la taille de window size incluera moins de contexte dans la représentation apprise

Il existe deux approches différentes pour entrainer ce réseau de neurone, qui différe sur la facon d'utiliser les mots voisins comme contexte:

Continous Bag-of-words (CBoW)¶

Dans cette approche, le réseau de neurone prédit le mot courant à partir d'une fenêtre de mots voisins et l'ordre des mots contenus dans cette fenêtre n'influence pas la prédiction (approche Bag-of-word)

No description has been provided for this image

Skip-gram¶

Dans cette approche, le réseau de neurone utilise le mot cible pour prédire les mots voisins de la fenêtre et accordera plus de poids aux mots proches de la fenêtre que ceux plus lointain

No description has been provided for this image

Exemple de Word2Vec avec gensim¶

Import des modules et du vocabulaire (ici common_texts)

In [20]:
from gensim.models import Word2Vec
from gensim.test.utils import common_texts

common_texts
Out[20]:
[['human', 'interface', 'computer'],
 ['survey', 'user', 'computer', 'system', 'response', 'time'],
 ['eps', 'user', 'interface', 'system'],
 ['system', 'human', 'system', 'eps'],
 ['user', 'response', 'time'],
 ['trees'],
 ['graph', 'trees'],
 ['graph', 'minors', 'trees'],
 ['graph', 'minors', 'survey']]

Import du modèle Word2Vec pré-entrainé :

In [21]:
model = Word2Vec(sentences=common_texts, vector_size=100, window=5, min_count=1, workers=4)

On peut ensuite calculer des vecteurs de mots :

In [23]:
model.wv['computer']
Out[23]:
array([-0.00515774, -0.00667028, -0.0077791 ,  0.00831315, -0.00198292,
       -0.00685696, -0.0041556 ,  0.00514562, -0.00286997, -0.00375075,
        0.0016219 , -0.0027771 , -0.00158482,  0.0010748 , -0.00297881,
        0.00852176,  0.00391207, -0.00996176,  0.00626142, -0.00675622,
        0.00076966,  0.00440552, -0.00510486, -0.00211128,  0.00809783,
       -0.00424503, -0.00763848,  0.00926061, -0.00215612, -0.00472081,
        0.00857329,  0.00428459,  0.0043261 ,  0.00928722, -0.00845554,
        0.00525685,  0.00203994,  0.0041895 ,  0.00169839,  0.00446543,
        0.0044876 ,  0.0061063 , -0.00320303, -0.00457706, -0.00042664,
        0.00253447, -0.00326412,  0.00605948,  0.00415534,  0.00776685,
        0.00257002,  0.00811905, -0.00138761,  0.00808028,  0.0037181 ,
       -0.00804967, -0.00393476, -0.0024726 ,  0.00489447, -0.00087241,
       -0.00283173,  0.00783599,  0.00932561, -0.0016154 , -0.00516075,
       -0.00470313, -0.00484746, -0.00960562,  0.00137242, -0.00422615,
        0.00252744,  0.00561612, -0.00406709, -0.00959937,  0.00154715,
       -0.00670207,  0.0024959 , -0.00378173,  0.00708048,  0.00064041,
        0.00356198, -0.00273993, -0.00171105,  0.00765502,  0.00140809,
       -0.00585215, -0.00783678,  0.00123305,  0.00645651,  0.00555797,
       -0.00897966,  0.00859466,  0.00404816,  0.00747178,  0.00974917,
       -0.0072917 , -0.00904259,  0.0058377 ,  0.00939395,  0.00350795],
      dtype=float32)

Et même obtenir les vecteurs de mots les plus similaires :

In [25]:
model.wv.most_similar('computer', topn=3)
Out[25]:
[('system', 0.21617139875888824),
 ('survey', 0.04468922317028046),
 ('interface', 0.015203381888568401)]

Ou les différences de vecteurs de mots :

In [26]:
# vecteur correspondant à human - interface
model.wv.most_similar(['human','interface'], topn=3)
Out[26]:
[('response', 0.14778193831443787),
 ('eps', 0.12549911439418793),
 ('system', 0.09567634016275406)]

Doc2Vec¶

Il s'agit d'une extension du modèle Word2Vec dans laquelle l'embedding apprend une représentation des documents dans le corpus:
Alors que Word2Vec crée comme feature un vecteur pour chaque mot dans le corpus, Doc2Vec crée comme feature un vecteur pour chaque document dans le corpus

GloVe : Global Vectors for word representation¶

Alors que la méthode Word2Vec utilise des informations locales uniquement (réglées par la window size) pour apprendre son embedding, GloVe utilise des des statistiques globales supplémentaires, apprise sur tout le corpus, pour apprendre son embedding.

Ces statistiques globales supplémentaires, sont apprises en utilisant la LSA sur une matrice composée en ligne, de la co-occurence des mots et en colonne du contexte des mots

No description has been provided for this image

Quelques algorithmes utilisés en TALN (avec Bag of word ou des words embedding)¶

Dans des tâches de classification: par ex filtre anti spam¶

  • Naives Bayes
  • Régression Logistique
  • ...

Tâche de topic modeling:¶

  • Latent Dirichlet Allocation
  • RNN

Tâche de production de texte¶

  • RNN
  • Les Transformers

Quel word embedding utiliser ?¶

  • Utiliser des embedding pré-entrainés sur de vastes corpus de texte (souvent en anglais) : utile lorsque vous utiliser un vocabulaire assez générique dans la langue que vous souhaitez étudié
  • Entrainer son propre embedding : utile lorsque le langage étudié et/ou le vocabulaire est spécifique

Les Transformers¶

Le Transformer est un modèle de deep learning relativement récent (imaginés à partir de 2014 mais mis en place plus tardivement) qui propose une innovation par rapport aux modèles dominants utilisés jusqu'a alors, à savoir les réseaux de neurones récurrents comme les LSTM

La particularité des Transformers est de ne pas utiliser la structure de ces réseaux réccurent, mais des mécanismes d'attention.

Ces derniers réseaux sont parmi les plus gros jamais imaginés en deep learning (en nombre de paramètres) ! Ils ont été entrainés de manière non supervisée à prédire le prochain mot dans un texte, sur de très gros corpus

On peut citer les modèles Bert de Google et GPT-2 de OpenAI qui sont capables une fois entrainés de répondre à plusieurs tâches de TALN !!!

  • générer du texte
  • répondre à des questions
  • faire de la traduction

Ces modéles ont été libérés en open source (notemment dans le framework Huggin Face) et peuvent être utilisés dans leur version pré-entrainée.

OpenAI a récemment publié la nouvelle version de son célèbre transformer, GPT-3, qui n'est pour l'instant accessible que par des API payantes.

Sources¶

  • La page de documentation dédiée à l'extraction de feature textuelles de sckit-learn
  • Article de Devopedia sur le word embedding: bon article bien résumé et référencé qui contient également des exemples de code
  • Cet article du blog Towards Data Science consacré aux word embeddings: permet de comprendre leur construction
  • Cet article du blog Towards Data Science consaccé aux word embeddings: recense et explique différents algorithmes historiques
  • Cet article de blog résume bien les principes basiques des Transformers