Prétraitement et normalisation du texte

P

Le prétraitement (preprocessing) est une étape incontournable avant de lancer vos tests et analyses.

Prétraitement = normalisation du texte !

Plusieurs étapes de normalisation sont possibles (liste non exhaustive qui dépend de vos objectifs) :

  1. Supprimer les stopwords
  2. Normaliser les accents du texte (cette normalisation permet de supprimer des doublons liés à des fautes d’orthographe par exemple)
  3. Normaliser le texte en minuscules
  4. Nettoyage des caractères isolés
  5. Supprimer les chiffres
  6. Supprimer les URLs
  7. Supprimer les caractères spéciaux
  8. Gérer les Entités Nommées
  9. Normaliser : Racinisation (Stemming) et Lemmatisation

Ici, nous allons utiliser des expressions régulières (Regex) en Python pour normaliser les textes avant les traitements statistiques et l’exploration sémantique.
Les fonctions “Regex” du module “re” de Python permettent d’extraire et de réécrire/transformer des formes récurrentes (chiffres, nombres, URL,…) que l’on (peut) trouve(r) dans un texte.

Vous pouvez tester tous les scripts Python qui décrivent les principales étapes de normalisation (sauf les étapes de “stemming” et de “lemmatisation” qui seront traitées ultérieurement) : ICI

Ce que l’on peut réaliser à l’aide des fonctions Regex, votre éditeur de texte est également capable de le réaliser, notamment avec la fonction : « Rechercher/Remplacer ». C’est simple et efficace, mais souvent plus chronophage et moins reproductible qu’un script Python qui vise a automatiser la normalisation du texte.

Un conseil : dupliquez votre corpus avant de commencer à travailler sur la normalisation de votre texte 😉

La maîtrise des Regex demande de la pratique… et c’est pas hyper sexy à coder… À l’heure des LLM (Grok, ChatGPT,…), vous pouvez vous appuyer sur ces outils pour vérifier une syntaxe, faire expliquer un motif, ou même générer un script Python à partir d’un objectif de prétraitement : C’est un réel gain de temps.
Enfin, vous pouvez tester vos règles dans des environnements dédiés pour valider leur comportement avant intégration dans votre workflow : https://regex101.com/ et https://extendsclass.com/regex-tester.html

1. Utilisation des expressions régulières REGEX

En Python, « RegEx » (REGular EXpressions) désigne le langage de “motifs” que le module re utilise.
re.sub
(librairie “re”, “sub” -> substituer) est une fonction du module qui à pour fonction de faire des “remplacements” de “motifs”. Le “motif” est l’élément que vous souhaitez chercher ou extraire.

 

2. Chevauchement des règles de normalisation

Plusieurs règles peuvent se chevauchent. Par exemple, supprimer les URLs qui commencent par “http(s)” recoupe la « suppression des caractères spéciaux » parce qu’une URL contient précisément des caractères que votre filtre général aura peut être déjà supprimé… ( (:// ? & = % etc…).

Attention, l’ordre d’application des règles de normalisation peut biaiser le résultat final.

Si vous supprimez d’abord les « caractères spéciaux », l’URL peut être fragmentée en “https exemple com…”, ce qui peut empêcher la règle « supprimer http… » de fonctionner ensuite.

La bonne pratique est d’appliquer d’abord les règles spécifiques (URLs, emails, @mentions, #hashtags,…), puis les règles générales.
Ainsi, on supprime d’abord ce qu’on reconnaît précisément,
ensuite on passe le grand coup de balai !

Enfin, un dernier point : faites attention à conserver des “repères” (ponctuations) pour ensuite réaliser une segmentation du texte, si nécessaire.
Les principaux logiciels d’analyse statistique textuelle travaillent à partir de l’unité lexicale qu’est le segment de texte. Dans ce cas précis, la ponctuation (. ? ! , 😉 ne devra jamais être supprimée.

3. Suppression des stopwords

Les stopwords sont des mots très fréquents (mots-outils) tels que les articles (« le », « la », « un »), prépositions (« de », « à », « en »), pronoms (« il », « elle », « nous »), conjonctions (« et », « mais », « ou »).
Ces mots-outils n’apportent pas forcement de plus-value dans l’analyse textuelle, cette étape est donc quasi incontournable.
Les retirer permet de dégager les termes porteurs de sens. La distribution de fréquence s’épure immédiatement : les mots restants correspondent mieux aux notions centrales du texte.
Dans cette approche en Python il existe deux librairies simple et efficace permettant de supprimer les stopwords : NLTK et SpaCy (modeles : sm, md, lg).

3.1 Utilisation de la librairie NLTK

# pip install nltk

import re

# charger les stopwords français nltk
try:
    from nltk.corpus import stopwords
    stop_fr = set(stopwords.words("french"))
except LookupError:
    import nltk
    nltk.download("stopwords")
    from nltk.corpus import stopwords
    stop_fr = set(stopwords.words("french"))

def supprimer_mots_vides_nltk(texte):
    """supprime les mots vides français avec nltk et conserve l'ordre des mots"""
    mots = re.findall(r"\w+", texte.lower(), flags=re.UNICODE)
    mots_filtres = [m for m in mots if m not in stop_fr]
    return " ".join(mots_filtres)

# exécution directe
phrase = input("Entrez une phrase : ")
print(supprimer_mots_vides_nltk(phrase))

 

3.2 Utilisation de la librairie SpaCy

Dans cet exemple, nous utiliserons le modèle small (sm).
Pour l’analyse textuelle, il est toutefois conseillé d’utiliser, a minima, le modèle medium (md) ou large (lg).

# pip install spacy
# python -m spacy download fr_core_news_sm


import spacy

# charger le modèle français de spaCy
# installez-le au besoin : python -m spacy download fr_core_news_sm
nlp = spacy.load("fr_core_news_sm")

def supprimer_mots_vides_spacy(texte):
    """supprime les mots vides français avec spacy et conserve l'ordre des mots"""
    doc = nlp(texte)
    mots = [t.text for t in doc if not t.is_stop and not t.is_punct and not t.is_space]
    return " ".join(mots)

# exécution directe
phrase = input("Entrez une phrase : ")
print(supprimer_mots_vides_spacy(phrase))

 

4. Normaliser les caractères accentués

Normaliser les caractères accentués consiste à transformer chaque lettre accentuée en sa lettre de base. La normalisation Unicode « NFKD » décompose une lettre accentuée en deux éléments : la lettre sans accent et un petit signe d’accent. On supprime ensuite ces signes d’accent (caractères de catégorie Unicode commençant par « M »), ce qui donne « é → e », « À → A ». Avant cela, on remplace explicitement les ligatures pour conserver le sens : « œ → oe », « æ → ae ». Résultat : « Élève → Eleve », « cœur → coeur ».

L’argument « NFKD » de decomp = unicodedata.normalize("NFKD", texte) met le texte dans une forme standard et décompose les lettres accentuées.
Ensuite, en supprimant les accents, on garde la lettre de base et on perd seulement l’accent.

unicodedata.normalize("NFKD", texte) « divise » chaque lettre accentuée en deux morceaux :
la lettre sans accent + un petit signe d’accent.
Exemples :
"é" devient "e" + (accent aigu) ;
"À" devient "A" + (accent grave).

# normalisation des mots avec accents

import unicodedata

def supprimer_accents(texte):
    """supprime accents et ligatures (œ→oe, æ→ae) en conservant casse et ponctuation"""
    # gérer les ligatures courantes
    texte = (texte
             .replace("œ", "oe").replace("Œ", "OE")
             .replace("æ", "ae").replace("Æ", "AE"))
    # normaliser puis retirer tous les diacritiques (catégories Unicode M*)
    decomp = unicodedata.normalize("NFKD", texte)
    return "".join(ch for ch in decomp if not unicodedata.category(ch).startswith("M"))

# exécution directe
phrase = input("Entrez une phrase avec accents et ligatures : ")
resultat = supprimer_accents(phrase)
print("\nRésultat :")
print(resultat)

# Exemple à tester :
# À Â Ä Ç É È Ê Ë Î Ï Ô Ö Ù Û Ü Ÿ — à â ä ç é è ê ë î ï ô ö ù û ü ÿ — œ, Œ, æ, Æ.

 

5. Normaliser le texte en minuscule

Ici on passe tout le texte en minuscule.

# fonction de nettoyage : mettre le texte en minuscules
def nettoyer_texte(texte, conserver_casse=True):
    """retourne le texte en minuscules si conserver_casse est False, sinon tel quel."""
    if not conserver_casse:
        texte = texte.lower()
    return texte

# exécution
texte_utilisateur = input("Entrez un texte à tester : ")
resultat = nettoyer_texte(texte_utilisateur, conserver_casse=False)
print("\nRésultat :")
print(resultat)

 

6. Nettoyage des caractères isolés

re.sub (librairie “re”, “sub” -> substituer) remplace toutes les occurrences d’un motif d’expression régulière par une chaîne donnée ; ici, elle est utilisée deux fois.
La première, re.sub(r"(?<!\w)[A-Za-zÀ-ÖØ-öø-ÿ](?!\w|')", " ", texte), remplace par un espace chaque mot d’une seule lettre isolé et non suivi d’une apostrophe, afin de conserver les c’, j’, l’.
La seconde, re.sub(r"\s+", " ", texte).strip(), condense toutes les suites d’espaces (tabulations, retours à la ligne…) en un seul espace puis retire les espaces de début et de fin.

# Nettoyage de caractères isolés

import re

def supprimer_caracteres_isoles(texte):
    """supprime les mots d'une seule lettre tout en conservant les formes avec apostrophe (c', j', l', etc.)"""
    # normaliser l’apostrophe typographique en apostrophe droite
    texte = texte.replace("’", "'")
    # retirer une lettre isolée (pas précédée/suivie d'un caractère de mot) et non suivie d'une apostrophe
    texte = re.sub(r"(?<!\w)[A-Za-zÀ-ÖØ-öø-ÿ](?!\w|')", " ", texte)
    # nettoyer les espaces multiples
    texte = re.sub(r"\s+", " ", texte).strip()
    return texte

# exécution
phrase = input("Entrez une phrase : ")
resultat = supprimer_caracteres_isoles(phrase)
print("\nRésultat :")
print(resultat)

7. Suppression des chiffres

return re.sub(r"\d+", " ", texte)
On renvoie le résultat de re.sub, c’est-à-dire une nouvelle chaîne où :

  • r"\d+" est l’expression régulière (regex) qui repère toute suite d’au moins un chiffre.
    \d
    veut dire « un chiffre » et + veut dire « une ou plusieurs fois ». Le préfixe r indique une chaîne « brute » pour que \d soit transmis tel quel au moteur regex.
  • " " est la chaîne de remplacement : un espace.
  • texte est la chaîne d’entrée à transformer.

Concrètement, chaque bloc de chiffres est remplacé par un espace. Exemple :
Entrée : Rapport 2024 avec 3 annexes
Sortie : Rapport avec annexes

import re

def nettoyer_texte(texte):
    """supprime tous les chiffres du texte."""
    return re.sub(r"\d+", " ", texte)

# exécution directe
texte_utilisateur = input("Entrez une phrase contenant des chiffres : ")
resultat = nettoyer_texte(texte_utilisateur)
print("\nRésultat :")
print(resultat)

Pour éviter le double espace :

# Suppression des chiffres et du double espaces

import re

def nettoyer_texte(texte):
    """supprime tous les chiffres du texte et normalise les espaces."""
    return re.sub(r"\s+", " ", re.sub(r"\d+", " ", texte)).strip()

# exécution directe
texte_utilisateur = input("Entrez une phrase contenant des chiffres : ")
resultat = nettoyer_texte(texte_utilisateur)
print("\nRésultat :")
print(resultat)

 

7. Suppression des URLs

re.sub(r"http\S+", " ", texte) cherche tout morceau de texte qui commence par http (ou https) et continue jusqu’au prochain séparateur blanc.
\S+ signifie « un ou plusieurs caractères qui ne sont pas des espaces ». On remplace chaque lien trouvé par un espace.
Exemple :
"voir https://exemple.org/page?id=3 merci" devient "voir merci".

Ensuite, re.sub(r"\s+", " ", texte).strip() sert à nettoyer la mise en forme. \s+ regroupe toutes les suites d’espaces, tabulations ou retours à la ligne en un seul espace.
strip() enlève l’espace en début et fin de chaîne. Avec l’exemple précédent, on obtient "voir merci".

# -*- coding: utf-8 -*-

import re

def supprimer_http(texte):
    """supprime les expressions qui commencent par http (ou https) et tout ce qui suit jusqu'au prochain espace"""
    # enlever chaque segment 'http...' (jusqu'au premier séparateur d'espace)
    texte = re.sub(r"http\S+", " ", texte)
    # normaliser les espaces
    return re.sub(r"\s+", " ", texte).strip()

# exécution directe
phrase = input("Entrez une phrase contenant des liens http : ")
resultat = supprimer_http(phrase)
print("\nRésultat :")
print(resultat)

 

8. Suppression des caractères spéciaux

motif = r"[^0-9A-Za-zÀ-ÖØ-öø-ÿ\s\.\,\;\:\!\?\'\"\«\»\(\)\-\–\—\…]"
C’est une expression régulière qui décrit “ce qu’on veut enlever”.
Les crochets [...] définissent un ensemble de caractères autorisés. Le chapeau ^ placé juste après le [ veut dire “tout sauf”.
Donc ce “motif ” repère chaque caractère qui n’est PAS dans la liste autorisée.

Que contient la liste autorisée  ?
– les chiffres 0-9
– les lettres latines non accentuées A–Z et a–z
– les lettres accentuées usuelles en français via les plages À-Ö, Ø-ö, ø-ÿ
– les espaces et retours à la ligne \s
– la ponctuation que vous voulez garder : . , ; : ! ? ' " « » ( ) -
Les barres obliques inverses \ servent juste à « échapper » certains signes pour dire qu’on les prend au sens littéral dans le motif.

texte = re.sub(motif, " ", texte)
Ici on applique le motif : chaque caractère qui n’est pas autorisé est remplacé par un espace.
On obtient une nouvelle chaîne où seuls les lettres/chiffres/espaces et la ponctuation listée sont conservés.

import re

def supprimer_speciaux_sauf_ponctuation(texte):
    """supprime les caractères spéciaux en conservant lettres, chiffres, espaces et ponctuation courante"""
    # on autorise : . , ; : ! ? ' " « » ( ) - – — …
    motif = r"[^0-9A-Za-zÀ-ÖØ-öø-ÿ\s\.\,\;\:\!\?\'\"\«\»\(\)\-\–\—\…]"
    texte = re.sub(motif, " ", texte)
    # normaliser les espaces
    return re.sub(r"\s+", " ", texte).strip()

# exécution directe
chaine = input("Entrez un texte : ")
print(supprimer_speciaux_sauf_ponctuation(chaine))

9. Gestion des entités nommées (NER)

Dans l’absolu, la NER (Named Entity Recognition) renvoie à l’utilisation d’un modèle de langage tel que spaCy afin d’entraîner le modèle à reconnaître les entités nommées, (termes/mots absents de son lexique).
Ici, nous aborderons cette notion sans entraîner le modèle : nous appliquerons une méthode manuelle, en transformant ces entités.

Ici, ces “groupes de mots” : « Paris Match » ou “Steve Jobs” doivent être traités comme une seule unité.

  • sans traitement particulier, « Paris » et « Match » apparaissent séparément,
  • avec un regroupement un peu en mode “bricolage”, ils deviennent « Paris_Match » et leur fréquence est correctement calculée.
# texte :
# Paris Match publie un dossier sur Steve Jobs et son impact sur l’industrie.
# L’article met aussi en regard les promesses de l’intelligence artificielle.
# Certains lecteurs comparent Paris match d’hier et d’aujourd’hui, tandis que d’autres évoquent l’Intelligence Artificielle dans les produits d’Apple inspirés par Steve Jobs.

import re

def remplacer_occurrences_multiples(texte, remplacements):
    """remplace toutes les occurrences par leur valeur correspondante"""
    for a_chercher, remplacement in remplacements.items():
        motif = re.escape(a_chercher)  # traiter la clé comme texte littéral
        texte = re.sub(motif, remplacement, texte, flags=re.IGNORECASE) # on ignore la CASE
    return texte

# saisir le texte à transformer
texte_source = input("Entrez le texte à transformer : ")

# DÉFINIR LES RÈGLES DE REMPLACEMENT
# exemple : "Paris Match" -> "Paris_Match"
remplacements = {
    "Paris Match": "Paris_Match",
    "Steve Jobs": "Steve_Jobs",
    "intelligence artificielle": "intelligence_artificielle"
    # ajoutez d'autres lignes si nécessaire :
}

# appliquer les remplacements et afficher le résultat
resultat = remplacer_occurrences_multiples(texte_source, remplacements)
print("\nTexte transformé :")
print(resultat)

 

10. Racinisation (stemming) et lemmatisation

Il existe deux procédés :

10.1 Lemmatisation

Ce procédé est fondé sur un dictionnaire linguistique (SpaCy,…). Le résultat est la forme canonique du mot (le lemme), telle qu’elle figure dans le dictionnaire.
mangeaient → manger (verbe à l’infinitif)
étudiants → étudiant (nom au singulier)

À noter que le lexique intégré de la librairie Python “SpaCy” n’est pas toujours la solution idéale : l’idéal serait d’intégrer votre propre dictionnaire (cf. Le logiciel IRaMuTeQ), afin de pouvoir modifier et enrichir le formes.

10.2 Stemming (racinisation)

Le stemming consiste à raccourcir mécaniquement les mots pour garder une racine commune, en supprimant surtout les suffixes.
parlaient → parl (racine non lexicale)
organisations → organis (racine non lexicale)

Ces deux méthodes de normalisation seront détaillée dans un prochain article.

Conclusion

En une phrase : pas de prétraitement,… pas d’analyse textuelle…

A propos de l'auteur

Stéphane Meurisse

Ajouter un commentaire

Stéphane Meurisse