Module 4 — Naive Bayes
Naive Bayes turns Bayes’ rule from Course 2, Module 3 into a working classifier — and it is still the textbook spam filter. It is fast, needs little data, and is shockingly hard to beat on text. In this module you will build a live spam filter: toggle words on and off and watch the spam probability swing.
Bayes’ rule, as a classifier
We want \( P(\text{spam} \mid \text{words}) \) — the chance an email is spam given the words it contains. Bayes flips it into things we can count from past email:
\( P(\text{spam}) \) is the prior — how common spam is overall. \( P(\text{words} \mid \text{spam}) \) is the likelihood — how typical those words are of spam.
The "naive" trick
Computing \( P(\text{whole sentence} \mid \text{spam}) \) is impossible — most sentences have never been seen before. So naive Bayes makes one bold simplifying assumption: treat each word as independent given the class, and just multiply the per-word probabilities:
That assumption is technically false — "york" is far more likely after "new" — yet the classifier works remarkably well anyway, because it only needs to get the winner right, not the exact probability. Working in log space (Course 2, Module 1) turns that long product into a stable sum.
Build the filter
Each word below tilts the odds: a spam-ish word multiplies the spam odds up, a work-ish word multiplies them down. Toggle the words in your "email" and watch the verdict. Slide the prior to make spam rarer or more common.
This activity needs JavaScript. The lesson below still covers everything.
from sklearn.naive_bayes import MultinomialNB from sklearn.feature_extraction.text import CountVectorizer X = CountVectorizer().fit_transform(emails) # word counts clf = MultinomialNB().fit(X, labels) # learns P(word | class) clf.predict_proba(new_email) # P(spam | words)from sklearn.naive_bayes import MultinomialNB from sklearn.feature_extraction.text import CountVectorizer # A tiny labeled inbox emails = [ "win money now free prize", "free money claim your prize", "cheap pills buy now cheap", "win a free vacation click here", "meeting tomorrow at noon", "can you review the report today", "lunch with the team today", "project deadline moved to friday", ] labels = ["spam", "spam", "spam", "spam", "ham", "ham", "ham", "ham"] vec = CountVectorizer() X = vec.fit_transform(emails) # word counts clf = MultinomialNB().fit(X, labels) # learns P(word | class) tests = ["free money now", "see you at the meeting today"] proba = clf.predict_proba(vec.transform(tests)) # P(spam | words) for text, p in zip(tests, proba): by_class = dict(zip(clf.classes_, p)) print(f"{text!r:32} P(spam)={by_class['spam']:.3f} P(ham)={by_class['ham']:.3f}")
The word probabilities you see below are exactly what .fit() estimates by counting words in known spam vs. known ham. Hit Run it yourself, then add your own lines to tests and rerun.
Check your filter sense
A few questions on priors, independence, and why the trick works. You will get a score.
This activity needs JavaScript.