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

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

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

Le naive bayes bernoulli est l’un des algorithmes de classification supervisée les plus efficaces pour traiter des données strictement binaires. Dans ce guide complet, nous explorerons ses fondements mathématiques, son intuition profonde et son implémentation pratique en Python avec scikit-learn.


Principe mathématique

Le naive bayes bernoulli repose sur une hypothèse fondamentale : chaque caractéristique (ou feature) du jeu de données suit une distribution de Bernoulli. Contrairement au Naive Bayes gaussien — qui modélise des variables continues — ou au Naive Bayes multinomial — qui traite des comptages entiers, le Naive Bayes bernoulli ne connaît que deux états possibles : 0 ou 1, absent ou présent, oui ou non.

Le modèle de Bernoulli

Pour chaque caractéristique xᵢ et chaque classe y, le modèle estime la probabilité conditionnelle :

P(xᵢ | y) = θᵢ,y^xᵢ × (1 − θᵢ,y)^(1 − xᵢ)

où :

  • θᵢ,y est la probabilité que la caractéristique i soit présente (égale à 1) sachant que l’observation appartient à la classe y.
  • xᵢ ∈ {0, 1} est la valeur binaire de la caractéristique i.
  • Si xᵢ = 1, la probabilité vaut θᵢ,y.
  • Si xᵢ = 0, la probabilité vaut 1 − θᵢ,y.

Estimation des paramètres θᵢ,y

Étant donné un ensemble d’entraînement, on estime θᵢ,y par la fréquence d’apparition de la caractéristique i dans les observations de classe y :

θᵢ,y = (nombre d’observations de classe y où xᵢ = 1) / (nombre total d’observations de classe y)

Lissage de Laplace

Un problème majeur survient lorsqu’une caractéristique n’apparaît jamais dans une classe donnée : l’estimateur du maximum de vraisemblance produirait θᵢ,y = 0, ce qui annulerait complètement le produit de probabilités (rappel : le modèle suppose l’indépendance conditionnelle des caractéristiques). Pour résoudre ce problème catastrophique, on applique le lissage de Laplace :

θᵢ,y = (Nᵢ,y + α) / (N_y + 2α)

où :

  • Nᵢ,y est le nombre d’observations de classe y ayant xᵢ = 1.
  • N_y est le nombre total d’observations de classe y.
  • α est le paramètre de lissage (par défaut α = 1, ce qui correspond au lissage de Laplace classique).

Le dénominateur contient 2α car la distribution de Bernoulli possède exactement deux modalités (0 et 1), contrairement au cas multinomial où le dénominateur serait N_y + kα avec k le nombre de modalités.

Règle de décision

La prédiction pour une observation x s’obtient en calculant pour chaque classe y :

P(y | x) ∝ P(y) × ∏ᵢ P(xᵢ | y)

La classe prédite est celle qui maximise cette probabilité a posteriori :

ŷ = argmax_y [ log P(y) + Σᵢ log P(xᵢ | y) ]

En pratique, on travaille en espace logarithmique pour éviter les problèmes de sous-flottant (underflow) lors de la multiplication de nombreuses petites probabilités.


Intuition

Imaginez que vous devez trier des courriers électroniques en deux catégories : spam ou non-spam. Plutôt que de compter combien de fois le mot « gratuit » apparaît — ce que ferait le Naive Bayes multinomial — le naive bayes bernoulli se contente de se demander : le mot « gratuit » est-il présent ou absent dans cet e-mail ?

Cette approche binaire simplifie considérablement le modèle. Chaque caractéristique devient une question fermée :

  • Le mot « urgence » est-il présent ? → Oui (1) / Non (0)
  • Le destinataire contient-il « .edu » ? → Oui (1) / Non (0)
  • Le message contient-il plus de 3 points d’exclamation ? → Oui (1) / Non (0)

Pourquoi pas le Naive Bayes multinomial alors ?

C’est une excellente question. Le Naive Bayes multinomial est plus adapté lorsque la fréquence d’un mot ou d’un événement est informative. Par exemple, si un e-mail contient le mot « gagnant » 50 fois, il est presque certainement un spam. Le Naive Bayes bernoulli ignorerait cette information précieuse : il enregistrerait simplement présence = 1.

En revanche, le Naive Bayes bernoulli excelle dans les situations où :

  1. La présence importe plus que la fréquence. Qu’un mot apparaisse une ou cent fois, l’information qualitative est la même.
  2. Les données sont naturellement binaires. Par exemple, un profil patient en médecine : présence ou absence de symptômes.
  3. L’espace des caractéristiques est très élevé (des milliers ou des dizaines de milliers de mots). La binarisation réduit le bruit causé par les comptages extrêmes.

Implémentation Python complète

Voici une implémentation complète et exécutable du naive bayes bernoulli avec scikit-learn, incluant des données synthétiques, une comparaison avec le Naive Bayes multinomial et une analyse visuelle.

import numpy as np
import matplotlib.pyplot as plt
from sklearn.naive_bayes import BernoulliNB, MultinomialNB
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import (
    confusion_matrix, classification_report,
    accuracy_score, ConfusionMatrixDisplay
)
from sklearn.feature_extraction.text import CountVectorizer

# ============================================================
# 1. Génération de données binaires synthétiques
# ============================================================
np.random.seed(42)

# On crée un jeu de données binaire avec 3 classes
n_samples = 600
n_features = 15

# On définit des profils distincts pour chaque classe
theta_class0 = np.array([0.9, 0.8, 0.7, 0.1, 0.1, 0.2, 0.3, 0.1, 0.9, 0.8, 0.7, 0.1, 0.2, 0.3, 0.1])
theta_class1 = np.array([0.1, 0.2, 0.1, 0.8, 0.9, 0.8, 0.1, 0.9, 0.1, 0.2, 0.1, 0.8, 0.9, 0.8, 0.1])
theta_class2 = np.array([0.5, 0.1, 0.9, 0.5, 0.1, 0.9, 0.8, 0.1, 0.5, 0.9, 0.1, 0.9, 0.1, 0.9, 0.8])

X_list = []
y_list = []

n_per_class = n_samples // 3
for theta, label in zip([theta_class0, theta_class1, theta_class2], [0, 1, 2]):
    for _ in range(n_per_class):
        # Chaque caractéristique suit une distribution de Bernoulli
        row = np.random.binomial(1, theta)
        X_list.append(row)
        y_list.append(label)

X = np.array(X_list)
y = np.array(y_list)

print(f"Jeu de données : {n_samples} échantillons, {n_features} caractéristiques binaires")
print(f"Classes : {np.unique(y, return_counts=True)}")

# ============================================================
# 2. Division entraînement / test
# ============================================================
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

print(f"\nEntraînement : {X_train.shape[0]} échantillons")
print(f"Test         : {X_test.shape[0]} échantillons")

# ============================================================
# 3. Naive Bayes Bernoulli avec différents alpha
# ============================================================
# On évalue l'impact du paramètre de lissage alpha
alphas = [0.01, 0.1, 0.5, 1.0, 2.0, 5.0, 10.0]
train_scores = []
test_scores = []

for alpha in alphas:
    bnb = BernoulliNB(alpha=alpha)
    bnb.fit(X_train, y_train)
    train_scores.append(bnb.score(X_train, y_train))
    test_scores.append(bnb.score(X_test, y_test))

# Visualisation de l'impact de alpha
plt.figure(figsize=(10, 6))
plt.plot(alphas, train_scores, 'o-', label="Score entraînement", linewidth=2, markersize=8)
plt.plot(alphas, test_scores, 's-', label="Score test", linewidth=2, markersize=8)
plt.xscale('log')
plt.xlabel("Paramètre de lissage alpha (échelle logarithmique)", fontsize=12)
plt.ylabel("Précision (accuracy)", fontsize=12)
plt.title("Impact du lissage de Laplace sur le Naive Bayes Bernoulli", fontsize=14, fontweight='bold')
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('bnb_alpha_impact.png', dpi=150, bbox_inches='tight')
plt.close()
print("Graphique de l'impact de alpha sauvegardé : bnb_alpha_impact.png")

# ============================================================
# 4. Meilleur modèle et rapport de classification
# ============================================================
best_alpha = alphas[np.argmax(test_scores)]
print(f"\nMeilleur alpha : {best_alpha} (précision test : {max(test_scores):.4f})")

bnb_best = BernoulliNB(alpha=best_alpha)
bnb_best.fit(X_train, y_train)
y_pred = bnb_best.predict(X_test)

print("\n" + "="*60)
print("RAPPORT DE CLASSIFICATION — NAIVE BAYES BERNOULLI")
print("="*60)
print(classification_report(y_test, y_pred))

# ============================================================
# 5. Matrice de confusion
# ============================================================
cm = confusion_matrix(y_test, y_pred)
fig, ax = plt.subplots(figsize=(8, 6))
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=["Classe 0", "Classe 1", "Classe 2"])
disp.plot(ax=ax, cmap='Blues', values_format='d')
ax.set_title('Matrice de confusion — Naive Bayes Bernoulli', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.savefig('bnb_confusion_matrix.png', dpi=150, bbox_inches='tight')
plt.close()
print("Matrice de confusion sauvegardée : bnb_confusion_matrix.png")

# ============================================================
# 6. Comparaison avec Naive Bayes Multinomial
# ============================================================
print("\n" + "="*60)
print("COMPARAISON BERNOULLI vs MULTINOMIAL")
print("="*60)

# Pour le MultinomialNB, les données binaires restent valides
# (0 et 1 sont aussi des entiers non négatifs)
mnb = MultinomialNB(alpha=best_alpha)
mnb.fit(X_train, y_train)
y_pred_mnb = mnb.predict(X_test)

print(f"BernoulliNB   — Précision test : {accuracy_score(y_test, y_pred):.4f}")
print(f"MultinomialNB — Précision test : {accuracy_score(y_test, y_pred_mnb):.4f}")

print("\nRapport MultinomialNB :")
print(classification_report(y_test, y_pred_mnb))

# Validation croisée pour comparaison robuste
bnb_cv = cross_val_score(BernoulliNB(alpha=best_alpha), X, y, cv=5, scoring='accuracy')
mnb_cv = cross_val_score(MultinomialNB(alpha=best_alpha), X, y, cv=5, scoring='accuracy')

print(f"\nValidation croisée (5 plis) :")
print(f"BernoulliNB   : {bnb_cv.mean():.4f} (+/- {bnb_cv.std():.4f})")
print(f"MultinomialNB : {mnb_cv.mean():.4f} (+/- {mnb_cv.std():.4f})")

# ============================================================
# 7. Application au texte avec CountVectorizer(binary=True)
# ============================================================
print("\n" + "="*60)
print("CLASSIFICATION DE TEXTE AVEC NAIVE BAYES BERNOULLI")
print("="*60)

documents = [
    "achat gratuit offre spéciale prix imbattable",
    "réunion demain matin dix heures bureau",
    "gagnez argent rapidement compte bancaire",
    "rapport projet livraison vendredi équipe",
    "félicitations vous avez été sélectionné cliquez ici",
    "note de service mise à jour politique entreprise",
    "promotion exceptionnelle dernier jour ne ratez pas",
    "compte rendu analyse financière trimestrielle",
    "loterie jackpot millionnaire fortune immédiate",
    "ordre du jour conseil administration mensuel"
]

labels_texte = [1, 0, 1, 0, 1, 0, 1, 0, 1, 0]  # 1 = spam, 0 = normal

# CountVectorizer avec binary=True est essentiel pour BernoulliNB
vectorizer = CountVectorizer(binary=True)
X_texte = vectorizer.fit_transform(documents)
print(f"Vocabulaire ({len(vectorizer.get_feature_names_out())} termes) : {vectorizer.get_feature_names_out()}")
print(f"Matrice binaire : {X_texte.shape}")

# Entraînement sur le jeu de texte
bnb_texte = BernoulliNB()
bnb_texte.fit(X_texte, labels_texte)
y_pred_texte = bnb_texte.predict(X_texte)
print(f"\nPrécision sur texte (entraînement) : {accuracy_score(labels_texte, y_pred_texte):.4f}")

# Affichage des probabilités pour un nouveau document
nouveaux_docs = [
    "offre exceptionnelle gratuit cliquez maintenant",
    "présentation résultats annuels département finance"
]
X_nouveau = vectorizer.transform(nouveaux_docs)
probas = bnb_texte.predict_proba(X_nouveau)

for i, doc in enumerate(nouveaux_docs):
    pred = bnb_texte.predict(X_nouveau)[i]
    etiquette = "SPAM" if pred == 1 else "NORMAL"
    print(f'\nDocument : "{doc}"')
    print(f"Prédiction : {etiquette}")
    print(f"Probabilités — Normal : {probas[i][0]:.4f}, Spam : {probas[i][1]:.4f}")

print("\n" + "="*60)
print("L'implémentation est terminée avec succès !")
print("="*60)

Ce code est entièrement exécutable et démontre toutes les facettes du naive bayes bernoulli : génération de données synthétiques binaires, exploration de l’hyperparamètre alpha, comparaison avec le MultinomialNB, matrices de confusion et classification de texte avec binarisation par CountVectorizer(binary=True). Tous les commentaires sont rédigés en français pour une meilleure compréhension.


Hyperparamètres

Hyperparamètre Type Valeur par défaut Description
alpha float 1.0 Paramètre de lissage additif (Laplace). Plus α est grand, plus les probabilités sont lissées vers la moyenne. Un α très petit (< 0.1) peut causer du surapprentissage, un α très grand (> 10) du sous-apprentissage.
binarize float ou None 0.0 Seuil de binarisation. Si un nombre flottant est fourni, toutes les valeurs supérieures à ce seuil sont converties en 1, les autres en 0. Si None, les données d’entrée sont déjà supposées binaires. Très utile pour transformer rapidement des données continues en données binaires.
fit_prior bool True Si True, le modèle apprend les probabilités a priori P(y) à partir des données d’entraînement. Si False, on suppose une distribution uniforme des classes (toutes les classes sont équiprobables).
class_prior array-like ou None None Probabilités a priori explicites pour chaque classe. Si fourni, remplace l’estimation automatique. Utile quand on connaît la distribution réelle des classes dans la population cible (par exemple, en cas de déséquilibre connu).

Avantages / Limites

Avantages

  • Extrêmement rapide : L’entraînement se résume à des comptages et des produits de probabilités. La complexité est linéaire par rapport au nombre d’échantillons et de caractéristiques : O(n × d) où n est le nombre d’échantillons et d le nombre de features.
  • Peu de données nécessaires : Grâce au lissage de Laplace, le modèle fonctionne correctement même avec peu d’observations par classe.
  • Interprétabilité : Les probabilités θᵢ,y sont directement lisibles : on sait exactement combien chaque caractéristique contribue à la décision.
  • Pas de surapprentissage avec des données binaires : Contrairement à des modèles complexes, le Naive Bayes Bernoulli généralise bien même avec un nombre élevé de caractéristiques.
  • Parfait pour les données binaires : C’est le modèle mathématiquement adapté quand les features sont intrinsèquement binaires (présence/absence, oui/non).
  • Prédictions probabilistes : Le modèle produit directement predict_proba(), ce qui est précieux pour le tri, le scoring et la prise de décision nuancée.

Limites

  • Hypothèse d’indépendance conditionnelle : Comme tous les modèles Naive Bayes, le naive bayes bernoulli suppose que les caractéristiques sont indépendantes sachant la classe. Cette hypothèse est presque toujours violée en pratique (les mots dans un texte ne sont pas indépendants !). Paradoxalement, le modèle fonctionne quand même bien.
  • Perte d’information par binarisation : En réduisant les features à 0 ou 1, on perd l’information de fréquence. Un mot apparaissant 100 fois et un mot apparaissant 1 fois reçoivent le même traitement.
  • Sensible aux features corrélées : Les caractéristiques fortement corrélées sont comptées plusieurs fois, ce qui fausse l’estimation des probabilités.
  • Probabilités mal calibrées : Les probabilités prédites tendent à être extrêmes (très proches de 0 ou 1), même quand la confiance réelle du modèle devrait être modérée.

4 cas d’usage concrets

1. Détection de spam par présence de mots-clés

Le cas d’usage historique et le plus classique du naive bayes bernoulli. On construit un vocabulaire de mots indicateurs de spam (« gratuit », « gagnez », « urgence », « cliquez ») et on représente chaque e-mail comme un vecteur binaire : le mot est-il présent ou non ? La binarisation évite qu’un spammeur ne contourne le filtre en répétant un mot suspect des centaines de fois.

2. Diagnostic médical à partir de symptômes binaires

Un patient présente-t-il de la fièvre ? Des nausées ? Des maux de tête ? Chaque symptôme est codé binairement (présent/absent). Le naive bayes bernoulli peut classifier rapidement le type de pathologie à partir de ce profil binaire. C’est particulièrement adapté aux questionnaires médicaux structurés où la réponse est systématiquement oui ou non.

3. Authentification biométrique comportementale

Dans les systèmes de sécurité, on peut modéliser le comportement d’un utilisateur par des caractéristiques binaires : l’utilisateur se connecte-t-il depuis son appareil habituel ? À son horaire habituel ? Depuis sa localisation habituelle ? Chaque écart par rapport à la normale est un 1, chaque conformité un 0. Le modèle détecte les anomalies comportementales de manière efficace et rapide.

4. Classification de documents par présence de balises

En traitement automatique du langage, on peut classifier des documents web selon les balises HTML ou les métadonnées qu’ils contiennent : présence d’un formulaire (1/0), présence de balises <script> (1/0), présence de mots-clés dans les métadonnées (1/0). Cette approche binaire est robuste et rapide pour classifier de grands volumes de pages web.


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 →