Module 3 — Sacs de mots, TF-IDF et leurs limites
Le corpus est nettoyé (module 1) et tokenisé (module 2). Avant de sortir la lourde artillerie neuronale, on construit une référence. Une bonne référence a trois qualités : elle prend dix lignes, elle s'entraîne en dix secondes, et elle sert de plancher que tout modèle plus complexe doit battre nettement pour justifier son coût.
Le sac de mots : oublier l'ordre pour compter les mots
Un sac de mots (bag of words) représente un document par un vecteur de comptes : autant de composantes que de mots dans le vocabulaire, chacune valant le nombre d'occurrences du mot dans le document. L'ordre disparaît complètement : « le chien mord l'homme » et « l'homme mord le chien » ont le même vecteur.
Sur un vocabulaire de trente mille mots et un corpus de dix mille avis, la matrice résultante a trente mille colonnes et dix mille lignes, soit trois cents millions de cases. Heureusement, chaque avis n'utilise que quelques dizaines de mots, donc plus de 99 % des cases valent zéro. On stocke ce genre de matrice en format creux (scipy.sparse.csr_matrix), qui n'écrit que les non-zéros.
from sklearn.feature_extraction.text import CountVectorizer
corpus = ["L'hôtel était parfait", "L'hôtel était sale", "Parfait service"]
vec = CountVectorizer()
X = vec.fit_transform(corpus)
print(X.shape) # (3, 6)
print(vec.get_feature_names_out())
print(X.toarray()) # dense, pour l'affichage seulement
TF-IDF : pondérer par l'importance discriminante
Compter brutalement les mots donne trop de poids aux mots fréquents (« le », « et ») et pas assez aux mots rares mais discriminants (« impeccable », « catastrophique »). TF-IDF corrige ce déséquilibre en multipliant deux quantités.
La fréquence de terme (Term Frequency), pour un mot dans un document , est simplement le nombre d'occurrences de dans , éventuellement normalisé par la longueur du document.
La fréquence documentaire inverse (Inverse Document Frequency) mesure la rareté d'un mot dans la collection. Si est le nombre total de documents et le nombre de documents contenant :
Un mot présent dans tous les documents a un IDF quasi nul. Un mot présent dans un seul document a un IDF maximal. Le poids TF-IDF est alors le produit :
Prenons trois documents et calculons à la main pour un mot rare et un mot fréquent :
| Mot | df | tf(d1) | idf | tf-idf(d1) |
|---|---|---|---|---|
| « le » | 3 | 2 | 2 | |
| « catastrophique » | 1 | 1 | 2,10 |
Le mot rare, pourtant apparu une seule fois, pèse davantage que le mot fréquent apparu deux fois. C'est précisément le but.
N-grammes : rendre un peu d'ordre au sac
Le sac de mots seul ne distingue pas « très bon » de « pas très bon ». On récupère un morceau d'ordre en incluant des bigrammes (paires de mots consécutifs) et parfois des trigrammes. Le vocabulaire explose, mais les phrases avec négation ou intensification cessent d'être aveugles.
from sklearn.feature_extraction.text import TfidfVectorizer
vec = TfidfVectorizer(
ngram_range=(1, 2), # unigrammes et bigrammes
min_df=5, # ignorer les mots rares
max_df=0.9, # ignorer les quasi-omniprésents
sublinear_tf=True, # 1 + log(tf), atténue les répétitions
)
Trois options sont critiques. min_df=5 élimine les mots vus moins de cinq fois — presque toujours des coquilles ou du bruit. max_df=0.9 retire les mots présents dans plus de 90 % des documents, qui ne discriminent rien. sublinear_tf=True remplace tf par 1 + log(tf), ce qui empêche un mot répété trente fois dans un long avis de dominer.
Régression logistique : la ligne d'horizon
TF-IDF plus régression logistique constitue le pipeline classique de classification de texte depuis vingt ans, et il reste imbattable en rapport qualité-prix sur les tâches simples.
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
corpus_textes, y_labels, test_size=0.2, stratify=y_labels, random_state=42)
vec = TfidfVectorizer(ngram_range=(1, 2), min_df=5, sublinear_tf=True)
X_train_v = vec.fit_transform(X_train)
X_test_v = vec.transform(X_test)
modele = LogisticRegression(max_iter=1000, C=1.0, class_weight="balanced")
modele.fit(X_train_v, y_train)
print(classification_report(y_test, modele.predict(X_test_v)))
Sur le corpus d'avis avec cinq classes (1 à 5 étoiles), on obtient couramment une exactitude de 55 à 65 % — imbattable en dix secondes de calcul, et le point à partir duquel juger tout ce qui vient ensuite.
Petit corpus (moins de 100 000 exemples), vocabulaire spécialisé, contraintes de latence millisecondes, absence de GPU, besoin d'expliquer chaque prédiction : TF-IDF plus régression logistique reste souvent la meilleure réponse. Les coefficients du modèle donnent, mot par mot, la contribution à chaque classe — un audit dont aucun Transformer ne s'approche.
Ce que TF-IDF ne captera jamais
Trois limites structurelles, que les modules suivants viendront lever une à une.
Aucun sens partagé. « Chambre » et « pièce » sont, pour TF-IDF, deux dimensions orthogonales. Un avis qui parle de « pièce spacieuse » n'a rien en commun avec un avis qui parle de « chambre spacieuse », sauf « spacieuse ». Les plongements du module 4 résolvent cela.
Aucun ordre au-delà du n-gramme. « L'hôtel n'était pas mal du tout » et « L'hôtel était mal du tout » diffèrent d'un seul mot ; TF-IDF ne fait pas la différence entre les deux. Les bigrammes en capturent une partie, jamais tout.
Aucun contexte. « Avocat » désigne un fruit dans « salade d'avocat » et un métier dans « rendez-vous avocat ». TF-IDF donne le même vecteur au mot dans les deux phrases. Les plongements contextuels du module 5 sont exactement la réponse à ce problème.
Le premier réflexe devrait être TF-IDF plus régression logistique, mesuré sur le jeu de test. Un Transformer qui améliore de deux points d'exactitude, au prix d'un temps d'inférence cent fois supérieur et d'une infrastructure GPU, n'est pas toujours le bon investissement. La question à se poser est : combien vaut chaque point d'exactitude pour l'utilisateur final ?
En résumé
- Le sac de mots représente un document par ses comptes de mots ; il perd totalement l'ordre, mais tient en une matrice creuse de trois cents millions de cases pour dix mille avis.
- TF-IDF pondère chaque mot par sa fréquence dans le document et sa rareté dans le corpus, ce qui donne du poids aux termes discriminants ; les n-grammes rendent un peu d'ordre.
- TF-IDF plus régression logistique est la référence à battre : dix lignes, dix secondes, résultats explicables mot par mot.
- Les trois limites structurelles — pas de sens partagé, pas d'ordre au-delà du n-gramme, pas de contexte — motivent tout ce qui suit, à commencer par les plongements du module 4.
Module suivant : Word2Vec et GloVe, comment construire des vecteurs de mots où la géométrie porte du sens, et pourquoi ces vecteurs encodent aussi nos biais.