训练
内置模型开箱即用。用你自己的邮件训练的模型效果更好,因为它会学到你的 ham想要的邮件:垃圾邮件的反面。(正常邮件)是什么样子:你订阅的通讯、你同事的写作风格、你收到的语言。
训练模型#
让 train 指向垃圾邮件和正常邮件的文件夹:
spamscanner train --spam ~/Maildir/.Junk --ham ~/Maildir/cur --ham ~/Maildir/.Archive --out my-model.json来源可以是:
- mbox 文件,也可以是 gzip 压缩的(
.mbox.gz), - Maildir(读取其
cur和new文件夹,跳过tmp), - 存放
.eml文件的文件夹,递归读取, - 数据集:带有文本列和标签列的 CSV 或 JSON Lines 文件(
--dataset)。名为text、message、body、email或content的列,以及名为label、category、class、spam或is_spam的列会被自动识别;否则请使用--text-column和--label-column。可以识别spam、1、phishing以及ham、0、not_spam、legitimate等标签。
重复的邮件只计一次。要在内置模型的基础上训练而不是从空模型开始,请添加 --merge。
使用模型:
spamscanner scan message.eml --model my-model.json
SPAMSCANNER_MODEL=/var/lib/spamscanner/model.json spamscanner milterconst scanner = new SpamScanner({classifier: '/var/lib/spamscanner/model.json'});多少邮件才够:每类几百封就能得到有用的模型,几千封就能得到好的模型。让两类数量大致平衡,并把你不希望被过滤的邮件(密码重置、你自己的供应商发来的账单)放在正常邮件中。
测量效果#
留出一部分邮件不参与训练,并在其上测量:
spamscanner eval --model my-model.json --spam held-out/spam --ham held-out/ham内置模型在 21 种语言、它从未见过的短信上的结果,其中大多数语言它几乎不了解:
Messages: 13028 spam, 92406 ham
Precision: 89.72% Recall: 11.18% F1: 19.89%
False positives: 167 (0.18% of ham) False negatives: 132
Unsure: 91247 (86.54%)精确率是指它判为垃圾邮件的邮件中有多少确实是垃圾邮件;召回率是指垃圾邮件中有多少被它识别出来。这里“不确定”的邮件计为漏判的垃圾邮件,尽管在实际扫描中,其他检查和语言模型仍可能识别出它们。需要关注的数字是误报:被判为垃圾邮件的正常邮件。在上面的运行中,模型对这些邮件中的大多数是不确定,而不是判错,这正是对于它见过邮件很少的语言所预期的行为。
--json 为脚本提供同样的数字。
从举报中学习#
当用户把邮件移入或移出 Junk 文件夹时,逐封训练模型:
spamscanner learn spam message.eml --model /var/lib/spamscanner/model.json
spamscanner learn ham message.eml --model /var/lib/spamscanner/model.json第一次 learn 会基于内置模型创建该文件。通过 HTTP,HTTP API 上的 POST /learn/spam 和 /learn/ham 作用相同;使用 --allow-tell 时,spamc -L spam 也可以对 spamd 服务器使用。Dovecot 的 IMAPSieve 可以在邮件被移动时调用其中任一种。
在 Node.js 中:
await scanner.learn(raw, 'spam');
await scanner.unlearn(raw, 'spam'); // undo, before learning it as ham
scanner.saveModel('/var/lib/spamscanner/model.json');如果一封被举报为分类错误的邮件之前已被学习过,应先将它从错误的类别中撤销学习,再将它作为正确的类别学习。
内置模型#
model/classifier.json 由 npm run model:train 从 Hugging Face 上的以下公开数据集构建,均采用开放许可证:
| 数据集 | 许可证 | 内容 |
|---|---|---|
| FredZhang7/all-scam-spam | Apache-2.0 | 43 种语言的消息和电子邮件 |
| SetFit/enron_spam | 公开研究语料库 | Enron-Spam 语料库 |
| alt-gnome/telegram-spam | CC0-1.0 | 俄语 Telegram 消息 |
| tanaos/synthetic-spam-detection-dataset-german、-italian、-spanish | MIT | 合成邮件 |
它从 62,480 封垃圾邮件和 76,489 封正常邮件中学习。脚本每十封留出一封,用其余邮件训练,并只测量分类器本身,不含其他检查:
| 留出测试 | 邮件 | 精确率 | 召回率 | 误报 | 不确定 |
|---|---|---|---|---|---|
| 英语 | 6,564 | 100.0% | 97.0% | 0.0% | 2.4% |
| 俄语 | 1,682 | 100.0% | 97.4% | 0.0% | 2.2% |
| 意大利语 | 1,389 | 98.1% | 85.3% | 1.8% | 10.9% |
| 德语 | 1,309 | 97.7% | 76.1% | 2.2% | 20.7% |
| 西班牙语 | 1,281 | 97.5% | 82.5% | 2.6% | 16.8% |
| Enron-Spam | 2,888 | 100.0% | 93.1% | 0.0% | 4.5% |
| all-scam-spam | 4,236 | 100.0% | 88.8% | 0.0% | 11.2% |
| 全部 | 13,840 | 99.2% | 85.1% | 0.5% | 12.4% |
这里的垃圾邮件是指分类器概率达到 99% 或以上,即分类器单独达到垃圾邮件阈值的那一点。在实际扫描中,它不那么确定的垃圾邮件仍会得到分值,其他检查也会加上各自的分值。
德语、西班牙语和意大利语的结果来自合成数据集,其中包含几乎相同却分别被标为垃圾邮件和正常邮件的邮件:部分误差来自标签,而不是模型。使用你自己语言的邮件是最好的解决办法。包含每种语言和每个数据集的数字记录在模型的 metadata.metrics 中。
更多语言#
npm run model:train -- --with multilingual-sms 会加入 SMS Spam Multilingual Collection:机器翻译成 21 种语言的 SMS Spam Collection。它没有包含在内置模型中,因为其数据集卡片标注的是 GPL 许可证;请确认它是否适合你分享模型的方式。用它训练后,内置模型几乎不了解的语言的留出测试结果如下:
| 语言 | 邮件 | 精确率 | 召回率 | 误报 |
|---|---|---|---|---|
| 中文 | 430 | 100.0% | 82.3% | 0.0% |
| 阿拉伯语 | 430 | 100.0% | 84.6% | 0.0% |
| 韩语 | 412 | 100.0% | 80.4% | 0.0% |
| 日语 | 486 | 96.0% | 85.7% | 0.5% |
| 印地语 | 412 | 100.0% | 63.9% | 0.0% |
| 法语 | 480 | 98.6% | 94.2% | 0.6% |
| 土耳其语 | 220 | 100.0% | 73.1% | 0.0% |
重新训练#
npm run model:train # downloads the datasets into data/ the first time
npm run model:train -- --no-download # reuse data/
npm run model:train -- --out /tmp/model.json --max-features 200000模型文件#
模型是一个 JSON 文件:包含已学习的垃圾邮件和正常邮件数量,以及每个经过哈希的特征出现在多少封垃圾邮件和正常邮件中,排序后以 base64 编码。它不包含任何词语或邮件文本。--max-features 只保留最常见的特征,--min-count 丢弃罕见特征,以准确率换取体积;内置模型保留 400,000 个特征,约 6 MB。
Spam Scanner 6 及更早版本的模型无法加载:它们哈希的是不同的特征。请用同样的邮件训练一个新模型。