Is This Spam?
A filter that has never understood a sentence, deciding whether to trust one, by counting words and multiplying its guesses.
Is this message spam?
Type a message, or pick one below. The filter has never read a sentence in its life, it only knows how often each word turned up in junk versus in real mail. Each word slides the verdict a little, right toward spam or left toward safe, and the running total decides.
Four words, every one of them a spam flag. No single word convicts, but four in a row leave no doubt.
Add the word votes to the starting line and read the sum. Positive means spam-leaning, negative means safe, and the further from zero, the more sure. Turn that total back into a probability with P = odds / (1 + odds) and you have your answer. No word decides alone; the verdict is a running total, exactly the odds-form update from the base-rate drop, now run once per word.
Where each word's vote comes from
Nobody hand-picked these weights. The filter counted how many times each word appeared across 12 junk and 12 real messages (83 and 85 words in all, a vocabulary of 100), and a word’s vote is just the log of a ratio: how much more often it shows up in spam than in ham.
counts read spam/ham, then the word’s log-odds vote
A word that never appears in ham would divide by zero and shout “infinitely spammy,” which is a lie the moment one real message uses it. So every count gets a +1 before dividing (Laplace smoothing): unseen words start at a whisper, not a scream. Notice the strongest ham word is the, a word with no meaning at all. The filter has no idea what any of this means. It is counting, nothing more.
The word it should have heard once
Naive Bayes assumes every word is independent of the others, that seeing free tells you nothing about whether the next word is also free. That is plainly false, and you can watch it break: repeat one word and the filter counts it as fresh evidence every single time.
One free is a fair signal. But free free free is not three independent hints, it is one word said loudly, and the honest reading of its evidence is barely more than a single free. The filter cannot tell the difference, so its confidence runs away. Real filters blunt this by counting each word once per message(presence, not tally) or by capping how far any one token can push, small patches on a wrong but useful assumption. That is the whole trick: Paul Graham’s 2002 essay A Plan for Spamput exactly this counting into the world’s inboxes, and for a decade it held the line.
Every weight on this page is trained live in your browser from the 24 example messages, no model is shipped, nothing is stored. Naive Bayes is wrong on purpose: it pretends the words of a sentence are independent, which they never are. It is also good enough that it filtered the world's email for a decade.
Somewhere between your outbox and someone else's inbox, a small program reads a message it will never understand and makes a decision anyway: junk, or keep. It does not parse grammar. It does not know what a sentence is. It has one skill, counting how often words turn up in mail people wanted versus mail they didn't, and from that alone it sorts the flood well enough that you forgot the flood was ever there.
That program is almost always some descendant of naive Bayes, and it is the plainest possible answer to a question the base-rate drop left open. There, a single positive test barely moved you off a rare prior, and the fix was to stack independent tests: each one multiplies your odds by its likelihood ratio, and enough of them haul you across the line. A spam filter is that idea taken to its limit. Each word is a test. A message is hundreds of weak tests fired at once. Multiply.
Every word is a weak witness
Pick a word, say free. In the sample of junk above it shows up constantly; in real mail almost never. So the word is a witness: it testifies "spam," but quietly, because plenty of honest messages could say it too. Give it a number. Ask how much more likely the word is in spam than in ham, and take the logarithm so that stacking evidence becomes adding instead of multiplying:
vote(word) = ln( P(word | spam) / P(word | ham) )
A word commoner in junk gets a positive vote; a word commoner in real mail gets a
negative one; a word that splits evenly sits near zero and shrugs. To judge a
whole message, start from the prior (how common spam is at all) and add up the
votes of its words. The sum is the log-odds that the message is spam, and
P = odds / (1 + odds) turns it back into a percent. That is the entire
classifier. The panel above is running it live: type anything and watch each
word slide the verdict, right toward junk or left toward safe.
In our 24-message corpus the loudest witnesses come out as you'd guess. free and now each vote +1.96; click votes +1.80; claim, +1.62. Every one of them appears several times in the junk pile and never in the real one. Their opposites are the vocabulary of a working day, meeting and tomorrow each vote -1.38, report votes -1.09.
No single word convicts
Here is the payoff, and it is the same lesson the base-rate drop taught about
tests: one witness rarely settles anything. Feed the filter free money click now and it lands at 99.9% spam, but not because any word was decisive, it is
four positive votes piled on top of each other. Feed it a coworker's are we still on for the meeting tomorrow and it reads essentially 0%, eight ordinary
words all pulling the same way.
The interesting case is the fight. free meeting tomorrow opens with free at
+1.96, a strong shove toward the junk folder. Then meeting (-1.38) and
tomorrow (-1.38) answer, and the sum settles at -0.79: about 31%
spam. Verdict, probably fine. One loud word lost to two quiet ones, because the
filter never asks "is there a spammy word here?" It asks "which way does the
whole crowd lean?" That is why a single unlucky "free" in a real email does
not doom it, and why one careful, clean-sounding word cannot rescue a message
that is otherwise screaming.
The weights train themselves
Nothing above was hand-tuned. The second panel shows where every vote comes from: the filter counted words across 12 junk and 12 real messages (83 and 85 words, a vocabulary of 100 distinct ones), and each vote is just the log of a ratio of those counts. Give it more mail and the numbers sharpen on their own. This is the quiet reason the method spread: you do not teach it the rules of spam, it learns them from examples, and it keeps learning as the spam changes.
One booby trap has to be defused first. What is P(word | ham) for a word that
never once appeared in real mail? Zero, and dividing by zero says the word is
infinitely spammy, so a single occurrence would convict any message forever.
That is obviously wrong: absence in a tiny sample is not proof. The standard cure
is Laplace smoothing, add 1 to every count before dividing, so an unseen word
starts as a whisper instead of a scream and can be talked out of it by real
evidence later. Look up the in the panel and you'll find the punchline: the
single strongest ham signal in the whole corpus is the most meaningless word in
the language, purely because real messages happen to say it ten times and the
junk never does. The filter has no idea what the means. It is counting, and
counting is enough.
Why it is called naive
The method's own name is a confession. Naive Bayes assumes the words of a message are statistically independent, that once you know a message is spam, seeing free tells you nothing about whether the next word is money. Language does not work remotely like that; "free" and "money" travel together, "New" is usually followed by "York." The assumption is false in every real sentence.
You can watch it break. The third panel repeats one word, free, and each copy
adds another +1.96 as if it were brand-new testimony, so the confidence sprints
to certainty. But free free free is not three independent witnesses, it is one
witness shouting, and its honest evidentiary weight is barely more than a single
free. The filter cannot hear the difference. Real classifiers paper over this
by counting each word once per message (does it appear, yes or no) or by
capping how far any one token may push, small patches on a wrong but useful
assumption.
And useful is the word that matters. Wrong-but-useful is not a consolation prize here; it was the state of the art. In 2002 Paul Graham's essay A Plan for Spam put close to this exact arithmetic into the world's mail clients, and a filter that does not know what a single word means went on to sort billions of messages a day. It is the same bargain the base-rate drop struck from the other side: no lone signal is worth much, but weak evidence, added up honestly, becomes a decision you can live by. A spam filter is Bayes' theorem with the volume turned all the way up, run once per word, quietly, on everything you have ever been sent.
Every number here is recomputed in your browser from the 24 example messages, no model is downloaded and nothing is stored. The classifier was checked against a hand count of the corpus before this was written: the per-word votes, the running log-odds, and each verdict match to the digit. The independence assumption is the honest flaw, stated plainly and shown breaking, because a filter that hides its own naivety is the one you should not trust.
Sequel to the base-rate drop (#024): that stacked a few independent tests by multiplying likelihood ratios; a spam filter is the same move with hundreds of weak tests, one per word. Every weight is trained live in the browser from 24 example messages, so the page computes itself. Verified the classifier offline against a hand count before writing.