Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Classifying text with word counts

After this lesson: I can classify a text with word counts.

60 min lesson2 commandsJupyterLab data science lab: pandas, Matplotlib and scikit-learn in the browser

Sorting messages into spam and not spam

Text classification means giving a text a label, such as spam or work, from the words it contains. A naive Bayes classifier does it with nothing more than word counts and a little probability.

It is called naive because it treats every word as if it were unrelated to the others. That is not true, and it still works well enough to filter email.

By the end of this lesson

Note

I can classify a text with word counts.

Key words

WordWhat it meansExample
Labelthe class a text belongs tospam
Training settexts whose labels are already known
Priorhow common a label is before you read the text
Smoothingadding 1 to every count so no word has chance 0
Log scorethe logarithms of the chances added together

The cells students fill in are left blank in this preview.

A classifier learns from labelled texts

You need a training set: texts that people have already labelled. The program counts how often each word appears under each label.

Words such as "free", "prize" and "click" will be common under spam. Words such as "meeting" and "report" will be common under work.

Count the words under each label

Code: python
messages = ["win a free prize now", "free voucher click now", "meeting moved to monday"]
labels = ["spam", "spam", "work"]
counts = {"spam": {}, "work": {}}
for n in range(len(messages)):
    for word in messages[n].split():
        table = counts[labels[n]]
        if word in table:
            table[word] = table[word] + 1
        else:
            table[word] = 1
print(counts["spam"])
# Add "free lunch at noon" as work. Does "free" appear under work now?

Count the messages that contain a word

Command
docker exec jupyter sh -c "printf 'win a free prize now\nfree voucher click now\nmeeting moved to monday\n' | grep -c free"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

A word has a chance under each label

The chance of a word under a label is its count there, divided by all the tokens under that label. A word never seen under a label would get 0, and one zero would wipe out the whole answer.

Smoothing fixes that: add 1 to every count, and add the size of the vocabulary to the total.

Work out a smoothed chance

Code: python
import math

spam_free = 3        # "free" appears 3 times in the spam messages
spam_total = 13      # the spam messages hold 13 tokens in all
vocabulary = 27      # 27 different words in all the messages
chance = (spam_free + 1) / (spam_total + vocabulary)
print(round(chance, 3))
print(round(math.log(chance), 3))
# Use 0 for spam_free. Is the chance still above 0?

Worked example: two words, two labels

WordCount in spamChance if spamCount in workChance if work
free34 / 40 = 0.101 / 48 = 0.021
report01 / 40 = 0.02523 / 48 = 0.0625

The cells students fill in are left blank in this preview.

Add logarithms to score each label

To score a text under a label, start with the prior: the share of training texts with that label. Then multiply by the chance of each word. Many small chances multiplied make a tiny number.

So take the logarithm of each chance and add them. That is the log score. The label with the highest score wins.

Take the log of two chances

Command
docker exec jupyter python3 -c "import math; print(round(math.log(3 / 7), 3), round(math.log(4 / 40), 3))"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Worked example: scoring "free prize"

LabelPriorfreeprizeLog score
spamlog(3/7) = -0.847log(0.1) = -2.303log(0.075) = -2.590-5.740
worklog(4/7) = -0.560log(1/48) = -3.871log(1/48) = -3.871-8.302

The cells students fill in are left blank in this preview.

A common mistake

Warning

Many people think the lowest score wins because the numbers are negative. A log of a chance below 1 is always negative. The score closest to zero is the highest, so -5.7 beats -8.3, and that label wins.

What a word-count classifier cannot do

It ignores order, so "not free" looks like "free". It only knows the words in its training set, so a new scam wording may slip through.

Judge it on messages it has not seen, and compare it with a baseline. A filter that always says "work" is right most of the time if most mail is work.

About this lab and its source: JupyterLab data science lab: pandas, Matplotlib and scikit-learn in the browser

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.