On 27 August 2002, Tim Peters said:
Setting this up has been a bitch. All early attempts floundered because it turned out there was *some* systematic difference between the ham and spam archives that made the job trivial.
The ham archive: I selected 20,000 messages, and broke them into 5 sets of 4,000 each, at random, from a python-list archive Barry put together, containing msgs only after SpamAssassin was put into play on python.org. It's hoped that's pretty clean, but nobody checked all ~= 160,000+ msgs. As will be seen below, it's not clean enough.
One of the other perennial-seeming topics on spamassassin-devel (a list that I follow only sporodically) is that careful manual cleaning of your corpus is *essential*. The concern of the main SA developers is that spam in your non-spam folder (and vice-versa) will prejudice the genetic algorithm that evolves SA's scores in the wrong direction. Gut instinct tells me the Bayesian approach ought to be more robust against this sort of thing, but even it must have a breaking point at which misclassified messages throw off the probabilities.
But that's entirely consistent with your statement:
Another lesson reinforces one from my previous life in speech recognition: rigorous data collection, cleaning, tagging and maintenance is crucial when working with statisical approaches, and is damned expensive to do.
On corpus collection...
The spam archive: This is essentially all of Bruce Guenter's 2002 spam collection, at http://www.em.ca/~bruceg/spam/. It was broken at random into 5 sets of 2,750 spams each.
One possibility occurs to me: we could build our own corpus by collecting spam on python.org for a few weeks. Here's a rough breakdown of mail rejected by mail.python.org over the last 10 days, eyeball-estimated messages per day:
bad RCPT 150 - 300  bad sender 50 - 190  relay denied 20 - 180  known spammer addr/domain 15 - 60 8-bit chars in subject 130 - 200 8-bit chars in header addrs 10 - 60 banned charset in subject 5 - 50  "ADV" in subject 0 - 5 no Message-Id header 100 - 400  invalid header address syntax 5 - 50  no valid senders in header 10 - 15  rejected by SpamAssassin 20 - 50  quarantined by SpamAssassin 5 - 50 
 this includes mail accidentally sent to eg. firstname.lastname@example.org, but based on scanning the reject logs, I'd say the vast majority is spam. However, such messages are rejected after RCPT TO, so we never see the message itself. Most of the bad recipient addrs are either ancient (email@example.com, firstname.lastname@example.org) or fictitious (email@example.com, firstname.lastname@example.org).
 sender verification failed, eg. someone tried to claim an envelope sender like email@example.com. Usually spam, but innocent bystanders can be hit by DNS servers suddenly exploding (hello, comcast.net). This only includes hard failures (DNS "no such domain"), not soft failures (DNS timeout).
 I'd be leery of accepting mail that's trying to hijack mail.python.org as an open relay, even though that would be a goldmine of spam. (OTOH, we could reject after the DATA command, and save the message anyways.)
 mail.python.org rejects any message with a properly MIME-encoded subject using any of the following charsets: big5, euc-kr, gb2312, ks_c_5601-1987
 includes viruses as well as spam (and no doubt some innocent false positives, although I have added exemptions for the MUA/MTA combinations that most commonly result in legit mail reaching mail.python.org without a Message-Id header, eg. KMail/qmail)
 eg. "To: all my friends" or "From: <>"
 no valid sender address in any header line -- eg. someone gives a valid MAIL FROM address, but then puts "From: firstname.lastname@example.org" in the headers. Easily defeated with a "Sender" or "Reply-to" header.
 any message scoring >= 10.0 is rejected at SMTP time; any message scoring >= 5.0 but < 10 is saved in /var/mail/spam for later review
* it's a good thing we do all those easy checks before involving SA, or the load on the server would be a lot higher
* give me 10 days of spam-harvesting, and I can equal Bruce Guenter's spam archive for 2002. (Of course, it'll take a couple of days to set the mail server up for the harvesting, and a couple more days to clean through the ~2000 caught messages, but you get the idea.)
Mailman added distinctive headers to every message in the ham archive, which appear nowhere in the spam archive. A Bayesian classifier picks up on that immediately.
Mailman also adds "[name-of-list]" to every Subject line.
Perhaps that spam-harvesting run should also set aside a random selection of apparently-non-spam messages received at the same time. Then you'd have a corpus of mail sent to the same server, more-or-less to the same addresses, over the same period of time.
Oh, any custom corpus should also include the ~300 false positives and ~600 false negatives gathered since SA started running on mail.python.org in April.