path = window.location.pathname.replace(".html", ".qmd");
path_modified = (path.includes('en/content')) ? path.replace('en/content', 'content/en') : path
html`${printBadges({fpath: path_modified})}`
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 for token in doc if not token.is_stop and not token.is_punct
token.lemma_
]# Join tokens back into a single string
= " ".join(cleaned_tokens)
cleaned_text 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
= "https://github.com/GU4243-ADS/spring2018-project1-ginnyqg/raw/master/data/spooky.csv"
url # 1. Import des données
= pd.read_csv(url, encoding="latin-1")
horror # 2. Majuscules aux noms des colonnes
= horror.columns.str.capitalize()
horror.columns # 3. Retirer le prefixe id
"ID"] = horror["Id"].str.replace("id", "")
horror[= horror.set_index("Id") horror
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}} \]
où \(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()
= [preprocess(doc) for doc in documents]
tokenized_docs
# Term frequency (TF)
def term_frequency(term, tokenized_doc):
= tokenized_doc.count(term)
term_count 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
= len(corpus) + 1
count_of_documents = sum([1 for doc in corpus if word in doc]) + 1
count_of_documents_with_word = np.log10(count_of_documents / count_of_documents_with_word) + 1
idf return idf
# Calculate TF-IDF scores in each document
def tf_idf_term(term):
= pd.DataFrame(
tf_idf_scores
[
[
term_frequency(term, doc),
inverse_document_frequency(term, tokenized_docs),
]for doc in tokenized_docs
],=["TF", "IDF"],
columns
)"TF-IDF"] = tf_idf_scores["TF"] * tf_idf_scores["IDF"]
tf_idf_scores[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 :
"chat") tf_idf_term(
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.
"renard") tf_idf_term(
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.
Exercice 6 : TF-IDF : calcul de fréquence
- Utiliser le vectoriseur TF-IdF de
scikit-learn
pour transformer notre corpus en une matricedocument x terms
. Au passage, utiliser l’optionstop_words
pour ne pas provoquer une inflation de la taille de la matrice. Nommer le modèletfidf
et le jeu entraînétfs
. - Après avoir construit la matrice de documents x terms avec le code suivant, rechercher les lignes où les termes ayant la structure
abandon
sont non-nuls. - Trouver les 50 extraits où le score TF-IDF du mot “fear” est le plus élevé et l’auteur associé. Déterminer la répartition des auteurs dans ces 50 documents.
- Observer les 10 scores où TF-IDF de “fear” sont les plus élevés
Aide pour la question 2
= tfidf.get_feature_names_out()
feature_names = [n for n in list(tfidf.vocabulary_.keys())]
corpus_index = pd.DataFrame(tfs.todense(), columns=feature_names) horror_dense
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.
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', ...])
/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
"genesis")
nltk.download("english-web.txt") nltk.corpus.genesis.words(
[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
:
= horror.loc[horror["Author"] == "EAP"]
eap_clean = " ".join(eap_clean["Text"])
eap_clean = eap_clean.split()
tokens print(tokens[:10])
= nltk.Text(tokens)
text 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
:
Exercice 7 : n-grams et contexte du mot fear
- Utiliser la méthode
concordance
pour afficher le contexte dans lequel apparaît le termefear
. - Sélectionner et afficher les meilleures collocations, par exemple selon le critère du ratio de vraisemblance.
Lorsque deux mots sont fortement associés, cela est parfois dû au fait qu’ils apparaissent rarement. Il est donc parfois nécessaire d’appliquer des filtres, par exemple ignorer les bigrammes qui apparaissent moins de 5 fois dans le corpus.
Refaire la question précédente en utilisant toujours un modèle
BigramCollocationFinder
suivi de la méthodeapply_freq_filter
pour ne conserver que les bigrammes présents au moins 5 fois. Puis, au lieu d’utiliser la méthode de maximum de vraisemblance, testez la méthodenltk.collocations.BigramAssocMeasures().jaccard
.Ne s’intéresser qu’aux collocations qui concernent le mot fear
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
= "https://www.gutenberg.org/files/17989/17989-0.txt"
url = request.urlopen(url)
response = response.read().decode("utf8")
raw
= raw.split(
dumas "*** 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.lower() # mettre les mots en minuscule
text = " ".join(text.split())
text return text
= clean_text(dumas)
dumas
10000:10500] dumas[
- 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
= spacy.load("fr_core_news_sm")
nlp = nlp(dumas[15000:17000])
doc ="ent", jupyter=True) displacy.render(doc, style
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 :
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) |
Notes de bas de page
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.