NLP & fastai | Topic Modeling

Pierre Guillou
10 min readAug 22, 2019

--

Topic model (image credit: DSI Workshop — Topic Modeling with LDA)

Ce post concerne les vidéos 1 à 3 du cours fastai de Rachel Thomas sur NLP (A code-first introduction to NLP) ainsi que la vidéo 2 de son cours sur Computational Linear Algebra. Son objectif est d’expliquer les concepts clés du Topic Modeling (SVD, NMF) présentés dans ces vidéos et leurs notebooks 3 et 4 associés.

Autres posts de la série NLP & fastai: Sentiment Classification | Language Model | Transfer Learning | ULMFiT | MultiFit | French Language Model | Portuguese Model Language | RNN | LSTM & GRU | SentencePiece | Sequence-to-Sequence Model (seq2seq) | Attention Mechanism | Transformer Model | GPT-2

Motivation

Un document textuel, même spécialisé (ex: article sur un sport), contient plusieurs thèmes (topic en anglais). Prenons l’exemple du texte ci-dessous: ce texte possède un thème principal (topic 2) sur les fake videos mais également au moins 2 autres thèmes (corps humain et président).

Un paragraphe avec plusieurs thèmes (Image Credit: A Stepwise Introduction to Topic Modeling using Latent Semantic Analysis (using Python))

Pouvoir automatiquement détecter/extraire les thèmes de documents textuels est très utile pour améliorer la classification/étiquetage de documents, permettre la recommandation de documents à partir d’un document initial et aider à la détection de tendances.

Ainsi, l’objet de ce post est de présenter 2 techniques d’extractions de thèmes de documents textuels (Topic Modeling en anglais): SVD et NMF. Il est à noter que ces techniques sont des techniques statistiques, pas sémantiques, même si leur utilisation nous permet de mieux comprendre les thèmes d’un corpus, donc in fine sa sémantique. Mais, elles ne recherchent pas à comprendre les textes afin de déterminer leurs thèmes. Elles reposent uniquement sur une fréquence d’occurrence de mots par rapport aux thèmes, et plus le corpus est grand et plus elles sont performantes.

Topic Modeling

Le Topic Modeling (TM) consiste à trouver l’information contenue dans des documents textuels (information retrieval en anglais) et de la présenter sous la forme de thèmes (en fonction de la technique utilisée, l’importance relative des thèmes peut aussi être trouvée).

Le TM est donc une technique non supervisée de classement de documents dans de multiples thèmes (Unsupervised Learning en anglais).

Du point de vue de l’espace de représentation, le TM est une réduction de dimensions dans la représentation vectorielle d’un document: au lieu de représenter un document d’un corpus par un vecteur dans l’espace des mots composant le vocabulaire de ce corpus, on le représente par un vecteur dans l’espace des thèmes de ce corpus, chaque valeur de ce vecteur correspondant à l’importance relative du thème dans ce document.

Comme le montre l’image ci-dessous, le TM consiste alors à détecter les thèmes du corpus et à décomposer la matrice initiale Document-Term du corpus (à chaque document est associée sa distribution en mots du vocabulaire du corpus) en matrices Document-Topic (à chaque document, son vecteur dans l’espace des thèmes) et Topic-Term (à chaque thème, son vecteur dans l’espace des mots du vocabulaire du corpus).

Le Topic Modeling appliqué à un corpus de documents (Image Credit: A Stepwise Introduction to Topic Modeling using Latent Semantic Analysis (using Python))

Note: les techniques présentées ici sont qualifiées de bag-of-words car elles ne prennent pas en compte ni l’ordre des mots, ni la syntaxe des documents.

Définitions

  • Corpus: ensemble fini de documents.
  • Document: ensemble de thèmes (topics).
  • Thème (topic): ensemble de termes portant sur un sujet particulier.
  • Terme: entité (ou unité) lexicale.
  • Vocabulaire: ensemble des termes du corpus.
  • Topic Modeling: processus d’extraction des différents thèmes (topics) d’un corpus.
  • LSA (Latent Semantic Analysis): LSA est l’une des techniques fondamentales de la modélisation par thème (Topic Modeling). Elle consiste à prendre une matrice du corpus sous la forme Document-Term et de la décomposer (factoriser) en une matrice Document-Topic et une matrice Topic-Term.
  • Stop words: liste de mots retirés du vocabulaire du corpus car non distinctifs (ex: NLTK, spaCy et scikit-learn disposent de telles listes dans différentes langues). Cette option est utilisée (souvent 200 à 300 mots) pour le traitement d’un corpus avec peu de documents afin de ne pas donner de l’importance à des mots communs mais la tendance générale en NLP est de ne plus utiliser ce type de liste qui vient enlever de l’information.

Implémentation en Python

Le code ci-dessous utilise Python et différentes bibliothèques dans un Jupyter Notebook. Nous n’y utilisons pas toutes les options possibles de codage des techniques de Topic Modeling afin d’attirer l’attention du lecteur sur les points essentiels. Cependant, à la fin du post, nous proposons des améliorations du code pour celles et ceux qui souhaiteraient aller plus loin (par exemple, l’utilisation de poids TF-IDF pour créer la matrice Document-Term au lieu de poids comptabilisant uniquement la fréquence d’apparition d’un mot dans un document).

Les 4 étapes basiques d’un code de Topic Modeling que nous détaillons ci-après sont ainsi les suivantes:

  1. Initialisation (importation des bibliothèques de notre environnement de travail)
  2. Données: importation et vérification
  3. Matrice Document-Term (modélisation des données en une matrice qui pourra être utilisée par les algorithmes de Topic Modeling)
  4. SVD ou NMF (application des algorithmes de Topic Modeling qui permettent d’extraire les thèmes de la matrice Document-Term)

Voici à présent des explications détaillées sur ces 4 étapes.

1. Initialisation

Il s’agit de construire notre environnement de travail en important les bibliothèques que nous utiliserons dans le Jupyter Notebook.

Nous avons besoin d’une extension python pour manipuler des matrices (puisque nous allons modéliser nos données textuelles en une matrice donnant pour chaque post sa distribution dans le vocabulaire du corpus, puis nous allons décomposer cette matrice) et d’importer des fonctions SVD et NMF pour décomposer (factoriser) la matrice initiale en matrices Document-Topic et Topic-Term.

Pour cela, voici les bibliothèques que nous devons importer au début de notre notebook:

  • numpy: extension du langage python qui permet d’effectuer des opérations mathématiques sur des matrices.
  • linalg: bibliothèque d’algèbre linéaire de scipy permettant d’appliquer SVD.
  • decomposition: bibliothèque de sklearn permettant d’appliquer NMF.
  • matplotlib: bibliothèque pour visualiser les graphes.

Enfin, voici le code correspondant à implémenter:

import numpy as np # operations on matricesfrom scipy import linalg # SVD
from sklearn import decomposition # NMF
import matplotlib.plot as plt
%matplotlib inline # plot in the notebook
np.set_printoptions(suppress=True) # do not use Scientific Notation

2. Données : importation et vérification

Il nous faut importer nos données textuelles puis, avant de les manipuler, il s’agit de vérifier leur nombre et d’en visualiser quelque unes (note: nous limitons l’import des données à 4 catégories afin d’avoir une quantité faible de données permettant ainsi de vérifier rapidement l’exactitude de notre code).

Note: comme bonne pratique, il est recommandé d’importer d’abord un échantillon des données afin de développer notre code avant de l’appliquer à l’ensemble des données.

Nous utilisons ici comme données fetch_20newsgroups qui est un dataset de 18 000 posts étiquetés en 20 catégories (thèmes) issu de sklearn.datasets.

Importation

`from sklearn.datasets import fetch_20newsgroups # dataset

Vérification

# select 4 categories out of 18
categories = ['alt.atheism', 'talk.religion.misc', 'comp.graphics', 'sci.space']
remove = ('headers', 'footers', 'quotes')
# get train and test datasets
newsgroups_train = fetch_20newsgroups(subset='train', categories=categories, remove=remove)
newsgroups_test = fetch_20newsgroups(subset='test', categories=categories, remove=remove)
# check the number of reviews
newsgroups_train.filenames.shape, newsgroups_train.target.shape
# display the first 3 reviews
print("/n".join(newsgroups_train.data[:3]))

3. Matrice Document-Term

Afin de pouvoir appliquer les techniques de Topic Modeling à nos données textuelles, il nous faut avant toute chose les modéliser en une matrice Document-Term (note: en plus de cette matrice, nous obtiendrons aussi le vocabulaire du corpus). Nous utilisons ici la classe CountVectorizer de scikit-learn avec comme option une liste de stop words (cf. Annexe en fin de post pour une explication détaillée sur l’utilisation de la classe CountVectorizer).

Note: pour utiliser le TF-IDF de chaque terme du vocabulaire au lieu de sa fréquence d’apparition par document, il faut importer la classe TfidfVectorizer de scikit-learn au lieu de CountVectorizer.

from sklearn.features_extraction.text import CountVectorizer# get the Document-Term matrix (vectors)
vectorizer = CountVectorizer(stop_words="english")
vectors = vectorizer.fit_transform(newsgroups_train.data).todense() # (documents, vocab)
# check the vectors shape
print(len(newsgroups_train.data), vectors.shape)
# get the vocab
vocab = np.array(vectorizer.get_features_names())

4. SVD ou NMF

4.1 Singular Value Decomposition (SVD)

Il s’agit à présent de décomposer (factoriser) notre matrice Document-Term en 3 matrices montrant les thèmes détectés dans le corpus de documents (U = matrice Document-Topic, s = matrice diagonale classant les thèmes par ordre croissant d’importance), Vh = matrice Topic-Term).

%time U, s, Vh = linalg.svd(vectors, full_matrices=False)
print(U.shape, s.shape, Vh.shape)
# Topics
plt.plot(s)

Afin de comprendre à quoi correspondent les thèmes trouvés, le code ci-après affichent les 8 premiers mots des 10 thèmes principaux.

num_top_words=8def show_topics(a):
top_words = lambda t: [vocab[i] for i in np.argsort(t)[:-num_top_words-1:-1]]
topic_words = ([top_words(t) for t in a])
return [' '.join(t) for t in topic_words]
show_topics(Vh[:10])

4.2 Non-negative Matrix Factorization (NMF)

NMF est une autre technique de LSA. Il s’agit à présent de décomposer (factoriser) notre matrice Document-Term en 2 matrices montrant les thèmes détectés dans le corpus de documents (W1 = matrice Document-Topic, H1 = matrice Topic-Term).

m,n=vectors.shape
d=5 # num topics
clf = decomposition.NMF(n_components=d, random_state=1)
W1 = clf.fit_transform(vectors)
H1 = clf.components_
show_topics(H1)

4.3 Truncated SVD

SVD ne permet pas de limiter le nombre de thèmes à extraire du corpus alors que NMF le permet, ce qui accélère d’autant l’algorithme NMF. En utilisant un algorithme Randomized SVD au lieu de SVD (par exemple, fbpca de Facebook), on peut choisir le nombre de thèmes et le traitement est bien plus rapide (de l’ordre de 400% dans notre exemple).

import fbpca
%time u, s, v = fbpca.pca(vectors, 10)

Pour aller plus loin: la “tokenisation”

Comme expliqué, nous nous sommes volontairement limités ci-dessus aux étapes basiques d’un code de Topic Modeling. Différentes options existes pour optimiser notre codage, en particulier créer un vocabulaire de tokens et non de mots.

La “tokenisation” revient à appliquer des règles sur l’ensemble des caractères du corpus (ex: lemmatization des mots, insérer un ensemble de caractères symbolisant le début/fin d’une phrase, garder les symboles de ponctuation, garder les chiffres, etc.), ce qui a pour conséquence de créer un vocabulaire de tokens qui sont un ensemble de mots et de caractères spécifiques. L’intérêt de la tokenisation est de pouvoir préserver la syntaxe des documents (et donc la sémantique) alors que le traditionnel bag-of-words la perd en ne gardant que la fréquence d’utilisation des mots.

Image Credit: Tokenisation with spaCy

La “tokenisation” est appliquée lors de la création de la matrice Document-Term par un Vectorizer. Il est en effet possible d’ajouter des options en plus de la liste de stop words que nous avons utilisée dans le code ci-dessus. Voici les possibilités de CountVectorizer de scikit-learn:

CountVectorizer(
["input='content'", "encoding='utf-8'", "decode_error='strict'", 'strip_accents=None', 'lowercase=True', 'preprocessor=None', 'tokenizer=None', 'stop_words=None', "token_pattern='(?u)\\\\b\\\\w\\\\w+\\\\b'", 'ngram_range=(1, 1)', "analyzer='word'", 'max_df=1.0', 'min_df=1', 'max_features=None', 'vocabulary=None', 'binary=False', "dtype=<class 'numpy.int64'>"])

Les options principales sont les suivantes:

  • Stemming / Lemmatization: ces 2 options consistent à remplacer des termes similaires par un seul terme. La technique de Stemming en ne gardant que la racine commune des mots n’est pas une technique fiable. La technique de Lemmatization se base sur des règles allant au-delà d’une racine commune mais elle prend plus de temps de traitement. Elle s’implémente de la manière suivante: tokenizer=LemmaTokenizer()
  • min_df: ce paramètre permet de ne garder dans le vocabulaire que les termes qui apparaissent au moins min_df fois dans le corpus.

Comparatif SVD vs NMF

SVD

Image Credit: Facebook Research: Fast Randomized SVD
  • [positif] SVD effectue une décomposition exacte de la matrice Document-Term, permet de détecter des thèmes distinctes (du fait de l’orthogonalité des thèmes dans les matrices de la décomposition) et donne l’importance relative des thèmes entre eux.
  • [négatif] SVD contient des termes négatifs difficilement interprétables et le calcul des matrices de la décomposition peut être relativement long (en particulier par le nombre important de thèmes recherchés, qui est un paramètre non modifiable).

Applications

  • semantic analysis
  • collaborative filtering/recommendations (winning entry for Netflix Prize)
  • calculate Moore-Penrose pseudoinverse
  • data compression
  • principal component analysis
  • Topic Modeling

NMF

Image Credit: NMF Tutorial
  • [positif] NMF ne contient pas de termes négatifs dans les matrices de la décomposition, ce qui rend plus facile l’interprétation des valeurs. Son temps de traitement est plus court que celui de SVD et on peut choisir le nombre de thèmes à trouver dans le corpus.
  • [négatif] NMF ne factorise pas la matrice Document-Term en matrices exactes et uniques, et les thèmes ne sont pas orthogonaux dans ces matrices. De plus, il faut choisir le nombre de thèmes à trouver (hyperparamètre). Enfin, l’importance relative des thèmes n’est pas calculée.

Applications

Annexe

Dans ce post, nous avons utilisé la classe CountVectorizer de scikit-learn pour créer un vocabulaire à partir de notre corpus et transformer chacun de nos documents texte en un vecteur de nombre, vecteurs regroupés dans une matrice Document-Term.

Cette manipulation est classique en NLP et nécessaire afin de pouvoir traiter un corpus comme une matrice de nombres. Nous pouvons d’ailleurs résumer les techniques NLP de la manière suivante:

NLP consiste à transformer un texte en vecteur.

Les 4 étapes d’utilisation de la classe CountVectorizer pour obtenir une matrice Document-Term sont les suivantes (plus de détails dans ce post):

  1. Importer la classe CountVectorizer
  2. Créer une instance à partir de cette classe
  3. Tokenisation du texte et création de son vocabulaire
  4. Transformation du texte en une matrice Document-Term
# import
from sklearn.feature_extraction.text import CountVectorizer
# create an instance
vectorizer = CountVectorizer()
# get text
text = ["The sky is blue with clouds."]
# tokenize and get vocab
vectorizer.fit(text)
vocab_itos = vectorizer.get_feature_names() # list integer to string
vocab_stoi = vectorizer.vocabulary_ # dictionary string to integer
# transform text to matrice Document-Term
vectors = vectorizer.transform(text) # create a CSR sparse matrix
print(vectors.shape)
print(type(vectors))
print(vectors.toarray())

Note: les étapes 3 et 4 peuvent être réalisées en une seule opération avec la fonction fit_transform() de la manière suivante:

vectors = vectorizer.fit_transform(text)

À propos de l’auteur: Pierre Guillou est consultant en Intelligence Artificielle au Brésil et en France. Merci de le contacter via son profil Linkedin.

--

--