贝叶斯 n-gram 集成“垃圾邮件”分类器
一种使用词语和字符 n-gram 以及贝叶斯定理统计分析的弹性文本分类器
引言
没有人喜欢垃圾邮件——机器如何自动标记垃圾邮件?
一条消息如果不包含多少垃圾邮件,它就是垃圾邮件吗?
垃圾邮件需要有多少垃圾邮件词语才能被分类为垃圾邮件、垃圾邮件、垃圾邮件?
文本分类可能很困难,因为人类使用各种语言交流,使用俚语,拼写错误,而且垃圾邮件发送者会不断更新他们的方法。
背景
文本分类方法
- 关键词和短语:“赢家”、“免费”、“如在…上看到”、“翻倍你的”、“新规则”
- 不是统计学,只是搜索文本;但垃圾邮件发送者有你难以置信的技巧!
- 词语统计
英语中最常见的词是“THE”(其他最高频词是:AND I OF A IS OR AT)...- 垃圾邮件发送者可以变得有创意,拼写错误
“m0rtgage”、“F R E E”、“ca$h”、“\/laGr@”,视觉上相似的 Unicode 字符 - 垃圾邮件发送者可以粘贴大段文本(例如梅尔维尔或坡的作品)来扰乱统计数据。
- 一些语言没有单词分隔:中文、日文、泰文
- 一些语言将单词组合在一起:粘着语
德语“Rindfleischetikettierungsüberwachungsaufgabenübertragungsgesetz” - 减少文本搜索数据的一个常用方法是排除“停用词”
停用词按定义是最常见的词,移除它们可能会丢失可能有用的上下文。
- 垃圾邮件发送者可以变得有创意,拼写错误
- 多词统计(n-gram / shingles)
多个单词可以为统计提供更多上下文,但需要更多训练数据。
“你赢了!” vs “赢家是你”- 仍然容易受到拼写错误的影响
- 不同的单词形式会膨胀模型大小
动词形式/名词-动词一致:单数/复数、性别、时态、正式程度、主语/宾语...
- 多词+字符统计 <<-- 这是本文将要测试的内容
- + 多词用于上下文
- + 回退到较少的词语以处理未训练的短语
- + 回退到字符以处理未训练的单词
- + 可以轻松编辑模型,并快速重新运行它
- - 模型尺寸大
- 维基百科大小示例
~10 种语言,每种语言减少到 ~300k n-gram(每种 n-gram 类型 50k)
以 GB 为单位下载和解压
在多核上花费数小时将一种语言文本减少到词频数据
并使用约 1GB RAM 加载已缩减的模型
可以有效地识别一个包含几个单词的语言片段(例如,一本书或戏剧的名称) - - 运行:大型模型速度不是超快,但优化后也不是很慢...
* 可调整:预处理、gram 大小/权重、模型大小、多线程 - 人工神经网络
* 一个快速发展的领域;有许多,许多选择- 可能难以至乎不可能追溯为什么某些东西有效或无效
- 大规模训练可能耗时且需要大量硬件资源
- 编辑模型通常需要重新训练
概率与统计
贝叶斯规则:P(A|B) = P(B|A) * P(A) / P(B)
在 B 发生的情况下 A 发生的概率 = 在 A 发生的情况下 B 发生的概率 * 任何时候 A 发生的概率 / 任何时候 B 发生的概率
应用于垃圾邮件
P(垃圾邮件 | 词语) = P(词语 | 垃圾邮件) * P(垃圾邮件) / ( P(词语 | 垃圾邮件) + P(词语 | 非垃圾邮件) )
链式法则:P(A & B) = P(A) * P(B)
优化:乘法比加法慢,而且非常小的数字可能会下溢
所以使用对数
Log(A * B) = Log(A) + Log(B)
Log(A / B) = Log(A) - Log(B)
模糊逻辑:现实世界的状态可能不像我们建模的那样是二元的
如果未知的未知数比假设的要大,或者涉及隐藏变量...
那么假设一个事物排除另一个事物可能不安全:P(A) = 1 - P(B)
一条消息(或网页)可能同时包含垃圾邮件和非垃圾邮件,或者是一门模型无法做出明智决定的外语。
文本预处理:以可重复的方式标准化空格、处理符号、数字等极其重要。
Using the Code
var bec = new BayesEnsembleClassifier();
bec.Train(files);
bec.TrainPost();
bec.Test(filename_test);
//-Run the test data with increasingly misspelled words
for (int r = 5; r <= 95; r += 5) {
bec.TestRandom(filename_test, randomness);
}
训练步骤对文本进行分词,去除 HTML,并为垃圾邮件和非垃圾邮件构建 1-3 个单词的 n-gram shingles 和 3、5、7 个字符的频率表。字符 gram 可以跨越单词。n-gram 的权重和大小/长度是可定制的。
类别是动态的(但高效的存储需要更复杂的数据结构来减小尺寸并避免周转)。
例如
- 前 2 个单词的非垃圾邮件条目是:“. the”、“the .”、“the the”、“and the”...
- 前 2 个单词的垃圾邮件条目是:“and .”、“to .”、“his .”、“he .”
- 前垃圾邮件字符 gram 是“ th”、“the”、“ and ”、“ his ”、“ childe”
- 前非垃圾邮件字符 gram 是“ th”、“the”、“ing”、“ the ”、“ and ”、“ that”、“evermor”
后处理:过滤稀有 n-gram 并预先计算 Log 概率
测试:评估测试文件中的行,并显示 TruePositive/TrueNegative/FalseNegative/FalsePositive & F-Score
TestRandom
在处理前对测试文本进行更改(它会递增随机选择的字符),并显示分数。
关注点
内存使用情况:(提供的训练集)
加载峰值 135MB
加载后 47MB
时序
加载/训练 6809ms(但优化使用会节省缩减和压缩的训练数据)
评估 250ms(提供的测试集:100 条消息,35583 个单词)
弹性
由于模型包含字符 gram,因此即使文本中高达 50% 的字符被更改,它也能成功分类文本。(远超过单个单词可被人眼识别的程度)
% Chars Changed, F-Score, Test Text (partial first line)
0% 1.00 bust by this expressing at stepped and . my my dreary a and . shaven we spoken
5% 1.00 bust by this eypressinh at stepped bnd . my my dreary a and . shaven we spoken
10% 1.00 bust by this eypressinh at suepped bnd . my my dreary a and . siaven we spoken
15% 1.00 bust by this eypressinh at suepped bnd . ny my ereary a and . siaven we sppkeo
20% 1.00 bust by this eypressinh at sufpped bnd . ny my ereary a and . siawen we sppkeo
25% 1.00 busu by this eypressinh at sufpped bnd . ny my eseary a and . siawen we sppkeo
30% 1.00 busu by uiis eypressinh at sufpqed bnd . nz my eseary a and . siawen we sppkeo
35% 1.00 busu by uiis eypretsinh at sufpqed bne . nz mz eseary a and . siawen we sppkeo
40% 1.00 busu by uiis eypretsinh at sufpqed boe . nz mz eseary a and . siawen we sppkeo
45% 1.00 busu bz uiis eypretsinh at sufpqed boe . nz mz esfary a and . siawen we sppkeo
50% 0.98 busu bz uijs eypretsinh at sufqqed boe . nz mz esfarz a and . siawen wf sppkeo
55% 0.82 busu bz uijs eypretsinh bt tufqqed boe . nz mz esfarz a and . siawen wf sppkeo
60% 0.49 butu bz uijs eypretsinh bt tufqqed boe . nz mz esfarz a and . siawen wf sppkeo
65% 0.09 butu bz uijt eypretsinh bt tufqqee boe . nz mz esfasz a and . siawfn wf sppkeo
70% 0.00 butu bz uijt eyprftsinh bt tufqqee boe . nz mz esfasz a ane . siawfn wf sppkeo
75% 0.00 butu bz uijt eyprftsinh bt tufqqee boe . nz mz esfbsz a bne . siawfn wf tpplfo
80% 0.00 bvtu bz uijt eyprftsinh bt tufqqee boe . nz nz esfbsz a bne . siawfn wf tpplfo
85% 0.00 cvtu bz uijt eyprfttjnh bt tufqqee boe . nz nz esfbsz a bne . sibwfo wf tqplfo
90% 0.00 cvtu bz uijt eyqrfttjnh bt tufqqee boe . nz nz esfbsz b boe . tibwfo wf tqplfo
95% 0.00 cvtu cz uijt eyqrfttjnh bu tufqqee boe . nz nz esfbsz b boe . tibwfo wf tqplfo
注释
最后的 PredictLine()
有点复杂...
将循环写入多路分组和聚合需要大量的代码。
如果遗漏了什么,LINQ 查询几乎不可能调试。
不知道哪个更糟糕。
历史
- 2018-03-04:初始帖子