C# 的朴素贝叶斯垃圾邮件过滤器






4.87/5 (35投票s)
Paul Graham 朴素贝叶斯垃圾邮件过滤器算法的 C# 实现。

引言
这是 Paul Graham 的朴素贝叶斯垃圾邮件过滤器算法 的 C# 实现。适用于集成到 ASP.NET 博客、论坛、电子邮件或 Wiki 应用程序中。
背景
我经营着一个名为 Blogabond 的小型 旅行博客网站,多年来,它吸引了越来越多的垃圾邮件发送者。起初,我能够通过简单的反机器人措施来阻止明显不是网络浏览器的帖子。很快,我就不得不实施一个简单的后台无声人类检测脚本,以确保是真人坐在真实的键盘前手工输入博客条目。
这种方法在很长一段时间内都效果很好。偶尔,一些雄心勃勃的旅行社开始发布广告,我不得不手动删除,直到他们明白这样做是无效的。尽管如此,在后台,每天大约有 10,000 条自动化评论垃圾邮件被拦截。这还不错。
现在是 2008 年,情况发生了变化。我们在 Blogabond 上开始看到一种新的垃圾邮件,而且情况每天都在恶化。这是人工垃圾邮件。由真人通过真实的键盘发送,地点在工资足够低,广告商可以雇佣工人手工复制/粘贴评论垃圾邮件的地方。所有自动化的欺骗手段都对此无效,因为它不再是自动化的了。
是时候转向贝叶斯了
现代电子邮件客户端都使用贝叶斯垃圾邮件过滤,所以我认为我需要实现这个。在 Google 上搜索 "Bayesian C#",令我惊讶的是,我发现没有人为 C# 发布一个可以直接集成到代码库中的朴素贝叶斯垃圾邮件过滤器。这是怎么回事?这项技术自 2002 年就存在了。真的很难实现吗?恐怕是的。不过,每天手动管理 Blogabond 已经变得非常烦人了。我想我来试试。你知道吗?其实并没有那么难。
我不会详细介绍算法本身。毕竟,我的只是对 Paul Graham 的原始朴素贝叶斯垃圾邮件过滤算法 的直接实现,我并不认为我对他的分析有什么独特的见解。
Using the Code
在本文附带的 zip 文件中,您会找到使这一切工作的两个类。有一个 Corpus
类,它保存单词列表,以及它们在给定文本中出现的频率计数。还有一个 SpamFilter
类,它将两个 Corpus(Corpi?Corpus's?)进行比较,从而生成一个概率列表,表示包含某个给定单词的文档是垃圾邮件的可能性。
一旦您填充了 SpamFilter
,您就可以将其他文档喂给它,并询问它认为它正在处理的是垃圾邮件还是非垃圾邮件。在我的测试中,我发现它的表现相当不错。在输入的 10,000 条博客条目中,它发现了大约 6% 的假阴性(未被标记为垃圾邮件的垃圾邮件),以及仅 0.2% 的假阳性(被错误地标记为垃圾邮件的正常消息)。实际上,它做得很好,以至于它标记的“假阳性”中,只有一个是真正绕过了我的管理尝试的垃圾邮件。
我包含了一个示例 WinForms 应用程序,以便您可以看到过滤器的实际运行效果。它读取了几个文本文件来填充 SpamFilter
,并提供了足够多的正常和垃圾邮件内容,以便进行有用的演示。我还提供了 3 个示例博客条目进行测试。一个是真实的博客条目,一个是明显的垃圾邮件,另一个是精心编写的垃圾邮件,它作为假阴性而漏过。示例应用程序允许您编辑消息的文本,以查看添加更多或更少“垃圾”内容的效果。
投入生产
这一切对于测试来说都很棒,但如何将这种很棒的过滤功能投入实际使用呢?让我快速介绍一下我们今天在 Blogabond 上的做法。
我们将一个静态的 SpamFilter
对象保存在服务器内存中,这样我们就不必每次需要时都重新构建一个。每天运行一个作业,从数据库内容中重新构建 SpamFilter
,将其存储在内存中,并通过 .ToFile()
方法保存一个备份副本。如果我们发现 SpamFilter
对象丢失(由于服务器重启),我们只需使用 .FromFile()
方法加载最后一个状态。
当保存新的博客条目时,我们会通过 SpamFilter
运行它,并在必要时为该博客条目设置一个 IsSpam
位。我们所有的显示代码都知道要检查这个位,并相应地抑制显示或返回 404 响应给被标记为垃圾邮件的条目。有一个例外:当新垃圾邮件条目发布后,我们有一个一分钟的窗口期,在此期间我们会像对待非垃圾邮件一样显示它。这足以让垃圾邮件发送者查看条目并为自己的出色工作感到自豪,但不足以让页面被搜索引擎索引。我们自然也会从 RSS feed、站点地图和 Blog Ping 服务请求中排除任何垃圾邮件条目。
作为网站管理工具的一部分,我们有一个包含所有近期帖子及其状态的列表。这使我们可以快速将假阳性和假阴性纠正回其正确状态,并大致掌握网站的运行情况。
结论
我已经将这段代码发布到互联网上供大家使用,希望人们会觉得它有用。我看到许多运行 ASP.NET 的博客和论坛网站都饱受评论垃圾邮件的困扰。如果幸运的话,有人可能会采用这两个简单的类,并将它们变成有用的东西。如果您这样做了,请告诉我您的使用体验!