Naive Bayes : Guide Complet — Principes, Exemples et Implémentation Python

Naive Bayes : Guide Complet — Principes, Exemples et Implémentation Python

Naive Bayes : Classification Bayésienne Naïve — Guide Complet

Résumé

Naive Bayes (ou classification bayésienne naïve) est un algorithme de classification probabiliste fondé sur le théorème de Bayes. Son originalité réside dans une hypothèse simplificatrice : toutes les variables prédictives sont considérées comme conditionnellement indépendantes sachant la classe cible. Cette approximation, bien que souvent irréaliste, donne naissance à un algorithme extrêmement rapide, robuste et étonnamment performant dans de nombreux contextes réels, notamment le traitement automatique du langage naturel.

Malgré sa simplicité conceptuelle, Naive Bayes reste une référence incontournable pour la classification de texte, le filtrage de spam et tout problème où le nombre de caractéristiques est élevé. Dans ce guide, nous explorerons en détail les principes mathématiques, les différentes variantes, l’implémentation en Python et les cas d’usage concrets de l’approche bayésienne naïve.

Principe Mathématique de Naive Bayes

Le Théorème de Bayes

Le cœur de l’algorithme repose sur le théorème de Bayes, formulé au XVIIIe siècle par le mathématicien Révérend Thomas Bayes. Ce théorème permet de calculer la probabilité a posteriori d’une classe sachant des observations :

P(C|X) = P(X|C) · P(C) / P(X)

Où :

  • P(C|X) est la probabilité a posteriori de la classe C sachant l’observation X.
  • P(X|C) est la vraisemblance, c’est-à-dire la probabilité d’observer X sachant la classe C.
  • P(C) est la probabilité a priori de la classe C.
  • P(X) est la probabilité marginale de l’observation (constante pour toutes les classes).

L’Hypothèse d’Indépendance Conditionnelle

L’idée centrale de Naive Bayes est l’hypothèse d’indépendance conditionnelle. On suppose que les caractéristiques x_1, x_2, …, x_n sont indépendantes entre elles sachant la classe C. Cela permet de factoriser la vraisemblance :

P(X|C) = P(x_1, x_2, ..., x_n | C) = Π P(x_i | C)

C’est-à-dire que la probabilité conjointe de l’ensemble des caractéristiques sachant la classe est égale au produit des probabilités individuelles de chaque caractéristique sachant la classe. Cette hypothèse est rarement vérifiée dans la pratique — les mots d’un texte ou les caractéristiques d’un patient sont souvent corrélés — mais elle simplifie considérablement les calculs.

Formule de Prédiction

Pour classer une nouvelle observation X, on cherche la classe qui maximise la probabilité a posteriori :

C* = argmax_c P(C=c) · Π P(x_i | C=c)

On n’a pas besoin de calculer P(X) car il est identique pour toutes les classes. La décision repose donc sur le produit de la probabilité a priori de chaque classe par le produit des vraisemblances individuelles.

Le Lissage de Laplace

Un problème pratique majeur survient lorsqu’une caractéristique n’apparaît jamais dans les données d’entraînement pour une classe donnée. Dans ce cas, P(x_i|C) = 0, et comme les probabilités sont multipliées, tout le produit devient nul — même si les autres caractéristiques pointent fortement vers cette classe.

La solution est le lissage de Laplace (ou additif) : on ajoute une petite constante α (généralement 1) au numérateur et α × K au dénominateur (où K est le nombre de valeurs possibles) :

P(x_i | C) = (count(x_i, C) + α) / (count(C) + α × K)

Cette technique garantit qu’aucune probabilité ne sera jamais exactement zéro, tout en restant fidèle aux proportions observées dans les données.

Intuition : Le Détective Probabiliste

Imaginez un détective qui doit déterminer si un e-mail est un spam ou non. Il examine chaque mot présent dans le message : « gratuit », « urgence », « gagné », « réunion », etc. Pour chaque mot, il se demande : « quelle est la probabilité de trouver ce mot dans un spam ? Dans un mail légitime ? »

Le détective naïf de Naive Bayes évalue chaque mot séparément, comme s’il était indépendant des autres. Dans la réalité, les mots « gagner » et « loterie » apparaissent souvent ensemble — ils ne sont pas indépendants. Mais le détective ignore cette corrélation et multiplie simplement toutes les probabilités individuelles.

On pourrait penser que cette approximation est trop grossière pour être utile. Pourtant, elle fonctionne étonnamment bien. Pourquoi ? Parce que l’objectif n’est pas d’estimer des probabilités parfaites, mais de comparer les classes entre elles. Même si les probabilités individuelles sont imparfaites, leur combinaison conserve suffisamment d’information pour prendre la bonne décision dans la majorité des cas.

C’est là toute la beauté de Naive Bayes : une hypothèse naïve qui produit des résultats remarquablement solides. Ce détective probabiliste, avec sa méthode simpliste, bat souvent des modèles beaucoup plus sophistiqués, surtout lorsque les données sont bruitées ou que l’ensemble d’entraînement est limité.

Implémentation Python de Naive Bayes

Les Trois Variantes Principales

Scikit-learn propose trois implémentations de Naive Bayes, chacune adaptée à un type de données différent :

  1. GaussianNB : suppose que les caractéristiques continues suivent une distribution normale (gaussienne).
  2. MultinomialNB : conçu pour des données discrètes (comptages), idéal pour la classification de texte.
  3. BernoulliNB : adapté aux caractéristiques binaires (présence/absence).

1. GaussianNB — Classification de Données Continues

GaussianNB modélise chaque caractéristique par une distribution gaussienne spécifique à chaque classe. Pour chaque classe c et chaque caractéristique i, on estime la moyenne μ{i,c} et la variance σ²{i,c}, puis on calcule :

P(x_i | C=c) = (1 / √(2πσ²_{i,c})) · exp(-(x_i - μ_{i,c})² / (2σ²_{i,c}))
from sklearn.naive_bayes import GaussianNB
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report

# Chargement des données
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.3, random_state=42
)

# Entraînement du classifieur
gnb = GaussianNB()
gnb.fit(X_train, y_train)

# Prédictions et évaluation
y_pred = gnb.predict(X_test)
print(f"Précision : {accuracy_score(y_test, y_pred):.2%}")
print(classification_report(y_test, y_pred, target_names=iris.target_names))

# Probabilités a posteriori pour une nouvelle observation
probas = gnb.predict_proba(X_test[:3])
print("\nProbabilités pour les 3 premières prédictions :")
for i, proba in enumerate(probas):
    print(f"  Observation {i}: {proba}")

Sur le jeu de données Iris, GaussianNB atteint généralement une précision supérieure à 95 %, démontrant que même avec l’hypothèse d’indépendance, les résultats sont excellents lorsque les données sont propres et bien séparées.

2. MultinomialNB — Classification de Texte

MultinomialNB est la variante la plus utilisée en traitement automatique du langage naturel. Elle fonctionne sur des comptages de mots (ou de tokens) et suppose que la fréquence d’apparition de chaque mot suit une distribution multinomiale.

from sklearn.naive_bayes import MultinomialNB
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import train_test_split

# Corpus d'entraînement
textes = [
    "gagnez de l argent facilement cliquez ici maintenant",
    "offre exceptionelle prix imbattable livraison gratuite",
    "réunion d équipe demain à neuf heures salle trois",
    "le rapport trimestriel sera présenté vendredi prochain",
    "promotion limitee cinquante pourcent de reduction aujourd hui",
    "merci de confirmer votre présence pour la conférence",
    "félicitations vous êtes le gagnant cliquez pour réclamer",
    "bonjour je souhaite réserver une chambre pour deux nuits",
]
labels = ["spam", "spam", "ham", "ham", "spam", "ham", "spam", "ham"]

# Vectorisation : conversion des textes en matrices de comptage
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(textes)

# Séparation entraînement/test
X_train, X_test, y_train, y_test = train_test_split(
    X, labels, test_size=0.25, random_state=42
)

# Entraînement du classifieur multinomial
mnb = MultinomialNB(alpha=1.0)
mnb.fit(X_train, y_train)

# Prédiction sur de nouveaux messages
nouveaux_messages = [
    "réduction exceptionelle offre limitee",
    "le directeur demande votre rapport avant vendredi",
]
X_new = vectorizer.transform(nouveaux_messages)
predictions = mnb.predict(X_new)

for msg, pred in zip(nouveaux_messages, predictions):
    print(f"  \"{msg}\" → {pred}")

# Analyse des probabilités par classe
log_proba = mnb.predict_log_proba(X_new)
for i, (msg, lp) in enumerate(zip(nouveaux_messages, log_proba)):
    print(f"  \"{msg}\" : spam={lp[0]:.2f}, ham={lp[1]:.2f}")

Le lissage de Laplace (α) est crucial ici : il empêche les mots inconnus dans le jeu d’entraînement d’annuler complètement la probabilité d’une classe.

3. BernoulliNB — Caractéristiques Binaires

BernoulliNB est conçu pour des caractéristiques strictement binaires. Dans le contexte textuel, on l’utilise avec une représentation binaire (présence/absence du mot) plutôt que des comptages.

from sklearn.naive_bayes import BernoulliNB
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import train_test_split

# Mêmes données que précédemment
textes = [
    "gagnez de l argent facilement cliquez ici maintenant",
    "offre exceptionelle prix imbattable livraison gratuite",
    "réunion d équipe demain à neuf heures salle trois",
    "le rapport trimestriel sera présenté vendredi prochain",
    "promotion limitee cinquante pourcent de reduction aujourd hui",
    "merci de confirmer votre présence pour la conférence",
    "félicitations vous êtes le gagnant cliquez pour réclamer",
    "bonjour je souhaite réserver une chambre pour deux nuits",
]
labels = ["spam", "spam", "ham", "ham", "spam", "ham", "spam", "ham"]

# Binary encoding : on ne compte pas les occurrences, on note la présence
vectorizer = CountVectorizer(binary=True)
X = vectorizer.fit_transform(textes)

# entraînement
bnb = BernoulliNB(alpha=1.0)
bnb.fit(X, labels)

# Prédiction
nouveaux = ["gagnez cliquez maintenant pour réclamer", "confirmez votre rendez-vous médical"]
X_new = vectorizer.transform(nouveaux)
for msg, pred in zip(nouveaux, bnb.predict(X_new)):
    print(f"  \"{msg}\" → {pred}")

BernoulliNB peut surpasser MultinomialNB lorsque la présence d’un mot est plus informative que sa fréquence. Dans le filtrage de spam, le mot « gagnant » apparaît généralement une seule fois dans un e-mail, donc la version binaire suffit amplement.

4. Implémentation From Scratch de GaussianNB

Pour vraiment comprendre Naive Bayes, rien de tel que de l’implémenter de zéro :

import numpy as np

class NaiveBayesFromScratch:
    """Gaussian Naive Bayes implémenté from scratch."""

    def __init__(self):
        self.classes = None
        self.means = None
        self.variances = None
        self.priors = None

    def fit(self, X, y):
        """Estime les paramètres gaussiens et les probabilités a priori."""
        self.classes = np.unique(y)
        n_samples, n_features = X.shape

        self.means = np.zeros((len(self.classes), n_features))
        self.variances = np.zeros((len(self.classes), n_features))
        self.priors = np.zeros(len(self.classes))

        for idx, c in enumerate(self.classes):
            X_c = X[y == c]
            # Moyenne et variance pour chaque caractéristique
            self.means[idx, :] = X_c.mean(axis=0)
            self.variances[idx, :] = X_c.var(axis=0)
            # Probabilité a priori de la classe
            self.priors[idx] = X_c.shape[0] / n_samples

    def _gaussian_density(self, x, mean, var):
        """Calcule la densité gaussienne."""
        coefficient = 1.0 / np.sqrt(2.0 * np.pi * var)
        exponent = np.exp(-(x - mean) ** 2 / (2.0 * var))
        return coefficient * exponent

    def predict_proba(self, X):
        """Calcule les probabilités a posteriori pour chaque classe."""
        n_samples = X.shape[0]
        posteriors = np.zeros((n_samples, len(self.classes)))

        for idx, c in enumerate(self.classes):
            # Log-probabilité a priori
            prior = np.log(self.priors[idx])

            # Log-vraisemblance : somme des logs de densités gaussiennes
            likelihood = 0
            for feature in range(X.shape[1]):
                likelihood += np.log(
                    self._gaussian_density(X[:, feature], self.means[idx, feature],
                                           self.variances[idx, feature]) + 1e-9
                )

            # Log-probabilité a posteriori
            posteriors[:, idx] = prior + likelihood

        # Normalisation pour obtenir des probabilités valides
        log_max = np.max(posteriors, axis=1, keepdims=True)
        posteriors = np.exp(posteriors - log_max)
        posteriors /= posteriors.sum(axis=1, keepdims=True)

        return posteriors

    def predict(self, X):
        """Retourne la classe la plus probable."""
        probas = self.predict_proba(X)
        return self.classes[np.argmax(probas, axis=1)]

# Test avec les données Iris
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.3, random_state=42
)

nb = NaiveBayesFromScratch()
nb.fit(X_train, y_train)
predictions = nb.predict(X_test)
accuracy = np.mean(predictions == y_test)
print(f"Précision (from scratch) : {accuracy:.2%}")
print(f"Classes : {nb.classes}")
print(f"Priors : {nb.priors}")

Cette implémentation manuelle révèle la beauté de l’algorithme : il suffit d’estimer quelques statistiques descriptives (moyennes et variances par classe) pour construire un classifieur complet. L’utilisation des logarithmes pour la combinaison des probabilités évite les problèmes de sous-flottant (underflow).

Hyperparamètres de Naive Bayes

Alpha (Lissage)

C’est l’hyperparamètre le plus important pour MultinomialNB et BernoulliNB :

  • alpha=1.0 (défaut) : lissage de Laplace standard, adapté dans la majorité des cas.
  • alpha < 1.0 (ex: 0.1) : lissage plus faible, le modèle colle davantage aux données observées. Risque de surapprentissage si certaines catégories sont peu représentées.
  • alpha > 1.0 (ex: 2.0) : lissage plus fort, le modèle est plus régulier mais peut perdre en précision.
# Recherche de l'hyperparamètre optimal
from sklearn.model_selection import GridSearchCV

param_grid = {"alpha": [0.01, 0.1, 0.5, 1.0, 2.0, 5.0]}
grid = GridSearchCV(MultinomialNB(), param_grid, cv=5, scoring="accuracy")
grid.fit(X_train, y_train)
print(f"Meilleur alpha : {grid.best_params_['alpha']}")

Var_smoothing (GaussianNB)

Ce paramètre contrôle la fraction de la plus grande variance ajoutée aux variances pour la stabilité numérique :

  • var_smoothing=1e-9 (défaut) : généralement suffisant.
  • Augmenter cette valeur si les données contiennent des caractéristiques avec une variance très faible (voire nulle), ce qui pourrait provoquer des divisions par zéro.

Avantages et Limites de Naive Bayes

Avantages

  • Rapidité d’entraînement : Simple calcul de statistiques descriptives (moyennes, variances, comptages). Aucune itération complexe nécessaire.
  • Efficacité avec peu de données : Fonctionne bien même avec quelques centaines d’exemples. Là où un réseau de neurones aurait besoin de milliers d’observations, Naive Bayes donne déjà des résultats convaincants.
  • Gestion du bruit : L’hypothèse d’indépendance agit comme une forme implicite de régularisation. Le modèle ne s’attache pas aux corrélations fortuites dans les données d’entraînement.
  • Pas de surapprentissage excessif : Le modèle est intrinsèquement simple et rarement trop complexe pour les données.
  • Probabilités calibrées : Les probabilités produites sont souvent bien calibrées (bien que pas parfaites).
  • Idéal pour le texte : Les comptages de mots s’alignent naturellement avec l’approche bayésienne.

Limites

  • Hypothèse d’indépendance irréaliste : Les caractéristiques sont rarement indépendantes dans la pratique. Des mots comme « machine » et « learning » apparaissent toujours ensemble dans les documents techniques.
  • Problème des corrélations : Si deux caractéristiques fortement corrélées sont présentes, leur influence est comptée deux fois, ce qui peut biaiser la prédiction.
  • GaussianNB suppose la normalité : Si les données suivent une distribution très différente (exponentielle, uniforme, multimodale), les performances chutent significativement.
  • Sensible aux catégories non vues : Même avec le lissage, les catégories absentes de l’entraînement sont mal gérées et peuvent réduire la qualité des prédictions.
  • Probabilités imperfectes : Les valeurs absolues des probabilités ne sont pas fiables à cause de l’indépendance supposée. On peut faire confiance au classement des classes, mais pas aux probabilités elles-mêmes.
  • Moins puissant que les modèles complexes : Pour les problèmes avec des interactions complexes entre caractéristiques, des algorithmes comme Random Forest, XGBoost ou les réseaux de neurones sont nettement supérieurs.

4 Cas d’Usage Concrets de Naive Bayes

1. Filtrage de Spam et E-mails Indésirables

C’est l’application historique de Naive Bayes, popularisée par Paul Graham dans son essai « A Plan for Spam » en 2002. Le principe est simple et élégant :

  • Chaque mot du message contribue à la décision finale.
  • Les mots comme « loterie », « urgence », « cliquez ici » augmentent la probabilité de spam.
  • Les mots comme « réunion », « budget », « compte-rendu » diminuent cette probabilité.
  • Le lissage de Laplace gère les mots jamais rencontrés auparavant.
# Exemple concret de filtrage de spam
spam_emails = [
    "URGENT Votre compte sera fermé cliquez ici immédiatement",
    "GAGNEZ 1000 euros gratuitement offre limitée aujourd'hui",
    "Médicament miracle perte de poids garantie sans ordonnance",
]
legit_emails = [
    "Bonjour, la réunion de projet est reportée à jeudi prochain.",
    "Veuillez trouver ci-joint le rapport mensuel des ventes.",
    "Merci de confirmer votre présence pour le séminaire d'entreprise.",
]

# Entraînement et évaluation
all_textes = spam_emails + legit_emails
all_labels = ["spam"] * len(spam_emails) + ["ham"] * len(legit_emails)

vec = CountVectorizer()
X = vec.fit_transform(all_textes)

mnb = MultinomialNB(alpha=0.5)
mnb.fit(X, all_labels)

# Test sur de nouveaux messages
test_messages = [
    "Votre colis sera livré demain avant midi",
    "Félicitations vous avez été sélectionné pour recevoir un prix",
]
X_test = vec.transform(test_messages)
for msg, pred in zip(test_messages, mnb.predict(X_test)):
    print(f"  {pred.upper()}: \"{msg}\"")

2. Analyse de Sentiments sur les Avis Clients

Naive Bayes est couramment utilisé pour déterminer si un avis client est positif, négatif ou neutre. Chaque mot porte un sentiment :

  • « excellent », « fantastique », « recommandé » → positif
  • « déçu », « catastrophe », « à fuir » → négatif
  • « correct », « moyen », « bof » → neutre

L’avantage principal est la rapidité : on peut analyser des millions d’avis en quelques secondes, ce qui est crucial pour le suivi en temps réel de la réputation d’une marque sur internet. Les entreprises de e-commerce l’utilisent pour trier automatiquement les avis et identifier les problèmes récurrents signalés par les clients.

3. Diagnostic Médical Assisté

Dans le domaine médical, Naive Bayes peut aider à évaluer la probabilité d’une maladie en fonction des symptômes présentés par un patient :

  • Chaque symptôme contribue indépendamment au diagnostic probabiliste.
  • La probabilité a priori de chaque maladie peut être calée sur les statistiques épidémiologiques régionales.
  • Le résultat est une liste de diagnostics possibles classés par probabilité, que le médecin peut utiliser comme aide à la décision.

Bien que l’hypothèse d’indépendance soit discutable en médecine (les symptômes sont souvent liés), Naive Bayes reste un outil de dépistage initial rapide et interprétable. Sa transparence — chaque symptôme a un poids clair — est un atout majeur dans un domaine où l’explicabilité est cruciale.

4. Détection de Fraude Bancaire

Les institutions financières utilisent Naive Bayes pour détecter les transactions suspectes en temps réel :

  • Chaque caractéristique de la transaction (montant, lieu, horaire, type de commerçant) est évaluée indépendamment.
  • Une transaction de montant élevé effectuée à l’étranger à trois heures du matin déclenche une alerte probabiliste.
  • La rapidité de classification (quelques millisecondes) est essentielle pour bloquer une transaction avant qu’elle ne soit validée.

L’algorithme produit une probabilité de fraude qui peut être comparée à un seuil configurable. Cette approche est particulièrement utile en complément d’autres systèmes de détection, formant ainsi un ensemble de modèles qui se renforcent mutuellement pour une sécurité maximale.

Voir Aussi

About Salah YAHIAOUI

Salah YAHIAOUI publie sur The Pingouin des tutoriels Python et des décryptages sur l’intelligence artificielle. Les cas pratiques récents précisent leurs données, leurs tests et leurs limites. À propos, méthode éditoriale et contact.

View all posts by Salah YAHIAOUI →