Naive Bayes
Naive Bayes is classical ML at its purest: apply an 18th-century theorem (Bayes, 1763) with one bold simplifying assumption, and get a classifier that is fast, needs little data, and filtered your spam for two decades. It also ties Part IV back to text representation β bag-of-words is its natural habitat.
Bayes' theorem
For a class \(y\) and observed features \(x\):
Read as a learning rule: start from the prior (how common is each class?), weigh by the likelihood (how typical are these features for that class?), and obtain the posterior (how probable is the class, given what we observed?). Classify by the largest posterior β \(P(x)\) is the same for all classes and cancels:
The "naive" assumption
The likelihood \(P(x_1, \dots, x_d \mid y)\) is a joint distribution over all feature combinations β hopeless to estimate. Naive Bayes assumes features are conditionally independent given the class:
Now each \(P(x_j \mid y)\) is a simple one-dimensional estimate: count frequencies (discrete features) or fit a Gaussian (continuous ones). Training = counting. One pass over the data.
The assumption is almost always false (in real spam, "free" and "offer" co-occur far more than independence predicts). Why does it still work? Classification needs only the ranking of posteriors, not their exact values. Naive Bayes usually gets \(\arg\max\) right even when the probabilities themselves are badly distorted β typically over-confident (pushed toward 0 or 1). Trust its labels, not its probabilities.
The classic: spam filtering
With bag-of-words features, Multinomial Naive Bayes models word counts per class. Estimate from training counts:
The \(+1\) is Laplace smoothing: without it, a single word never seen in spam training data gives \(P(w \mid \text{spam}) = 0\), and one zero annihilates the entire product β any e-mail containing that word could never be spam. Smoothing pretends every word was seen once.
In practice, multiply-many-small-numbers underflows, so implementations sum logs:
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import make_pipeline
spam_filter = make_pipeline(CountVectorizer(), MultinomialNB(alpha=1.0)) # alpha = smoothing
spam_filter.fit(docs_train, y_train)
spam_filter.predict(["WIN a FREE prize now!!!"])
The Naive Bayes family
| Variant | Feature type | \(P(x_j \mid y)\) model | Typical use |
|---|---|---|---|
MultinomialNB | counts | multinomial over counts | text (bag-of-words / TF-IDF) |
BernoulliNB | binary | Bernoulli (presence/absence) | short text, binary flags |
GaussianNB | continuous | Gaussian per feature per class | numeric tabular data |
For GaussianNB, each class simply stores a mean and variance per feature:
Practical profile
| Strengths | trains in one pass (fastest learner in the course); works with little data; handles very high dimensions (10β΅ word features); naturally multi-class; online-updatable (partial_fit); no scaling needed |
| Weaknesses | probability estimates poorly calibrated; correlated features double-count evidence; linear-ish decision boundaries; the independence fiction can genuinely hurt |
| Reach for it when | text baselines, many features + few samples, latency-critical or streaming prediction |
The perfect baseline
Whatever fancy model you plan for a text problem, fit CountVectorizer + MultinomialNB first. It takes seconds, and anything that cannot beat it is not worth deploying.