Bag-of-Words (Sac de mots)
Le bag-of-words (BoW) est une technique fondamentale de NLP qui représente un document sous forme de vecteur numérique en comptant la fréquence d’apparition de chaque mot, sans tenir compte de l’ordre des mots ni de la grammaire.
Le principe est intuitif : imaginez que vous videz un document dans un sac. Vous mélangez tous les mots, puis vous comptez combien de fois chaque mot apparaît. « Le chat mange la souris » et « La souris mange le chat » produisent exactement le même vecteur, car les deux phrases contiennent les mêmes mots aux mêmes fréquences. L’ordre est perdu, mais l’information sur le contenu lexical est préservée. Et c’est souvent suffisant pour des tâches comme la classification de texte, la détection de spam ou le clustering thématique.
- Catégorie
- Représentation vectorielle de texte / NLP
- Type
- Comptage de fréquences (pas de pondération)
- Entrée
- Texte brut (après tokenisation)
- Sortie
- Matrice terme-document (sparse)
- Librairie
- scikit-learn (CountVectorizer), NLTK, Gensim
- Évolution
- TF-IDF (pondération), word embeddings (sémantique)
Comment fonctionne le bag-of-words
Le processus se déroule en trois étapes distinctes.
Étape 1 : Construction du vocabulaire
Le modèle parcourt l’ensemble du corpus (tous les documents) et crée un dictionnaire de tous les mots uniques rencontrés. Si votre corpus contient 10 000 mots uniques, votre vocabulaire a 10 000 entrées. Chaque mot devient une dimension de l’espace vectoriel.
Étape 2 : Tokenisation
Chaque document est découpé en tokens (mots individuels) par le tokenizer. Cette étape peut inclure la mise en minuscules, la suppression de la ponctuation et le retrait des stopwords.
Étape 3 : Vectorisation
Chaque document est converti en un vecteur dont la longueur est égale à la taille du vocabulaire. La valeur à chaque position correspond au nombre d’occurrences du mot correspondant dans le document.
Prenons un exemple concret avec trois documents :
Document 1 : « Le chat dort »
Document 2 : « Le chien court »
Document 3 : « Le chat court vite »
Le vocabulaire extrait est : {le, chat, dort, chien, court, vite}. Chaque document est alors représenté comme un vecteur de dimension 6 :
| Document | le | chat | dort | chien | court | vite |
|---|---|---|---|---|---|---|
| Doc 1 : « Le chat dort » | 1 | 1 | 1 | 0 | 0 | 0 |
| Doc 2 : « Le chien court » | 1 | 0 | 0 | 1 | 1 | 0 |
| Doc 3 : « Le chat court vite » | 1 | 1 | 0 | 0 | 1 | 1 |
Cette matrice terme-document est la représentation bag-of-words du corpus. Chaque ligne est un vecteur document exploitable par un algorithme de machine learning.
Implémentation Python avec scikit-learn
CountVectorizer : le standard
scikit-learn fournit CountVectorizer, la classe de référence pour créer des représentations bag-of-words :
from sklearn.feature_extraction.text import CountVectorizer
import pandas as pd
corpus = [
"Le chat dort sur le canapé",
"Le chien court dans le jardin",
"Le chat et le chien jouent ensemble"
]
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(corpus)
# Afficher la matrice sous forme lisible
df = pd.DataFrame(
X.toarray(),
columns=vectorizer.get_feature_names_out(),
index=["Doc 1", "Doc 2", "Doc 3"]
)
print(df)
# canapé chat chien court dans dort ensemble et jardin jouent le sur
# Doc 1 1 1 0 0 0 1 0 0 0 0 2 1
# Doc 2 0 0 1 1 1 0 0 0 1 0 2 0
# Doc 3 0 1 1 0 0 0 1 1 0 1 2 0
Notez que « le » apparaît 2 fois dans chaque document et reçoit un poids de 2. C’est la faiblesse principale du BoW : les mots courants dominent les vecteurs. C’est exactement ce que TF-IDF corrige en pondérant par la rareté.
Paramètres importants de CountVectorizer
vectorizer = CountVectorizer(
stop_words='english', # Supprimer les stopwords (ou liste custom)
ngram_range=(1, 2), # Unigrams + bigrams
max_df=0.95, # Ignorer les mots présents dans 95%+ des docs
min_df=2, # Ignorer les mots dans moins de 2 docs
max_features=5000, # Limiter à 5000 features
binary=True, # 1/0 au lieu du comptage (présence/absence)
lowercase=True # Tout en minuscules (par défaut)
)
Le paramètre binary=True est intéressant : au lieu de compter les occurrences, il enregistre simplement la présence (1) ou l’absence (0) du mot. Cette variante, parfois appelée « binary bag-of-words », est plus performante dans certains cas de classification car elle réduit l’influence des mots très fréquents dans un document donné.
Bag-of-Words avec n-grams
Par défaut, BoW travaille avec des unigrams (mots individuels). En ajoutant des n-grams, vous capturez des séquences de mots consécutifs qui portent du sens en tant que groupe :
vectorizer = CountVectorizer(ngram_range=(1, 2))
X = vectorizer.fit_transform(corpus)
# Les bigrams comme "chat dort", "chien court" deviennent des features
print(vectorizer.get_feature_names_out())
# [..., 'chat dort', 'chat et', 'chien court', 'chien jouent', ...]
Les bigrams atténuent partiellement la perte d’ordre des mots. « New York » est capturé comme un seul feature au lieu de deux mots séparés « new » et « york ». Le prix : le vocabulaire explose. Un corpus de 10 000 unigrams peut facilement générer 100 000+ bigrams.
Les variantes du bag-of-words
Term Count (comptage brut)
C’est le BoW de base décrit ci-dessus. La valeur de chaque feature est le nombre d’occurrences du mot dans le document. Simple mais biaisé vers les mots fréquents.
Binary BoW (présence/absence)
Chaque feature vaut 1 si le mot est présent, 0 sinon, quel que soit le nombre d’occurrences. Utile quand la présence d’un mot compte plus que sa fréquence, par exemple pour la détection de thèmes.
Term Frequency (TF)
Le comptage est normalisé par la longueur du document. Un mot qui apparaît 5 fois dans un document de 100 mots reçoit un TF de 0,05. Cela élimine le biais des documents longs.
TF-IDF
TF-IDF est l’évolution naturelle du bag-of-words. Il ajoute l’Inverse Document Frequency qui pénalise les mots omniprésents et valorise les mots rares. C’est la méthode recommandée dans la quasi-totalité des cas pratiques où vous envisageriez un bag-of-words simple.
Cas d’usage
Classification de texte
Le cas d’usage historique. Un bag-of-words combiné avec un classifieur Naive Bayes est souvent le premier modèle enseigné en NLP. C’est la baseline contre laquelle toutes les approches plus sophistiquées sont comparées. La littérature discute régulièrement le BoW aux côtés de Naive Bayes, et cette combinaison reste efficace pour la détection de spam, la catégorisation de documents et l’assignation de tags.
Une étude publiée en 2025 dans le journal Natural Language Processing (ScienceDirect) a démontré que les modèles BoW enrichis restent compétitifs face aux approches neuronales sur de nombreuses tâches de classification de texte, et dominent même les tâches de profilage d’auteur. Les chercheurs ont caractérisé les tâches où BoW est encore pertinent et souligné que ces modèles peuvent être à la fois lexicaux et sémantiques, avec des prédictions transparentes et explicables.
Détection de spam
La détection de spam email est l’un des premiers succès du bag-of-words. Les mots comme « gratuit », « gagner », « urgent », « offre » apparaissent fréquemment dans les spams et rarement dans les emails légitimes. Un simple modèle BoW + Naive Bayes détecte ces patterns avec une précision surprenante.
Analyse de sentiment basique
Pour une analyse de sentiment simplifiée, le BoW fonctionne : les mots « excellent », « superbe », « génial » apparaissent dans les avis positifs, « horrible », « décevant », « nul » dans les négatifs. Cependant, cette approche échoue sur les cas subtils : « pas mauvais » contient « mauvais » mais exprime un sentiment positif.
Clustering de documents
En combinant BoW avec des algorithmes de clustering (K-Means, DBSCAN), vous pouvez regrouper automatiquement des documents par thème. Les documents sur le même sujet partagent des mots similaires et se retrouvent proches dans l’espace vectoriel.
Continuous Bag-of-Words (CBOW) dans Word2Vec
Le concept de bag-of-words a inspiré l’architecture CBOW de Word2Vec. Dans CBOW, le modèle prédit un mot à partir de son contexte (les mots environnants), en utilisant une approche de type « sac de mots du contexte ». C’est le pont entre les méthodes classiques et les word embeddings modernes.
Limites du bag-of-words
Perte de l’ordre des mots
C’est la limite fondamentale. « Le chat mange la souris » et « La souris mange le chat » produisent le même vecteur. Pour un humain, le sens est radicalement différent. L’utilisation de n-grams (bigrams, trigrams) atténue partiellement le problème en capturant des séquences courtes, mais ne le résout pas complètement.
Aucune compréhension sémantique
« voiture » et « automobile » sont deux features complètement indépendantes. « heureux » et « content » n’ont aucune relation dans l’espace BoW. Le modèle ne comprend ni les synonymes, ni les analogies, ni les relations sémantiques. Les word embeddings résolvent ce problème en plaçant les mots sémantiquement proches dans des régions voisines de l’espace vectoriel.
Haute dimensionnalité et matrices creuses
Un vocabulaire de 50 000 mots produit des vecteurs de dimension 50 000. La plupart des valeurs sont nulles (un document typique n’utilise qu’une fraction du vocabulaire total), ce qui crée des matrices extrêmement creuses (sparse). scikit-learn gère cela efficacement avec des matrices sparse CSR, mais la dimensionnalité reste un défi. Les paramètres max_features, max_df et min_df aident à contenir la taille du vocabulaire.
Biais vers les mots courants
Les mots fréquents comme « le », « de », « est » dominent les vecteurs BoW car ils reçoivent les comptages les plus élevés. C’est exactement le problème que TF-IDF résout en introduisant l’Inverse Document Frequency. Dans la pratique, utilisez TF-IDF plutôt que le BoW brut sauf si vous avez une raison spécifique de ne pas le faire.
Bag-of-Words vs approches modernes
L’évolution des représentations textuelles suit une trajectoire claire : BoW (comptage) → TF-IDF (pondération) → Word2Vec/GloVe (embeddings statiques) → BERT/GPT (embeddings contextuels). Chaque génération corrige les faiblesses de la précédente.
| Critère | Bag-of-Words | TF-IDF | Word2Vec | BERT |
|---|---|---|---|---|
| Type de vecteur | Épars, comptage | Épars, pondéré | Dense, statique | Dense, contextuel |
| Ordre des mots | Ignoré | Ignoré | Fenêtre locale | Séquence complète |
| Sémantique | Aucune | Aucune | Similarité par cooccurrence | Compréhension contextuelle |
| Vitesse | Très rapide | Très rapide | Rapide | Lent (GPU) |
| Interprétabilité | Totale | Totale | Partielle | Faible |
| Mots courants | Problématiques | Automatiquement filtrés | Représentés | Contextualisés |
Prétraitement recommandé avant le BoW
La qualité d’un modèle bag-of-words dépend directement de la qualité du prétraitement appliqué en amont. Voici le pipeline standard :
Premièrement, la mise en minuscules. Sans elle, « Chat » et « chat » sont deux features distinctes. CountVectorizer le fait par défaut (lowercase=True). Deuxièmement, la suppression de la ponctuation et des caractères spéciaux. Les points, virgules et parenthèses n’apportent pas d’information dans un BoW. Troisièmement, le retrait des stopwords. Les mots vides dominent les vecteurs BoW car ils sont les plus fréquents. Les supprimer améliore significativement les résultats. Quatrièmement, la lemmatisation ou le stemming. Regrouper « mangeait », « mangera » et « mange » sous la forme « manger » réduit la dimensionnalité et améliore la couverture du vocabulaire.
import spacy
from sklearn.feature_extraction.text import CountVectorizer
nlp = spacy.load("fr_core_news_sm")
def preprocess(text):
"""Tokenisation + lemmatisation + suppression stopwords avec spaCy"""
doc = nlp(text.lower())
tokens = [
token.lemma_ for token in doc
if not token.is_stop
and not token.is_punct
and not token.is_space
and len(token.text) > 1
]
return " ".join(tokens)
corpus = [
"Les chats dorment sur les canapés",
"Le chien court dans le jardin",
"Les chats et les chiens jouent ensemble"
]
# Prétraiter chaque document
corpus_clean = [preprocess(doc) for doc in corpus]
# ['chat dormir canapé', 'chien courir jardin', 'chat chien jouer ensemble']
# Puis vectoriser
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(corpus_clean)
Le résultat est un vocabulaire plus compact et plus informatif. « Les » et « le » ont disparu (stopwords), « dorment » est devenu « dormir » (lemme), et les vecteurs sont plus discriminants.
Quand utiliser le bag-of-words
Utilisez le BoW (ou mieux, TF-IDF) quand vous avez des contraintes de ressources et ne pouvez pas utiliser de GPU. Quand vous avez besoin d’interprétabilité totale, ce qui est courant en environnement réglementé (finance, santé). Quand votre corpus est petit (quelques centaines à quelques milliers de documents) et qu’un transformer serait excessif. Quand vous construisez un prototype rapide pour valider une hypothèse avant d’investir dans un modèle plus complexe. Et quand la tâche est « facile » au sens NLP (catégories bien séparées, vocabulaire discriminant) : un BoW + Naive Bayes peut atteindre 95 %+ de précision sur la détection de spam, rendant tout modèle plus complexe superflu.
Ne l’utilisez pas quand la compréhension sémantique est critique (synonymes, paraphrases), quand l’ordre des mots compte (traduction, génération de texte), ou quand vous avez suffisamment de données et de ressources pour entraîner un modèle transformer qui sera significativement meilleur.
Malgré ses limites, le bag-of-words n’est pas mort. Son interprétabilité totale, sa vitesse d’exécution et sa simplicité en font un outil précieux pour le prototypage rapide, les baselines de comparaison et les environnements à ressources limitées. C’est toujours le premier modèle à essayer avant d’investir dans des approches plus complexes.
Questions fréquentes sur le bag-of-words
Qu’est-ce que le bag-of-words simplement ?
Le bag-of-words convertit un texte en vecteur numérique en comptant combien de fois chaque mot apparaît. Imaginez vider une phrase dans un sac : vous perdez l’ordre des mots mais vous savez quels mots sont présents et combien de fois. Ce vecteur numèrique peut ensuite être utilisé par des algorithmes de machine learning pour la classification, le clustering ou la recherche de documents.
Quelle est la différence entre bag-of-words et TF-IDF ?
Le bag-of-words compte les occurrences de chaque mot sans distinction. TF-IDF ajoute une pondération qui pénalise les mots courants (comme « le », « de ») et valorise les mots rares et distinctifs. Résultat : en BoW, le mot « le » domine le vecteur car il est fréquent. En TF-IDF, son poids est quasi nul. TF-IDF est supérieur au BoW brut dans la quasi-totalité des cas pratiques.
Le bag-of-words est-il encore utilisé avec les LLM ?
Pas directement. Les LLM et transformers utilisent des représentations bien plus sophistiquées (embeddings contextuels). Cependant, le bag-of-words reste pertinent comme baseline de comparaison, pour les projets avec des contraintes de ressources (pas de GPU), quand l’interprétabilité est nécessaire (compliance, audit), et dans les systèmes hybrides combinant recherche lexicale et sémantique. Une étude de 2025 a confirmé que les modèles BoW enrichis restent compétitifs face aux approches neuronales sur de nombreuses tâches.
Comment améliorer un modèle bag-of-words ?
Plusieurs leviers existent. Passer à TF-IDF plutôt que le comptage brut. Ajouter des n-grams (bigrams, trigrams) pour capturer les expressions composées. Supprimer les stopwords pour réduire le bruit. Appliquer le stemming ou la lemmatisation pour regrouper les variantes morphologiques. Limiter le vocabulaire avec max_features, max_df et min_df. Tester le mode binaire (binary=True) pour certaines tâches de classification.
Comment implémenter un bag-of-words en Python ?
La méthode standard utilise scikit-learn : from sklearn.feature_extraction.text import CountVectorizer, puis vectorizer = CountVectorizer() et X = vectorizer.fit_transform(corpus). Le résultat est une matrice sparse où chaque ligne est un document et chaque colonne un mot du vocabulaire. Pour passer à TF-IDF (recommandé), remplacez simplement CountVectorizer par TfidfVectorizer.