How did Gmail know that the email subject "RE: URGENT — claim your prize now!!!" is spam, even though it had never seen that exact email before? Because of a 250-year-old equation from a Presbyterian minister named Thomas Bayes. The recipe: count how often each word appears in spam vs. real email, multiply the probabilities together, pick the bigger one. That's it. By the end of this lesson, you'll classify an email by hand using only multiplication and a small table — and you'll understand why a wildly unrealistic assumption ("every word is independent of every other word") gives a classifier that still beats elaborate deep models on small text datasets in 2026.
Learning Objectives
After this lesson, you will be able to:
Understand the 'naive' assumption — pretend features are independent *given the class* — and why this shortcut works surprisingly well even though it is technically wrong
Build the Naive Bayes formula step by step from Bayes' theorem and handle the 'never-seen-this-word-before' problem with Laplace smoothing
Know the three flavors of Naive Bayes (Gaussian, Multinomial, Bernoulli) and pick the right one for numbers vs. word counts vs. yes/no features
Explain why multiplying many small probabilities causes underflow and how log space solves it
Recognize when Naive Bayes outperforms more complex models — specifically in high-dimensional, low-data text classification scenarios
Don't worry if the math looks intense — Naive Bayes is really just multiplying a bunch of simple probabilities together and picking the biggest answer. Once you see it work on a spam filter example, it will click!
The denominator P(x1, ..., xn) is the same for all classes, so when comparing classes we only need the numerator. The classification rule becomes: pick the class with the highest numerator.
Computing P(x1, x2, ..., xn | c) exactly requires knowing the joint distribution of all features given the class -- exponentially many parameters. With 10,000 vocabulary words, you would need to estimate 2^10000 probabilities. Impossible.
The naive assumption: all features are conditionally independent given the class.
P(x1,x2,…,xn∣c)≈i=1∏nP(xi∣c)
So the full Naive Bayes classifier is:
Naive Bayes Spam Filter — Toggle Words and Watch P(spam) MultiplyInteractive
Loading visualization...
c^=argcmaxP(c)i=1∏nP(xi∣c)
Try it! Open the Python REPL and type these lines yourself. Build a one-line spam classifier: from sklearn.naive_bayes import MultinomialNB; from sklearn.feature_extraction.text import CountVectorizer; v = CountVectorizer(); X = v.fit_transform(["free money now","meeting tomorrow","win prize","project update"]); y = [1,0,1,0]; m = MultinomialNB().fit(X,y); print(m.predict(v.transform(["free prize"]))) — it predicts spam!
What happens if the word "cryptocurrency" appears in a test email but was never seen in the training spam? P(cryptocurrency | spam) = 0, and since we multiply all probabilities, the entire product becomes zero -- regardless of all other evidence. One unseen word kills the classifier.
Laplace smoothing (add-one smoothing) fixes this by adding a small count to every word:
P(xi∣c)=count(c)+α⋅∣V∣count(xi,c)+α
With alpha = 1 (add-one smoothing), a word that never appeared in spam gets a probability of 1/(total_spam_words + vocab_size) instead of 0. This is a crucial practical detail -- without smoothing, Naive Bayes breaks on any email containing new words.
Best for text classification where features are word counts or frequencies. This is what we used in the spam example. It models how many times each word appears.
Features are binary (word present or absent, not counts). It also considers the absence of words as evidence. If "meeting" is absent, that is evidence against ham.
Use for: short text classification, feature presence/absence, binary survey data.
Features are continuous and assumed normally distributed. For each class, it estimates the mean and variance of each feature.
Use for: iris flower classification, medical diagnosis with continuous lab values, any continuous feature set.
Now let's actually run Multinomial Naive Bayes on text. The cell below tokenizes a small set of restaurant reviews, fits the model, and prints the per-word log-probabilities so you can see exactly which words pull toward "positive" versus "negative".
Loading visualization...
Quick check
Naive Bayes assumes features are CONDITIONALLY INDEPENDENT given the class. For a spam classifier, what does that assumption claim about the words 'free' and 'click'?
Try it: Watch Naive Bayes classify data in real timeInteractive
Loading visualization...
Try this: Generate two clusters of data and watch Naive Bayes draw its decision boundary. Notice how the boundary is always a straight line (for Gaussian NB with equal variance) or a curve. Compare it to logistic regression -- the boundaries look similar because both are linear classifiers in feature space. Then try making the clusters overlap and see how the prior affects where the boundary falls.
Interactive Lab
Switch to the Naive Bayes algorithm to see its boundary on the same dataset. Compare with logistic regression — both linear, but driven by very different math under the hood.
Despite assuming word independence (clearly wrong -- "New York" is not independent), Naive Bayes works surprisingly well because it only needs the RANKING of probabilities to be correct, not the exact values. Consider: if the true P(spam|email) is 0.92 and Naive Bayes estimates 0.73, the classification is still correct -- spam wins. The probabilities are wrong, but the ordering is right.
This is analogous to a judge ranking contestants. Even if the judge's scores are miscalibrated (giving everyone lower scores than they deserve), as long as the ranking is preserved, the right contestant wins. Naive Bayes is a consistently miscalibrated judge who still usually picks the right winner.
Tests · Verify 'free winner click now' is classified as spam. Verify 'meeting report hello' is classified as ham. Check that probabilities sum to approximately 100%.
On the Optimality of the Simple Bayesian Classifier under Zero-One Loss
Pedro Domingos and Michael Pazzani (1997)
The seminal paper explaining why Naive Bayes works despite the clearly wrong independence assumption. Shows that the classifier only needs to get the ranking right, not the exact probabilities, and that the independence assumption often does not affect the ranking.
Naive Bayes is one member of a larger family — generative classifiers — all of which model P(X | y) · P(y) and apply Bayes' rule to recover P(y | X). They only differ in what they assume about P(X | y). Gaussian Naive Bayes assumes per-class Gaussians with diagonal covariance (features conditionally independent). Drop that diagonal restriction and you get Linear Discriminant Analysis (LDA) — same shared covariance across classes, but full matrices. Let each class wear its own covariance and you get Quadratic Discriminant Analysis (QDA). These are the next lesson — a discriminative-looking cousin family of Gaussian Naive Bayes, with linear vs. quadratic boundaries falling out of one assumption. See Linear & Quadratic Discriminant Analysis for the full story, including Fisher's 1936 criterion for supervised dimensionality reduction.
Naive Bayes is Bayes' theorem with a strong independence shortcut -- it classifies by computing P(class) times the product of P(feature|class) for each feature, picking the class with the highest score
The 'naive' assumption is wrong but works -- features are rarely independent, but the classifier only needs correct ranking (which class scores highest), not exact probabilities; this makes it a surprisingly strong baseline
Laplace smoothing prevents zero probabilities -- adding a small count (alpha) to every feature ensures that one unseen word does not zero out the entire probability; without it, Naive Bayes breaks on new vocabulary
Three variants for different data types -- Multinomial NB for word counts (text), Bernoulli NB for binary features, Gaussian NB for continuous features; Multinomial NB is the workhorse of text classification
Log probabilities prevent underflow -- always work in log space when implementing Naive Bayes to avoid multiplying many tiny numbers into zero
Next up: Linear & Quadratic Discriminant Analysis -- Gaussian Naive Bayes' more flexible cousin, where dropping the diagonal-covariance assumption gives you linear (LDA) and quadratic (QDA) decision boundaries, and Fisher's 1936 trick repurposes the same math for supervised dimensionality reduction.
Gradient Boosting (LightGBM/XGBoost)
Captures interactions NB cannot model by assumption