Naive Bayes for Text

Imagine sorting ten thousand emails into "spam" and "not spam." There are no labeled examples of tomorrow's spam. A classifier must generalize from word patterns alone. It decides on the fly whether "viagra" and "free money now" carry more evidence than "meeting" and "attached invoice." Naive Bayes for Text solves this by turning classification into a probability calculation. It multiplies how surprising each word is under each candidate label. Then it picks the label that makes the observed words least surprising overall. This idea matters because it is the simplest instance of a pattern that recurs throughout NLP. Build a generative story for how data is produced. Then use Bayes' rule to invert that story and infer the hidden label. Naive Bayes classifiers still power real spam filters, sentiment baselines, and language-identification tools. They are fast to train and need almost no tuning. They also remain surprisingly competitive on short, high-dimensional text, where more complex models overfit. The big picture is that Naive Bayes trades a false assumption for enormous computational simplicity. That assumption is that words are conditionally independent given the class. Estimating a full joint distribution over vocabulary-sized feature vectors is intractable. Assuming independence collapses that joint into a product of easy one-dimensional estimates. The assumption is almost always wrong, since words are correlated. Yet the classifier's decisions are often right. Classification only needs the correct label to win the probability race. It does not need an accurate probability estimate. This article builds the model from Bayes' rule up. It gives the exact training and inference algorithms with worked pseudocode. It derives the smoothing correction that prevents zero-probability catastrophes. It traces a complete numeric example. Finally, it connects the generative-model perspective forward to Hidden Markov Models. These models extend this same "independent emissions given a hidden state" idea across a sequence.

Key takeaways
  • Naive Bayes classifies text by picking the class that maximizes P(class)×∏iP(wordi∣class)P(\text{class}) \times \prod_i P(\text{word}_i \mid \text{class}).
  • The "naive" conditional independence assumption lets a full joint distribution collapse into simple word-count statistics.
  • Training is just counting: class priors and per-word likelihoods from labeled documents, using the Bag-of-Words Model representation.
  • Laplace (add-one) smoothing prevents any unseen word from zeroing out an entire class's probability.
  • Inference works in log-space to avoid numerical underflow from multiplying thousands of small probabilities.
  • Despite its wrong independence assumption, Naive Bayes is fast, robust on small data, and a standard baseline in Text Classification Fundamentals.
The core insight

Naive Bayes turns text classification into counting: it picks the class whose word-by-word "generation story" makes the observed document least surprising, assuming words appear independently given the class.

The idea

A Naive Bayes classifier is a probabilistic model that assigns a document to the class cc maximizing the posterior probability P(c∣d)P(c \mid d), where dd is the document. By Bayes' rule,

P(c∣d)=P(d∣c) P(c)P(d).P(c \mid d) = \frac{P(d \mid c)\, P(c)}{P(d)}.

Since P(d)P(d) is the same for every candidate class, classification only requires comparing P(d∣c) P(c)P(d \mid c)\,P(c) across classes — the denominator can be dropped entirely. P(c)P(c) is the prior, the overall frequency of class cc in the training data. P(d∣c)P(d \mid c) is the likelihood, the probability the model assigns to generating exactly this document under class cc. The likelihood term is where the "naive" assumption enters. Representing dd as a bag-of-words sequence of tokens w1,w2,…,wnw_1, w_2, \dots, w_n, the model assumes each word's occurrence is conditionally independent of every other word, given the class:

P(d∣c)=P(w1,w2,…,wn∣c)≈∏i=1nP(wi∣c).P(d \mid c) = P(w_1, w_2, \dots, w_n \mid c) \approx \prod_{i=1}^{n} P(w_i \mid c).

This is false in general — "not" and "good" are strongly correlated in sentiment text — but it converts an intractable joint distribution over exponentially many word combinations into ∣V∣|V| (vocabulary size) independent one-dimensional distributions, each estimable from simple word counts. The full decision rule, called the Maximum A Posteriori (MAP) class, is:

c^=arg⁡max⁡c∈C  P(c)∏i=1nP(wi∣c).\hat{c} = \arg\max_{c \in C} \; P(c) \prod_{i=1}^{n} P(w_i \mid c).

Two design choices complete the model. First, the multinomial event model treats a document as a sequence of word draws from a per-class die with ∣V∣|V| faces, so word frequency matters — a word appearing three times contributes three factors. This is the standard choice for text and the one developed below. An alternative, the Bernoulli event model, only asks whether each vocabulary word is present or absent, ignoring repeat counts; it tends to work better on very short documents. Second, because every P(wi∣c)P(w_i \mid c) is a fraction less than one, the product over a long document underflows floating-point precision, which is why real implementations always work in log-space, turning the product into a sum of log-probabilities.

How it works

Training estimates two sets of parameters from labeled data: class priors and word likelihoods. For each class cc, the prior is the fraction of training documents labeled cc:

P^(c)=NcNtotal.\hat{P}(c) = \frac{N_c}{N_{\text{total}}}.

For each word ww and class cc, the likelihood is estimated by counting how often ww occurs across all documents labeled cc, relative to the total word count in class cc:

P^(w∣c)=count(w,c)∑w′∈Vcount(w′,c).\hat{P}(w \mid c) = \frac{\text{count}(w, c)}{\sum_{w' \in V} \text{count}(w', c)}.

This raw maximum-likelihood estimate has a fatal flaw: if a word never appears in class cc's training documents, P^(w∣c)=0\hat{P}(w \mid c) = 0, and because the model multiplies probabilities, a single zero factor forces the entire class likelihood to zero — no matter how strongly every other word favors cc. Laplace (add-one) smoothing fixes this by pretending every vocabulary word was seen one extra time in every class:

P^(w∣c)=count(w,c)+1(∑w′∈Vcount(w′,c))+∣V∣.\hat{P}(w \mid c) = \frac{\text{count}(w, c) + 1}{\left(\sum_{w' \in V} \text{count}(w', c)\right) + |V|}.

The +∣V∣+|V| in the denominator is required so the smoothed probabilities still sum to one over the vocabulary — adding 1 to every one of the ∣V∣|V| numerators must be balanced by adding ∣V∣|V| to the shared denominator. At test time, an unseen document is tokenized, out-of-vocabulary words are dropped (the model has no parameters for them), and the remaining words are scored against every class using log-probabilities, and the arg-max class is returned. ``` // ---------- TRAINING ---------- function TRAIN_NAIVE_BAYES(documents, labels, classes): V := set of all distinct words seen in documents // vocabulary N_total := number of documents for each class c in classes: N_c := number of documents with label c logprior[c] := log(N_c / N_total) // concatenate all class-c documents into one "mega-document" text_c := concatenation of documents where label == c for each word w in V: count[w][c] := number of occurrences of w in text_c total_c := sum over w in V of count[w][c] for each word w in V: // Laplace smoothing: +1 numerator, +|V| denominator loglikelihood[w][c] := log( (count[w][c] + 1) / (total_c + |V|) ) return logprior, loglikelihood, V // ---------- INFERENCE ---------- function PREDICT(doc, classes, logprior, loglikelihood, V): best_class := null best_score := -infinity for each class c in classes: score := logprior[c] for each word w in TOKENIZE(doc): if w in V: // ignore out-of-vocabulary words score := score + loglikelihood[w][c] // else: skip — no evidence for or against any class if score > best_score: best_score := score best_class := c return best_class ``` Note the invariant maintained by training: after smoothing, ∑w∈VP^(w∣c)=1\sum_{w \in V} \hat{P}(w \mid c) = 1 for every class cc, since

∑w∈Vcount(w,c)+1totalc+∣V∣=totalc+∣V∣totalc+∣V∣=1.\sum_{w \in V} \frac{\text{count}(w,c)+1}{\text{total}_c + |V|} = \frac{\text{total}_c + |V|}{\text{total}_c + |V|} = 1.

This guarantees each class's likelihood model is a genuine probability distribution over the vocabulary, not merely a scoring heuristic.

Figure
From labeled documents to a class decision: the Naive Bayes pipeline
Training docs "free money now" → spam "meeting at noon" → ham "free tickets win" → spam Count per class count(w, spam), count(w, ham) total_spam, total_ham N_spam, N_ham Laplace smoothing P(w|c) = (count+1) / (total_c + |V|) logprior[c], loglikelihood[w][c] Trained model logprior + loglikelihood table New document "free tickets meeting" score(spam) = logprior[spam] + ll[free|spam] + ll[tickets|spam] + ll[meeting|spam] = −4.02 score(ham) = logprior[ham] + ll[free|ham] + ll[tickets|ham] + ll[meeting|ham] = −5.41 arg max ⇒ spam wins (−4.02 > −5.41)

Complexity & correctness

Training time is O(D⋅L)O(D \cdot L), where DD is the number of training documents and LL is the average document length, since each word token requires a single increment to a count table; the smoothing pass afterward touches every (word, class) pair once, adding O(∣V∣⋅∣C∣)O(|V| \cdot |C|). Total training time is O(DL+∣V∣∣C∣)O(D L + |V||C|), linear in the size of the corpus — no iterative optimization, no gradient descent, no convergence loop. This is a major practical advantage over discriminative alternatives like logistic regression, which require multiple passes over the data to converge.

Training space is O(∣V∣⋅∣C∣)O(|V| \cdot |C|) to store the likelihood table, plus O(∣C∣)O(|C|) for priors — the model size depends only on vocabulary and class count, not on the number of training documents, so it does not grow as more data arrives (only the counts inside the table become more accurate).

Inference time per document is O(L′⋅∣C∣)O(L' \cdot |C|), where L′L' is the length of the test document, since the algorithm performs one table lookup and addition per word per class. This is a single linear pass with no search, making Naive Bayes attractive for high-throughput filtering systems.

Correctness argument. The MAP decision rule is provably Bayes-optimal — it minimizes expected classification error — if the model's assumed joint distribution P(c)∏iP(wi∣c)P(c)\prod_i P(w_i \mid c) equals the true data-generating distribution P(c,w1,…,wn)P(c, w_1, \dots, w_n). The independence assumption is the model's only approximation; everything else (Bayes' rule, arg-max decision) is exact probability theory. The practical claim is weaker but still meaningful: even when features are correlated, the arg-max decision can remain correct as long as the ranking of classes by score is preserved, because classification only needs the correct class to have the highest score, not an accurate probability value. This explains empirically why Naive Bayes performs far better as a classifier than its probability estimates would suggest — the miscalibration from ignoring correlation often biases all classes' scores in a similar direction, leaving the relative ranking intact.

Honest tradeoffs. Correlated features that consistently co-occur get double-counted — if "not" and "good" always appear together in negative reviews, the model effectively counts that evidence twice, overconfidently favoring whichever class that pair supports. Naive Bayes also produces poorly calibrated probability estimates (often extremely close to 0 or 1) even when its class decisions are accurate, so its raw probability outputs should not be trusted as confidence scores without recalibration. On large datasets with abundant training examples, discriminative classifiers that model P(c∣d)P(c \mid d) directly (e.g., logistic regression, or later, neural classifiers) typically overtake Naive Bayes in accuracy, because they can adjust for feature correlations that Naive Bayes cannot represent. Naive Bayes's advantage is sharpest precisely when training data is scarce, since it needs only simple counts and never overfits the way a high-parameter discriminative model can.

Worked example

Consider a two-class sentiment task with a tiny training set (following the standard textbook-style illustration used to sanity-check implementations):

DocumentWordsClass
d1d_1"just plain boring"−
d2d_2"entirely predictable and lacks energy"−
d3d_3"no surprises and very few laughs"−
d4d_4"very powerful"+
d5d_5"the most fun film of the summer"+

Vocabulary VV (unique words across all documents, 20 words); class counts: N−=3N_- = 3, N+=2N_+ = 2, Ntotal=5N_{\text{total}} = 5.

Priors:

P^(−)=3/5=0.6,P^(+)=2/5=0.4.\hat{P}(-) = 3/5 = 0.6, \qquad \hat{P}(+) = 2/5 = 0.4.

Suppose the negative-class mega-document has 14 word tokens and the positive-class mega-document has 9 word tokens (small numbers chosen for hand-tracing). With ∣V∣=20|V| = 20:

denominator−=14+20=34,denominator+=9+20=29.\text{denominator}_- = 14 + 20 = 34, \qquad \text{denominator}_+ = 9 + 20 = 29.

Now classify the test sentence "predictable with no fun". Suppose "with" never appeared in training (out-of-vocabulary, so it is dropped), and the remaining words have these smoothed counts:

Wordcount(w, −)P^(w∣−)\hat P(w\mid -)count(w, +)P^(w∣+)\hat P(w\mid +)
predictable1(1+1)/34=0.0588(1+1)/34 = 0.05880(0+1)/29=0.0345(0+1)/29 = 0.0345
no1(1+1)/34=0.0588(1+1)/34 = 0.05880(0+1)/29=0.0345(0+1)/29 = 0.0345
fun0(0+1)/34=0.0294(0+1)/34 = 0.02941(1+1)/29=0.0690(1+1)/29 = 0.0690

Score for negative class (log base 10, for readability):

log⁡P(−)+log⁡0.0588+log⁡0.0588+log⁡0.0294\log P(-) + \log 0.0588 + \log 0.0588 + \log 0.0294
=−0.222+(−1.231)+(−1.231)+(−1.532)=−4.216.= -0.222 + (-1.231) + (-1.231) + (-1.532) = -4.216.

Score for positive class:

log⁡P(+)+log⁡0.0345+log⁡0.0345+log⁡0.0690\log P(+) + \log 0.0345 + \log 0.0345 + \log 0.0690
=−0.398+(−1.462)+(−1.462)+(−1.161)=−4.483.= -0.398 + (-1.462) + (-1.462) + (-1.161) = -4.483.

Since −4.216>−4.483-4.216 > -4.483, the model predicts negative. This is sensible: "predictable" and "no" are strong negative-class signals present in training, and even though "fun" leans positive, it alone cannot outweigh two negative-leaning words plus the negative class's higher prior. Notice also that "with," an out-of-vocabulary word, contributed nothing to either score — the model simply has no opinion about words it never saw during training, which is a deliberate and correct behavior, not a bug.

Common pitfalls & traps

PitfallWhy it happensFix
Zero-probability collapseA test word never appeared in one class's training data, making that class's raw likelihood exactly zeroApply Laplace (or more general additive) smoothing to every word/class pair before inference
Forgetting the smoothing denominatorAdding 1 to every numerator without adding ∣V∣\lvert V\rvert to the denominatorProbabilities no longer sum to 1; always add ∣V∣\lvert V\rvert, the vocabulary size, to each class's total
Underflow from multiplying raw probabilitiesLong documents multiply hundreds of small fractions, driving the product to zero in floating pointAlways accumulate log-probabilities and sum instead of multiplying
Treating unseen test words as evidenceTrying to look up a word that never appeared in any training documentSkip out-of-vocabulary words entirely; they carry no information under this model
Trusting raw output probabilities as confidenceNaive Bayes probabilities are known to be poorly calibrated even when the class decision is correctUse the arg-max decision for classification; calibrate separately (e.g., Platt scaling) if confidence scores are needed
Vocabulary built only from training data at test timeNew domain-specific words at test time silently vanishAcceptable by design, but monitor out-of-vocabulary rate — high rates signal domain mismatch

Pioneers & history

The probabilistic engine behind Naive Bayes is Bayes' theorem itself, but its application to automated text categorization crystallized during the growth of statistical NLP and information retrieval in the late twentieth century, when researchers needed fast, trainable classifiers for exploding volumes of digital text — email, newswire, and web documents.

Origins · 1 minThomas Bayes and how this was discovered
TB
Thomas Bayes
c. 1701–1761 · English statistician and Presbyterian minister whose posthumously published essay introduced the theorem inverting conditional probabilities

Bayes never saw his idea applied to language. His 1763 essay, "An Essay towards solving a Problem in the Doctrine of Chances," published after his death by Richard Price, established how to update a belief about an unobserved cause given observed evidence — the mathematical seed that, two centuries later, would let machines infer whether an email is spam from nothing but the words it contains.

History
1763
— Bayes' theorem published posthumously, establishing the mathematics of inverting conditional probabilities.
1961
— Early Bayesian text-classification experiments applied to document categorization and authorship attribution.
1998
— Naive Bayes spam filters (e.g., in academic and early commercial systems) demonstrate strong practical performance despite the false independence assumption.
2001
— Comparative studies formalize why Naive Bayes remains competitive with more expressive classifiers on small or high-dimensional text datasets.
2000
s — Naive Bayes becomes a standard baseline in NLP courses and toolkits, later superseded in leaderboard accuracy by discriminative and neural models but retained for speed and interpretability.

Where this matters

Naive Bayes remains embedded in production systems where speed, interpretability, and low data requirements outweigh the accuracy gains of heavier models. Early and many current email spam filters use Naive Bayes variants because the classifier retrains instantly as users flag new spam, needing no gradient-based fine-tuning. Sentiment analysis baselines in industry and academic benchmarks routinely report Naive Bayes accuracy first, since any more complex model must justify its added cost by beating this cheap reference point — a core habit taught alongside Text Classification Fundamentals. Language identification systems (detecting whether a string of text is English, French, or Japanese) often use character-level or word-level Naive Bayes because the independence assumption is nearly harmless when the discriminating signal is simple character or n-gram frequency. Document-routing systems in customer-support platforms, and simple content-moderation triage layers, use Naive Bayes as a fast first-pass filter before escalating uncertain cases to slower, more accurate models. Pedagogically, Naive Bayes is the cleanest bridge from the Bag-of-Words Model representation to fully generative sequence models: the next conceptual step, Hidden Markov Models, keeps the same "independent emissions given a hidden variable" structure but adds a Markov chain over the hidden states themselves, turning a single hidden class into a sequence of hidden states that evolve over time.

Ask the tutor: How would the decision rule and training procedure change if we wanted to use a Bernoulli event model instead of the multinomial one?

Why does Naive Bayes still work well even though words are clearly not independent?

Classification only requires the correct class to receive the highest score, not an accurate probability estimate. When features are correlated, the independence assumption distorts every class's computed likelihood in a similar direction, so the ranking of classes by score often stays correct even though the absolute probability values are wrong. This is why Naive Bayes decisions frequently match human judgments while its predicted probabilities are badly overconfident (very close to 0 or 1). The mismatch between "correct decisions" and "wrong probabilities" is a hallmark of the model and should be expected rather than treated as a bug.

What is the difference between the multinomial and Bernoulli event models?

The multinomial model treats a document as a sequence of word draws, so a word occurring three times contributes three identical factors to the likelihood — frequency matters. The Bernoulli model instead asks, for every vocabulary word, whether it is present or absent in the document at all, ignoring how many times it occurred. Multinomial Naive Bayes generally performs better on longer documents where word frequency carries real signal (e.g., topic classification), while Bernoulli Naive Bayes can be competitive on very short texts where presence/absence is the dominant signal.

How does Naive Bayes handle out-of-vocabulary words at test time?

It simply ignores them. Since the model has no trained likelihood parameters P(w∣c)P(w \mid c) for a word it never saw during training, that word contributes no term to any class's score — it is dropped from the summation entirely. This is a deliberate design choice, not an approximation error: the model genuinely has zero evidence about an unseen word, so contributing nothing is the correct behavior rather than guessing.

Can Naive Bayes handle more than two classes?

Yes, directly. The decision rule arg⁡max⁡cP(c)∏iP(wi∣c)\arg\max_c P(c)\prod_i P(w_i \mid c) generalizes to any number of classes without modification — the training algorithm computes a prior and a full likelihood table for every class, and inference simply scores all classes and takes the maximum. This is one practical advantage over classifiers like standard logistic regression, which need explicit multinomial extension or one-vs-rest wrapping to handle more than two labels.

Why is the model called "generative" rather than "discriminative"?

Naive Bayes models the joint distribution P(c,d)=P(c)P(d∣c)P(c, d) = P(c)P(d \mid c), effectively describing a story for how a document could be generated given its class — pick a class, then draw words independently from that class's word distribution. Discriminative models like logistic regression instead model P(c∣d)P(c \mid d) directly, without ever describing how the words themselves were produced. The generative framing is what lets Naive Bayes be trained with pure counting rather than iterative optimization, and it is the same framing later extended by Hidden Markov Models to model sequences.

What causes Naive Bayes to be poorly calibrated, and does that matter?

Repeated correlated features get "double-counted" because the independence assumption treats them as separate, independent pieces of evidence even when they always co-occur. This systematically pushes computed probabilities toward the extremes (near 0 or 1) rather than reflecting true uncertainty. It matters if downstream systems need trustworthy confidence scores (e.g., to set a rejection threshold), in which case a calibration step such as Platt scaling should be applied. It matters far less if only the top-1 predicted class is used, since miscalibration rarely flips the arg-max decision.

How does Naive Bayes relate to the Bag-of-Words representation?

Naive Bayes is essentially a probabilistic classifier layered directly on top of Bag-of-Words counts: the word-count vectors produced by the Bag-of-Words representation become exactly the sufficient statistics — count(w, c) and total_c — that training needs to estimate P(w∣c)P(w \mid c). Because Bag-of-Words discards word order, Naive Bayes inherits that same blindness to order, which is precisely why sequence-aware generative models like Hidden Markov Models were later developed for tasks where order carries essential information, such as part-of-speech tagging.

Before this

Where this leads