A Naive Bayes spam filter

Build the lesson's Naive Bayes spam filter and use it on new messages.

For a message and a class (spam or ham), the score is

log P(class) + Σ over the message's words of log((count(word, class) + 1) / (total words in class + V))

where P(class) is the share of training messages in that class, count(word, class) is how often the word appears in that class's training messages, and V is the number of different words in all the training messages. Words are compared exactly (they are all lower case). Predict the class with the higher score; if the scores are equal, predict ham.

Input

The first line has t (2 ≤ t ≤ 100). Each of the next t lines is a training message: its label, spam or ham, then its words. Both labels appear. Then a line with q (1 ≤ q ≤ 50) and q lines, each a message to classify (one or more words).

Output

spam or ham for each message, one per line.

Examples

Example 1 · Input
6
spam win money now
spam free money offer
spam win a free prize
ham meeting at noon
ham project meeting tomorrow
ham lunch at noon tomorrow
2
free money
meeting tomorrow at noon
Expected output
spam
ham
Example 2 · Input
3
spam buy now
ham see you now
ham call me
3
buy
now
call now
Expected output
spam
ham
ham

Prompts inside input() are ignored when checking, so input("n: ") is fine.

Hints

Hint 1

Count, for each class, how many messages it has and how often each word appears in it.

Hint 2

score(class) = log P(class) + Σ log((count(word, class) + 1) / (words in class + V)), where V is the number of different words in all the training messages.

Hint 3

Choose spam only if its score is strictly bigger than ham's.

Solution

Show one correct solution (try it yourself first)
from math import log

t = int(input())
counts = {"spam": {}, "ham": {}}
messages = {"spam": 0, "ham": 0}
for _ in range(t):
    label, *words = input().split()
    messages[label] += 1
    for word in words:
        counts[label][word] = counts[label].get(word, 0) + 1
vocabulary = {w for c in counts.values() for w in c}

def score(words, label):
    total = sum(counts[label].values())
    s = log(messages[label] / t)
    for word in words:
        s += log((counts[label].get(word, 0) + 1) / (total + len(vocabulary)))
    return s

for _ in range(int(input())):
    words = input().split()
    print("spam" if score(words, "spam") > score(words, "ham") else "ham")

Lesson: Bayes' rule and Naive Bayes →