CadetX
CX Learn | Complete Machine Learning Guide
Scikit-Learn · Python
CadetX logo CadetX CX Learn
ML-101 · Module 10 Intermediate about 30 minutes 9 Lessons Prereq: Modules 1 to 5

Naive Bayes

Naive Bayes classifies things by weighing up evidence, one clue at a time, using simple probability. It's the algorithm behind the first successful email spam filters, and it's still one of the fastest and most useful tools for working with text.

  • Level: Intermediate
  • Time: about 30 minutes
  • Needs: Modules 1 to 5

By the end of this module you will be able to

  • Explain conditional probability in plain English
  • Use Bayes' theorem to update a belief when new evidence arrives
  • Explain why Naive Bayes is called "naive"
  • Combine several clues to classify a message as spam or not
  • Explain the zero-frequency problem and Laplace smoothing
  • Choose between Multinomial, Bernoulli and Gaussian Naive Bayes
  • Build a text spam filter in scikit-learn

10.1What is Naive Bayes?

Think about how you decide if an email is spam. You notice clues: it says "WINNER", it's asking you to "click now", and it offers something "free". Each clue on its own isn't proof, but each one makes spam more likely. Put enough clues together and you're pretty sure.

Naive Bayes does exactly this, using probability. It starts with a basic belief (for example, "30% of emails are spam"), then updates that belief with each clue it finds.

Naive Bayes in one sentence

Naive Bayes starts with how common each class is, then uses Bayes' theorem to update the probability of each class with every piece of evidence, and picks the most likely one.

It's named after Thomas Bayes, an English minister and mathematician from the 1700s whose theorem sits at its heart. The "naive" part is explained in lesson 10.4.

10.2A quick guide to probability

You only need two ideas for this module.

IdeaWritten asPlain EnglishExample
ProbabilityP(spam)How likely something is, from 0 (never) to 1 (certain)30% of all emails are spam: P(spam) = 0.3
Conditional probabilityP(A | B)How likely A is, given that we know B is true. The "|" means "given"P(contains "free" | spam) = 0.6: 60% of spam emails contain "free"

Order matters

P("free" | spam) and P(spam | "free") are completely different questions. The first asks "of all spam emails, how many say free?" The second asks "of all emails that say free, how many are spam?" Mixing them up is one of the most common mistakes in statistics, and Bayes' theorem is how you get from one to the other.

10.3Bayes' theorem

Here's what we know about a company's inbox:

  • 30% of emails are spam.
  • 60% of spam emails contain the word "free".
  • Only 5% of normal emails contain the word "free".

A new email arrives containing "free". How likely is it to be spam? The easiest way to see the answer is to imagine 1,000 emails:

A tree of 1,000 emails. 300 are spam, and 180 of those contain the word free. 700 are normal, and 35 of those contain free. So of the 215 emails containing free, 180, or 84 percent, are spam.1,000 emailsall incoming300 spam30%700 normal70%180contain "free"120no "free"35contain "free"665no "free"Emails with "free": 180 spam out of 215 = 84%
Figure 1. Of 1,000 emails, 215 contain "free": 180 spam and 35 normal. So if an email contains "free", there's an 84% chance it's spam. Seeing the word raised our belief from 30% to 84%.

That's Bayes' theorem in action. Written as a formula, it looks like this:

P(spam | "free") = P("free" | spam) × P(spam) ÷ P("free")
PartNameIn our example
P(spam)Prior: what we believed before seeing the evidence0.30
P("free" | spam)Likelihood: how common the evidence is in that class0.60
P("free")Evidence: how common the clue is overall215 ÷ 1,000 = 0.215
P(spam | "free")Posterior: our updated belief after the evidence0.60 × 0.30 ÷ 0.215 = 0.84

Why the prior matters so much

Here's a famous example that surprises almost everyone. A rare illness affects 1 in 100 people. A test for it catches 90% of people who are ill, but also wrongly flags 9% of healthy people. You test positive. What's the chance you're actually ill?

1,000 people. 10 have the illness: 9 test positive and 1 tests negative. Of the 990 healthy people, 89 wrongly test positive. So only 9 of the 98 positive results are really ill.Ill, test positive (9)Healthy, test positive (89)Ill, test negative (1)Positive results: 98. Actually ill: 9. Chance of being ill: about 9%
Figure 2. 1,000 people, one square each. Only 10 are ill, and 9 of them test positive (red). But 89 of the 990 healthy people also test positive (amber). Of the 98 positive results, only 9 are really ill: about 9%, not 90%.

Most people guess 90%. The real answer is about 9%, because the illness is so rare (a low prior) that false alarms from the huge healthy group outnumber the true cases. This is why doctors usually repeat a test before making a diagnosis, and why Naive Bayes always starts from the prior.

10.4Why "naive"? Combining many clues

Real emails contain many words, not just one. To combine them, Naive Bayes makes a big simplifying assumption: every clue is independent of the others. It assumes that seeing "free" tells you nothing about whether you'll also see "winner", once you know whether the email is spam.

That's clearly not true in real life ("click" and "here" often appear together). That's why it's called naive. But the assumption makes the maths very simple: you can just multiply the evidence from each word together.

Score(spam) = P(spam) × P(word₁ | spam) × P(word₂ | spam) × …

Do the same for "not spam", compare the two scores, and pick the bigger one. That's the whole algorithm.

Like a detective adding up clues

A detective might think: "Muddy boots make the gardener more likely. A torn glove makes him more likely still. But an alibi makes him much less likely." Each clue nudges the odds up or down. Naive Bayes treats each clue separately and adds up their nudges, without worrying about how the clues relate to each other.

Try it yourself with a spam filter that has learned how often eight words appear in spam and normal emails.

Try it: build an email, word by word

Tap words to add them to the email. The filter starts from the prior: 30% of emails are spam.

Probability of spam
WordIn spamIn normalEffect on the odds

Notice how a word like "meeting", which is much more common in normal email, can pull a spammy-looking message back down. Each word multiplies the odds by how much more common it is in spam than in normal email.

Surprisingly, naive works

Even though the independence assumption is wrong, Naive Bayes often classifies very well. It only needs to get the ranking right (spam scores higher than normal), not the exact probabilities. One side effect: its probabilities tend to be overconfident, often very close to 0% or 100%, so don't trust them as exact numbers.

10.5The zero-frequency problem

Imagine the word "voucher" never appeared in any normal email in the training data. Then P("voucher" | normal) = 0. Because Naive Bayes multiplies everything, a single zero makes the whole score zero. One word would make an email "impossible" to be normal, however many other normal words it contains.

The fix is Laplace smoothing (also called add-one smoothing). We pretend we've seen every word at least once in every class, by adding 1 to every count.

WordCount in 100 normal emailsWithout smoothingWith smoothing (+1)
"voucher"00 ÷ 100 = 0 (kills the score)1 ÷ 102 ≈ 0.01 (small, but not zero)
"meeting"2020 ÷ 100 = 0.2021 ÷ 102 ≈ 0.21

In scikit-learn, this is the alpha setting: alpha=1.0 is classic Laplace smoothing (the default). Smaller values smooth less. It's another hyperparameter you can tune with cross-validation.

10.6Three types of Naive Bayes

The idea is always the same; what changes is how the likelihood of each feature is worked out.

TypeFeaturesTypical usescikit-learn
MultinomialCounts: how many times each word appearsText classification: spam, topics, sentimentMultinomialNB
BernoulliYes/no: does each word appear at all?Short texts where presence matters more than countBernoulliNB
GaussianContinuous numbers, like height or priceNumeric data, quick baselinesGaussianNB

How Gaussian Naive Bayes handles numbers

You can't count how often "a radius of 15.53" appears. Instead, Gaussian Naive Bayes assumes each feature follows a bell curve (a normal distribution) within each class. It learns the average and spread of each curve, then checks how high each curve is at the new value.

Two bell curves of tumour radius. Benign tumours centre around 12, malignant tumours around 17.5. For a new tumour with radius 15, the malignant curve is higher, so it is more likely malignant.51015202530Average tumour radiusBenignMalignantNew tumour: 15.5
Figure 3. Bell curves of tumour size for benign (blue) and malignant (red) tumours. For a new tumour of 15.5, the red curve is almost three times higher than the blue one, so this clue points towards malignant. Gaussian Naive Bayes does this for every feature and multiplies the results.

10.7Naive Bayes in Python

This example has two parts. First, a tiny spam filter trained on 14 text messages. Second, Gaussian Naive Bayes on the real breast cancer dataset from Module 5, compared with logistic regression.

For text, we use CountVectorizer, which turns each message into word counts: one column for every word it has seen. This is called a bag of words, because it keeps the words but throws away their order, like tipping them into a bag.

naive_bayes.py
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB, GaussianNB
from sklearn.pipeline import Pipeline
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

# ---------- Part 1: a text spam filter ----------
messages = [
    "WINNER! Claim your free prize now, click here",
    "Free entry to win a luxury holiday, reply WIN",
    "URGENT: your account is locked, click to verify",
    "You have won a £500 voucher, claim it free today",
    "Cheap loans approved instantly, click now",
    "Congratulations, you are our lucky winner, call now",
    "Are we still on for lunch tomorrow?",
    "Please find the invoice attached for last month",
    "Can you send me the slides before the meeting?",
    "Running 10 minutes late, see you at the station",
    "Happy birthday! Hope you have a lovely day",
    "The meeting has moved to 3pm in room 2",
    "Thanks for your help with the report today",
    "Don't forget to bring the charger tonight",
]
labels = ["spam"] * 6 + ["ham"] * 8     # "ham" = a normal message

spam_filter = Pipeline([
    ("count", CountVectorizer()),          # turn text into word counts
    ("nb", MultinomialNB(alpha=1.0)),      # alpha=1 is Laplace smoothing
])
spam_filter.fit(messages, labels)

new = ["Click now to claim your free holiday",
       "Can we move lunch to 1pm?",
       "Urgent: invoice for the meeting room"]
for text, probs in zip(new, spam_filter.predict_proba(new)):
    spam_prob = probs[list(spam_filter.classes_).index("spam")]
    print(f"{spam_prob:6.1%} spam | {text}")

# ---------- Part 2: Gaussian NB on number features ----------
print()
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=42, stratify=y)

for name, model in [("Gaussian NB", GaussianNB()),
                    ("Logistic regression", Pipeline([
                        ("scale", StandardScaler()),
                        ("lr", LogisticRegression(max_iter=1000))]))]:
    model.fit(X_train, y_train)
    print(f"{name:20} accuracy={model.score(X_test, y_test):.3f}")
Output
 99.9% spam | Click now to claim your free holiday
 13.1% spam | Can we move lunch to 1pm?
  0.8% spam | Urgent: invoice for the meeting room

Gaussian NB          accuracy=0.937
Logistic regression  accuracy=0.986

What the output tells us

  • "Click now to claim your free holiday" is 99.9% spam. Every one of its key words appeared in the spam examples. Notice how confident it is: that's the overconfidence mentioned earlier.
  • "Can we move lunch to 1pm?" is only 13.1% spam. "Lunch" appeared in a normal message.
  • "Urgent: invoice for the meeting room" is just 0.8% spam. "Urgent" is a spam word, but "invoice", "meeting" and "room" all point strongly the other way, and three clues beat one.
  • With only 14 training messages, this filter is a toy. Real spam filters learn from millions of emails, but the code is exactly the same.
  • On the cancer data, Gaussian Naive Bayes reaches 93.7%, while logistic regression reaches 98.6%. Naive Bayes is a quick, decent baseline, but here its "independent features" assumption hurts, because many tumour measurements (radius, perimeter, area) are closely linked.

Why Naive Bayes loves text

Text has thousands of features (one per word) and relatively few examples. Many algorithms struggle with that, but Naive Bayes just counts words, so it trains in a blink and still performs well. That's why it's a go-to first model for any text problem.

10.8Strengths, weaknesses and real-world uses

StrengthsWeaknesses
Extremely fast to train and predict, even with millions of rowsThe independence assumption is almost never true, which limits accuracy
Works well with little dataProbabilities are overconfident; don't trust them as exact numbers
Handles thousands of features easily (great for text)Usually beaten by ensembles or SVMs when there's plenty of data
Easy to explain: "these words pushed it towards spam"Gaussian version assumes bell-shaped features
Can learn on the fly, updating counts as new data arrivesNeeds smoothing to avoid the zero-frequency problem
UseHow Naive Bayes helps
Spam filteringThe classic use: weighing up the words in each email
Sentiment analysisIs a product review or tweet positive or negative?
Sorting support ticketsRouting messages to billing, technical or sales teams by their words
News categorisationTagging articles as sport, politics, business or technology
Medical screeningQuick first-pass risk estimates from symptoms

SummaryKey takeaways

  • Naive Bayes classifies by updating probabilities with each piece of evidence.
  • P(A | B) means "the probability of A, given B". P(word | spam) and P(spam | word) are different.
  • Bayes' theorem: posterior = likelihood × prior ÷ evidence. It turns what we know into what we want to know.
  • The prior matters: a rare event stays fairly unlikely even after a positive test.
  • It's "naive" because it assumes every feature is independent, so it can simply multiply the evidence.
  • Laplace smoothing (alpha) stops a single unseen word from zeroing out a score.
  • Use Multinomial for word counts, Bernoulli for yes/no features and Gaussian for numbers.
  • It's very fast and great for text, but its probabilities are overconfident and it's often beaten on numeric data.

Check your understanding

Your machine learning roadmap

  1. 01
    Introduction to Machine LearningWhat ML is and how it works
  2. 02
    Preparing Data for Machine LearningFeatures, encoding, scaling, train/test split
  3. 03
    Linear RegressionPredicting numbers
  4. 04
    Logistic RegressionPredicting yes or no
  5. 05
    Evaluating ModelsAccuracy, precision, recall, overfitting, cross-validation
  6. 06
    K-Nearest NeighboursLearning from similar examples
  7. 07
    Decision TreesFlowcharts that learn
  8. 08
    Random Forest and Gradient BoostingMany models working together
  9. 09
    Support Vector MachinesFinding the best boundary
  10. 10
    Naive BayesYou are here
  11. 11
    Clustering with K-MeansFinding groups without labels
  12. 12
    Dimensionality Reduction with PCASimplifying big datasets
  13. 13
    Capstone ProjectBuild and present a full ML project