07

Naive Bayes

Cantonese podcast title: 樸素貝葉斯

Learning Objectives

  1. State Bayes' theorem in the classification setting and identify prior, likelihood, evidence, and posterior for a concrete example.
  2. State the conditional-independence assumption of naive Bayes, give a real case where it fails, and explain what the failure does to the predicted probabilities.
  3. Distinguish prior from posterior with arithmetic on real numbers and explain why the prior matters even when the model is highly accurate.
  4. Compute the parameters of a multinomial naive Bayes classifier from token counts and predict a class for a new document using the log-sum form.
  5. Choose between Gaussian, multinomial, and Bernoulli naive Bayes given the type of features (continuous, counts, binary) and link the multinomial variant to TF-IDF weighting.
  6. Argue why naive Bayes is still a strong baseline for text classification: training cost, prediction cost, and behaviour under sparse data.
Naive Bayes — visual guide
Naive Bayes conditional independence Naive Bayes: the assumption that makes it fast document buy cheap now × independent P(class | document) ∝ P(class) · Π P(wᵢ | class) posterior ∝ prior × likelihoods, compared across every class — the largest posterior wins Why 'naive' assumes features are conditionally independent, which is never true Text + TF-IDF count / multinomial NB on TF-IDF vectors, still a strong baseline Trade-off O(words) train O(words) predict double-counts correlated tokens needs smoothing

Naive Bayes is a family of probabilistic classifiers that apply Bayes' theorem with a strong — and almost always false — assumption that every feature is conditionally independent of every other feature given the class label. Despite that assumption, the classifier remains a stubbornly strong baseline on text problems: it trains in a single pass over the data, predicts in microseconds, and is remarkably tolerant of small training sets and high-dimensional sparse feature vectors. This lesson starts from Bayes' theorem, makes the prior-vs-posterior distinction concrete with real numbers, states the conditional-independence assumption explicitly and explains why it is usually wrong, then specialises to the multinomial variant used for text classification and ties it to TF-IDF features. A comparison table of the Gaussian, multinomial, and Bernoulli variants, a pros-and-cons table, and a worked spam-detection example close the lesson.

Learning Objectives

  1. State Bayes' theorem in the classification setting and identify prior, likelihood, evidence, and posterior for a concrete example.
  2. State the conditional-independence assumption of naive Bayes, give a real case where it fails, and explain what the failure does to the predicted probabilities.
  3. Distinguish prior from posterior with arithmetic on real numbers and explain why the prior matters even when the model is highly accurate.
  4. Compute the parameters of a multinomial naive Bayes classifier from token counts and predict a class for a new document using the log-sum form.
  5. Choose between Gaussian, multinomial, and Bernoulli naive Bayes given the type of features (continuous, counts, binary) and link the multinomial variant to TF-IDF weighting.
  6. Argue why naive Bayes is still a strong baseline for text classification: training cost, prediction cost, and behaviour under sparse data.

1. Bayes' Theorem as a Classifier

The starting point is Bayes' theorem, which expresses a conditional probability in terms of its inverse:

P(C∣X)  =  P(X∣C) P(C)P(X).P(C \mid X) \;=\; \frac{P(X \mid C) \, P(C)}{P(X)}.

In the classification setting the four pieces play distinct roles:

SymbolNameMeaning in a classifier
P(C)P(C)PriorHow common the class is overall, before seeing any features
P(X∣C)P(X \mid C)LikelihoodHow probable the observed feature vector is under class CC
P(X)P(X)EvidenceHow probable the feature vector is overall; a normalising constant that does not depend on CC
P(C∣X)P(C \mid X)PosteriorThe updated belief about CC after observing XX — what the classifier actually returns

A classifier only needs the relative values of the posterior across classes, so the evidence P(X)P(X) can be dropped at prediction time. The decision rule reduces to

C^  =  arg⁡max⁡C  P(X∣C) P(C).\hat{C} \;=\; \arg\max_{C} \; P(X \mid C) \, P(C).

This is the maximum a posteriori (MAP) rule: pick the class whose product of likelihood and prior is largest.

1.1 A toy one-feature example

Imagine a single feature — the colour of a light, red or green — and a binary class {stop, go}. Suppose 70% of lights are stop lights, 30% are go lights. Of all stop lights, 90% show red; of all go lights, 85% show red. The joint probabilities:

P(red∣stop)=0.90,P(red∣go)=0.85.P(\text{red} \mid \text{stop}) = 0.90, \quad P(\text{red} \mid \text{go}) = 0.85.

Given a red light, the posterior probability that it is a stop light is

P(stop∣red)  =  P(red∣stop) P(stop)P(red∣stop) P(stop)+P(red∣go) P(go)  =  0.90×0.700.90×0.70+0.85×0.30  =  0.6300.885  ≈  0.712.P(\text{stop} \mid \text{red}) \;=\; \frac{P(\text{red} \mid \text{stop}) \, P(\text{stop})} {P(\text{red} \mid \text{stop}) \, P(\text{stop}) + P(\text{red} \mid \text{go}) \, P(\text{go})} \;=\; \frac{0.90 \times 0.70}{0.90 \times 0.70 + 0.85 \times 0.30} \;=\; \frac{0.630}{0.885} \;\approx\; 0.712.

A red light is therefore about a 71% chance of being a stop light. The classifier predicts "stop" because 0.712>0.2880.712 > 0.288. Note that the prior of 0.70 is doing real work: if the priors were balanced (50/50), the same likelihoods would give

P(stop∣red)  =  0.90×0.500.90×0.50+0.85×0.50  =  0.450.875  ≈  0.514.P(\text{stop} \mid \text{red}) \;=\; \frac{0.90 \times 0.50}{0.90 \times 0.50 + 0.85 \times 0.50} \;=\; \frac{0.45}{0.875} \;\approx\; 0.514.

The decision still favours stop, but only barely. The lesson: the prior matters most when the likelihoods are similar.

2. Prior, Likelihood, and Posterior with Real Numbers

The prior P(C)P(C) summarises what the classifier knows about class prevalence before seeing any features. The likelihood P(X∣C)P(X \mid C) encodes how the features behave within each class. The posterior combines the two.

2.1 A worked diagnostic example

A medical test for a rare disease has the following properties:

  • Disease prevalence: P(disease)=0.01P(\text{disease}) = 0.01 (1 in 100 people have it).
  • Sensitivity: P(positive∣disease)=0.99P(\text{positive} \mid \text{disease}) = 0.99.
  • Specificity: P(negative∣no disease)=0.95P(\text{negative} \mid \text{no disease}) = 0.95, so the false-positive rate is P(positive∣no disease)=0.05P(\text{positive} \mid \text{no disease}) = 0.05.

A patient tests positive. What is the probability they actually have the disease?

P(disease∣positive)  =  P(positive∣disease) P(disease)P(positive),P(\text{disease} \mid \text{positive}) \;=\; \frac{P(\text{positive} \mid \text{disease}) \, P(\text{disease})} {P(\text{positive})},

with the denominator computed by expanding over the two classes:

P(positive)=P(positive∣disease) P(disease)+P(positive∣no disease) P(no disease)=0.99×0.01+0.05×0.99=0.0099+0.0495=0.0594.\begin{aligned} P(\text{positive}) &= P(\text{positive} \mid \text{disease}) \, P(\text{disease}) + P(\text{positive} \mid \text{no disease}) \, P(\text{no disease}) \\ &= 0.99 \times 0.01 + 0.05 \times 0.99 \\ &= 0.0099 + 0.0495 = 0.0594. \end{aligned}

The posterior is

P(disease∣positive)  =  0.00990.0594  ≈  0.167.P(\text{disease} \mid \text{positive}) \;=\; \frac{0.0099}{0.0594} \;\approx\; 0.167.

A positive test means only about a 17% chance of actually having the disease. The counter-intuitive result comes entirely from the prior: with a 1% base rate, the pool of false positives from the 99% disease-free population overwhelms the true positives from the 1% sick population. The 99% sensitivity and 95% specificity that sound excellent in isolation become much weaker once the prior is honoured.

2.2 What the classifier actually does

The naive Bayes MAP rule

C^  =  arg⁡max⁡C  P(C) ∏j=1dP(Xj∣C)\hat{C} \;=\; \arg\max_C \; P(C) \, \prod_{j=1}^{d} P(X_j \mid C)

is mathematically equivalent to saying "multiply the prior by the per-feature likelihoods, pick the largest product." With thousands of features the product underflows numerically, so the classifier is implemented in log-space:

C^  =  arg⁡max⁡C  log⁡P(C)+∑j=1dlog⁡P(Xj∣C).\hat{C} \;=\; \arg\max_C \; \log P(C) + \sum_{j=1}^{d} \log P(X_j \mid C).

The log⁡\log transform turns the product into a sum and the underflow disappears. The prediction — which class wins — is unchanged.

3. The Conditional-Independence Assumption

The naive Bayes model assumes that, given the class label, the features are mutually independent:

P(X1,X2,…,Xd∣C)  =  ∏j=1dP(Xj∣C).P(X_1, X_2, \ldots, X_d \mid C) \;=\; \prod_{j=1}^{d} P(X_j \mid C).

In English: once you know whether a document is spam, the presence of the word "free" tells you nothing further about whether it also contains the word "viagra". Whether a tumour is malignant tells you nothing about the patient's blood-pressure reading. Whether a transaction is fraudulent tells you nothing about the merchant category.

This assumption is almost never true in real data. Words in natural language are strongly correlated with one another; tumour features are correlated through biological pathways; fraud features are correlated through fraud rings. The naive Bayes assumption is therefore not a description of the world. It is a modelling choice that makes the classifier tractable.

3.1 What goes wrong when features are correlated

Two failure modes appear when the assumption is violated.

  1. Double-counting evidence. If two features carry overlapping information, the classifier multiplies the likelihoods twice and over-weights the evidence. The resulting posterior is too confident in the winning class.
  2. Calibration breaks down. Probabilities predicted by a naive Bayes model on correlated data are systematically distorted. The argmax is often correct (because the ranking survives mild violations) but the actual numbers — 0.99, 0.97, 0.83 — cannot be trusted as probabilities.

A concrete example: classifying email as spam. Suppose the word "free" appears in 60% of spam and 5% of ham; the word "viagra" appears in 30% of spam and 0.1% of ham. In real spam "free" and "viagra" co-occur far more often than chance, but naive Bayes treats them as independent. The likelihood product for spam with both words is

P(free∣spam)⋅P(viagra∣spam)=0.60×0.30=0.18.P(\text{free} \mid \text{spam}) \cdot P(\text{viagra} \mid \text{spam}) = 0.60 \times 0.30 = 0.18.

The true joint probability is higher (because the words are positively correlated in spam) but the model does not know that. The model still tends to predict spam correctly because both likelihoods point the same direction; the failure shows up when the calibration matters — for instance, when the classifier is part of a thresholded system that rejects borderline cases.

3.2 Why naive Bayes still works despite being wrong

Three reasons.

  • The ranking survives. Even when probabilities are mis-calibrated, the relative ordering of classes is often preserved under moderate violations of independence. When only the argmax matters, naive Bayes keeps winning.
  • The bias is conservative. When features are positively correlated and the classifier multiplies their likelihoods, the result is a stronger vote for the winning class. The argmax does not flip; the confidence just inflates.
  • The variance is low. Independence makes the model have many fewer parameters than a model that learns the joint distribution. With a small training set the low-variance estimator beats a higher-variance joint model.

These three points are why naive Bayes is the textbook "surprising baseline" — a model whose assumptions are wrong but whose decisions are still useful.

4. Maximum A Posteriori Prediction

Putting the prior and the likelihood together, the MAP decision rule is

C^  =  arg⁡max⁡C  P(C)∏j=1dP(Xj∣C).\hat{C} \;=\; \arg\max_C \; P(C) \prod_{j=1}^{d} P(X_j \mid C).

In practice three implementation details matter.

4.1 Log-space

Numerical underflow happens as soon as the product involves more than a few factors smaller than 1. Working in log-space:

C^  =  arg⁡max⁡C  log⁡P(C)+∑j=1dlog⁡P(Xj∣C).\hat{C} \;=\; \arg\max_C \; \log P(C) + \sum_{j=1}^{d} \log P(X_j \mid C).

The argmax is unchanged. Underflow disappears. Sums are faster than products. Every production implementation uses this form.

4.2 Laplace (additive) smoothing

A feature value that never appears in the training data for a class produces P(Xj∣C)=0P(X_j \mid C) = 0, which then zeroes out the entire product. To prevent that, add a small count α>0\alpha > 0 to every per-class count:

P^(Xj=v∣C)  =  count(Xj=v,C)+α∑v′count(Xj=v′,C)+α Vj,\hat{P}(X_j = v \mid C) \;=\; \frac{\text{count}(X_j = v, C) + \alpha} {\sum_{v'} \text{count}(X_j = v', C) + \alpha \, V_j},

where VjV_j is the number of possible values of feature jj. With α=1\alpha = 1 this is Laplace smoothing; smaller α\alpha values are called add-k smoothing or Lidstone smoothing. The smoothing regularises the maximum-likelihood estimate toward the uniform distribution, which dramatically improves performance on rare words in text classification.

4.3 A two-feature numeric walkthrough

import numpy as np

"""Three classes with priors and per-feature likelihoods."""
priors = np.array([0.5, 0.3, 0.2])                          # P(C)
P_x1_given_C = np.array([0.4, 0.1, 0.7])                    # P(X1 | C)
P_x2_given_C = np.array([0.2, 0.8, 0.3])                    # P(X2 | C)

"""New observation: X1 = yes, X2 = yes."""
log_posterior = np.log(priors) + np.log(P_x1_given_C) + np.log(P_x2_given_C)
prediction = int(np.argmax(log_posterior))
print(f"log posteriors: {log_posterior}")
print(f"predicted class: {prediction}")

The output (log values truncated to three decimals) is roughly

log posteriors: [-2.526  -1.172  -1.561]
predicted class: 1

Class 1 wins even though its prior is the smallest of the three, because its likelihoods dominate. The posterior itself (after normalising) is

P(C∣X1,X2)  ∝  [0.0800.3100.210],P(C \mid X_1, X_2) \;\propto\; \begin{bmatrix} 0.080 \\ 0.310 \\ 0.210 \end{bmatrix},

which, after dividing by the sum 0.6000.600, gives [0.133,0.517,0.350][0.133, 0.517, 0.350]. Class 1 is predicted with 51.7% confidence.

5. The Multinomial Variant for Text Classification

The variant used most often for text classification is the multinomial naive Bayes. The features are word counts in a document: each document is a vector of token counts, and the likelihood of the document given class CC is

P(document∣C)  =  Nd!∏jndj!∏jP(tj∣C)ndj,P(\text{document} \mid C) \;=\; \frac{N_d !}{\prod_{j} n_{dj}!} \prod_{j} P(t_j \mid C)^{n_{dj}},

where ndjn_{dj} is the count of token tjt_j in document dd and Nd=∑jndjN_d = \sum_j n_{dj} is the document length. The multinomial coefficient Nd!∏ndj!\frac{N_d!}{\prod n_{dj}!} does not depend on CC, so it drops out of the MAP rule.

Taking logs,

log⁡P(C∣document)  ∝  log⁡P(C)+∑jndjlog⁡P(tj∣C).\log P(C \mid \text{document}) \;\propto\; \log P(C) + \sum_{j} n_{dj} \log P(t_j \mid C).

The per-class parameter P(tj∣C)P(t_j \mid C) is the probability of token tjt_j under class CC, estimated as the smoothed count

P^(tj∣C)  =  count(tj,C)+α∑t′count(t′,C)+α ∣V∣,\hat{P}(t_j \mid C) \;=\; \frac{\text{count}(t_j, C) + \alpha} {\sum_{t'} \text{count}(t', C) + \alpha \, |V|},

with ∣V∣|V| the vocabulary size. The estimator is the maximum-likelihood multinomial with Laplace smoothing. A 1,000-document training corpus with a 20,000-word vocabulary produces at most 20,000 parameters per class — about 60,000 floats total for a three-class problem.

5.1 Worked training: a tiny corpus

Consider a four-document training corpus with vocabulary {free, viagra, meeting, project, the}, classified as spam ({C₁}) or ham ({C₂}):

DocTokensClass
1free, viagra, thespam
2free, meeting, thespam
3meeting, project, theham
4project, theham

Class priors:

P(spam)=24=0.5,P(ham)=24=0.5.P(\text{spam}) = \frac{2}{4} = 0.5, \qquad P(\text{ham}) = \frac{2}{4} = 0.5.

Per-class token counts:

Tokenspam countham count
free20
viagra10
meeting11
project02
the22
total65

With Laplace smoothing (α=1\alpha = 1, vocabulary size ∣V∣=5|V| = 5), the per-token probabilities are

P^(t∣spam)=count(t,spam)+16+5,P^(t∣ham)=count(t,ham)+15+5.\hat{P}(t \mid \text{spam}) = \frac{\text{count}(t, \text{spam}) + 1}{6 + 5}, \qquad \hat{P}(t \mid \text{ham}) = \frac{\text{count}(t, \text{ham}) + 1}{5 + 5}.
TokenP^(t∣spam)\hat{P}(t \mid \text{spam})P^(t∣ham)\hat{P}(t \mid \text{ham})
free3/11≈0.2733/11 \approx 0.2731/10=0.1001/10 = 0.100
viagra2/11≈0.1822/11 \approx 0.1821/10=0.1001/10 = 0.100
meeting2/11≈0.1822/11 \approx 0.1822/10=0.2002/10 = 0.200
project1/11≈0.0911/11 \approx 0.0913/10=0.3003/10 = 0.300
the3/11≈0.2733/11 \approx 0.2733/10=0.3003/10 = 0.300

5.2 Predicting a new document

A new document is ["free", "viagra"]. The MAP scores in log-space:

log⁡P(spam)+log⁡P^(free∣spam)+log⁡P^(viagra∣spam)=log⁡0.5+log⁡(3/11)+log⁡(2/11)≈−0.693−1.299−1.704=−3.696.\begin{aligned} \log P(\text{spam}) + \log \hat{P}(\text{free} \mid \text{spam}) + \log \hat{P}(\text{viagra} \mid \text{spam}) &= \log 0.5 + \log(3/11) + \log(2/11) \\ &\approx -0.693 - 1.299 - 1.704 = -3.696. \end{aligned} log⁡P(ham)+log⁡P^(free∣ham)+log⁡P^(viagra∣ham)=log⁡0.5+log⁡(1/10)+log⁡(1/10)≈−0.693−2.303−2.303=−5.299.\begin{aligned} \log P(\text{ham}) + \log \hat{P}(\text{free} \mid \text{ham}) + \log \hat{P}(\text{viagra} \mid \text{ham}) &= \log 0.5 + \log(1/10) + \log(1/10) \\ &\approx -0.693 - 2.303 - 2.303 = -5.299. \end{aligned}

Spam wins by a margin of 1.6031.603 in log-space, which corresponds to a posterior ratio of about e1.603≈4.97e^{1.603} \approx 4.97 in favour of spam. Normalising gives

P(spam∣doc)≈4.971+4.97≈0.832,P(ham∣doc)≈0.168.P(\text{spam} \mid \text{doc}) \approx \frac{4.97}{1 + 4.97} \approx 0.832, \qquad P(\text{ham} \mid \text{doc}) \approx 0.168.

The classifier predicts spam with 83.2% confidence. With more training data the same algorithm produces reliable spam filters; the toy corpus just shows the arithmetic.

5.3 The full training and prediction loop

import numpy as np
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB

"""Toy corpus: four documents and their labels."""
corpus = [
    "free viagra the",      # spam
    "free meeting the",     # spam
    "meeting project the",   # ham
    "project the",          # ham
]
labels = ["spam", "spam", "ham", "ham"]

"""Convert text to a document-term count matrix."""
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(corpus)

"""Fit multinomial naive Bayes with Laplace smoothing (alpha=1.0 by default)."""
model = MultinomialNB(alpha=1.0)
model.fit(X, labels)

"""Predict a new document."""
new_doc = vectorizer.transform(["free viagra"])
print(model.predict(new_doc))         # ['spam']
print(model.predict_proba(new_doc))   # [[0.168 0.832]]

The predict_proba output reproduces the manual calculation. The whole pipeline — vectoriser, smoothing, log-space MAP — fits inside a handful of lines.

6. Linking Naive Bayes to TF-IDF Features

The previous section used raw term frequency counts. A common refinement is to weight features by TF-IDF (term frequency–inverse document frequency), which down-weights tokens that appear in nearly every document and up-weights rare-but- informative tokens:

tfidf(t,d)  =  tf(t,d)⋅idf(t),idf(t)  =  log⁡N1+df(t).\text{tfidf}(t, d) \;=\; \text{tf}(t, d) \cdot \text{idf}(t), \qquad \text{idf}(t) \;=\; \log \frac{N}{1 + \text{df}(t)}.

Here df(t)\text{df}(t) is the number of documents containing tt and NN is the total number of documents.

Naive Bayes assumes features are non-negative counts (or non-negative frequencies), and TF-IDF values are non-negative — but they are not integer counts. The right way to combine the two is to treat TF-IDF values as scaled counts: feed them to multinomial naive Bayes as if they were counts. Empirically this often hurts slightly compared to raw counts, because the multinomial likelihood model prefers integer count data and the TF-IDF transform violates the assumption.

A more principled pairing is:

  • Raw counts →\to Multinomial naive Bayes. The textbook choice.
  • TF-IDF weights →\to Multinomial naive Bayes as a pragmatic baseline; expected to be slightly weaker than counts but often close.
  • Binary presence/absence →\to Bernoulli naive Bayes. The right choice when document length is noisy.

In scikit-learn the swap is one argument:

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB

X = TfidfVectorizer().fit_transform(corpus)
model = MultinomialNB(alpha=1.0).fit(X, labels)

Empirically the accuracy difference between count features and TF-IDF features under multinomial naive Bayes is usually within a percentage point on standard benchmarks like 20 Newsgroups or the Enron spam corpus. The lesson: the choice of model (multinomial vs Bernoulli vs Gaussian) usually matters more than the choice of feature weighting (counts vs TF-IDF).

7. Variants: Gaussian, Multinomial, Bernoulli

The name "naive Bayes" covers three standard variants, distinguished by the assumed distribution of P(Xj∣C)P(X_j \mid C).

VariantFeature typeModel for P(Xj∣C)P(X_j \mid C)Typical use
GaussianContinuousN(μj,C,σj,C2)\mathcal{N}(\mu_{j,C}, \sigma_{j,C}^2), fit by per-class mean and varianceSmall continuous feature sets, sensor measurements
MultinomialNon-negative integer countsMultinomial over the vocabulary; parameters are per-token probabilitiesText classification (bag of words), bag of n-grams, TF-IDF as scaled counts
BernoulliBinary (presence/absence)Bernoulli with per-class, per-feature probability of presenceShort documents, sentiment with "word present" features, spam filtering on binary word indicators

7.1 Gaussian naive Bayes

When features are continuous, the natural model is the Gaussian:

P(Xj=x∣C)  =  12πσj,C2exp⁡(−(x−μj,C)22σj,C2).P(X_j = x \mid C) \;=\; \frac{1}{\sqrt{2 \pi \sigma_{j,C}^2}} \exp\left( - \frac{(x - \mu_{j,C})^2}{2 \sigma_{j,C}^2} \right).

The parameters μj,C\mu_{j,C} and σj,C2\sigma_{j,C}^2 are the per-class mean and variance of feature jj, fit by maximum likelihood on the training set. The classifier is fast but assumes each feature is normally distributed within each class — a strong assumption that rarely holds in practice. Gaussian naive Bayes is a useful baseline for small continuous datasets where the alternative is a kernel density estimate.

7.2 Multinomial naive Bayes

Covered in detail in section 5. Each class is a probability distribution over the vocabulary, the per-token counts form a multinomial, and the document likelihood is the multinomial probability of the observed counts. This is the variant of choice for text classification and is the basis of scikit-learn's MultinomialNB.

7.3 Bernoulli naive Bayes

When features are binary, the natural model is the Bernoulli:

P(Xj∣C)  =  pj,CXj(1−pj,C)1−Xj,P(X_j \mid C) \;=\; p_{j,C}^{X_j} (1 - p_{j,C})^{1 - X_j},

where pj,Cp_{j,C} is the probability that feature jj is present in class CC. Bernoulli naive Bayes treats "free appeared" and "free did not appear" symmetrically and uses both signals. It often beats multinomial naive Bayes on short documents, where the absence of common words is informative; it loses on longer documents, where the count signal is more useful than the presence signal.

7.4 Choosing between them

The decision is usually forced by the feature type:

If features are…Use…
Continuous, roughly GaussianGaussian naive Bayes
Counts (word counts, n-gram counts)Multinomial naive Bayes
Binary (presence/absence)Bernoulli naive Bayes
TF-IDF valuesMultinomial naive Bayes (as a pragmatic baseline)

When in doubt, fit all three and pick the best on a held-out validation set. The classifier is fast enough that fitting all three takes seconds.

8. Pros and Cons of Naive Bayes

Naive Bayes
ProsTrains in a single pass over the data; each parameter is one count. Predictions are O(d)O(d) per example. Works on small training sets because the number of parameters is O(d⋅k)O(d \cdot k), far fewer than logistic regression's O(d⋅k)O(d \cdot k) with cross-terms or a neural network's much larger parameter count. Robust to irrelevant features: an irrelevant feature just contributes a near-uniform likelihood that cancels between classes. Handles missing features gracefully by ignoring them at prediction time. Probabilistic output (after renormalisation) that can be thresholded. Tends to win on text classification benchmarks despite its wrong assumption.
ConsThe conditional-independence assumption is almost always wrong in real data; the resulting probabilities are mis-calibrated, even if the argmax is correct. Cannot learn interactions between features (the words "New" and "York" are independent given the class, which is not true for location-classification). Zero-frequency problem without smoothing; must choose α\alpha carefully. Continuous features usually do not satisfy the Gaussian assumption, so Gaussian naive Bayes is a weak baseline outside its narrow regime.
When it shinesText classification (spam, sentiment, topic), small datasets, very high-dimensional sparse features, online or streaming settings where training must be one pass, baseline models that have to be ready in minutes.
When it strugglesStrong feature interactions (vision, position-sensitive NLP), calibrated probability estimates, regression-style problems where the target is continuous.

The headline point: naive Bayes is fast, simple, and surprisingly hard to beat on text. The headline caveat: never trust its probabilities as probabilities.

9. Worked Example: Spam Detection on Tiny Data

Pull the threads together by training a multinomial naive Bayes spam filter on a 20-message toy corpus and predicting the class of a new message.

9.1 Training data

#Message tokensClass
1free viagra thespam
2free meeting thespam
3meeting project thespam
4free the projectspam
5project meeting freespam
6meeting project theham
7project theham
8meeting the projectham
9project meeting theham
10meeting theham
11project meeting theham
12meeting theham
13project the meetingham
14meeting theham
15project theham
16meeting theham
17project theham
18meeting the projectham
19project theham
20meeting theham

There are 5 spam and 15 ham messages. Priors:

P(spam)=520=0.25,P(ham)=1520=0.75.P(\text{spam}) = \frac{5}{20} = 0.25, \qquad P(\text{ham}) = \frac{15}{20} = 0.75.

9.2 Token counts per class

Tokenspam countham count
free40
viagra10
meeting411
project411
the513
total1835

9.3 Smoothed likelihoods (α=1\alpha = 1, ∣V∣=5|V| = 5)

P^(t∣spam)=count(t,spam)+118+5,P^(t∣ham)=count(t,ham)+135+5.\hat{P}(t \mid \text{spam}) = \frac{\text{count}(t, \text{spam}) + 1}{18 + 5}, \qquad \hat{P}(t \mid \text{ham}) = \frac{\text{count}(t, \text{ham}) + 1}{35 + 5}.
TokenP^(t∣spam)\hat{P}(t \mid \text{spam})P^(t∣ham)\hat{P}(t \mid \text{ham})
free5/23≈0.2175/23 \approx 0.2171/40=0.0251/40 = 0.025
viagra2/23≈0.0872/23 \approx 0.0871/40=0.0251/40 = 0.025
meeting5/23≈0.2175/23 \approx 0.21712/40=0.30012/40 = 0.300
project5/23≈0.2175/23 \approx 0.21712/40=0.30012/40 = 0.300
the6/23≈0.2616/23 \approx 0.26114/40=0.35014/40 = 0.350

9.4 Predicting a new message

A new message is ["free", "viagra"]. The MAP scores in log-space:

log⁡P(spam∣doc)∝log⁡0.25+log⁡0.217+log⁡0.087≈−1.386−1.527−2.442=−5.355.\begin{aligned} \log P(\text{spam} \mid \text{doc}) &\propto \log 0.25 + \log 0.217 + \log 0.087 \\ &\approx -1.386 - 1.527 - 2.442 = -5.355. \end{aligned} log⁡P(ham∣doc)∝log⁡0.75+log⁡0.025+log⁡0.025≈−0.288−3.689−3.689=−7.666.\begin{aligned} \log P(\text{ham} \mid \text{doc}) &\propto \log 0.75 + \log 0.025 + \log 0.025 \\ &\approx -0.288 - 3.689 - 3.689 = -7.666. \end{aligned}

The difference is 7.666−5.355=2.3117.666 - 5.355 = 2.311 in log-space, equivalent to a posterior ratio of e2.311≈10.08e^{2.311} \approx 10.08 in favour of spam. Normalising gives

P(spam∣doc)≈10.081+10.08≈0.910,P(ham∣doc)≈0.090.P(\text{spam} \mid \text{doc}) \approx \frac{10.08}{1 + 10.08} \approx 0.910, \qquad P(\text{ham} \mid \text{doc}) \approx 0.090.

The classifier predicts spam with 91% posterior. The two signals — the rare word "viagra" and the elevated rate of "free" in spam — both push in the same direction; the prior of 25% spam drags the posterior down from near 1.0 but not enough to flip the prediction.

9.5 Comparison with logistic regression

A logistic regression on the same data (one-hot encoded message, L2L_2 regularised) typically lands within one percentage point of the multinomial naive Bayes accuracy on a held-out set. The naive Bayes model has 5 parameters per class (the per-token probabilities plus the prior), while logistic regression has 10 weights per class (an intercept plus one per token). With only 20 training examples the smaller parameter count of naive Bayes gives it a small variance advantage; with 20,000 examples the two are indistinguishable.

9.6 A reusable pipeline

import numpy as np
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import cross_val_score
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import make_pipeline

"""Real-world pipeline: text in, class out."""
pipeline = make_pipeline(
    CountVectorizer(lowercase=True, ngram_range=(1, 2)),
    MultinomialNB(alpha=1.0),
)

"""5-fold cross-validation on a labelled corpus."""
scores = cross_val_score(pipeline, texts, labels, cv=5, scoring="accuracy")
print(f"mean accuracy: {scores.mean():.3f} +/- {scores.std():.3f}")

The pipeline converts raw text to a count matrix, fits multinomial naive Bayes with Laplace smoothing, and reports 5-fold cross-validation accuracy. On the Enron spam corpus this combination reaches 98–99% accuracy with a few seconds of training, outperforming logistic regression trained on the same features at a fraction of the cost. That empirical result is the lesson's headline: naive Bayes is a strong baseline precisely because it is fast, simple, and almost always at least as good as a heavier model on text.

Key Takeaways

  • Bayes' theorem rewrites the posterior P(C∣X)P(C \mid X) as the prior P(C)P(C) times the likelihood P(X∣C)P(X \mid C), divided by the evidence P(X)P(X); classification only needs the unnormalised product because the evidence is constant across classes.
  • The conditional-independence assumption P(X1,…,Xd∣C)=∏jP(Xj∣C)P(X_1, \ldots, X_d \mid C) = \prod_j P(X_j \mid C) is almost always false in real data; when features are correlated the classifier over-counts evidence and the predicted probabilities are mis-calibrated, although the argmax often survives mild violations.
  • The prior matters even when the likelihood signal is strong. A positive medical test with 99% sensitivity and 95% specificity only implies a 17% chance of disease when the base rate is 1%.
  • The multinomial variant is the workhorse for text classification; counts (or TF-IDF weights used as scaled counts) feed into per-class multinomial likelihoods and the MAP rule is computed in log-space.
  • The three standard variants — Gaussian (continuous), multinomial (counts), Bernoulli (binary) — are picked by feature type; the right choice on text data is usually multinomial with raw counts.
  • Naive Bayes is fast, simple, and tolerant of small training sets and high-dimensional sparse features, which is why it remains a strong baseline on text classification. Never trust its output probabilities as probabilities.

Check your understanding

8 questions · 80% to complete the lesson

1 / 8

7 correct to pass

In Bayes' theorem $P(C \mid X) = P(X \mid C) \, P(C) / P(X)$ applied to classification, which term is the constant that does not depend on the class $C$ and can be dropped when comparing posteriors across classes?

0 of 8 answered

Pick a lesson to start the audio.