L’analyse fréquentiste par l’approche bag-of-words : intérêt et limites

Ce chapitre présente l’approche bag of words pour synthétiser l’information présente dans des corpus non structurés.

NLP
Exercice
Auteur·rice

Lino Galiana

Date de publication

2024-11-20

Pour avancer dans ce chapitre, nous avons besoin de quelques installations préalables:

!pip install spacy
!python -m spacy download en_core_web_sm
!python -m spacy download fr_core_news_sm

Il est également utile de définir la fonction suivante, issue de notre chapitre précédent :

def clean_text(doc):
    # Tokenize, remove stop words and punctuation, and lemmatize
    cleaned_tokens = [
        token.lemma_ for token in doc if not token.is_stop and not token.is_punct
    ]
    # Join tokens back into a single string
    cleaned_text = " ".join(cleaned_tokens)
    return cleaned_text

1 Introduction

Nous avons vu, précédemment, l’intérêt de nettoyer les données pour dégrossir le volume d’information présent dans nos données non structurées. L’objectif de ce chapitre est d’approfondir notre compréhension de l’approche fréquentiste appliquée aux données textuelles. Nous allons évoquer la manière dont cette analyse fréquentiste permet de synthétiser l’information présente dans un corpus textuel. Nous allons également voir la manière dont on peut raffiner l’approche bag of words en tenant compte de l’ordre de la proximité des termes dans une phrase.

1.1 Données

Nous allons reprendre le jeu de données anglo-saxon du chapitre précédent, à savoir des textes des auteurs fantastiques Edgar Allan Poe (EAP), HP Lovecraft (HPL) et Mary Wollstonecraft Shelley (MWS).

import pandas as pd

url = "https://github.com/GU4243-ADS/spring2018-project1-ginnyqg/raw/master/data/spooky.csv"
# 1. Import des données
horror = pd.read_csv(url, encoding="latin-1")
# 2. Majuscules aux noms des colonnes
horror.columns = horror.columns.str.capitalize()
# 3. Retirer le prefixe id
horror["ID"] = horror["Id"].str.replace("id", "")
horror = horror.set_index("Id")

2 La mesure TF-IDF (term frequency - inverse document frequency)

2.1 La matrice documents - termes

Comme nous l’avons évoqué précédemment, nous construisons une représentation synthétique de notre corpus comme un sac de mots dans lesquels on pioche plus ou moins fréquemment des mots selon leur fréquence d’apparition. C’est bien sûr une représentation simpliste de la réalité : les séquences de mots ne sont pas une suite aléatoire indépendante de mots.

Cependant, avant d’évoquer ces enjeux, il nous reste à aller au bout de l’approche sac de mots. La représentation la plus caractéristique de ce paradigme est la matrice document-terme, principalement utilisée pour comparer des corpus. Celle-ci consiste à créer une matrice où chaque document est représenté par la présence ou l’absence des termes de notre corpus. L’idée est de compter le nombre de fois où les mots (les termes, en colonne) sont présents dans chaque phrase ou libellé (le document, en ligne). Cette matrice fournit alors une représentation numérique des données textuelles.

Considérons un corpus constitué des trois phrases suivantes :

  • “La pratique du tricot et du crochet”
  • “Transmettre la passion du timbre”
  • “Vivre de sa passion”

La matrice document-terme associée à ce corpus est la suivante :

crochet de du et la passion pratique sa timbre transmettre tricot vivre
La pratique du tricot et du crochet 1 0 2 1 1 0 1 0 0 0 1 0
Transmettre sa passion du timbre 0 0 1 0 0 1 0 1 1 1 0 0
Vivre de sa passion 0 1 0 0 0 1 0 1 0 0 0 1

Chaque phrase du corpus est associée à un vecteur numérique. Par exemple, la phrase “La pratique du tricot et du crochet”, qui n’a pas de sens en soi pour une machine, devient un vecteur numérique intelligible pour elle égal à [1, 0, 2, 1, 1, 0, 1, 0, 0, 0, 1, 0]. Ce vecteur numérique est une représentation creuse (sparse) du langage puisque chaque document (ligne) ne comportera qu’une petite partie du vocabulaire total (l’ensemble des colonnes). Pour tous les mots qui n’apparaîtront pas dans le document, on aura des 0, d’où un vecteur sparse. Comme nous le verrons par la suite, cette représentation numérique diffère grandement des approches modernes d’embeddings, basées sur l’idée de représentation denses.

2.2 Utilisation pour l’extraction d’informations

Différents documents peuvent alors être rapprochés sur la base de ces mesures. C’est l’une des manières de procéder des moteurs de recherche même si les meilleurs utilisent des approches bien plus sophistiquées. La métrique tf-idf (term frequency–inverse document frequency) permet de calculer un score de proximité entre un terme de recherche et un document à partir de deux composantes :

\[ \text{tf-idf}(t, d, D) = \text{tf}(t, d) \times \text{idf}(t, D) \]

avec \(t\) un terme particulier (par exemple un mot), \(d\) un document particulier et \(D\) l’ensemble de documents dans le corpus.

  • La partie tf calcule une fonction croissante de la fréquence du terme de recherche dans le document à l’étude ;

  • La partie idf calcule une fonction inversement proportionnelle à la fréquence du terme dans l’ensemble des documents (ou corpus).

  • La première partie (term-frequency, TF) est la fréquence d’apparition du terme terme \(t\) dans le document \(d\). Il existe des mesures de normalisation pour éviter de biaiser la mesure en cas de documents longs.

\[ \text{tf}(t, d) = \frac{f_{t,d}}{\sum_{t' \in d} f_{t',d}} \]

\(f_{t,d}\) est le nombre brut de fois que le terme \(t\) apparaît dans le document \(d\) et le dénominateur est le nombre de termes dans le document \(d\).

  • La seconde partie (inverse document frequency, IDF) mesure la rareté, ou au contraire l’aspect commun, d’un terme dans l’ensemble du corpus. Si \(N\) est le nombre total de documents dans le corpus \(D\), cette partie de la mesure sera

\[ \text{idf}(t, D) = \log \left( \frac{N}{|\{d \in D : t \in d\}|} \right) \]

Le dénominateur \(( |\{d \in D : t \in d\}| )\) correspond au nombre de documents contenant le terme \(t\), s’il apparaît. Plus le mot est rare, plus sa présence dans un document sera surpondérée.

De nombreux moteurs de recherche utilisent cette logique pour rechercher les documents les plus pertinents pour répondre à des termes de recherche. C’est notamment le cas d’ ElasticSearch, le logiciel qui permet d’implémenter des moteurs de recherche efficaces. Pour classer les documents les plus pertinents par rapport à des termes de recherche, ce logiciel utilise notamment la mesure de distance BM25 qui est une version sophistiquée de la mesure TF-IDF.

2.3 Exemple

Prenons une illustration à partir d’un petit corpus. Le code suivant implémente une mesure TF-IDF. Celle-ci est légèrement différente de celle définie ci-dessus pour s’assurer de ne pas effectuer de division par zéro.

import numpy as np

# Documents d'exemple
documents = [
    "Le corbeau et le renard",
    "Rusé comme un renard",
    "Le chat est orange comme un renard",
]


# Tokenisation
def preprocess(doc):
    return doc.lower().split()


tokenized_docs = [preprocess(doc) for doc in documents]


# Term frequency (TF)
def term_frequency(term, tokenized_doc):
    term_count = tokenized_doc.count(term)
    return term_count / len(tokenized_doc)


# Inverse document frequency (DF)
def document_frequency(term, tokenized_docs):
    return sum(1 for doc in tokenized_docs if term in doc)


# Calculate inverse document frequency (IDF)
def inverse_document_frequency(word, corpus):
    # Normalisation avec + 1 pour éviter la division par zéro
    count_of_documents = len(corpus) + 1
    count_of_documents_with_word = sum([1 for doc in corpus if word in doc]) + 1
    idf = np.log10(count_of_documents / count_of_documents_with_word) + 1
    return idf


# Calculate TF-IDF scores in each document
def tf_idf_term(term):
    tf_idf_scores = pd.DataFrame(
        [
            [
                term_frequency(term, doc),
                inverse_document_frequency(term, tokenized_docs),
            ]
            for doc in tokenized_docs
        ],
        columns=["TF", "IDF"],
    )
    tf_idf_scores["TF-IDF"] = tf_idf_scores["TF"] * tf_idf_scores["IDF"]
    return tf_idf_scores

Commençons par calculer TF-IDF du mot “chat” pour chaque document. De manière naturelle, c’est le troisième document, le seul où apparaît le mot qui a la valeur maximale :

tf_idf_term("chat")
TF IDF TF-IDF
0 0.000000 1.30103 0.000000
1 0.000000 1.30103 0.000000
2 0.142857 1.30103 0.185861

Qu’en est-il du terme renard qui apparaît dans tous les documents (dont la partie \(\text{idf}\) est donc égale à 1) ? Dans ce cas, c’est le document où le mot est le plus fréquent, en l’occurrence le 2e, qui a la mesure maximale.

tf_idf_term("renard")
TF IDF TF-IDF
0 0.200000 1.0 0.200000
1 0.250000 1.0 0.250000
2 0.142857 1.0 0.142857

2.4 Application

L’exemple précédent ne passait pas très bien à l’échelle. Heureusement, Scikit propose une implémentation de la recherche par vecteur TF-IDF que nous pouvons explorer avec un nouvel exercice.

Le vectoriseur obtenu à l’issue de la question 1 est le suivant :

TfidfVectorizer(stop_words=['whoever', 'or', 'nobody', 'meanwhile', 'due',
                            'must', 'show', 'never', 'name', 'beside', 'former',
                            'since', 'while', 'because', 'am', 'across',
                            'latterly', 'say', 'put', 'that', '’s', 'in',
                            'them', 'amount', 'very', 'toward', 'yours',
                            'became', 'hereby', 'above', ...])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
/opt/conda/lib/python3.12/site-packages/sklearn/feature_extraction/text.py:406: UserWarning:

Your stop_words may be inconsistent with your preprocessing. Tokenizing the stop words generated tokens ['ll', 've'] not in stop_words.
aaem ab aback abaft abandon abandoned abandoning abandonment abaout abased ... zodiacal zoilus zokkar zone zones zopyrus zorry zubmizzion zuro á¼
0 0.0 0.0 0.0 0.0 0.0 0.000000 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1 0.0 0.0 0.0 0.0 0.0 0.000000 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
2 0.0 0.0 0.0 0.0 0.0 0.000000 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
3 0.0 0.0 0.0 0.0 0.0 0.000000 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
4 0.0 0.0 0.0 0.0 0.0 0.267616 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

5 rows × 24783 columns

Les lignes où le terme “abandon” apparait sont les suivantes (question 2) :

Index([    4,   116,   215,   571,   839,  1042,  1052,  1069,  2247,  2317,
        2505,  3023,  3058,  3245,  3380,  3764,  3886,  4425,  5289,  5576,
        5694,  6812,  7500,  9013,  9021,  9077,  9560, 11229, 11395, 11451,
       11588, 11827, 11989, 11998, 12122, 12158, 12189, 13666, 15259, 16516,
       16524, 16759, 17547, 18019, 18072, 18126, 18204, 18251],
      dtype='int64')

La matrice document-terme associée à celles-ci est la suivante :

aaem ab aback abaft abandon abandoned abandoning abandonment abaout abased ... zodiacal zoilus zokkar zone zones zopyrus zorry zubmizzion zuro á¼
4 0.0 0.0 0.0 0.0 0.000000 0.267616 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
116 0.0 0.0 0.0 0.0 0.000000 0.359676 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
215 0.0 0.0 0.0 0.0 0.249090 0.000000 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
571 0.0 0.0 0.0 0.0 0.000000 0.153280 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
839 0.0 0.0 0.0 0.0 0.312172 0.000000 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

5 rows × 24783 columns

On remarque ici l’inconvénient de ne pas avoir fait de racinisation. Les variations de “abandon” sont éclatées sur de nombreuses colonnes. “abandoned” est aussi différent d’“abandon” que de “fear”. C’est l’un des problèmes de l’approche bag of words.

Text    50
dtype: int64

Les 10 scores les plus élevés sont les suivants :

['We could not fear we did not.',
 '"And now I do not fear death.',
 'Be of heart and fear nothing.',
 'Indeed I had no fear on her account.',
 'I smiled, for what had I to fear?',
 'I did not like everything about what I saw, and felt again the fear I had had.',
 'At length, in an abrupt manner she asked, "Where is he?" "O, fear not," she continued, "fear not that I should entertain hope Yet tell me, have you found him?',
 'I have not the slightest fear for the result.',
 '"I fear you are right there," said the Prefect.']

On remarque que les scores les plus élevés sont soient des extraits courts où le mot apparait une seule fois, soit des extraits plus longs où le mot “fear” apparaît plusieurs fois.

3 Un premier enrichissement de l’approche sac de mots : les n-grams

Nous avons évoqué deux principales limites à l’approche sac de mot : l’absence de prise en compte du contexte et la représentation sparse du langage qui rend les rapprochements entre texte parfois moyennement pertinents. Dans le paradigme du sac de mots, il est néanmoins possible de prendre en compte la séquence d’enchainement de sèmes (tokens) par le biais des ngrams.

Pour rappel, jusqu’à présent, dans l’approche bag of words, l’ordre des mots n’avait pas d’importance. On considère qu’un texte est une collection de mots tirés indépendamment, de manière plus ou moins fréquente en fonction de leur probabilité d’occurrence. Cependant, tirer un mot particulier n’affecte pas les chances de tirer certains mots ensuite, de manière conditionnelle.

Une manière d’introduire des liens entre les séries de tokens sont les n-grams. On s’intéresse non seulement aux mots et à leur fréquence, mais aussi aux mots qui suivent. Cette approche est essentielle pour désambiguiser les homonymes. Le calcul de n-grams 1 constitue la méthode la plus simple pour tenir compte du contexte.

Pour être en mesure de mener cette analyse, il est nécessaire de télécharger un corpus supplémentaire :

import nltk

nltk.download("genesis")
nltk.corpus.genesis.words("english-web.txt")
[nltk_data] Downloading package genesis to /github/home/nltk_data...
[nltk_data]   Package genesis is already up-to-date!
['In', 'the', 'beginning', 'God', 'created', 'the', ...]

NLTK offre des methodes pour tenir compte du contexte. Pour ce faire, nous calculons les n-grams, c’est-à-dire l’ensemble des co-occurrences successives de mots n-à-n. En général, on se contente de bi-grams, au mieux de tri-grams :

  • les modèles de classification, analyse du sentiment, comparaison de documents, etc. qui comparent des n-grams avec n trop grands sont rapidement confrontés au problème de données sparse, cela réduit la capacité prédictive des modèles ;
  • les performances décroissent très rapidement en fonction de n, et les coûts de stockage des données augmentent rapidement (environ n fois plus élevé que la base de données initiale).

On va, rapidement, regarder dans quel contexte apparaît le mot fear dans l’oeuvre d’Edgar Allan Poe (EAP). Pour cela, on transforme d’abord le corpus EAP en tokens NLTK :

eap_clean = horror.loc[horror["Author"] == "EAP"]
eap_clean = " ".join(eap_clean["Text"])
tokens = eap_clean.split()
print(tokens[:10])
text = nltk.Text(tokens)
print(text)
['This', 'process,', 'however,', 'afforded', 'me', 'no', 'means', 'of', 'ascertaining', 'the']
<Text: This process, however, afforded me no means of...>

Vous aurez besoin des fonctions BigramCollocationFinder.from_words et BigramAssocMeasures.likelihood_ratio :

Avec la méthode concordance (question 1), la liste devrait ressembler à celle-ci :

Exemples d'occurences du terme 'fear' :
Displaying 13 of 13 matches:
d quick unequal spoken apparently in fear as well as in anger. What he said wa
hutters were close fastened, through fear of robbers, and so I knew that he co
to details. I even went so far as to fear that, as I occasioned much trouble, 
years of age, was heard to express a fear "that she should never see Marie aga
ich must be entirely remodelled, for fear of serious accident I mean the steel
 my arm, and I attended her home. 'I fear that I shall never see Marie again.'
clusion here is absurd. "I very much fear it is so," replied Monsieur Maillard
bt of ultimately seeing the Pole. "I fear you are right there," said the Prefe
er occurred before.' Indeed I had no fear on her account. For a moment there w
erhaps so," said I; "but, Legrand, I fear you are no artist. It is my firm int
 raps with a hammer. Be of heart and fear nothing. My daughter, Mademoiselle M
e splendor. I have not the slightest fear for the result. The face was so far 
arriers of iron that hemmed me in. I fear you have mesmerized" adding immediat

Même si on peut facilement voir le mot avant et après, cette liste est assez difficile à interpréter car elle recoupe beaucoup d’informations.

La collocation consiste à trouver les bi-grammes qui apparaissent le plus fréquemment ensemble. Parmi toutes les paires de deux mots observées, il s’agit de sélectionner, à partir d’un modèle statistique, les “meilleures”. On obtient donc avec cette méthode (question 2):

[('of', 'the'),
 ('in', 'the'),
 ('had', 'been'),
 ('to', 'be'),
 ('have', 'been'),
 ('I', 'had'),
 ('It', 'was'),
 ('it', 'is'),
 ('could', 'not'),
 ('from', 'the'),
 ('upon', 'the'),
 ('more', 'than'),
 ('it', 'was'),
 ('would', 'have'),
 ('with', 'a'),
 ('did', 'not'),
 ('I', 'am'),
 ('the', 'a'),
 ('at', 'once'),
 ('might', 'have')]

Si on modélise les meilleures collocations :

"Gad Fly"
'Hum Drum,'
'Rowdy Dow,'
Brevet Brigadier
Barrière du
ugh ugh
Ourang Outang
Chess Player
John A.
A. B.
hu hu
General John
'Oppodeldoc,' whoever
mille, mille,
Brigadier General

Cette liste a un peu plus de sens, on a des noms de personnages, de lieux mais aussi des termes fréquemment employés ensemble (Chess Player par exemple).

En ce qui concerne les collocations du mot fear :

[('fear', 'of'), ('fear', 'God'), ('I', 'fear'), ('the', 'fear'), ('The', 'fear'), ('fear', 'him'), ('you', 'fear')]

Si on mène la même analyse pour le terme love, on remarque que de manière logique, on retrouve bien des sujets généralement accolés au verbe :

[('love', 'me'), ('love', 'he'), ('will', 'love'), ('I', 'love'), ('love', ','), ('you', 'love'), ('the', 'love')]

4 Quelques applications

Nous venons d’évoquer un premier cas d’application de l’approche bag of words qui est le rapprochement de textes par leurs termes communs. Ce n’est pas le seul cas d’application de l’approche précédente. Nous allons en évoquer deux qui nous amènent vers la modélisation du langage : la reconnaissance d’entités nommées et la classification.

4.1 Reconnaissance des entités nommées

La reconnaissance d’entités nommées, également connue sous l’acronyme NER pour named entity recognition, est une méthode d’extraction d’information permettant d’identifier, dans un texte, la nature de certains termes dans une certain classification : lieu, personne, quantité, etc.

Pour illustrer cela, reprenons le Comte de Monte Cristo et regardons sur un petit morceau de cette oeuvre ce qu’implique la reconnaissance d’entités nommées :

from urllib import request

url = "https://www.gutenberg.org/files/17989/17989-0.txt"
response = request.urlopen(url)
raw = response.read().decode("utf8")

dumas = raw.split(
    "*** START OF THE PROJECT GUTENBERG EBOOK LE COMTE DE MONTE-CRISTO, TOME I ***"
)[1].split(
    "*** END OF THE PROJECT GUTENBERG EBOOK LE COMTE DE MONTE-CRISTO, TOME I ***"
)[
    0
1]

import re


def clean_text(text):
    text = text.lower()  # mettre les mots en minuscule
    text = " ".join(text.split())
    return text


dumas = clean_text(dumas)

dumas[10000:10500]
1
On extrait de manière un petit peu simpliste le contenu de l’ouvrage
" mes yeux. --vous avez donc vu l'empereur aussi? --il est entré chez le maréchal pendant que j'y étais. --et vous lui avez parlé? --c'est-à-dire que c'est lui qui m'a parlé, monsieur, dit dantès en souriant. --et que vous a-t-il dit? --il m'a fait des questions sur le bâtiment, sur l'époque de son départ pour marseille, sur la route qu'il avait suivie et sur la cargaison qu'il portait. je crois que s'il eût été vide, et que j'en eusse été le maître, son intention eût été de l'acheter; mais je lu"
import spacy
from spacy import displacy

nlp = spacy.load("fr_core_news_sm")
doc = nlp(dumas[15000:17000])
displacy.render(doc, style="ent", jupyter=True)
us LOC donne tout loisir de faire les vôtres. avez LOC -vous besoin d'argent? --non, monsieur; j'ai tous mes appointements du voyage, c'est-à-dire près de trois mois de solde. --vous êtes un garçon rangé, edmond. PER --ajoutez que j'ai un père pauvre, monsieur morrel. --oui, oui, je sais que vous êtes un bon fils. allez donc voir votre père: j'ai un fils aussi, et j'en voudrais fort à celui qui, après un voyage de trois mois, le retiendrait loin de moi. --alors, vous permettez? dit le jeune homme en saluant. --oui, si vous n'avez rien de plus à me dire. --non MISC . --le capitaine leclère ne vous a pas, en mourant, donné une lettre pour moi? --il lui eût été impossible d'écrire, monsieur; mais cela me rappelle que j'aurai un congé de quinze jours à vous demander. --pour vous marier? --d'abord; puis pour aller à paris LOC . --bon, bon! vous prendrez le temps que vous voudrez, dantès; le temps de décharger le bâtiment nous prendra bien six semaines, et nous ne nous remettrons guère en mer avant trois mois.... seulement, dans trois mois, il faudra que vous soyez là. le _pharaon_, continua l'armateur en frappant sur l'épaule du jeune marin, ne pourrait pas repartir sans son capitaine. --sans son capitaine! s'écria dantès les yeux brillants de joie; faites bien attention à ce que vous dites là, monsieur, car vous venez de répondre aux plus secrètes espérances de mon coeur. votre intention serait-elle de me nommer capitaine du _pharaon_? --si j'étais seul, je vous tendrais la main, mon cher dantès LOC , et je vous dirais: «c'est fait.» mais j'ai un associé, et vous savez le proverbe italien: _che a compagne a padrone_. mais la moitié de la besogne LOC est faite au moins, puisque sur deux voix vous en avez déjà une. rapportez-vous-en à moi pour avoir l'autre, et je ferai de mon mieux. --oh! MISC monsieur morrel, s'écria le jeune marin, saisissant, les larmes aux yeux, les mains de l'armateur; monsieur morrel, je vous remercie, au nom de mon père et de mercédès. --c' PER est bien, c'est bien, edmond, il y a un dieu

La reconnaissance d’entités nommées disponible par défaut dans les librairies généralistes est souvent décevante ; il est souvent nécessaire d’enrichir les règles par défaut par des règles ad hoc, propres à chaque corpus.

En pratique, récemment, l’approche de reconnaissance d’entités nommées a été utilisée par Etalab pour pseudonymiser des documents administratifs. Il s’agit d’identifier certaines informations sensibles (état civil, adresse…) par reconnaissance d’entités pour les remplacer par des pseudonymes.

4.2 Classification de données textuelles : l’algorithme Fasttext

Fasttext est un réseau de neurone à une couche développé par Meta en 2016 pour faire de la classification de texte ou de la modélisation de langage. Comme nous allons pouvoir le voir, ce modèle va nous amener à faire la transition avec une modélisation plus raffinée du langage, bien que celle de Fasttext soit beaucoup plus frustre que celle des grands modèles de langage (LLM). L’un des principaux cas d’utilisation de Fasttext est la classification supervisée de données textuelles. Il s’agit, à partir d’un texte, de déterminer sa catégorie d’appartenance. Par exemple, à partir d’un texte de chanson, s’il s’agit de rap ou de rock. Ce modèle est supervisé puisqu’il apprend à reconnnaître les features, en l’occurrence des morceaux de texte, qui permettent d’avoir une bonne performance de prédiction sur le jeu d’entraînement puis de test.

Le concept de feature peut sembler étonnant pour des données textuelles, qui sont, par essence, non structurées. Pour des données structurées, la démarche évoquée dans la partie modélisation apparaîssait naturelle : nous avions des variables observées dans nos données pour les features et l’algorithme de classification consistait à trouver la combinaison entre elles permettant de prédire, au mieux, le label. Avec des données textuelles, ce concept de feature observé n’est plus naturel. Il est nécessaire de le construire à partir d’un texte. L’information destructurée devient de l’information structurée. C’est là qu’interviennent les concepts que nous avons vu jusqu’à présent.

FastText est un “sac de n-gram”. Il considère donc que les features sont à construire à partir des mots de notre corpus mais aussi des ngrams à plusieurs niveaux. L’architecture générale de FastText ressemble à celle-ci :

Illustration de l’architecture de FastText

Illustration de l’architecture de FastText

Ce qui nous intéresse ici est la partie gauche de ce diagramme, la “feature extraction” car la partie embedding correspond à des concepts que nous verrons lors des prochains chapitres. Avec l’exemple de cette figure, on voit que le texte “Business engineering and services” est tokenisé comme nous avons pu le voir plus tôt en mots. Mais Fasttext créé également des ngrams à plusieurs niveaux. Par exemple, il va créer des bigrams de mots : “Business engineering”, “engineering and”, “and services”. mais aussi des quadrigrammes de caractères “busi”, “usin” et “sine”. Ensuite, Fasttext transformera tous ces termes en vecteurs numériques. Contrairement à ce que nous avons vu jusqu’à présent, ces vecteurs ne sont pas des fréquences d’apparition dans le corpus (principe de la matrice origine-document), ce sont des plongements de mots (word embedding). Nous découvrirons leurs principes dans les prochains chapitres.

Ce modèle Fasttext est très utilisé dans la statistique publique car de nombreuses sources de données textuelles sont à hiérarchiser dans des nomenclatures agrégées.

TO DO: plus d’éléments

Voici un exemple d’utilisation d’un tel modèle pour la classification d’activités

Informations additionnelles

environment files have been tested on.

Latest built version: 2024-11-20

Python version used:

'3.12.6 | packaged by conda-forge | (main, Sep 30 2024, 18:08:52) [GCC 13.3.0]'
Package Version
affine 2.4.0
aiobotocore 2.15.1
aiohappyeyeballs 2.4.3
aiohttp 3.10.8
aioitertools 0.12.0
aiosignal 1.3.1
alembic 1.13.3
altair 5.4.1
aniso8601 9.0.1
annotated-types 0.7.0
appdirs 1.4.4
archspec 0.2.3
asttokens 2.4.1
attrs 24.2.0
babel 2.16.0
bcrypt 4.2.0
beautifulsoup4 4.12.3
black 24.8.0
blinker 1.8.2
blis 0.7.11
bokeh 3.5.2
boltons 24.0.0
boto3 1.35.23
botocore 1.35.23
branca 0.7.2
Brotli 1.1.0
cachetools 5.5.0
cartiflette 0.0.2
Cartopy 0.24.1
catalogue 2.0.10
cattrs 24.1.2
certifi 2024.8.30
cffi 1.17.1
charset-normalizer 3.3.2
click 8.1.7
click-plugins 1.1.1
cligj 0.7.2
cloudpathlib 0.20.0
cloudpickle 3.0.0
colorama 0.4.6
comm 0.2.2
commonmark 0.9.1
conda 24.9.1
conda-libmamba-solver 24.7.0
conda-package-handling 2.3.0
conda_package_streaming 0.10.0
confection 0.1.5
contextily 1.6.2
contourpy 1.3.0
cryptography 43.0.1
cycler 0.12.1
cymem 2.0.8
cytoolz 1.0.0
dask 2024.9.1
dask-expr 1.1.15
databricks-sdk 0.33.0
debugpy 1.8.6
decorator 5.1.1
Deprecated 1.2.14
diskcache 5.6.3
distributed 2024.9.1
distro 1.9.0
docker 7.1.0
duckdb 0.10.1
en-core-web-sm 3.7.1
entrypoints 0.4
et_xmlfile 2.0.0
exceptiongroup 1.2.2
executing 2.1.0
fastexcel 0.11.6
fastjsonschema 2.20.0
fiona 1.10.1
Flask 3.0.3
folium 0.17.0
fontawesomefree 6.6.0
fonttools 4.54.1
fr-core-news-sm 3.7.0
frozendict 2.4.4
frozenlist 1.4.1
fsspec 2023.12.2
funcy 2.0
gensim 4.3.2
geographiclib 2.0
geopandas 1.0.1
geoplot 0.5.1
geopy 2.4.1
gitdb 4.0.11
GitPython 3.1.43
google-auth 2.35.0
graphene 3.3
graphql-core 3.2.4
graphql-relay 3.2.0
graphviz 0.20.3
great-tables 0.12.0
greenlet 3.1.1
gunicorn 22.0.0
h2 4.1.0
hpack 4.0.0
htmltools 0.6.0
hyperframe 6.0.1
idna 3.10
imageio 2.36.0
importlib_metadata 8.5.0
importlib_resources 6.4.5
inflate64 1.0.0
ipykernel 6.29.5
ipython 8.28.0
itsdangerous 2.2.0
jedi 0.19.1
Jinja2 3.1.4
jmespath 1.0.1
joblib 1.4.2
jsonpatch 1.33
jsonpointer 3.0.0
jsonschema 4.23.0
jsonschema-specifications 2024.10.1
jupyter-cache 1.0.0
jupyter_client 8.6.3
jupyter_core 5.7.2
kaleido 0.2.1
kiwisolver 1.4.7
langcodes 3.5.0
language_data 1.3.0
lazy_loader 0.4
libmambapy 1.5.9
locket 1.0.0
lxml 5.3.0
lz4 4.3.3
Mako 1.3.5
mamba 1.5.9
mapclassify 2.8.1
marisa-trie 1.2.1
Markdown 3.6
markdown-it-py 3.0.0
MarkupSafe 2.1.5
matplotlib 3.9.2
matplotlib-inline 0.1.7
mdurl 0.1.2
menuinst 2.1.2
mercantile 1.2.1
mizani 0.11.4
mlflow 2.16.2
mlflow-skinny 2.16.2
msgpack 1.1.0
multidict 6.1.0
multivolumefile 0.2.3
munkres 1.1.4
murmurhash 1.0.10
mypy-extensions 1.0.0
narwhals 1.14.1
nbclient 0.10.0
nbformat 5.10.4
nest_asyncio 1.6.0
networkx 3.3
nltk 3.9.1
numexpr 2.10.1
numpy 1.26.4
opencv-python-headless 4.10.0.84
openpyxl 3.1.5
opentelemetry-api 1.16.0
opentelemetry-sdk 1.16.0
opentelemetry-semantic-conventions 0.37b0
OWSLib 0.28.1
packaging 24.1
pandas 2.2.3
paramiko 3.5.0
parso 0.8.4
partd 1.4.2
pathspec 0.12.1
patsy 0.5.6
Pebble 5.0.7
pexpect 4.9.0
pickleshare 0.7.5
pillow 10.4.0
pip 24.2
platformdirs 4.3.6
plotly 5.24.1
plotnine 0.13.6
pluggy 1.5.0
polars 1.8.2
preshed 3.0.9
prometheus_client 0.21.0
prometheus_flask_exporter 0.23.1
prompt_toolkit 3.0.48
protobuf 4.25.3
psutil 6.0.0
ptyprocess 0.7.0
pure_eval 0.2.3
py7zr 0.20.8
pyarrow 17.0.0
pyarrow-hotfix 0.6
pyasn1 0.6.1
pyasn1_modules 0.4.1
pybcj 1.0.2
pycosat 0.6.6
pycparser 2.22
pycryptodomex 3.21.0
pydantic 2.9.2
pydantic_core 2.23.4
Pygments 2.18.0
pyLDAvis 3.4.1
PyNaCl 1.5.0
pynsee 0.1.8
pyogrio 0.10.0
pyOpenSSL 24.2.1
pyparsing 3.1.4
pyppmd 1.1.0
pyproj 3.7.0
pyshp 2.3.1
PySocks 1.7.1
python-dateutil 2.9.0
python-dotenv 1.0.1
python-magic 0.4.27
pytz 2024.1
pyu2f 0.1.5
pywaffle 1.1.1
PyYAML 6.0.2
pyzmq 26.2.0
pyzstd 0.16.2
querystring_parser 1.2.4
rasterio 1.4.2
referencing 0.35.1
regex 2024.9.11
requests 2.32.3
requests-cache 1.2.1
retrying 1.3.4
rich 13.9.4
rpds-py 0.21.0
rsa 4.9
Rtree 1.3.0
ruamel.yaml 0.18.6
ruamel.yaml.clib 0.2.8
s3fs 2023.12.2
s3transfer 0.10.2
scikit-image 0.24.0
scikit-learn 1.5.2
scipy 1.13.0
seaborn 0.13.2
setuptools 74.1.2
shapely 2.0.6
shellingham 1.5.4
six 1.16.0
smart-open 7.0.5
smmap 5.0.0
sortedcontainers 2.4.0
soupsieve 2.5
spacy 3.7.5
spacy-legacy 3.0.12
spacy-loggers 1.0.5
SQLAlchemy 2.0.35
sqlparse 0.5.1
srsly 2.4.8
stack-data 0.6.2
statsmodels 0.14.4
tabulate 0.9.0
tblib 3.0.0
tenacity 9.0.0
texttable 1.7.0
thinc 8.2.5
threadpoolctl 3.5.0
tifffile 2024.9.20
toolz 1.0.0
topojson 1.9
tornado 6.4.1
tqdm 4.66.5
traitlets 5.14.3
truststore 0.9.2
typer 0.13.1
typing_extensions 4.12.2
tzdata 2024.2
Unidecode 1.3.8
url-normalize 1.4.3
urllib3 1.26.20
wasabi 1.1.3
wcwidth 0.2.13
weasel 0.4.1
webdriver-manager 4.0.2
websocket-client 1.8.0
Werkzeug 3.0.4
wheel 0.44.0
wordcloud 1.9.3
wrapt 1.16.0
xgboost 2.1.1
xlrd 2.0.1
xyzservices 2024.9.0
yarl 1.13.1
yellowbrick 1.5
zict 3.0.0
zipp 3.20.2
zstandard 0.23.0

View file history

SHA Date Author Description
5108922 2024-08-08 18:43:37 Lino Galiana Improve notebook generation and tests on PR (#536)
8d23a53 2024-07-10 18:45:54 Julien PRAMIL Modifs 02_exoclean.qmd (#523)
f32915b 2024-07-09 18:41:00 Julien PRAMIL Add badges NLP chapter (#522)
6f2a565 2024-06-16 16:23:01 linogaliana Détails tf-idf
8cb248a 2024-06-16 16:09:45 linogaliana TF-IDF
fcdd7b4 2024-06-14 15:11:24 linogaliana Add spacy corpus
4f41cf6 2024-06-14 15:00:41 Lino Galiana Une partie sur les sacs de mots plus cohérente (#501)
06d003a 2024-04-23 10:09:22 Lino Galiana Continue la restructuration des sous-parties (#492)
005d89b 2023-12-20 17:23:04 Lino Galiana Finalise l’affichage des statistiques Git (#478)
3437373 2023-12-16 20:11:06 Lino Galiana Améliore l’exercice sur le LASSO (#473)
4cd44f3 2023-12-11 17:37:50 Antoine Palazzolo Relecture NLP (#474)
deaafb6 2023-12-11 13:44:34 Thomas Faria Relecture Thomas partie NLP (#472)
1f23de2 2023-12-01 17:25:36 Lino Galiana Stockage des images sur S3 (#466)
a1ab3d9 2023-11-24 10:57:02 Lino Galiana Reprise des chapitres NLP (#459)
a06a268 2023-11-23 18:23:28 Antoine Palazzolo 2ème relectures chapitres ML (#457)
09654c7 2023-11-14 15:16:44 Antoine Palazzolo Suggestions Git & Visualisation (#449)
889a71b 2023-11-10 11:40:51 Antoine Palazzolo Modification TP 3 (#443)
a771183 2023-10-09 11:27:45 Antoine Palazzolo Relecture TD2 par Antoine (#418)
154f09e 2023-09-26 14:59:11 Antoine Palazzolo Des typos corrigées par Antoine (#411)
a8f90c2 2023-08-28 09:26:12 Lino Galiana Update featured paths (#396)
8082302 2023-08-25 17:48:36 Lino Galiana Mise à jour des scripts de construction des notebooks (#395)
3bdf3b0 2023-08-25 11:23:02 Lino Galiana Simplification de la structure 🤓 (#393)
f2905a7 2023-08-11 17:24:57 Lino Galiana Introduction de la partie NLP (#388)
78ea2cb 2023-07-20 20:27:31 Lino Galiana Change titles levels (#381)
a9b384e 2023-07-18 18:07:16 Lino Galiana Sépare les notebooks (#373)
29ff3f5 2023-07-07 14:17:53 linogaliana description everywhere
f21a24d 2023-07-02 10:58:15 Lino Galiana Pipeline Quarto & Pages 🚀 (#365)
934149d 2023-02-13 11:45:23 Lino Galiana get_feature_names is deprecated in scikit 1.0.X versions (#351)
164fa68 2022-11-30 09:13:45 Lino Galiana Travail partie NLP (#328)
f10815b 2022-08-25 16:00:03 Lino Galiana Notebooks should now look more beautiful (#260)
494a85a 2022-08-05 14:49:56 Lino Galiana Images featured ✨ (#252)
d201e3c 2022-08-03 15:50:34 Lino Galiana Pimp la homepage ✨ (#249)
12965ba 2022-05-25 15:53:27 Lino Galiana :launch: Bascule vers quarto (#226)
9c71d6e 2022-03-08 10:34:26 Lino Galiana Plus d’éléments sur S3 (#218)
3299f1d 2022-01-08 16:50:11 Lino Galiana Clean NLP notebooks (#215)
09b60a1 2021-12-21 19:58:58 Lino Galiana Relecture suite du NLP (#205)
495599d 2021-12-19 18:33:05 Lino Galiana Des éléments supplémentaires dans la partie NLP (#202)
17092b2 2021-12-13 09:17:13 Lino Galiana Retouches partie NLP (#199)
3c87483 2021-12-13 08:46:52 Lino Galiana Notebooks NLP update (#198)
2a8809f 2021-10-27 12:05:34 Lino Galiana Simplification des hooks pour gagner en flexibilité et clarté (#166)
2e4d586 2021-09-02 12:03:39 Lino Galiana Simplify badges generation (#130)
49e2826 2021-05-13 18:11:20 Lino Galiana Corrige quelques images n’apparaissant pas (#108)
4cdb759 2021-05-12 10:37:23 Lino Galiana :sparkles: :star2: Nouveau thème hugo :snake: :fire: (#105)
7f9f97b 2021-04-30 21:44:04 Lino Galiana 🐳 + 🐍 New workflow (docker 🐳) and new dataset for modelization (2020 🇺🇸 elections) (#99)
d164635 2020-12-08 16:22:00 Lino Galiana :books: Première partie NLP (#87)
Retour au sommet

Notes de bas de page

  1. On parle de bigrams pour les co-occurences de mots deux-à-deux, trigrams pour les co-occurences trois-à-trois, etc.↩︎

Citation

BibTeX
@book{galiana2023,
  author = {Galiana, Lino},
  title = {Python pour la data science},
  date = {2023},
  url = {https://pythonds.linogaliana.fr/},
  doi = {10.5281/zenodo.8229676},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
Galiana, Lino. 2023. Python pour la data science. https://doi.org/10.5281/zenodo.8229676.