François HU - Data scientist au DataLab de la Société Générale Assurances - 05/07/21
from jyquickhelper import add_notebook_menu
add_notebook_menu()
Les topic models (modèles thématiques en français) sont une famille de modèles qui sont capables de découvrir les topics (thèmes en français) d'une collection de documents textuels. Dans ce contexte, le terme "topic" désigne des groupes de mots qui se retrouvent souvent ensemble dans un même document. Par exemple, dans un recueil d'articles de journaux, un topic model peut identifier un topic composé des mots :
Les topics modeling ne peuvent pas affecter un titre à ces topics : c'est notre tâche d'interpréter ces topics et de leur donner des étiquettes telles que politique et football.
La particularité de ces méthodes c'est que nous n'avons pas de labels : ce sont des tâches non-supervisées.
L'un des modèles les plus populaires est le LDA. Le LDA est un modèle génératif qui considère chaque document comme un mélange de topics. Ce sont ces topics qui seront en charge de générer les mots. Par exemple,
L'un des contextes où la modélisation des topics est très utile est celui des questions ouvertes. Il nous permet d'explorer les différents topics abordés dans les réponses des gens.
Dans ce notebook nous allons explorer un ensemble complet de réponses du Grand Débat National, un débat public organisé par le président Macron. Le but du débat était de mieux comprendre les besoins et les opinions des Français suite aux manifestations des gilets jaunes. Les résultats de ce débat sont maintenant disponibles sous forme de données ouvertes.
%%capture captured
import pandas as pd
# indiquer le chemin du fichier
chemin = "https://raw.githubusercontent.com/curiousML/DSA/master/text_mining/data/LA_TRANSITION_ECOLOGIQUE.csv"
#"/chemin/menant/a/LA_TRANSITION_ECOLOGIQUE.csv"
# notre dataframe
raw_data = pd.read_csv(chemin, error_bad_lines=False, warn_bad_lines=False)
Chacune des lignes de ce dataframe raw_data
contient des réponses répondant à une liste de questions sur la transition écologique. Certaines de ces questions sont à choix multiples, tandis que d'autres sont des questions ouvertes.
raw_data.columns
Index(['reference', 'title', 'createdAt', 'publishedAt', 'updatedAt', 'trashed', 'trashedStatus', 'authorId', 'authorType', 'authorZipCode', 'Quel est aujourd'hui pour vous le problème concret le plus important dans le domaine de l'environnement ?', 'Que faudrait-il faire selon vous pour apporter des réponses à ce problème ?', 'Diriez-vous que votre vie quotidienne est aujourd'hui touchée par le changement climatique ?', 'Si oui, de quelle manière votre vie quotidienne est-elle touchée par le changement climatique ?', 'À titre personnel, pensez-vous pouvoir contribuer à protéger l'environnement ?', 'Si oui, que faites-vous aujourd'hui pour protéger l'environnement et/ou que pourriez-vous faire ?', 'Qu'est-ce qui pourrait vous inciter à changer vos comportements comme par exemple mieux entretenir et régler votre chauffage, modifier votre manière de conduire ou renoncer à prendre votre véhicule pour de très petites distances ?', 'Quelles seraient pour vous les solutions les plus simples et les plus supportables sur un plan financier pour vous inciter à changer vos comportements ?', 'Par rapport à votre mode de chauffage actuel, pensez-vous qu'il existe des solutions alternatives plus écologiques ?', 'Si oui, que faudrait-il faire pour vous convaincre ou vous aider à changer de mode de chauffage ?', 'Avez-vous pour vos déplacements quotidiens la possibilité de recourir à des solutions de mobilité alternatives à la voiture individuelle comme les transports en commun, le covoiturage, l'auto-partage, le transport à la demande, le vélo, etc. ?', 'Si oui, que faudrait-il faire pour vous convaincre ou vous aider à utiliser ces solutions alternatives ?', 'Si non, quelles sont les solutions de mobilité alternatives que vous souhaiteriez pouvoir utiliser ?', 'Et qui doit selon vous se charger de vous proposer ce type de solutions alternatives ?', 'Que pourrait faire la France pour faire partager ses choix en matière d'environnement au niveau européen et international ?', 'Y a-t-il d'autres points sur la transition écologique sur lesquels vous souhaiteriez vous exprimer ?'], dtype='object')
Nous allons étudier la dernière question :
Y a-t-il d'autres points sur la transition écologique sur lesquels vous souhaiteriez vous exprimer ?
car cette dernière laisse plus de liberté aux personnes. Notre objectif est d'analyser les topics sur lesquels portent leurs réponses grâce au modèle LDA.
question = "Y a-t-il d'autres points sur la transition écologique sur lesquels vous souhaiteriez vous exprimer ?"
data = raw_data[question]
data.head()
0 Multiplier les centrales géothermiques 1 Les problèmes auxquels se trouve confronté l’e... 2 NaN 3 NaN 4 Une vrai politique écologique et non économique Name: Y a-t-il d'autres points sur la transition écologique sur lesquels vous souhaiteriez vous exprimer ?, dtype: object
Nous pouvons remarquer qu'il y a beaucoup de données manquantes (comme toute question ouverte, les personnes décident oui ou non d'écrire un commentaire). Une étape de néttoyage est donc nécessaire.
Avant d'entraîner notre modèle LDA, nous avons besoin de tokenizer notre texte. Nous allons tokenizer grâce à la librarie spaCy car nous allons effectuer seulement quelques prétraitements de base. Nous allons juste initialiser un modèle vierge pour la langue française.
Remarques : Après avoir installé la librairie SpaCy (pip install spacy), il faut télécharger un pipeline entraîné pour la langue française.
python -m spacy download fr_core_news_sm
spaCy fournit une variété d'annotations linguistiques pour vous donner un aperçu de la structure grammaticale d'un texte. Cela inclut entre autres des preprocessing classique en NLP à savoir la lemmatisation.
import spacy
nlp = spacy.load("fr_core_news_sm")
Supprimons toutes les lignes du dataframe qui n'ont pas de réponse pour notre question (les NaN
s ci-dessus). Ce nouveau dataframe s'appellera textes
textes = data[data.notnull()] # A REMPLIR
textes.head()
0 Multiplier les centrales géothermiques 1 Les problèmes auxquels se trouve confronté l’e... 4 Une vrai politique écologique et non économique 5 Les bonnes idées ne grandissent que par le par... 6 Pédagogie dans ce sens là dés la petite école ... Name: Y a-t-il d'autres points sur la transition écologique sur lesquels vous souhaiteriez vous exprimer ?, dtype: object
Ensuite, nous utilisons spaCy pour effectuer notre premier prétraitement (cela peut prendre quelques minutes) :
%%time
spacy_docs = list(nlp.pipe(textes))
CPU times: user 3min 33s, sys: 42.2 s, total: 4min 16s Wall time: 4min 25s
Nous avons maintenant une liste de documents spaCy. Nous allons transformer chaque document spaCy en une liste de tokens. Au lieu des tokens originaux, nous allons travailler avec les lemmes à la place. Cela permettra à notre modèle de mieux généraliser. En effet, nous voulons par exemple que "continuation" et "continuations" représentent la même signification. Voici la liste complète des prétraitements :
%%time
docs = []
for doc in spacy_docs:
tokens = []
for token in doc:
if len(token.orth_) > 3 and not token.is_stop: # prétraitements 1 et 2
tokens.append( token.lemma_.lower() ) # TODO: prétraitements 3 et 4
docs.append( tokens )
CPU times: user 1.55 s, sys: 155 ms, total: 1.71 s Wall time: 1.72 s
Voici un aperçu du premier document tokenisé :
print(docs[0])
['multiplier', 'centrale', 'géothermique']
deuxième document tokenisé :
print(docs[1])
['problème', 'trouve', 'confronter', 'ensemble', 'planète', 'dénoncent', 'parfaire', 'désordre', 'gilet', 'jaune', 'france', 'il', 'surpopulation', 'mondial', 'population', 'passer', 'd’1,5', 'milliard', 'habitant', '1900', 'milliard', '2020', 'monter', 'bientôt', 'milliard', '2040', 'progrès', 'communication', 'village', 'mondial', 'individu', 'fondre', 'asie', 'fondre', 'afrique', 'passer', 'quartiers', 'campagne', 'pays', 'aspir', 'vivr', 'blâmer', 'lotir', 'concitoyen', 'logement', 'nourriture', 'bien', 'consommation', 'déplacement', 'etc.', 'mère', 'problème', 'bien', 'solution', 'problème', 'stabilisation', 'croissance', 'démographique', 'partage', 'richesse', 'partage', 'terre', 'partage', 'protection', 'biodiversité', 'règlement', 'conflit', 'lutte', 'contre', 'déforestation', 'lutte', 'contre', 'dérèglement', 'climatique', 'règlement', 'conflit', 'stabilisation', 'migration', 'concurrence', 'commercial', 'mondial', 'etc.', 'français', 'européen', 'mondial', 'france', 'jouer', 'rôle', 'moteur', 'autour', 'dérouler', 'grand', 'débat', 'paraître', 'anecdotique']
Afin de conserver un peu l'ordre des mots lors de notre modélisation, nous allons tenir en compte les bigrammes fréquents. Pour cela, nous allons utiliser la bibliothèque Gensim (excellente bibliothèque NLP pour les topics modeling et word embeddings)
Voici le processus retenu :
Cela signifie que les bigrammes ne seront pas dans leur position correcte dans le texte, mais comme les topic models sont des modèles de bag-of-words (sac-de-mots en français) qui ignorent la position des mots, cela ne pose pas de problèmes.
import re
from gensim.models import Phrases
bigram = Phrases(docs, min_count=10)
for index in range(len(docs)):
for token in bigram[docs[index]]:
if '_' in token: # les bigrammes peuvent être reconnus par "_" qui concatène les mots
docs[index].append(token) # TODO
/Users/Faugon/opt/anaconda3/envs/dsa_nlp/lib/python3.9/site-packages/gensim/similarities/__init__.py:15: UserWarning: The gensim.similarities.levenshtein submodule is disabled, because the optional Levenshtein package <https://pypi.org/project/python-Levenshtein/> is unavailable. Install Levenhstein (e.g. `pip install python-Levenshtein`) to suppress this warning. warnings.warn(msg)
print(docs[4])
['pédagogie', 'sens', 'petit', 'école', 'sensibilisation', 'parc', 'naturel', 'enfant', 'devenir', 'prescripteur', 'génération', 'futur', 'urgence', 'génération_futur']
Passons à la dernière étape du prétraitement spécifique à Gensim. Nous allons tout d'abord créer une représentation dictionnaire des documents. Ce dictionnaire mappera chaque mot à un identifiant unique et nous aidera à créer des représentations en sac-de-mot de chaque document. Ces représentations en sac-de-mots contiennent les identificateurs des mots du document ainsi que leur fréquence (ici en nombre d'occurences). De plus, nous pouvons supprimer les mots les moins fréquents et les plus fréquents du vocabulaire. Cela améliorera la qualité de notre modèle et accélèrera son entraînement.
from gensim.corpora import Dictionary
dictionary = Dictionary(docs)
print('Nombre de mots unique dans les documents initiaux :', len(dictionary))
dictionary.filter_extremes(no_below=3, no_above=0.25)
print('Nombre de mots unique dans les documents après avoir enlevé les mots fréquents/peu fréquents :', len(dictionary))
print("Exemple :", dictionary.doc2bow(docs[4]))
Nombre de mots unique dans les documents initiaux : 33319 Nombre de mots unique dans les documents après avoir enlevé les mots fréquents/peu fréquents : 12869 Exemple : [(170, 1), (171, 1), (172, 1), (173, 1), (174, 1), (175, 1), (176, 1), (177, 1), (178, 1), (179, 1), (180, 1), (181, 1), (182, 1)]
Ensuite, nous créons des représentations en Bag-of-Words pour chaque document du corpus voir la méthode doc2bow :
corpus = [ dictionary.doc2bow(doc) for doc in docs] # TODO
Maintenant, il est temps d'entraîner notre LDA ! Pour ce faire, nous utilisons les paramètres suivants :
Remarquons que l'entraînement peut durer quelques minutes
%%time
from gensim.models import LdaModel
model = LdaModel(corpus=corpus, id2word=dictionary, num_topics=10, chunksize=1000, passes=5, random_state=1) # A REMPLIR
CPU times: user 38.6 s, sys: 216 ms, total: 38.9 s Wall time: 38 s
Voyons ce que le modèle a appris. Pour ce faire, affichons les dix mots les plus caractéristiques pour chacun des topics. Nous obeservons déjà des tendances intéressantes : si certains topics sont plus généraux (comme le topic 3), d'autres font référence à des topics très pertinents :
for (topic, words) in model.print_topics():
print("***********")
print("* topic", topic+1, "*")
print("***********")
print(topic+1, ":", words)
print()
*********** * topic 1 * *********** 1 : 0.041*"produit" + 0.035*"agriculture" + 0.023*"pesticide" + 0.022*"environnement" + 0.018*"interdir" + 0.017*"agriculteur" + 0.016*"animal" + 0.015*"santé" + 0.015*"industriel" + 0.014*"chasse" *********** * topic 2 * *********** 2 : 0.043*"terme" + 0.027*"long" + 0.022*"logement" + 0.017*"bâtiment" + 0.017*"mode" + 0.015*"construction" + 0.015*"court" + 0.014*"long_terme" + 0.011*"chauffage" + 0.011*"public" *********** * topic 3 * *********** 3 : 0.021*"falloir" + 0.017*"faire" + 0.012*"bien" + 0.012*"france" + 0.010*"pays" + 0.010*"planète" + 0.009*"problème" + 0.008*"être" + 0.008*"politique" + 0.008*"monde" *********** * topic 4 * *********** 4 : 0.070*"énergie" + 0.042*"nucléaire" + 0.019*"renouvelable" + 0.017*"éolien" + 0.017*"développer" + 0.014*"centrale" + 0.013*"production" + 0.012*"fossile" + 0.012*"recherche" + 0.012*"falloir" *********** * topic 5 * *********** 5 : 0.010*"public" + 0.010*"investissement" + 0.009*"pouvoir" + 0.009*"aide" + 0.008*"coût" + 0.008*"économie" + 0.008*"travail" + 0.007*"entreprise" + 0.007*"dépense" + 0.007*"mettre" *********** * topic 6 * *********** 6 : 0.064*"écologique" + 0.058*"transition" + 0.036*"transition_écologique" + 0.028*"faire" + 0.018*"falloir" + 0.013*"citoyen" + 0.012*"taxe" + 0.012*"entreprise" + 0.011*"pollueur" + 0.011*"écologie" *********** * topic 7 * *********** 7 : 0.020*"produit" + 0.014*"local" + 0.012*"ville" + 0.012*"animal" + 0.011*"grand" + 0.009*"zone" + 0.008*"centre" + 0.008*"favoriser" + 0.006*"commerce" + 0.006*"consommation" *********** * topic 8 * *********** 8 : 0.047*"transport" + 0.024*"taxer" + 0.014*"route" + 0.014*"camion" + 0.014*"avion" + 0.011*"taxe" + 0.010*"vitesse" + 0.010*"carburant" + 0.009*"poids" + 0.009*"routier" *********** * topic 9 * *********** 9 : 0.031*"véhicule" + 0.022*"voiture" + 0.016*"diesel" + 0.014*"falloir" + 0.014*"plastique" + 0.012*"faire" + 0.012*"produit" + 0.012*"emballage" + 0.011*"acheter" + 0.011*"déchet" *********** * topic 10 * *********** 10 : 0.048*"électrique" + 0.035*"voiture" + 0.020*"voiture_électrique" + 0.017*"panneau" + 0.017*"batterie" + 0.016*"véhicule" + 0.015*"solaire" + 0.014*"hydrogène" + 0.012*"électricité" + 0.011*"écologique"
Une autre façon d'observer les topics est de les visualiser. Ceci peut être fait avec la bibliothèque pyLDAvis. PyLDAvis nous illustre :
Notez que cela peut prendre quelques minutes pour charger.
import pyLDAvis.gensim_models
import warnings
pyLDAvis.enable_notebook()
warnings.filterwarnings("ignore", category=DeprecationWarning)
pyLDAvis.gensim_models.prepare(model, corpus, dictionary, sort_topics=False)
/Users/Faugon/opt/anaconda3/envs/dsa_nlp/lib/python3.9/site-packages/pyLDAvis/_prepare.py:246: FutureWarning: In a future version of pandas all arguments of DataFrame.drop except for the argument 'labels' will be keyword-only default_term_info = default_term_info.sort_values( /Users/Faugon/opt/anaconda3/envs/dsa_nlp/lib/python3.9/site-packages/past/builtins/misc.py:45: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses from imp import reload /Users/Faugon/opt/anaconda3/envs/dsa_nlp/lib/python3.9/site-packages/past/builtins/misc.py:45: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses from imp import reload /Users/Faugon/opt/anaconda3/envs/dsa_nlp/lib/python3.9/site-packages/past/builtins/misc.py:45: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses from imp import reload /Users/Faugon/opt/anaconda3/envs/dsa_nlp/lib/python3.9/site-packages/past/builtins/misc.py:45: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses from imp import reload
Enfin, examinons les topics que le modèle reconnaît dans certains des documents individuels. Nous voyons ici comment LDA tend à attribuer une probabilité élevée à un faible nombre de sujets pour chaque document, ce qui rend ses résultats très interprétables.
# Nous en affichons que 4
n_doc = 4
i = 0
for (text, doc) in zip(textes[:n_doc], docs[:n_doc]):
i += 1
print("***********")
print("* doc", i, " *")
print("***********")
print(text)
print([(topic+1, prob) for (topic, prob) in model[dictionary.doc2bow(doc)] if prob > 0.1])
print()
*********** * doc 1 * *********** Multiplier les centrales géothermiques [(4, 0.77492505)] *********** * doc 2 * *********** Les problèmes auxquels se trouve confronté l’ensemble de la planète et que dénoncent, dans le plus parfait désordre, les gilets jaunes de France ne sont-ils pas dus, avant tout, à la surpopulation mondiale ? Cette population est passée d’1,5 milliards d’habitants en 1900 à 7 milliards en 2020 et montera bientôt à 10 milliards vers 2040. Avec les progrès de la communication dans ce village mondial, chaque individu, du fin fond de l’Asie au fin fond de l’Afrique, en passant par les « quartiers » et les « campagnes » de notre pays, aspire à vivre – et on ne peu l’en blâmer – comme les moins mal lotis de nos concitoyens (logement, nourriture, biens de consommation, déplacement,etc.). Voilà la mère de tous les problèmes. Si tel est bien le cas, la solution à tous les problèmes (stabilisation de la croissance démographique, partage des richesses, partage des terres, partage de l’eau, protection de la biodiversité, règlement des conflits, lutte contre la déforestation, lutte contre dérèglement climatique, règlement des conflits, stabilisation des migrations, concurrence commerciale mondiale, etc.) ne sera ni française, ni européenne, mais mondiale. La France se doit d’y jouer un rôle moteur. Le reste, autour duquel se déroulera « le Grand débat », paraît assez anecdotique. [(3, 0.8163948)] *********** * doc 3 * *********** Une vrai politique écologique et non économique [(6, 0.8199855)] *********** * doc 4 * *********** Les bonnes idées ne grandissent que par le partage. En ces jours pénibles où s'affrontent le peuple et ceux entre lesquels ils ont remis leur sort. (tous concernés puis qu’élus). Une idée qui flotte en ce moment sur la mobilité propre dans les zones non ou mal desservies et qui est encore expérimentale dans quelques communes. L'avenir est entre vos mains et les nôtres. Pourquoi ne pas planifier l'installation de flotte de véhicules électriques partagés en location, dans ces zones isolées. En construisant un partenariat avec : état, région, département, commune ou communauté, professionnel de la location, entreprises locales, constructeurs de véhicules, les bailleurs sociaux, pour la mise en place. Avec l'apport financier de la TICPE qui baissera au fil de la décroissance de la consommation, il faudra penser à la déconnecter du budget de fonctionnement de l'état ! Avec un forfait de location pour les utilisateurs, adapté à leur ressources et peut être tout simplement basé sur leurs dépenses de mobilité actuelles. Ainsi pas d'investissements inaccessibles pour eux. Tout le monde y gagnerait, cela pourrait prendre l'image du relais de poste comme par le passé et réglerait le problème d'autonomie actuel et engendrerait quelques emplois de service, nouveaux. [(5, 0.5128911), (9, 0.15113485), (10, 0.13670318)]
Conclusion
De nombreuses collections de textes non structurés ne sont pas accompagnées de labels. Les topic models (modèles thématiques en français) tels que le LDA sont une technique utile pour découvrir les topics les plus importants dans ces documents. Gensim facilite l'apprentissage sur ces sujets et pyLDAvis présente les résultats d'une manière visuellement attrayante. Ensemble, ils forment une puissante boîte à outils pour mieux comprendre et explorer des sous-ensembles de textes connexes. Si ces résultats sont souvent déjà très révélateurs, il est également possible de les utiliser comme point de départ, par exemple pour un exercice de labellisation pour la classification supervisée de textes. En somme, les modèles thématiques devraient figurer dans la boîte à outils de chaque data scientist comme un moyen très rapide d'obtenir un aperçu des grandes collections de documents.