Lesson 5 of 11

Probability and Bayes' rule

How likely is something, given what you just saw? Learn conditional probability, why base rates matter, and how a naive Bayes spam filter adds up evidence word by word.

Beginner18 min

Builds on Turning things into numbers (AI from zero)

In this lesson you will

  • Read a probability as a share of cases, and a conditional probability as a share within a group
  • Update a belief after new evidence with Bayes' rule, using counts or odds
  • Explain why a positive result for a rare condition is often a false alarm, even from an accurate test
  • Describe how naive Bayes classifies a message and what its independence assumption costs

A screening test for a disease is right about 90% of the time. Your result comes back positive. How worried should you be? A common answer is “about 90%”. For a rare condition the true answer can be closer to 9%. By the end of this lesson you will see exactly why, and you will have used the same reasoning to build a spam filter.

Probability as a share of cases

A ProbabilityA number from 0 to 1 saying how likely something is. One way to read it: out of many similar cases, the share in which it happens.Open in glossary is a number from 0 to 1 that says how likely something is. One useful way to read it: imagine many cases and count. “The chance of rain is 30%” means that on days like this one, it rains on about 30 out of 100.

The interesting questions are usually narrower: how likely is A among the cases where B happened? That is a Conditional probabilityThe probability of A given that B happened, written P(A | B): the share of B cases that are also A. P(A | B) and P(B | A) are usually different numbers.Open in glossary, written P(A∣B)P(A \mid B) and read “the probability of A given B”. It is the share of B cases that are also A:

P(A∣B)=P(A and B)P(B)P(A \mid B) = \frac{P(A \text{ and } B)}{P(B)}

The order matters. P(positive∣sick)P(\text{positive} \mid \text{sick}) asks: among sick people, what share test positive? P(sick∣positive)P(\text{sick} \mid \text{positive}) asks: among people who test positive, what share are sick? The first describes the test. The second is what the patient wants to know. They can be wildly different.

A test for a rare condition

Three numbers describe the situation:

  • The Base rateHow common something is in the population before any evidence, such as the share of people who have a condition. Ignoring it makes rare events look far more likely after a positive test than they are.Open in glossary: how common the condition is. Here 1% of people have it.
  • Sensitivity: the share of sick people the test catches, P(positive∣sick)P(\text{positive} \mid \text{sick}). Here 90%.
  • Specificity: the share of healthy people the test correctly clears, P(negative∣healthy)P(\text{negative} \mid \text{healthy}). Here 91%, so 9% of healthy people get a false alarm.

Now count. Of 1000 people, 10 are sick and 9 of them test positive. Of the 990 healthy people, 9% is about 89 who also test positive. Everyone with a positive result is in a group of 98, and only 9 of them are sick: about 9.2%.

Base rates

A thousand people take a test for a condition. Filled marks tested positive; faded marks tested negative.

  • Sick (10)
  • Healthy (990)

Everyone who tested positive: 98 people

9 sick 89 healthy

Counts are rounded to whole people; the percentages use exact probabilities.

1%
90%
91%
1
Chance a positive is really sick9.2%
Chance before any test1%
A positive multiplies the odds by10

Challenges

0 of 3 complete

  1. Keep the starting 1% rate and test. Find how many positive results in a row it takes before the person is more likely sick than not. (not yet)
  2. Make the test right at least 99% of the time for sick and for healthy people, then find a base rate where a single positive is still more likely a false alarm. (not yet)
  3. With the starting test, find the base rate at which a single positive result is a coin flip (between 48% and 52%). (not yet)

Try this

  • Before touching anything, guess the chance that someone who tests positive is sick. Then read it off the demo.
  • Drag the base rate up to 20%. The test has not changed, yet a positive result now means something very different.
  • Raise Positive results in a row to 2 and 3 with the starting settings. Each positive multiplies the odds by the same factor.
  • Set specificity to 99.9% and watch the false alarms in the grid nearly vanish.
Bayes' rule with counts and with oddsPython with no libraries
prevalence = 0.01   # 1% of people have the condition
sensitivity = 0.90  # the test catches 90% of sick people
specificity = 0.91  # and clears 91% of healthy people

# Natural frequencies: the same numbers as counts out of 1000 people.
people = 1000
sick = people * prevalence
healthy = people - sick
true_pos = sick * sensitivity
false_pos = healthy * (1 - specificity)
print(f"{true_pos:.1f} sick and {false_pos:.1f} healthy people test positive")

# Bayes' rule: the share of positives who are really sick.
print(f"P(sick | positive) = {true_pos / (true_pos + false_pos):.3f}")

# Odds form: each positive multiplies the odds by the likelihood ratio
# (assuming repeat tests make independent mistakes).
odds = prevalence / (1 - prevalence)
ratio = sensitivity / (1 - specificity)
for n in range(1, 4):
    odds *= ratio
    print(f"positives in a row: {n}, P(sick) = {odds / (1 + odds):.3f}")
9.0 sick and 89.1 healthy people test positive
P(sick | positive) = 0.092
positives in a row: 1, P(sick) = 0.092
positives in a row: 2, P(sick) = 0.503
positives in a row: 3, P(sick) = 0.910

The false alarms win because there are so many more healthy people to make mistakes on. A small error rate applied to 990 people produces more positives than a large hit rate applied to 10. Ignoring this, and judging only by how accurate the test is, is called base rate neglect.

Counting whole people, as the grid does, makes this much easier to see than percentages. Psychologists Gerd Gigerenzer and Ulrich Hoffrage found that people solve problems like this far more often when the numbers are given as counts (“9 of 10 sick people test positive”) than as probabilities (Psychological Review, 1995).

You will meet P(sick∣positive)P(\text{sick} \mid \text{positive}) again in Measuring a classifier, where it is called precision: of the cases flagged, how many were real.

Bayes’ rule

The counting you just did has a name. Bayes' ruleThe rule for turning P(evidence | hypothesis) into P(hypothesis | evidence): multiply by the prior and divide by the overall chance of the evidence.Open in glossary turns a probability in one direction into the other:

P(H∣E)=P(E∣H) P(H)P(E)P(H \mid E) = \frac{P(E \mid H)\,P(H)}{P(E)}

Here HH is a hypothesis (sick) and EE is the evidence (a positive test). Each piece has a name:

  • P(H)P(H) is the PriorWhat you believe about a hypothesis before seeing the evidence, as a probability. Bayes' rule combines it with the likelihood to give the posterior.Open in glossary: what you believed before the evidence, here the base rate.
  • P(E∣H)P(E \mid H) is the LikelihoodHow probable the observed evidence is if a hypothesis is true, P(evidence | hypothesis). Comparing it across hypotheses (the likelihood ratio) says how strongly the evidence favors one over another.Open in glossary: how expected the evidence is if the hypothesis is true, here the sensitivity.
  • P(H∣E)P(H \mid E) is the PosteriorWhat you should believe about a hypothesis after seeing the evidence, P(hypothesis | evidence), computed from the prior and the likelihood by Bayes' rule.Open in glossary: what you should believe after seeing the evidence.
  • P(E)P(E) is the overall chance of the evidence, counting both ways it can happen: 0.9×0.01+0.09×0.99≈0.0980.9 \times 0.01 + 0.09 \times 0.99 \approx 0.098.

Plugging in gives 0.9×0.01/0.098≈0.0920.9 \times 0.01 / 0.098 \approx 0.092, the same 9.2% the counts gave. The rule is named after Thomas Bayes, an English minister whose essay on the problem was published in 1763, two years after his death, by his friend Richard Price.

Adding up many pieces of evidence: naive Bayes

A spam filter faces the same question with more evidence. Given the words in a message, how likely is it to be spam? Bayes’ rule says

P(spam∣words)∝P(spam)×P(words∣spam)P(\text{spam} \mid \text{words}) \propto P(\text{spam}) \times P(\text{words} \mid \text{spam})

where ∝\propto means “proportional to”: compute the same product for “not spam”, then scale the two so they add to 1. The hard part is P(words∣spam)P(\text{words} \mid \text{spam}): no training set contains every possible message. Naive BayesA classifier that applies Bayes' rule assuming the features are independent once the class is known, so each word (or feature) adds its own evidence. Simple and fast, but its probabilities tend to be overconfident.Open in glossary makes a bold simplification. It assumes that, once you know the class, each word appears independently of the others, so the probability of the message is a product of one probability per word:

P(words∣spam)≈∏wP(w∣spam)P(\text{words} \mid \text{spam}) \approx \prod_{w} P(w \mid \text{spam})

Each P(w∣spam)P(w \mid \text{spam}) is easy to estimate: the share of all words in the spam training messages that are ww. To avoid zeros for words a class has never seen, every count gets a small extra amount α\alpha, called SmoothingAdding a small count to every possible word or value before turning counts into probabilities, so nothing unseen gets probability zero. Adding exactly 1 is called Laplace smoothing.Open in glossary:

P(w∣spam)=count of w in spam+αwords in spam+α×vocabulary sizeP(w \mid \text{spam}) = \frac{\text{count of } w \text{ in spam} + \alpha}{\text{words in spam} + \alpha \times \text{vocabulary size}}

Multiplying many small numbers is awkward, so the filter works with logarithms, which turn the product into a sum. Each word contributes its log ratio, ln⁡(P(w∣spam)/P(w∣not spam))\ln\big(P(w \mid \text{spam}) / P(w \mid \text{not spam})\big), and the prior contributes ln⁡(P(spam)/P(not spam))\ln\big(P(\text{spam}) / P(\text{not spam})\big). Positive totals point to spam, negative to not spam. That is exactly the odds form of Bayes’ rule, applied once per word.

Naive Bayes spam filter

Each word adds evidence: bars to the right push toward spam, bars to the left toward not spam. The total, plus the prior, gives the probability.

EvidenceSpamNot spamDirectionLog ratio
Prior46−0.41
free31+0.95
lunch03−1.13
today12−0.14
see02−0.84
you13−0.43
there01−0.43
  • Pushes toward not spam
  • Pushes toward spam
Training messages (10)
  • spam Win a free prize now
  • spam Claim your free cash prize today
  • spam Click here to win cash now
  • spam You have won a free phone, click now
  • not spam Are we still on for lunch today?
  • not spam Can you send me the notes from class
  • not spam Running late, see you at the meeting
  • not spam Did we win the game? See you there
  • not spam Meeting moved to 3, free for lunch now?
  • not spam Happy birthday! Lunch is on me today
1.0
Chance of spam8.2%
Total evidence (log-odds)−2.42
VerdictNot spam

Challenges

0 of 3 complete

  1. Make the filter more than 99% sure a message is spam using only words that also appear in normal messages. (not yet)
  2. Find a message whose verdict flips when you switch the prior on and off. (not yet)
  3. Set smoothing to 0, then write a message the filter cannot score at all. (not yet)

Try this

  • Type free free free. Each copy adds the same push toward spam.
  • Switch the prior off and on for the message you win.
  • Set smoothing to 0 and type lunch, then prize. A word seen in only one class now decides everything on its own.
  • Train it on a message of your own, then type something similar and see the new evidence.
A naive Bayes spam filter in a few linesPython with NumPy
import re
from collections import Counter

import numpy as np

spam = ["Win a free prize now", "Claim your free cash prize today",
        "Click here to win cash now", "You have won a free phone, click now"]
normal = ["Are we still on for lunch today?", "Can you send me the notes from class",
          "Running late, see you at the meeting", "Did we win the game? See you there",
          "Meeting moved to 3, free for lunch now?", "Happy birthday! Lunch is on me today"]

def words(text):
    return re.findall(r"[a-z0-9']+", text.lower())

counts = [Counter(w for m in normal for w in words(m)),
          Counter(w for m in spam for w in words(m))]
vocab = set(counts[0]) | set(counts[1])
alpha = 1.0  # smoothing: add 1 to every count

def p_spam(text, use_prior=True):
    # Start from the prior log-odds, then add one log ratio per word.
    log_odds = np.log(len(spam) / len(normal)) if use_prior else 0.0
    for w in words(text):
        if w not in vocab:
            continue  # never seen in training: skip it
        p1 = (counts[1][w] + alpha) / (sum(counts[1].values()) + alpha * len(vocab))
        p0 = (counts[0][w] + alpha) / (sum(counts[0].values()) + alpha * len(vocab))
        log_odds += np.log(p1 / p0)
    return 1 / (1 + np.exp(-log_odds))

for text in ["Free lunch today, see you there", "free now free now free now", "you win"]:
    print(f"{text!r}: {p_spam(text):.3f} (no prior: {p_spam(text, False):.3f})")
'Free lunch today, see you there': 0.082 (no prior: 0.118)
'free now free now free now': 0.995 (no prior: 0.997)
'you win': 0.457 (no prior: 0.558)

The default message, “Free lunch today, see you there”, contains free, a word with a strong spam lean. The filter still calls it 8.2% spam because lunch, see, you, and there all point the other way, and the prior adds a push of its own: 6 of the 10 training messages are not spam.

The independence assumption is clearly false: “click” and “here” travel together, and so do “see” and “you”. Naive Bayes classifies surprisingly well anyway, because it only needs the right class to come out on top, but its probabilities are often far too extreme. Repeating a word shows why: every copy is counted as new evidence. In 1998, Mehran Sahami and colleagues described a junk-mail filter built on a naive Bayesian classifier.

Key ideas

  • A conditional probability P(A∣B)P(A \mid B) is the share of B cases that are also A. Swapping A and B gives a different number.
  • When a condition is rare, false alarms from the many healthy people can outnumber true positives, even for an accurate test. Counting whole people makes this easy to see.
  • Bayes’ rule updates a prior into a posterior. In odds form, each piece of evidence multiplies the odds by its likelihood ratio.
  • Naive Bayes classifies by adding one log ratio per word to the prior, assuming words are independent given the class. It is simple and often accurate, but overconfident.
  • Smoothing keeps a word never seen in one class from zeroing out that class entirely.

Check yourself

Pick an answer to see why it is right or wrong. Nothing is graded. Your first answer is saved in this browser so the question can come back for review.

11% of people have a condition. A test catches 90% of sick people and clears 91% of healthy people. Someone tests positive. What is the chance they are sick?
2Most people with a cold have a runny nose. Does that mean most people with a runny nose have a cold?
3A naive Bayes filter sees the word "free" ten times in one message. Why does its spam probability shoot toward 100%?
4Why does a naive Bayes spam filter add a small count to every word (smoothing)?

Progress is saved in this browser only.

Up nextClassification and probability
Next
How machines learn
  1. 1The learning loop
  2. 2Linear regression and loss
  3. 3Gradient descent
  4. 4Better optimizers
  5. 5Probability and Bayes' rule
  6. 6Classification and probability
  7. 7Overfitting and generalization
  8. 8Decision trees
  9. 9Ensembles: forests and boosting
  10. 10Measuring a classifier
  11. 11Finding groups: k-means

Try "embedding", "softmax", "overfitting", or "backpropagation".