Part III — Probability, Information Theory & Bayesian Inference · Chapter 10

Build a Bayesian spam classifier & anomaly detector

Hook

A new message contains a word your spam class has never seen and a word your "not spam" class has never seen either. Raw maximum likelihood has an answer for that — and it's a silent, catastrophic zero.

Intuition
labelwords
spamwin, urgent
spamwin
spamurgent
not spamlink
not spamlink, win
not spam(none)
P(word present | class)spamnot spam
win0.6670.333
urgent (in message)0.6670.000
link (in message)0.0000.667

Toggle to MLE: two cells sit at exactly 0.0000.000, in red — words that never once appeared with that class in training. Toggle to MAP: the smoothing pulls every estimate off the boundary, exactly where MLE broke.

Formalize

Naive Bayes classifies a message by combining a class prior with a per-word likelihood, assuming word presence is conditionally independent given the class:

P(labelmessage)P(label)wvocabP(wmessagelabel)P(\text{label}\mid \text{message}) \propto P(\text{label}) \prod_{w \in \text{vocab}} P(w \in \text{message}\mid \text{label})
  • P(label)P(\text{label}) — the class prior, estimated as the fraction of training messages with that label.
  • P(wmessagelabel)P(w \in \text{message}\mid \text{label}) — the likelihood that word ww's presence/absence matches the label, estimated from training counts.

Raw (MLE) word likelihoods are just counts: count(w,label)/count(label)\text{count}(w, \text{label}) / \text{count}(\text{label}). Laplace smoothing reframes this as a MAP estimate under a Beta(1+α,1+α)(1{+}\alpha, 1{+}\alpha) prior on each word:

PMAP(wlabel)=count(w,label)+αcount(label)+2αP_{\text{MAP}}(w\mid \text{label}) = \frac{\text{count}(w,\text{label}) + \alpha}{\text{count}(\text{label}) + 2\alpha}
  • α\alpha — the smoothing strength (here α=1\alpha=1): imagined extra present and extra absent examples.
  1. A single zero collapses the whole product

    Naive Bayes multiplies likelihoods across every vocab word. One factor of exactly 00 makes the entire class likelihood 00 — no matter how strongly every other word points the other way.

  2. Two zero classes means an undefined posterior

    If every class's likelihood hits zero for a given message, Bayes' rule divides 00 by 00 — the classifier has no answer at all, not even a bad one.

  3. Smoothing is exactly the MAP fix from the last two chapters

    Add-α\alpha smoothing isn't a separate trick; it's the MAP estimate from the MLE/MAP chapters applied once per word, with a prior that assumes every word is plausible in every class until proven otherwise.

Play
P(word present | class)spamnot spam
win0.6000.400
urgent (in message)0.6000.200
link (in message)0.2000.600

message = [urgent, link]

prior: P(spam)=0.50, P(not spam)=0.50

P(message | spam)=0.0480, P(message | not spam)=0.0720

Under MLE, the message [urgent, link] earns a likelihood of exactly 00 under both classes — the classifier is stuck. Switch to MAP and a full, normalized posterior appears, because smoothing kept every likelihood strictly between 00 and 11.

Worked example

Vocabulary: win, urgent, link. New message: [urgent, link] — "win" is absent.

  1. MLE likelihoods hit zero on both sides

    "urgent" never appeared in a "not spam" message (P=0P=0); "link" never appeared in a "spam" message (P=0P=0). Both class likelihoods for this message come out to exactly 00 — MLE cannot classify it.

  2. MAP smooths every word likelihood off the boundary

    With α=1\alpha=1:

    • "urgent": P(urgentspam)=0.6P(\text{urgent}\mid\text{spam})=0.6, P(urgentnot spam)=0.2P(\text{urgent}\mid\text{not spam})=0.2
    • "link": P(linkspam)=0.2P(\text{link}\mid\text{spam})=0.2, P(linknot spam)=0.6P(\text{link}\mid\text{not spam})=0.6
    • "win" (absent): 0.40.4 under spam, 0.60.6 under not spam
  3. Multiply through and apply Bayes' rule
    P(msgspam)=0.4×0.6×0.2=0.048P(msgnot spam)=0.6×0.2×0.6=0.072P(\text{msg}\mid\text{spam}) = 0.4 \times 0.6 \times 0.2 = 0.048 \qquad P(\text{msg}\mid\text{not spam}) = 0.6 \times 0.2 \times 0.6 = 0.072

    With equal priors (0.50.5 each): unnormalized scores 0.0240.024 and 0.0360.036, summing to 0.060.06. Normalizing:

    P(spammsg)=0.0240.06=0.4P(not spammsg)=0.0360.06=0.6P(\text{spam}\mid\text{msg}) = \frac{0.024}{0.06} = 0.4 \qquad P(\text{not spam}\mid\text{msg}) = \frac{0.036}{0.06} = 0.6

    Predicted label: not spam — by a clean 40/6040/60 split, computed entirely from exact fractions.

Checkpoint

Using MAP (Laplace-smoothed) likelihoods, what is P(spam | [urgent, link])?

P(word present | class)spamnot spam
win0.6000.400
urgent (in message)0.6000.200
link (in message)0.2000.600
Pick a value to try it
Summary
P(labelmessage)P(label)wPMAP(wlabel)P(\text{label}\mid\text{message}) \propto P(\text{label}) \prod_w P_{\text{MAP}}(w\mid\text{label})

This is the whole pipeline the course has been building toward: a prior, a likelihood built one MAP estimate at a time, and Bayes' rule to combine them — the same three ingredients behind spam filters, anomaly detectors, and any classifier that needs to say "here's my probability," not just "here's my guess."