Cette page approfondit certains aspects présentés dans la partie introductive. Après avoir travaillé sur le Comte de Monte Cristo, on va continuer notre exploration de la littérature avec cette fois des auteurs anglophones :
- Edgar Allan Poe, (EAP) ;
- HP Lovecraft (HPL) ;
- Mary Wollstonecraft Shelley (MWS).
Les données sont disponibles sur un CSV mis à disposition sur Github
. L’URL pour les récupérer directement est
https://github.com/GU4243-ADS/spring2018-project1-ginnyqg/raw/master/data/spooky.csv.
Le but va être dans un premier temps de regarder dans le détail les termes les plus fréquemment utilisés par les auteurs et de les représenter graphiquement. Il s’agit donc d’une approche basée sur l’analyse de fréquences. On prendra appui sur l’approche bag of words présentée dans le chapitre précédent1. Il n’y aura pas de modélisation particulière, ceci est réservé aux chapitres suivants.
Ce chapitre s’inspire de plusieurs ressources disponibles en ligne:
- Un premier notebook sur
Kaggle
et un deuxième ; - Un dépôt
Github
;
Les chapitres suivants permettront d’introduire aux enjeux de modélisation
de corpus textuels. Dans un premier temps, le modèle LDA
permettra d’explorer
le principe des modèles bayésiens à couche cachées pour modéliser les sujets (topics)
présents dans un corpus et segmenter ces topics selon les mots qui les composent.
Le dernier chapitre de la partie visera à
prédire quel texte correspond à quel auteur à partir d’un modèle Word2Vec
.
Cela sera un pas supplémentaire dans la formalisation puisqu’il s’agira de
représenter chaque mot d’un texte sous forme d’un vecteur de grande dimension, ce
qui nous permettra de rapprocher les mots entre eux dans un espace complexe.
Cette technique, dite des plongements de mots (Word Embeddings),
permet ainsi de transformer une information complexe difficilement quantifiable
comme un mot
en un objet numérique qui peut ainsi être rapproché d’autres par des méthodes
algébriques. Pour découvrir ce concept, ce post de blog
est particulièrement utile. En pratique, la technique des
plongements de mots permet d’obtenir des tableaux comme celui-ci :
1 Librairies nécessaires
Cette page évoquera les principales librairies pour faire du NLP, notamment :
A celles-ci s’ajoute des librairies de représentations graphiques
synthétiques comme WordCloud,
Gensim
et Pywaffle
.
Hint
Comme dans la partie précédente, il faut télécharger quelques éléments pour que NTLK
puisse fonctionner correctement. Pour cela, faire :
import nltk
"stopwords")
nltk.download("punkt")
nltk.download("genesis")
nltk.download("wordnet")
nltk.download("omw-1.4") nltk.download(
La liste des modules à importer est assez longue, la voici :
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from wordcloud import WordCloud
import base64
import string
import re
import nltk
from collections import Counter
from time import time
# from sklearn.feature_extraction.stop_words import ENGLISH_STOP_WORDS as stopwords
from sklearn.metrics import log_loss
import matplotlib.pyplot as plt
#!pip install pywaffle
from pywaffle import Waffle
from nltk.stem import WordNetLemmatizer
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
from sklearn.decomposition import NMF, LatentDirichletAllocation
"stopwords")
nltk.download("punkt")
nltk.download("genesis")
nltk.download("wordnet")
nltk.download("omw-1.4") nltk.download(
[nltk_data] Downloading package stopwords to /github/home/nltk_data...
[nltk_data] Package stopwords is already up-to-date!
[nltk_data] Downloading package punkt to /github/home/nltk_data...
[nltk_data] Package punkt is already up-to-date!
[nltk_data] Downloading package genesis to /github/home/nltk_data...
[nltk_data] Unzipping corpora/genesis.zip.
[nltk_data] Downloading package wordnet to /github/home/nltk_data...
[nltk_data] Downloading package omw-1.4 to /github/home/nltk_data...
True
2 Données utilisées
Exercice 1 (optionnel): Importer les données spooky
Pour ceux qui ont envie de tester leurs connaissances en Pandas
- Importer le jeu de données
spooky
à partir de l’URL https://github.com/GU4243-ADS/spring2018-project1-ginnyqg/raw/master/data/spooky.csv sous le nomtrain
. L’encoding estlatin-1
- Mettre des majuscules au nom des colonnes.
- Retirer le prefix
id
de la colonneId
et appeler la nouvelle colonneID
. - Mettre l’ancienne colonne
Id
en index.
Si vous ne faites pas l’exercice 1, pensez à charger les données en executant la fonction get_data.py
:
import requests
= "https://raw.githubusercontent.com/linogaliana/python-datascientist/master/content/NLP/get_data.py"
url = requests.get(url, allow_redirects=True)
r open("getdata.py", "wb").write(r.content)
import getdata
= getdata.create_train_dataframes() train
Ce code introduit une base nommée train
dans l’environnement.
Le jeu de données met ainsi en regard un auteur avec une phrase qu’il a écrite :
train.head()
Text | Author | ID | |
---|---|---|---|
Id | |||
id26305 | This process, however, afforded me no means of... | EAP | 26305 |
id17569 | It never once occurred to me that the fumbling... | HPL | 17569 |
id11008 | In his left hand was a gold snuff box, from wh... | EAP | 11008 |
id27763 | How lovely is spring As we looked from Windsor... | MWS | 27763 |
id12958 | Finding nothing else, not even gold, the Super... | HPL | 12958 |
On peut se rendre compte que les extraits des 3 auteurs ne sont pas forcément équilibrés dans le jeu de données. Il faudra en tenir compte dans la prédiction.
= plt.figure()
fig = sns.barplot(
g =["Edgar Allen Poe", "Mary W. Shelley", "H.P. Lovecraft"],
x=train["Author"].value_counts(),
y )
Note
L’approche bag of words est présentée de manière plus extensive dans le chapitre précédent.
L’idée est d’étudier la fréquence des mots d’un document et la surreprésentation des mots par rapport à un document de référence (appelé corpus).
Cette approche un peu simpliste mais très efficace : on peut calculer des scores permettant par exemple de faire de classification automatique de document par thème, de comparer la similarité de deux documents. Elle est souvent utilisée en première analyse, et elle reste la référence pour l’analyse de textes mal structurés (tweets, dialogue tchat, etc.).
Les analyses tf-idf (term frequency-inverse document frequency) ou les constructions d’indices de similarité cosinus reposent sur ce type d’approche.
2.1 Fréquence d’un mot
Avant de s’adonner à une analyse systématique du champ lexical de chaque auteur, on va se focaliser dans un premier temps sur un unique mot, le mot fear.
Note
L’exercice ci-dessous présente une représentation graphique nommée waffle chart. Il s’agit d’une approche préférable aux camemberts qui sont des graphiques manipulables car l’oeil humain se laisse facilement berner par cette représentation graphique qui ne respecte pas les proportions.
Exercice 2 : Fréquence d'un mot
- Compter le nombre de phrases, pour chaque auteur, où apparaît le mot
fear
. - Utiliser
pywaffle
pour obtenir les graphiques ci-dessous qui résument de manière synthétique le nombre d’occurrences du mot “fear” par auteur. - Refaire l’analyse avec le mot “horror”.
A l’issue de la question 1, vous devriez obtenir le tableau de fréquence suivant :
Text | ID | wordtoplot | |
---|---|---|---|
Author | |||
EAP | This process, however, afforded me no means of... | 2630511008096741351519322166071718908441148621... | 70 |
HPL | It never once occurred to me that the fumbling... | 1756912958197641888620836080752790708121117330... | 160 |
MWS | How lovely is spring As we looked from Windsor... | 2776322965009121673712799131170076400683052582... | 211 |
Ceci permet d’obtenir le waffle chart suivant :
On remarque ainsi de manière très intuitive le déséquilibre de notre jeu de données lorsqu’on se focalise sur le terme “peur” où Mary Shelley représente près de 50% des observations.
Si on reproduit cette analyse avec le terme “horror”, on peut en conclure que la peur est plus évoquée par Mary Shelley (sentiment assez naturel face à la créature du docteur Frankenstein) alors que Lovecraft n’a pas volé sa réputation d’écrivain de l’horreur !
2.2 Premier wordcloud
Pour aller plus loin dans l’analyse du champ lexical de chaque auteur,
on peut représenter un wordcloud
qui permet d’afficher chaque mot avec une
taille proportionnelle au nombre d’occurrence de celui-ci.
Exercice 3 : Wordcloud
- En utilisant la fonction
wordCloud
, faire trois nuages de mot pour représenter les mots les plus utilisés par chaque auteur. - Calculer les 25 mots plus communs pour chaque auteur et représenter les trois histogrammes des décomptes.
Le wordcloud pour nos différents auteurs est le suivant :
Enfin, si on fait un histogramme des fréquences, cela donnera :
On voit ici que ce sont des mots communs, comme “the”, “of”, etc. sont très présents. Mais ils sont peu porteurs d’information, on peut donc les éliminer avant de faire une analyse syntaxique poussée. Ceci est une démonstration par l’exemple qu’il vaut mieux nettoyer le texte avant de l’analyser (sauf si on est intéressé par la loi de Zipf, cf. exercice suivant).
A noter que l’histogramme produit
par le biais de Matplotlib
ou Seaborn
est
peu lisible. Il vaut mieux privilégier Plotly
pour faire celui-ci afin d’avoir les mots qui s’affichent en
passant sa souris sur chaque barre.
2.3 Aparté : la loi de Zipf
La loi de Zipf
Dans son sens strict, la loi de Zipf prévoit que dans un texte donné, la fréquence d’occurrence \(f(n_i)\) d’un mot est liée à son rang \(n_i\) dans l’ordre des fréquences par une loi de la forme \(f(n_i) = c/n_i\) où \(c\) est une constante. Zipf, dans les années 1930, se basait sur l’oeuvre de Joyce, Ulysse pour cette affirmation.
Plus généralement, on peut dériver la loi de Zipf d’une distribution exponentielle des fréquences : \(f(n_i) = cn_{i}^{-k}\). Cela permet d’utiliser la famille des modèles linéaires généralisés, notamment les régressions poissonniennes, pour mesurer les paramètres de la loi. Les modèles linéaire traditionnels en log
souffrent en effet, dans ce contexte, de biais (la loi de Zipf est un cas particulier d’un modèle gravitaire, où appliquer des OLS est une mauvaise idée, cf. Galiana et al. (2020) pour les limites).
Un modèle exponentiel peut se représenter par un modèle de Poisson ou, si
les données sont très dispersées, par un modèle binomial négatif. Pour
plus d’informations, consulter l’annexe de Galiana et al. (2020).
La technique économétrique associée pour l’estimation est
les modèles linéaires généralisés (GLM) qu’on peut
utiliser en Python
via le
package statsmodels
2:
\[ \mathbb{E}\bigg( f(n_i)|n_i \bigg) = \exp(\beta_0 + \beta_1 \log(n_i)) \]
Prenons les résultats de l’exercice précédent et enrichissons les du rang et de la fréquence d’occurrence d’un mot :
= pd.DataFrame(
count_words
{"counter": train.groupby("Author")
apply(lambda s: " ".join(s["Text"]).split())
.apply(lambda s: Counter(s))
.apply(lambda s: s.most_common())
.
.explode()
}
)"word", "count"]] = pd.DataFrame(
count_words[["counter"].tolist(), index=count_words.index
count_words[
)= count_words.reset_index()
count_words
= count_words.assign(
count_words =lambda x: (x.groupby("Author")["count"].transform("sum")),
tot_mots_auteur=lambda x: x["count"] / x["tot_mots_auteur"],
freq=lambda x: x.groupby("Author")["count"].transform("rank", ascending=False),
rank )
/tmp/ipykernel_5393/3685081206.py:3: DeprecationWarning:
DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
Commençons par représenter la relation entre la fréquence et le rang:
from plotnine import *
= (
g
ggplot(count_words)+ geom_point(aes(y="freq", x="rank", color="Author"), alpha=0.4)
+ scale_x_log10()
+ scale_y_log10()
+ theme_minimal()
)
Nous avons bien, graphiquement, une relation log-linéaire entre les deux :
Avec statsmodels
, vérifions plus formellement cette relation:
import statsmodels.api as sm
= sm.add_constant(np.log(count_words["rank"].astype(float)))
exog
= sm.GLM(
model "freq"].astype(float), exog, family=sm.families.Poisson()
count_words[
).fit()
# Afficher les résultats du modèle
print(model.summary())
Generalized Linear Model Regression Results
==============================================================================
Dep. Variable: freq No. Observations: 69301
Model: GLM Df Residuals: 69299
Model Family: Poisson Df Model: 1
Link Function: Log Scale: 1.0000
Method: IRLS Log-Likelihood: -23.011
Date: Sat, 27 Apr 2024 Deviance: 0.065676
Time: 19:51:34 Pearson chi2: 0.0656
No. Iterations: 5 Pseudo R-squ. (CS): 0.0002431
Covariance Type: nonrobust
==============================================================================
coef std err z P>|z| [0.025 0.975]
------------------------------------------------------------------------------
const -2.4388 1.089 -2.239 0.025 -4.574 -0.303
rank -0.9831 0.189 -5.196 0.000 -1.354 -0.612
==============================================================================
Le coefficient de la régression est presque 1 ce qui suggère bien une relation quasiment log-linéaire entre le rang et la fréquence d’occurrence d’un mot. Dit autrement, le mot le plus utilisé l’est deux fois plus que le deuxième mot le plus fréquent qui l’est trois plus que le troisième, etc.
3 Nettoyage d’un texte
Les premières étapes dans le nettoyage d’un texte, qu’on a développé au cours du chapitre précédent, sont :
- suppression de la ponctuation ;
- suppression des stopwords.
Cela passe par la tokenisation d’un texte, c’est-à-dire la décomposition de celui-ci en unités lexicales (les tokens). Ces unités lexicales peuvent être de différentes natures, selon l’analyse que l’on désire mener. Ici, on va définir les tokens comme étant les mots utilisés.
Plutôt que de faire soi-même ce travail de nettoyage,
avec des fonctions mal optimisées,
on peut utiliser la librairie nltk
comme détaillé précédemment.
Exercice 4 : Nettoyage du texte
Repartir de train
, notre jeu de données d’entraînement.
- Tokeniser chaque phrase avec
nltk
. - Retirer les stopwords avec
nltk
.
Pour rappel, au début de l’exercice, train
présente l’aspect suivant :
Text | Author | ID | wordtoplot | |
---|---|---|---|---|
Id | ||||
id26305 | This process, however, afforded me no means of... | EAP | 26305 | 0 |
id17569 | It never once occurred to me that the fumbling... | HPL | 17569 | 0 |
Après tokenisation, il devrait avoir cet aspect :
/tmp/ipykernel_5393/3502394793.py:4: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
ID Author
00001 MWS [Idris, was, well, content, with, this, resolv...
00002 HPL [I, was, faint, even, fainter, than, the, hate...
dtype: object
Après le retrait des stopwords, cela donnera :
Hint
La méthode apply
est très pratique ici car nous avons une phrase par ligne. Plutôt que de faire un DataFrame
par auteur, ce qui n’est pas une approche très flexible, on peut directement appliquer la tokenisation
sur notre DataFrame
grâce à apply
, sans le diviser.
Ce petit nettoyage permet d’arriver à un texte plus intéressant en termes d’analyse lexicale. Par exemple, si on reproduit l’analyse précédente… :
Pour aller plus loin dans l’harmonisation d’un texte, il est possible de mettre en place les classes d’équivalence développées dans la partie précédente afin de remplacer différentes variations d’un même mot par une forme canonique :
la racinisation (stemming) assez fruste mais rapide, notamment en présence de fautes d’orthographe. Dans ce cas, chevaux peut devenir chev mais être ainsi confondu avec chevet ou cheveux. Cette méthode est généralement plus simple à mettre en oeuvre, quoique plus fruste.
la lemmatisation qui requiert la connaissance des statuts grammaticaux (exemple : chevaux devient cheval). Elle est mise en oeuvre, comme toujours avec
nltk
, à travers un modèle. En l’occurrence, unWordNetLemmatizer
(WordNet est une base lexicographique ouverte). Par exemple, les mots “women”, “daughters” et “leaves” seront ainsi lemmatisés de la manière suivante :
from nltk.stem import WordNetLemmatizer
= WordNetLemmatizer()
lemm
for word in ["women", "daughters", "leaves"]:
print(f"The lemmatized form of {word} is: {lemm.lemmatize(word)}")
The lemmatized form of women is: woman
The lemmatized form of daughters is: daughter
The lemmatized form of leaves is: leaf
Note
Pour disposer du corpus nécessaire à la lemmatisation, il faut, la première fois, télécharger celui-ci grâce aux commandes suivantes :
import nltk
"wordnet")
nltk.download("omw-1.4") nltk.download(
On va se restreindre au corpus d’Edgar Allan Poe et repartir de la base de données brute :
Exercice 5 : Lemmatisation avec nltk
Utiliser un WordNetLemmatizer
et observer le résultat.
Optionnel : Effectuer la même tâche avec spaCy
Le WordNetLemmatizer
donnera le résultat suivant :
This process , however , afforded me no means of ascertaining the dimensions of my dungeon ; as I might make its circuit , and return to the point whence I set out , without being aware of the fact ; so perfectly
---------------------------
This process , however , afforded me no mean of ascertaining the dimension of my dungeon ; a I might make it circuit , and return to the point whence I set out , without being aware of the fact ; so perfectly
4 TF-IDF: calcul de fréquence
Le calcul tf-idf (term frequency–inverse document frequency) permet de calculer un score de proximité entre un terme de recherche et un document (c’est ce que font les moteurs de recherche).
- 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).
Le score total, obtenu en multipliant les deux composantes, permet ainsi de donner un score d’autant plus élevé que le terme est surréprésenté dans un document (par rapport à l’ensemble des documents). Il existe plusieurs fonctions, qui pénalisent plus ou moins les documents longs, ou qui sont plus ou moins smooth.
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 est le plus élevé et l’auteur associé. Vous devriez obtenir le classement suivant :
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.253506 | 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 × 24937 columns
Les lignes où les termes de abandon sont non nuls sont les suivantes :
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')
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.253506 | 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.339101 | 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.235817 | 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.143788 | 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.285886 | 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 × 24937 columns
Author
MWS 22
HPL 15
EAP 13
Name: Text, 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.' 'I smiled, for what had I to fear?'
'Indeed I had no fear on her account.'
'I have not the slightest fear for the result.'
'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 fear you are right there," said the Prefect.'
'I went down to open it with a light heart, for what had I now to fear?']
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.
Note
La matrice document x terms
est un exemple typique de matrice sparse puisque, dans des corpus volumineux, une grande diversité de vocabulaire peut être trouvée.
5 Approche contextuelle : les n-grams
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 3 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
:
= train[train["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')]
6 Références
Informations additionnelles
environment files have been tested on.
Latest built version: 2024-04-27
Python version used:
'3.11.6 | packaged by conda-forge | (main, Oct 3 2023, 10:40:35) [GCC 12.3.0]'
Package | Version |
---|---|
affine | 2.4.0 |
aiobotocore | 2.12.2 |
aiohttp | 3.9.3 |
aioitertools | 0.11.0 |
aiosignal | 1.3.1 |
alembic | 1.13.1 |
aniso8601 | 9.0.1 |
annotated-types | 0.6.0 |
appdirs | 1.4.4 |
archspec | 0.2.3 |
astroid | 3.1.0 |
asttokens | 2.4.1 |
attrs | 23.2.0 |
Babel | 2.14.0 |
bcrypt | 4.1.2 |
beautifulsoup4 | 4.12.3 |
black | 24.4.2 |
blinker | 1.7.0 |
blis | 0.7.11 |
bokeh | 3.4.0 |
boltons | 23.1.1 |
boto3 | 1.34.51 |
botocore | 1.34.51 |
branca | 0.7.1 |
Brotli | 1.1.0 |
cachetools | 5.3.3 |
cartiflette | 0.0.2 |
Cartopy | 0.23.0 |
catalogue | 2.0.10 |
cattrs | 23.2.3 |
certifi | 2024.2.2 |
cffi | 1.16.0 |
charset-normalizer | 3.3.2 |
chromedriver-autoinstaller | 0.6.4 |
click | 8.1.7 |
click-plugins | 1.1.1 |
cligj | 0.7.2 |
cloudpathlib | 0.16.0 |
cloudpickle | 3.0.0 |
colorama | 0.4.6 |
comm | 0.2.2 |
commonmark | 0.9.1 |
conda | 24.3.0 |
conda-libmamba-solver | 24.1.0 |
conda-package-handling | 2.2.0 |
conda_package_streaming | 0.9.0 |
confection | 0.1.4 |
contextily | 1.6.0 |
contourpy | 1.2.1 |
cryptography | 42.0.5 |
cycler | 0.12.1 |
cymem | 2.0.8 |
cytoolz | 0.12.3 |
dask | 2024.4.1 |
dask-expr | 1.0.10 |
debugpy | 1.8.1 |
decorator | 5.1.1 |
dill | 0.3.8 |
distributed | 2024.4.1 |
distro | 1.9.0 |
docker | 7.0.0 |
duckdb | 0.10.1 |
en-core-web-sm | 3.7.1 |
entrypoints | 0.4 |
et-xmlfile | 1.1.0 |
exceptiongroup | 1.2.0 |
executing | 2.0.1 |
fastjsonschema | 2.19.1 |
fiona | 1.9.6 |
flake8 | 7.0.0 |
Flask | 3.0.2 |
folium | 0.16.0 |
fontawesomefree | 6.5.1 |
fonttools | 4.51.0 |
fr-core-news-sm | 3.7.0 |
frozenlist | 1.4.1 |
fsspec | 2023.12.2 |
GDAL | 3.8.4 |
gensim | 4.3.2 |
geographiclib | 2.0 |
geopandas | 0.12.2 |
geoplot | 0.5.1 |
geopy | 2.4.1 |
gitdb | 4.0.11 |
GitPython | 3.1.43 |
google-auth | 2.29.0 |
graphene | 3.3 |
graphql-core | 3.2.3 |
graphql-relay | 3.2.0 |
graphviz | 0.20.3 |
great-tables | 0.5.0 |
greenlet | 3.0.3 |
gunicorn | 21.2.0 |
h11 | 0.14.0 |
htmltools | 0.5.1 |
hvac | 2.1.0 |
idna | 3.6 |
imageio | 2.34.1 |
importlib_metadata | 7.1.0 |
importlib_resources | 6.4.0 |
inflate64 | 1.0.0 |
ipykernel | 6.29.3 |
ipython | 8.22.2 |
ipywidgets | 8.1.2 |
isort | 5.13.2 |
itsdangerous | 2.1.2 |
jedi | 0.19.1 |
Jinja2 | 3.1.3 |
jmespath | 1.0.1 |
joblib | 1.3.2 |
jsonpatch | 1.33 |
jsonpointer | 2.4 |
jsonschema | 4.21.1 |
jsonschema-specifications | 2023.12.1 |
jupyter-cache | 1.0.0 |
jupyter_client | 8.6.1 |
jupyter_core | 5.7.2 |
jupyterlab_widgets | 3.0.10 |
kaleido | 0.2.1 |
kiwisolver | 1.4.5 |
kubernetes | 29.0.0 |
langcodes | 3.4.0 |
language_data | 1.2.0 |
lazy_loader | 0.4 |
libmambapy | 1.5.7 |
llvmlite | 0.42.0 |
locket | 1.0.0 |
lxml | 5.2.1 |
lz4 | 4.3.3 |
Mako | 1.3.2 |
mamba | 1.5.7 |
mapclassify | 2.6.1 |
marisa-trie | 1.1.0 |
Markdown | 3.6 |
MarkupSafe | 2.1.5 |
matplotlib | 3.8.3 |
matplotlib-inline | 0.1.6 |
mccabe | 0.7.0 |
menuinst | 2.0.2 |
mercantile | 1.2.1 |
mizani | 0.11.2 |
mlflow | 2.11.3 |
mlflow-skinny | 2.11.3 |
msgpack | 1.0.7 |
multidict | 6.0.5 |
multivolumefile | 0.2.3 |
munkres | 1.1.4 |
murmurhash | 1.0.10 |
mypy | 1.9.0 |
mypy-extensions | 1.0.0 |
nbclient | 0.10.0 |
nbformat | 5.10.4 |
nest_asyncio | 1.6.0 |
networkx | 3.3 |
nltk | 3.8.1 |
numba | 0.59.1 |
numpy | 1.26.4 |
oauthlib | 3.2.2 |
opencv-python-headless | 4.9.0.80 |
openpyxl | 3.1.2 |
outcome | 1.3.0.post0 |
OWSLib | 0.28.1 |
packaging | 23.2 |
pandas | 2.2.1 |
paramiko | 3.4.0 |
parso | 0.8.4 |
partd | 1.4.1 |
pathspec | 0.12.1 |
patsy | 0.5.6 |
Pebble | 5.0.7 |
pexpect | 4.9.0 |
pickleshare | 0.7.5 |
pillow | 10.3.0 |
pip | 24.0 |
pkgutil_resolve_name | 1.3.10 |
platformdirs | 4.2.0 |
plotly | 5.19.0 |
plotnine | 0.13.5 |
pluggy | 1.4.0 |
polars | 0.20.18 |
preshed | 3.0.9 |
prometheus_client | 0.20.0 |
prometheus-flask-exporter | 0.23.0 |
prompt-toolkit | 3.0.42 |
protobuf | 4.25.3 |
psutil | 5.9.8 |
ptyprocess | 0.7.0 |
pure-eval | 0.2.2 |
py7zr | 0.20.8 |
pyarrow | 15.0.0 |
pyarrow-hotfix | 0.6 |
pyasn1 | 0.5.1 |
pyasn1-modules | 0.3.0 |
pybcj | 1.0.2 |
pycodestyle | 2.11.1 |
pycosat | 0.6.6 |
pycparser | 2.21 |
pycryptodomex | 3.20.0 |
pydantic | 2.7.1 |
pydantic_core | 2.18.2 |
pyflakes | 3.2.0 |
Pygments | 2.17.2 |
PyJWT | 2.8.0 |
pylint | 3.1.0 |
PyNaCl | 1.5.0 |
pynsee | 0.1.7 |
pyOpenSSL | 24.0.0 |
pyparsing | 3.1.2 |
pyppmd | 1.1.0 |
pyproj | 3.6.1 |
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.0 |
PyYAML | 6.0.1 |
pyzmq | 25.1.2 |
pyzstd | 0.15.10 |
QtPy | 2.4.1 |
querystring-parser | 1.2.4 |
rasterio | 1.3.10 |
referencing | 0.34.0 |
regex | 2023.12.25 |
requests | 2.31.0 |
requests-cache | 1.2.0 |
requests-oauthlib | 2.0.0 |
rpds-py | 0.18.0 |
rsa | 4.9 |
Rtree | 1.2.0 |
ruamel.yaml | 0.18.6 |
ruamel.yaml.clib | 0.2.8 |
s3fs | 2023.12.2 |
s3transfer | 0.10.1 |
scikit-image | 0.23.2 |
scikit-learn | 1.4.1.post1 |
scipy | 1.13.0 |
seaborn | 0.13.2 |
selenium | 4.20.0 |
setuptools | 69.2.0 |
shapely | 2.0.3 |
six | 1.16.0 |
smart-open | 6.4.0 |
smmap | 5.0.0 |
sniffio | 1.3.1 |
snuggs | 1.4.7 |
sortedcontainers | 2.4.0 |
soupsieve | 2.5 |
spacy | 3.7.4 |
spacy-legacy | 3.0.12 |
spacy-loggers | 1.0.5 |
SQLAlchemy | 2.0.29 |
sqlparse | 0.4.4 |
srsly | 2.4.8 |
stack-data | 0.6.2 |
statsmodels | 0.14.1 |
tabulate | 0.9.0 |
tblib | 3.0.0 |
tenacity | 8.2.3 |
texttable | 1.7.0 |
thinc | 8.2.3 |
threadpoolctl | 3.4.0 |
tifffile | 2024.4.24 |
tomli | 2.0.1 |
tomlkit | 0.12.4 |
toolz | 0.12.1 |
topojson | 1.8 |
tornado | 6.4 |
tqdm | 4.66.2 |
traitlets | 5.14.2 |
trio | 0.25.0 |
trio-websocket | 0.11.1 |
truststore | 0.8.0 |
typer | 0.9.4 |
typing_extensions | 4.11.0 |
tzdata | 2024.1 |
Unidecode | 1.3.8 |
url-normalize | 1.4.3 |
urllib3 | 1.26.18 |
wasabi | 1.1.2 |
wcwidth | 0.2.13 |
weasel | 0.3.4 |
webcolors | 1.13 |
webdriver-manager | 4.0.1 |
websocket-client | 1.7.0 |
Werkzeug | 3.0.2 |
wheel | 0.43.0 |
widgetsnbextension | 4.0.10 |
wordcloud | 1.9.3 |
wrapt | 1.16.0 |
wsproto | 1.2.0 |
xgboost | 2.0.3 |
xlrd | 2.0.1 |
xyzservices | 2024.4.0 |
yarl | 1.9.4 |
yellowbrick | 1.5 |
zict | 3.0.0 |
zipp | 3.17.0 |
zstandard | 0.22.0 |
View file history
SHA | Date | Author | Description |
---|---|---|---|
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) |
References
Galiana, Lino, and Milena Suarez Castillo. 2022. “Fuzzy Matching on Big-Data an Illustration with Scanner Data and Crowd-Sourced Nutritional Data.”
Galiana, Lino, François Sémécurbe, Benjamin Sakarovitch, and Zbigniew Smoreda. 2020. “Residential Segregation, Daytime Segregation and Spatial Frictions: An Analysis from Mobile Phone Data.”
Footnotes
L’approche bag of words est déjà, si on la pousse à ses limites, très intéressante. Elle peut notamment faciliter la mise en cohérence de différents corpus par la méthode des appariements flous (cf. Galiana and Castillo (2022). Le chapitre sur ElasticSearch présent dans cette partie du cours présente quelques éléments de ce travail sur les données de l’
OpenFoodFacts
.↩︎La littérature sur les modèles gravitaires, présentée dans Galiana et al. (2020), donne quelques arguments pour privilégier les modèles GLM à des modèles log-linéaires estimés par moindres carrés ordinaires.↩︎
On parle de bigrams pour les co-occurences de mots deux-à-deux, trigrams pour les co-occurences trois-à-trois, etc.↩︎
Citation
BibTeX citation:
@book{galiana2023,
author = {Galiana, Lino},
title = {Python Pour La Data Science},
date = {2023},
url = {https://pythonds.linogaliana.fr/},
doi = {10.5281/zenodo.8229676},
langid = {en}
}
For attribution, please cite this work as:
Galiana, Lino. 2023. Python Pour La Data Science. https://doi.org/10.5281/zenodo.8229676.