Naive Bayes Classifier
The Naive Bayes Classifier is a probabilistic supervised learning algorithm that predicts class labels by applying Bayes' Theorem under the strong ("naive") assumption that every feature is independent of every other feature given the class. It matters enormously in modern AI because it remains one of the fastest and most interpretable classifiers available, scaling effortlessly to millions of documents in spam filters and search engines. The counterintuitive fact: despite its independence assumption being almost always wrong in practice, Naive Bayes frequently achieves competitive accuracy — and sometimes beats far more complex models.
The "aha moment" is realising that you don't need to learn a complicated decision boundary — you just need to ask: "Given that I've seen these features, which class makes this data most probable?" Naive Bayes answers that question by multiplying together simple individual probabilities, one per feature, as if each feature were casting an independent vote for its favourite class. The "naive" shortcut makes the math tractable and the training instantaneous, yet the majority vote of many weak signals turns out to be surprisingly reliable. Think of it as a room full of independent witnesses each offering one piece of evidence — together they usually point to the right verdict.
How It Works
Naive Bayes applies Bayes' Theorem to flip a conditional probability into a useful prediction. The idea is: instead of directly estimating P(class | features), which requires a huge amount of data, we estimate the simpler quantities P(features | class) and P(class) from our training set, then combine them.
Explanation: The probability of class C given all the observed features equals the prior probability of C multiplied by the product of each individual feature's likelihood under that class.
Symbol guide:
- — the posterior: what we ultimately want, the probability of class C given the features we observed.
- — the prior: how often class C appears in the training data (e.g., 40% of emails are spam).
- — the likelihood of feature given class (e.g., how often the word "free" appears in spam emails).
- — multiply together all the per-feature likelihoods (the "naive" step — we treat them as independent).
We pick the class with the highest posterior score:
Explanation: The predicted class is simply whichever class produces the largest combined score when you multiply the prior by all the feature likelihoods.
Avoiding Underflow with Log Probabilities
Multiplying many small probabilities together causes numbers to shrink toward zero and create floating-point underflow. In practice, we take the logarithm of equation [1], turning the product into a sum:
Explanation: Summing log-probabilities gives identical ranking results to multiplying raw probabilities, but stays numerically stable even with thousands of features.
Laplace Smoothing
If a word never appeared in the training set for a given class, its likelihood is zero — and that zero kills the entire product. Laplace smoothing adds a small count (usually 1) to every feature, preventing zero probabilities:
Explanation: By adding 1 to the numerator and the vocabulary size |V| to the denominator, every feature gets a non-zero probability even if it was never seen during training.
Python: Training a Naive Bayes Spam Filter from Scratch
```python from collections import defaultdict import math class NaiveBayesClassifier: def __init__(self, alpha=1.0): self.alpha = alpha # Laplace smoothing parameter self.class_log_priors = {} # log P(C) self.feature_log_likelihoods = defaultdict(dict) # log P(word|C) self.vocab = set() def fit(self, X, y): """ X: list of lists of words (tokenised documents) y: list of class labels (e.g., 'spam', 'ham') """ class_word_counts = defaultdict(lambda: defaultdict(int)) class_doc_counts = defaultdict(int) for doc, label in zip(X, y): class_doc_counts[label] += 1 for word in doc: class_word_counts[label][word] += 1 self.vocab.add(word) total_docs = len(y) V = len(self.vocab) for cls, doc_count in class_doc_counts.items():
# log prior: how common is this class? self.class_log_priors[cls] = math.log(doc_count / total_docs) total_words = sum(class_word_counts[cls].values()) for word in self.vocab: count = class_word_counts[cls].get(word, 0)
# Laplace-smoothed log likelihood self.feature_log_likelihoods[cls][word] = math.log( (count + self.alpha) / (total_words + self.alpha * V) ) return self def predict(self, X): return [self._predict_single(doc) for doc in X] def _predict_single(self, doc): scores = {} for cls in self.class_log_priors:
# Start with the log prior (equation [3]) score = self.class_log_priors[cls] for word in doc: if word in self.vocab: score += self.feature_log_likelihoods[cls][word] scores[cls] = score
# Return the class with the highest log-probability (equation [2]) return max(scores, key=scores.get)
# ── Quick demo ───────────────────────────────────────────────────────────── train_docs = [ ["free", "money", "click", "now"], # spam ["free", "prize", "winner", "claim"], # spam ["meeting", "tomorrow", "agenda"], # ham ["project", "update", "please", "review"], # ham ["win", "cash", "free", "offer"], # spam ["lunch", "tomorrow", "office"], # ham ] train_labels = ["spam","spam","ham","ham","spam","ham"] clf = NaiveBayesClassifier(alpha=1.0) clf.fit(train_docs, train_labels) test = [["free", "cash", "win"], ["meeting", "agenda"]] print(clf.predict(test)) # → ['spam', 'ham'] ```
Using scikit-learn (Production Path)
```python from sklearn.naive_bayes import MultinomialNB from sklearn.feature_extraction.text import CountVectorizer from sklearn.pipeline import Pipeline
# Raw text documents train_texts = [ "free money click now", "free prize winner claim", "meeting tomorrow agenda", "project update please review", "win cash free offer", "lunch tomorrow office", ] train_labels = ["spam","spam","ham","ham","spam","ham"]
# Pipeline: tokenise → count → Naive Bayes pipeline = Pipeline([ ("vectorizer", CountVectorizer()), ("classifier", MultinomialNB(alpha=1.0)), # alpha = Laplace smoothing ]) pipeline.fit(train_texts, train_labels)
# Predict new examples test_texts = ["free cash win now", "project meeting tomorrow"] print(pipeline.predict(test_texts))
# → ['spam', 'ham']
# Get calibrated-ish probabilities (still interpret with caution) probs = pipeline.predict_proba(test_texts) print(probs) # columns correspond to pipeline.classes_ ```
Three Flavours of Naive Bayes
| Variant | Feature type | Typical use case |
|---|---|---|
| Multinomial NB | Word counts / frequencies | Text classification, spam detection |
| Bernoulli NB | Binary (word present / absent) | Short text, document classification |
| Gaussian NB | Continuous numeric values | Sensor data, medical features |
```python from sklearn.naive_bayes import GaussianNB import numpy as np
# Gaussian NB for continuous features (e.g., height, weight) X_train = np.array([[170, 65], [180, 80], [160, 55], [155, 50]]) y_train = ["male","male","female","female"] gnb = GaussianNB() gnb.fit(X_train, y_train) print(gnb.predict(165, 60)) # → ['female'] ```
Thomas Bayes was an English statistician and Presbyterian minister who never published his most important work during his lifetime — his essay on inverse probability was found among his papers and submitted to the Royal Society posthumously by his friend Richard Price in 1763. His theorem provided the mathematical engine that Naive Bayes is built on: the ability to update a prior belief with observed evidence to arrive at a posterior probability. Remarkably, Bayes himself was so uncertain about the validity of his result that he chose not to publish it — leaving posterity to name one of the most widely-used formulas in machine learning after a man who wasn't sure it was correct.
The popularisation of Naive Bayes for text classification is broadly credited to a 1998 paper by Andrew McCallum and Kamal Nigam, who conducted one of the first rigorous comparisons of Multinomial versus Bernoulli Naive Bayes on text corpora. Their work demonstrated that the Multinomial variant consistently outperformed the Bernoulli variant for longer documents — a finding that shaped how the field chose Naive Bayes variants for the next decade. McCallum later went on to build significant NLP infrastructure including the MALLET toolkit, which made Naive Bayes and related probabilistic models accessible to a generation of NLP researchers.
Intuition
Imagine you're a detective and you've just received a suspicious email. You don't have time to read it carefully — you need a fast verdict: spam or not spam? You flip through your mental "spam case file" — a notebook you built by studying thousands of past emails. You know:
- The word "free" appears in spam 70% of the time, but in normal email only 5%.
- The word "money" appears in spam 60% of the time, but only 2% in normal email.
- The word "meeting" appears in spam 3% of the time, but in normal email 40%.
For each word in the new email, you flip to that page in your notebook and see which verdict it supports. Each word casts a vote. You multiply all the votes together (plus your starting guess about how common spam is), and the class with the bigger final number wins. The "naive" part is that you treat each word as a completely independent witness — you don't ask "is it suspicious that both 'free' and 'money' appear together?" You just count the individual votes separately. This is clearly not how language really works — but the votes pile up so quickly that the correct class usually wins anyway. Think of it like judging a dish by rating each ingredient separately (sweetness, saltiness, texture) without worrying about how they interact. It's a simplification, but it works well enough in surprisingly many situations.
Historical Development
Origins · 1 minhow this was discovered
Why It Matters
Prerequisites
What This Leads To
See Also
Logistic Regression · Decision Trees · Support Vector Machine · Text Classification · Bayesian Networks · Probabilistic Graphical Models · Feature Engineering