Phase 02: ML Fundamentals

Bayes naïf

L'hypothèse naïve est fausse, et elle fonctionne quand même.

Type: Build

Language:Python

Prerequisites: Phase 2, Lessons 01-07 (classification, Bayes' theorem)

Time: ~75 minutes

Objectifs d'apprentissage

  • Implémenter des Bayes naïfs multinomiels à partir de zéro avec le lissage Laplace pour la classification du texte
  • Expliquez pourquoi l'hypothèse naïve d'indépendance est mathématiquement erronée mais produit des classements de classe corrects dans la pratique.
  • Comparez les variantes de Bayes naïves multinomial, bernoulli et gaussienne et sélectionnez la bonne pour un type de fonctionnalité donné
  • Évaluer les naïfs Bayes contre la régression logistique sur les données rares de haute dimension et expliquer le compromis de biais-variance au travail

Le problème

Vous devez classer le texte. Les e-mails en spam ou non-spam. Les avis des clients en positifs ou négatifs. Les billets de support en catégories. Vous avez des milliers de fonctionnalités (une par mot) et des données de formation limitées.

La plupart des classifiateurs s'étouffent ici. La régression logistique a besoin de suffisamment d'échantillons pour estimer des milliers de poids de manière fiable. Les arbres de décision se divisent sur un mot à la fois et s'adaptent de manière sauvage. KNN en 10 000 dimensions est sans sens car chaque point est également éloigné de tous les autres points.

Bayes naïf s'occupe de ça. Il fait une hypothèse mathématiquement erronée (que chaque fonctionnalité est indépendante de toutes les autres fonctionnalités données dans la classe), et il surpasse toujours les modèles "smarts" sur la classification des textes, en particulier avec de petits ensembles de formation. Il traîne en une seule fois les données. Il est à l'échelle de millions de caractéristiques. Il produit des estimations de probabilité (bien que souvent mal calibrées en raison de l'hypothèse d'indépendance).

Comprendre pourquoi une supposition erronée conduit à de bonnes prédictions vous apprend quelque chose de fondamental sur l'apprentissage automatique: le meilleur modèle n'est pas le plus correct, c'est celui qui a le meilleur compromis de variance de biais pour vos données.

Le concept

Théorème de Bayes (révision rapide)

Le théorème de Bayes renverse les probabilités conditionnelles:

P(class | features) = P(features | class) * P(class) / P(features)

Nous voulonsP(class | features)-- la probabilité qu'un document appartient à une classe compte tenu des mots qui y sont.

  • P(features | class)-- la probabilité de voir ces mots dans des documents de cette classe
  • P(class)-- la probabilité antérieure de la classe (combien de spam est commun en général?)
  • P(features)- les preuves, les mêmes pour toutes les classes, afin que nous puissions l'ignorer lors de la comparaison

La classe avec le plus haut .P(class | features)Il gagne.

La supposition naïve de l'indépendance

L' informatique P(features | class)Il faut calculer la probabilité commune de toutes les caractéristiques ensemble. avec un vocabulaire de 10 000 mots, vous devriez estimer une distribution sur 2^10 000 combinaisons possibles.

L'hypothèse naïve: chaque caractéristique est conditionnellement indépendante compte tenu de la classe.

P(w1, w2, ..., wn | class) = P(w1 | class) * P(w2 | class) * ... * P(wn | class)

Au lieu d'une distribution commune impossible, vous estimez n distributions simples par fonction.

Cette hypothèse est évidemment fausse. Les mots "machine" et "apprentissage" ne sont indépendants dans aucun document. Mais le classifiant n'a pas besoin d'estimations de probabilité correctes. Il a besoin de classements corrects - quelle classe a la plus grande probabilité. L'hypothèse d'indépendance introduit des erreurs systématiques, mais ces erreurs affectent toutes les classes de manière similaire, de sorte que le classement reste correct.

Pourquoi il fonctionne encore

Trois raisons:

  1. Ranking over calibration.La classification ne nécessite que la classe la plus haut classée pour être correcte. Même si P(spam) = 0,99999 lorsque la vraie probabilité est de 0,7, le classifiant choisit toujours correctement le spam. Nous n'avons pas besoin de probabilités correctes. Nous avons besoin du gagnant correct.
  1. High bias, low variance.L'hypothèse d'indépendance est un précédent fort. Elle restreint fortement le modèle, ce qui empêche le surpassage. Avec des données de formation limitées, un modèle légèrement erroné mais stable bat un modèle théoriquement correct mais extrêmement instable.
  1. Feature redundancy cancels out.Les caractéristiques corrélatives fournissent des preuves redondantes. Le classifiant compte ces preuves à double, mais il les compte à double pour la classe correcte aussi. Si "machine" et "apprentissage" apparaissent toujours ensemble, les deux fournissent des preuves pour la classe "tech". NB les compte deux fois, mais il les compte deux fois pour la classe correcte.

Une quatrième raison pratique: Naïf Bayes est extrêmement rapide. La formation est un seul passage à travers les fréquences de comptage des données. La prédiction est une multiplication de matrice. Vous pouvez vous entraîner sur un million de documents en quelques secondes. Cette vitesse signifie que vous pouvez iterer plus rapidement, essayer plus de fonctionnalités et exécuter plus d'expériences que avec des modèles plus lents.

Les mathématiques étape par étape

Prenons un exemple concret: supposons que nous ayons deux classes: spam et non-spam. Notre vocabulaire a trois mots: "gratuit", "argent", "réunion".

Données de formation:

  • Les e-mails de spam mentionnent "gratuit" 80 fois, "argent" 60 fois, "réunion" 10 fois (150 mots au total)
  • Les e-mails non-spam mentionnent "gratuit" 5 fois, "argent" 10 fois, "réunion" 100 fois (115 mots au total)
  • 40% des e-mails sont des spams, 60% ne sont pas des spams

Avec le lissage Laplace (alpha=1):

P(free | spam)    = (80 + 1) / (150 + 3) = 81/153 = 0.529
P(money | spam)   = (60 + 1) / (150 + 3) = 61/153 = 0.399
P(meeting | spam) = (10 + 1) / (150 + 3) = 11/153 = 0.072

P(free | not-spam)    = (5 + 1) / (115 + 3) = 6/118 = 0.051
P(money | not-spam)   = (10 + 1) / (115 + 3) = 11/118 = 0.093
P(meeting | not-spam) = (100 + 1) / (115 + 3) = 101/118 = 0.856

Le nouveau courriel contient: "gratuit" (2 fois), "argent" (1 fois), "réunion" (0 fois).

log P(spam | email) = log(0.4) + 2*log(0.529) + 1*log(0.399) + 0*log(0.072)
                    = -0.916 + 2*(-0.637) + (-0.919) + 0
                    = -3.109

log P(not-spam | email) = log(0.6) + 2*log(0.051) + 1*log(0.093) + 0*log(0.856)
                        = -0.511 + 2*(-2.976) + (-2.375) + 0
                        = -8.838

Le mot "gratuit" apparaissant deux fois est une preuve forte du spam. Notez que "réunion" ne apparaissant pas contribue à zéro à les deux log sumes (0 * log(P)) - dans le NB multivarié, les mots absents n'ont aucun effet. C'est Bernoulli NB qui modélise explicitement l'absence de mots.

Trois variantes

Bayes naïf est disponible en trois saveurs.P(feature | class)- C'est différent.

#### Bayes naïf à plusieurs noms

Modèles de chaque fonctionnalité comme un compte. Il est préférable pour les données de texte où les caractéristiques sont des fréquences de mots ou des valeurs TF-IDF.

P(word_i | class) = (count of word_i in class + alpha) / (total words in class + alpha * vocab_size)

Le alphaest le lissage Laplace (expliqué ci-dessous).

#### Bayes naïf gaussien

Modèle de chaque fonctionnalité comme une distribution normale.

P(x_i | class) = (1 / sqrt(2 * pi * var)) * exp(-(x_i - mean)^2 / (2 * var))

Chaque classe a sa propre moyenne et sa propre variance par fonctionnalité. Cela fonctionne bien lorsque les fonctionnalités suivent réellement une courbe de cloche au sein de chaque classe.

#### Bernoulli naïf Bayes

Modèles de chaque fonctionnalité comme binaire (présent ou absent).

P(word_i | class) = (docs in class containing word_i + alpha) / (total docs in class + 2 * alpha)

Contrairement à Multinomial, Bernoulli pénalise explicitement l'absence d'un mot. Si "libre" apparaît généralement dans le spam mais est absent de cet e-mail, Bernoulli le compte comme preuve contre le spam.

Quand utiliser chaque variante

VariantFeature TypeBest ForExample
MultinomialCounts or frequenciesText classification, bag-of-wordsEmail spam, topic classification
GaussianContinuous valuesTabular data with normal-ish featuresIris classification, sensor data
BernoulliBinary (0/1)Short text, binary feature vectorsSMS spam, presence/absence features

Légimentation de la place

Que se passe-t-il lorsqu'un mot apparaît dans les données d'essai mais n'apparaît jamais dans les données de formation pour une classe particulière?

Sans lissage:P(word | class) = 0/N = 0Un zéro multiplié par l' ensemble produit faitP(class | features) = 0Un seul mot invisible détruit toute la prédiction, peu importe combien d'autres preuves l'appuient.

Le lissage de la laplace ajoute un petit nombre alpha(généralement 1) pour chaque nombre de caractéristiques:

P(word_i | class) = (count(word_i, class) + alpha) / (total_words_in_class + alpha * vocab_size)

Avec alpha = 1, chaque mot obtient au moins une probabilité minuscule. Le mot "discombobulate" apparaissant dans un e-mail de test ne tue plus la probabilité de spam. Le smoothing a une interprétation bayésienne: il est équivalent à placer un Dirichlet uniforme avant les distributions de mots.

L'alpha supérieur signifie un raffinement plus fort (distributions plus uniformes).

L'effet de l' alpha:

AlphaEffectWhen to use
0.001Almost no smoothing, trust the dataVery large training set, no unseen features expected
0.1Light smoothingLarge training set
1.0Standard Laplace smoothingDefault starting point
10.0Heavy smoothing, flattens distributionsVery small training set, many unseen features expected

Compteur log-espace

Multiplication de centaines de probabilités (chacune inférieure à 1) provoque un sous-flux de point flottant. Le produit devient zéro dans le point flottant même si la valeur réelle est un nombre positif très petit.

La solution: travailler dans l'espace log. Au lieu de multiplier les probabilités, ajoutez leurs logarithmes:

log P(class | x1, x2, ..., xn) = log P(class) + sum_i log P(xi | class)

Cela transforme la prédiction en un produit de point:

log_scores = X @ log_feature_probs.T + log_class_priors
prediction = argmax(log_scores)

La multiplication de matrice. C'est pourquoi la prédiction de Bayes naïve est si rapide -- c'est la même opération qu'un modèle linéaire à couche unique.

Bayes naïf contre la régression logistique

Les deux sont des classifiants linéaires pour le texte.

AspectNaive BayesLogistic Regression
TypeGenerative (models P(X|Y))Discriminative (models P(Y|X))
TrainingCount frequenciesOptimize loss function
Small dataBetter (strong prior helps)Worse (not enough to estimate weights)
Large dataWorse (wrong assumption hurts)Better (flexible boundary)
FeaturesAssumes independenceHandles correlations
SpeedSingle pass, very fastIterative optimization
CalibrationPoor probabilitiesBetter probabilities

Règle générale: commencez par Naive Bayes. Si vous avez assez de données et des plateaux NB, passez à la régression logistique.

L'équipement de transport

flowchart LR
    A[Raw Text] --> B[Tokenize]
    B --> C[Build Vocabulary]
    C --> D[Count Word Frequencies]
    D --> E[Apply Smoothing]
    E --> F[Compute Log Probabilities]
    F --> G[Predict: argmax P class given words]

    style A fill:#f9f,stroke:#333
    style G fill:#9f9,stroke:#333

En pratique, nous travaillons dans l'espace log pour éviter le sous-flux des points flottants. Au lieu de multiplier de nombreuses petites probabilités, nous ajoutons leurs logarithmes:

log P(class | features) = log P(class) + sum_i log P(feature_i | class)

Faites-le

Le code dans code/naive_bayes.pyIl implémentera à la fois MultinomialNB et GaussianNB à partir de zéro.

Nombre de noms

La mise en œuvre à partir de zéro:

  1. fit(X, y)Pour chaque classe, comptez la fréquence de chaque fonctionnalité. Ajoutez le lissage Laplace. Comptez les probabilités de journaux. Réservez les prérogatives de classe (journaux de fréquences de classe).
  1. predict_log_proba(X)Pour chaque échantillon, calculer le log P(class) + la somme du log P(feature_i==class) pour toutes les classes.
  1. predict(X): Retournez la classe avec la plus grande probabilité de log.
pythonclass MultinomialNB:
    def __init__(self, alpha=1.0):
        self.alpha = alpha

    def fit(self, X, y):
        classes = np.unique(y)
        n_classes = len(classes)
        n_features = X.shape[1]

        self.classes_ = classes
        self.class_log_prior_ = np.zeros(n_classes)
        self.feature_log_prob_ = np.zeros((n_classes, n_features))

        for i, c in enumerate(classes):
            X_c = X[y == c]
            self.class_log_prior_[i] = np.log(X_c.shape[0] / X.shape[0])
            counts = X_c.sum(axis=0) + self.alpha
            self.feature_log_prob_[i] = np.log(counts / counts.sum())

        return self

La prédiction est juste une multiplication de matrice plus un biais.

Gaussie NB

Pour les caractéristiques continues, on estime la moyenne et la variance par classe par caractéristique:

pythonclass GaussianNB:
    def __init__(self):
        pass

    def fit(self, X, y):
        classes = np.unique(y)
        self.classes_ = classes
        self.means_ = np.zeros((len(classes), X.shape[1]))
        self.vars_ = np.zeros((len(classes), X.shape[1]))
        self.priors_ = np.zeros(len(classes))

        for i, c in enumerate(classes):
            X_c = X[y == c]
            self.means_[i] = X_c.mean(axis=0)
            self.vars_[i] = X_c.var(axis=0) + 1e-9
            self.priors_[i] = X_c.shape[0] / X.shape[0]

        return self

La prédiction utilise le PDF gaussien par fonctionnalité, multiplié par fonctionnalité (ajouté dans l'espace de journaux).

Démo: Classification du texte

Le code génère des données de sacs de mots synthétiques simulant deux classes (articles technologiques contre articles sportifs). Chaque classe a une distribution de fréquence de mots différente.

Les données synthétiques fonctionnent comme ceci: nous créons 200 "words" (columnes de fonctionnalités). Les mots 0-39 ont une fréquence élevée dans les articles techniques et faible dans le sport. Les mots 80-119 ont une fréquence élevée dans le sport et faible dans le domaine technique. Les mots 40-79 sont de fréquence moyenne dans les deux. Cela crée un scénario réaliste où certains mots sont des indicateurs de classe forts et d'autres sont du bruit.

Démo: fonctionnalités continues

Le code génère des données Iris-like (3 classes, 4 caractéristiques, clusters gaussiens). GaussianNB classifie en utilisant la moyenne et la variance par classe. Chaque classe a un centre différent (vecteur moyen) et une diffusion différente (variance), imitant les données du monde réel où les mesures diffèrent systématiquement entre les catégories.

Le code démontre également:

  • Smoothing comparison:Formation de MultinomialNB avec différentes valeurs alpha pour montrer l'effet de la force de lissage sur la précision.
  • Training size experiment:Comment la précision de NB s'améliore à mesure que les données de formation passent de 20 à 1600 échantillons. NB atteint une précision décente même avec très peu d'échantillons - c'est son principal avantage.
  • Confusion matrix:La précision par classe, le rappel et le score de F1 pour montrer où NB fait des erreurs.

Vite de prédiction

La prédiction naïve de Bayes est une multiplication de matrice. Pour n échantillons avec d caractéristiques et k classes:

  • MultinomialNB: une matrice multipliée (n x d) @ (d x k) = O(n d k)
  • GaussianNB: n k Évaluations PDF gaussiennes, chacune sur d caractéristiques = O(n d * k)

Les deux sont linéaires dans toutes les dimensions. Comparer ceci à KNN (qui nécessite le calcul de la distance à tous les points de formation) ou SVM avec le noyau RBF (qui nécessite l'évaluation du noyau contre tous les vecteurs de support). NB est plus rapide par ordre de magnitude au moment de la prédiction.

Utilisez-le

Avec sklearn, les deux variantes sont unlinées:

pythonfrom sklearn.naive_bayes import GaussianNB, MultinomialNB

gnb = GaussianNB()
gnb.fit(X_train, y_train)
print(f"GaussianNB accuracy: {gnb.score(X_test, y_test):.3f}")

mnb = MultinomialNB(alpha=1.0)
mnb.fit(X_train_counts, y_train)
print(f"MultinomialNB accuracy: {mnb.score(X_test_counts, y_test):.3f}")

Pour la classification du texte avec sklearn:

pythonfrom sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline

text_clf = Pipeline([
    ("vectorizer", CountVectorizer()),
    ("classifier", MultinomialNB(alpha=1.0)),
])

text_clf.fit(train_texts, train_labels)
accuracy = text_clf.score(test_texts, test_labels)

Le code dans naive_bayes.pyComparer les mises en œuvre à partir de zéro avec les données de sklearn sur les mêmes données pour vérifier leur exactitude.

TF-IDF avec Naive Bayes

Les nombres de mots bruts donnent à chaque mot le même poids par occurrence. Mais les mots communs comme "le" et "est" apparaissent fréquemment dans chaque classe - ils ne contiennent aucune information. TF-IDF (Term Frequency - Inverse Document Frequency) diminue le poids des mots communs et augmente le poids des mots rares et discriminatoires.

pythonfrom sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline

text_clf = Pipeline([
    ("tfidf", TfidfVectorizer()),
    ("classifier", MultinomialNB(alpha=0.1)),
])

Les valeurs TF-IDF sont non négatives, elles fonctionnent donc avec MultinomialNB. La combinaison de TF-IDF + MultinomialNB est l'une des lignes de base les plus fortes pour la classification du texte.

BernoulliNB pour texte court

Pour le texte court (tweets, SMS, messages de chat), BernoulliNB peut surpasser MultinomialNB. Les textes courts ont un faible nombre de mots, de sorte que les informations de fréquence sur lesquelles MultinomialNB s'appuie sont bruyantes.

pythonfrom sklearn.naive_bayes import BernoulliNB
from sklearn.feature_extraction.text import CountVectorizer

text_clf = Pipeline([
    ("vectorizer", CountVectorizer(binary=True)),
    ("classifier", BernoulliNB(alpha=1.0)),
])

Le binary=Trueflag dans CountVectorizer convertit tous les nombres en 0/1. Sans lui, BernoulliNB fonctionne toujours mais voit des nombres pour lesquels il n'a pas été conçu.

Calibration NB Probabilités

Les probabilités de NB sont mal calibrées. Lorsque NB dit P(spam) = 0,95, la vraie probabilité pourrait être de 0,7. Si vous avez besoin d'estimations de probabilité fiables (par exemple, pour définir un seuil ou combiner avec d'autres modèles), utilisez le CalibratedClassifierCV de sklearn:

pythonfrom sklearn.calibration import CalibratedClassifierCV

calibrated_nb = CalibratedClassifierCV(MultinomialNB(), cv=5, method="sigmoid")
calibrated_nb.fit(X_train, y_train)
proba = calibrated_nb.predict_proba(X_test)

Cela correspond à une régression logistique en plus des scores bruts de NB en utilisant la validation croisée.

Les gotches communes

  1. Negative feature values.MultinomialNB nécessite des caractéristiques non négatives. Si vous avez des valeurs négatives (comme TF-IDF avec certains paramètres ou caractéristiques standardisées), utilisez GaussianNB à la place, ou changez les caractéristiques pour être positives.
  1. Zero variance features.Le GaussianNB est divisé par variance. Si une fonction a une variance zéro pour une classe (toutes les valeurs sont identiques), le calcul de probabilité est cassé. Le code ajoute un petit terme de lissage (1e-9) à toutes les variances pour éviter cela.
  1. Class imbalance.Si 99% des e-mails ne sont pas spam, le P (non-spam) = 0,99 est si fort qu'il dépasse les preuves de probabilité. Vous pouvez définir manuellement les priorités de classe ou utiliser le paramètre class_prior dans sklearn.
  1. Feature scaling.Le multinomialNB n'a pas besoin d'échelle (il fonctionne sur les calculs). Le GaussianNB n'a pas besoin d'échelle non plus (il estime les statistiques par caractéristique).

La faire partir

Cette leçon donne:

  • outputs/skill-naive-bayes-chooser.md-- une compétence de décision pour choisir la bonne variante NB
  • code/naive_bayes.py-- MultinomialNB et GaussianNB à partir de zéro, avec une comparaison sklearn

Quand Bayes échoue

NB échoue lorsque l'hypothèse d'indépendance provoque des classements incorrects (et non seulement des probabilités incorrectes).

  1. Strong feature interactions.Si la classe dépend de la combinaison de deux caractéristiques mais pas l'une ou l'autre seule (patterns XOR-like), NB la manquera complètement.
  1. Highly correlated features with opposing evidence.Si la fonction A dit "spam" et la fonction B dit "non-spam", mais A et B sont parfaitement corrélés (ils sont toujours d'accord dans la réalité), NB verra des preuves contradictoires là où il n'y en a pas.
  1. Very large training sets.Avec suffisamment de données, des modèles discriminatifs tels que la régression logistique apprennent la véritable limite de décision et dépassent NB. L'hypothèse d'indépendance qui a aidé avec de petites données retient maintenant le modèle.

En pratique, ces modes d'échec sont rares pour la classification du texte. Les caractéristiques du texte sont nombreuses, individuellement faibles, et les erreurs de l'hypothèse d'indépendance ont tendance à annuler. Pour les données de tableau avec peu de caractéristiques fortement corrélatives, considérez d'abord la régression logistique ou les modèles basés sur des arbres.

Exercices

  1. Smoothing experiment.Prenez MultinomialNB sur les données de texte avec des valeurs alpha de 0,01, 0,1, 1,0, 10,0 et 100,0.
  1. Feature independence test.Prenez un ensemble de données de texte réel. Choisissez deux mots qui sont évidemment corrélés ("machine" et "apprentissage"). Comptez P "class word1′′) * P "word2′′) et comparez avec P "word1 AND word2′′ class. Quelle est la erreur de l'hypothèse d'indépendance?
  1. Bernoulli implementation.Extension du code avec une classe BernoulliNB. Convertir les sacs de mots en binaire (présent/absent) et comparer la précision contre MultinomialNB sur les données de texte. Quand Bernoulli gagne-t-il?
  1. NB vs Logistic Regression.Prenez les deux en fonction des données de texte. Commencez par 100 échantillons de formation et augmentez à 10 000.
  1. Spam filter.Construire un classifiateur de spam complet: jetonner le texte brut du courrier électronique, créer un vocabulaire, créer des fonctionnalités de sacs de mots, former MultinomialNB, évaluer avec précision et rappeler (pas seulement l'exactitude - pourquoi?).

Les termes clés

TermWhat people sayWhat it actually means
Naive Bayes"Simple probabilistic classifier"A classifier that applies Bayes' theorem with the assumption that features are conditionally independent given the class
Conditional independence"Features don't affect each other"P(A, B | C) = P(A | C) * P(B | C) -- knowing B tells you nothing new about A once you know C
Laplace smoothing"Add-one smoothing"Adding a small count to every feature to prevent zero probabilities from dominating the prediction
Prior"What you believed before seeing data"P(class) -- the probability of each class before observing any features
Likelihood"How well the data fits"P(features | class) -- the probability of observing these features if the class is known
Posterior"What you believe after seeing data"P(class | features) -- the updated probability of the class after observing the features
Generative model"Models how data is generated"A model that learns P(X | Y) and P(Y), then uses Bayes' theorem to get P(Y | X)
Discriminative model"Models the decision boundary"A model that directly learns P(Y | X) without modeling how X is generated
Log probability"Avoid underflow"Working with log P instead of P to prevent the product of many small numbers from becoming zero in floating point

Pour en savoir plus

This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.

Browse the complete course catalog or open this lesson on GitHub.