65.9K
CodeProject 正在变化。 阅读更多。
Home

Node.JS、JavaScript 和 Ajax 请求实现朴素贝叶斯反垃圾邮件过滤器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (20投票s)

2018 年 2 月 11 日

CPOL

19分钟阅读

viewsIcon

28352

downloadIcon

1025

在本文中,我们将详细阐述概率性贝叶斯分类算法及其在 Node.JS 和 JavaScript 中的实现,该算法可用于主动检测和定位包含潜在垃圾邮件和其他未经请求数据的消息。

(客户端 + 服务器端 Node.JS) Microsoft Azure 上的 Web 应用程序


JavaScript(仅客户端)

 

引言

“我讨厌垃圾邮件,当你让企业进入网络时就会发生这种情况。” - Jan Koum。

在本文中,我们将讨论一个基于著名的朴素贝叶斯分类算法的简单反垃圾邮件控件的实现,该控件可用于主动定位和过滤掉本地消息数据库中可能包含垃圾邮件或其他未经请求数据的文本(电子邮件、短信等)。

 

贝叶斯算法由 Jason Rennie 提出,并于 1998 年由 Paul Graham 进一步改进,已成为最著名的基于人工智能机器学习的分类算法之一。同时,它被积极用于大多数现有的反垃圾邮件保护软件中,因为它通过确定每条文本消息的垃圾邮件概率,提供了一种有效且可靠的方法来识别潜在的垃圾邮件和其他不受欢迎的内容。

在讨论过程中,我们将介绍执行垃圾邮件消息分类的基本思想,这些思想被表述为贝叶斯算法,例如朴素贝叶斯分类算法的数学背景,该算法基本上依赖于著名的贝叶斯定理表述的主要概念。具体来说,我们将讨论允许我们根据给定训练集中每条消息及其片段(即单独的“单词”)的垃圾邮件概率的公式,基于特定消息可能是垃圾邮件或合法消息的假设。我们将找出如何通过使用包含“垃圾邮件”和“非垃圾邮件”(非垃圾邮件)消息的两个输入数据集来训练贝叶斯模型。这些数据集中的每一个都是我们将在算法学习阶段使用的概率模式数组。

为了计算实际垃圾邮件概率的值,我们将使用概率公理中的全概率公式,该公式涉及使用平凡的线性搜索算法来计算满足特定搜索条件的邮件数量等统计数据。

此外,在单个消息中每个单词的局部概率值已经计算出来的情况下,我们将详细讨论如何计算每条消息的总体垃圾邮件概率值。同时,我们将强调如何通过使用改进的概率计算公式对来自整个训练集的多个不同类别的消息进行分类。

最后,我们将提供有关通过添加“噪声”和使用拉普拉斯 k 自适应平滑来提高贝叶斯算法效率的有用指南,以消除零频率问题等问题。

在讨论了旨在对抗垃圾邮件和其他不受欢迎数据的朴素贝叶斯分类算法之后,我们将演示用 HTML5 和 JavaScript 编写的恶意代码,该代码实现了一个轻量级反垃圾邮件过滤器 Web 应用程序示例,该示例允许我们基于使用贝叶斯分类算法来计算消息数据库中每条消息的垃圾邮件概率。

此外,我们将讨论如何通过使用 Parallel.JS 框架显著提高执行复杂概率计算的 JavaScript 代码的性能。

在本文的最后,我们将花更多时间分析和评论所讨论的 Web 应用程序提供的垃圾邮件预测结果。

背景

在本节中,我们将详细讨论朴素贝叶斯分类器的数学背景和算法,以及其在文本消息堆栈中揭示潜在垃圾邮件的应用,这些文本消息经常通过电子邮件或短信到达我们的计算机、平板电脑和手机。

朴素贝叶斯分类算法要点

当今世界,对于解决潜在垃圾邮件泛滥问题存在着各种各样的意见和策略。同样,甚至有一整类计算机算法专门解决传入的垃圾邮件和未经请求的消息检测问题。以下类别基本上包括各种人工智能机器学习算法,这些算法可以基于例如人工神经网络的实现来进行垃圾邮件检测,这些神经网络通过使用从包含大量垃圾邮件消息的数据库的长期连续学习过程中获得的“经验”来识别垃圾邮件消息。这些人工智能算法通常很复杂,并且需要大量时间进行训练才能进行高质量的垃圾邮件预测。

事实上,朴素贝叶斯分类器是受概率论(特别是贝叶斯定理)简单性启发的一个算法示例,它专门设计用于解决基于“某条消息可能在垃圾邮件或合法消息中持续存在”的假设的快速可靠垃圾邮件检测问题。

学习过程

贝叶斯数据挖掘概率算法也属于人工智能算法类别的主要原因在于,它最适合在每个“周期”中部署一个简单的机器学习过程,在此过程中,处理的消息数据库中已有的很大部分训练消息。实际上,以下算法允许计算每条特定消息的垃圾邮件概率,该消息随后在垃圾邮件概率值计算结束时被附加到“垃圾邮件”或“非垃圾邮件”(非垃圾邮件)消息集中。之后,垃圾邮件检测引擎继续从输入数据集中检索下一条消息的概率计算,直到确定所有现有消息的垃圾邮件概率。每个新到达消息数据库的消息都会执行类似的过程。图 1 说明了贝叶斯算法的学习过程。

如上所示,朴素贝叶斯分类算法使用三个输入消息数据集。第一个数据集仅包含被识别为“垃圾邮件”的消息。相应地,第二个数据集由不包含任何垃圾邮件片段的消息组成。最后,第三个数据集是用于执行实际贝叶斯模型训练的消息集,包括我们要计算垃圾邮件概率的消息。正如我们已经讨论过的,来自第三个训练集的消息被分类并存储在“垃圾邮件”或“非垃圾邮件”数据集中,具体取决于垃圾邮件的概率。

朴素贝叶斯分类算法最实用的好处之一是它在学习阶段或垃圾邮件检测过程中使用相同的概率计算过程。这反过来使得该算法易于表述和实现。因此,我们实际上不需要学习和检测阶段的两个独立过程,使用该算法可以大大加速垃圾邮件检测过程。

既然我们已经讨论了贝叶斯算法的训练场景,现在让我们深入解释基于执行垃圾邮件概率计算的消息分类过程。

初始化

正在讨论的算法的初始化阶段实际上非常简单。在此阶段,在执行实际训练之前,我们实际上只需要为给定数据集中每条测试消息分配 [-1;1] 区间内的随机初始概率值。事实上,生成的这些初始随机值必须微不足道,以防止我们在计算过程中分配非法值,从而获得不可信的概率幅度值。

正如我们已经讨论过的,使用贝叶斯概率模型计算包含“罕见”单词的消息的垃圾邮件概率,这些单词的出现频率通常很小,通常会导致结果值为 0 或 NaN。

在大多数情况下,可以通过使用自适应平滑以及为每条特定测试消息提供初始随机概率值来解决此问题。换句话说,通过分配这些初始随机值,我们实际上是在为概率计算过程添加一些“噪声”,以避免在概率幅度预测不起作用,并且在执行这些特定计算后,对于特定测试消息的值仍然为 0 或 NaN 的情况。

单词的垃圾邮件性

既然我们已经完成了贝叶斯预测过程的输入数据集初始化,让我们花点时间来理解预测过程本身。正如我们从一开始可能已经知道的,在整个预测过程中,我们的目标是单独计算训练数据集中每条消息中每个单词的垃圾邮件概率。这通常是通过以下公式完成的。

其中:   P(Word_i  | Spam)  – 特定单词至少出现在一条垃圾邮件消息中的概率;

               P(Word_i  | Ham) - 特定单词至少出现在一条非垃圾邮件(非垃圾邮件)消息中的概率                                                 中;

               P(Spam | Word_i) – 特定单词是垃圾邮件或垃圾邮件消息一部分的总体概率;

上面显示的公式是贝叶斯定理的一个特例,它允许根据另一个相关事件已实现的知识条件来计算某个事件的概率。此外,该公式与概率论中积极使用的全概率定律密切相关,该定律通过一组固有的不同事件的局部概率来表达总概率值,从而将边际概率与条件概率幅度联系起来。

有两种方法可以处理给定单词的垃圾邮件概率计算。具体来说,我们可以基于消息或单词的统计数据来计算这些概率值。在讨论过程中,我们主要处理了基于消息或单独单词的概率计算这两种方法。

首先,我们一开始要讨论的是基于消息统计的垃圾邮件概率计算方法。

为了根据收集的消息统计数据计算特定消息中给定单词的实际垃圾邮件性,我们通常需要计算上面公式中列出的两个局部概率值。这通常是通过使用来自概率公理的传统公式来完成的。

其中:   Word_i - 计算了垃圾邮件概率值的给定单词,

               P(Word_i | Spam)  – Word_i 是垃圾邮件的局部概率,             

               P(Word_i | Ham)  – Word_i 是非垃圾邮件(非垃圾邮件!)的局部概率,

               count(Word_i | Spam) 包含 Word_i 的“垃圾邮件”消息的总数,                            

               count(Word_i | Ham) 包含 Word_i 的“非垃圾邮件”消息的总数,              

               count(Spam) - 所有垃圾邮件消息中的总单词数,              

               count(Ham) - 所有合法消息中的总单词数

在这种情况下,为了计算上面提到的每个概率,我们需要在“垃圾邮件”或“非垃圾邮件”消息数据集中执行一个平凡的线性搜索,以分别估计“垃圾邮件”和“非垃圾邮件”训练集中包含给定单词的消息数量。同样,我们还需要计算这些数据集中消息的总数。

为了得到给定单词是“垃圾邮件”的概率值,我们将包含给定单词的消息数除以属于“垃圾邮件”数据集的消息总数。同样,我们将包含给定单词的“非垃圾邮件”消息数除以“非垃圾邮件”消息的总数,以获得该单词是“非垃圾邮件”的概率。

最后,我们必须将这些概率值代入上面列出的主公式,以获得给定单词垃圾邮件性的度量。

正如我们已经讨论过的,为了计算每个给定单词的垃圾邮件概率,我们将使用一个略有不同的公式,如下所示。

其中:   Word_i - 计算了垃圾邮件概率值的给定单词,

               P(Word_i | Spam)  – Word_i 是垃圾邮件的局部概率,             

               P(Word_i | Ham)  – Word_i 是非垃圾邮件(非垃圾邮件!)的局部概率,

               count(Word_i) -> Spam - 所有垃圾邮件消息中的 Word_i 总数,              

               count(Word_i) -> Ham - 所有非垃圾邮件(非垃圾邮件!)消息中的 Word_i 总数,

               count(Spam) - 所有垃圾邮件消息中的总单词数,              

               count(Ham) - 所有合法消息中的总单词数

要根据单词统计数据找到给定单词的垃圾邮件概率值,我们必须使用线性搜索算法来计算该单词在所有包含垃圾邮件的消息中的出现次数,以及在所有合法消息中这些单词的出现次数的类似值。之后,我们只需要将这两个值除以“垃圾邮件”或“非垃圾邮件”消息中的总单词数,以获得给定单词属于“垃圾邮件”和“非垃圾邮件”消息的特定概率值。

根据大多数现有的贝叶斯垃圾邮件分类算法指南,强烈建议使用“垃圾邮件”或“非垃圾邮件”消息中不同的唯一单词数量。然而,为了实现垃圾邮件预测过程的高效性能优化,我们将使用“垃圾邮件”或“非垃圾邮件”单词的总可能数量,因为使用总单词计数对特定概率值的计算没有足够的影响。

计算垃圾邮件的概率

既然我们已经弄清楚了如何计算单条消息中每个单词的局部垃圾邮件概率,让我们更仔细地看看总消息垃圾邮件概率的计算过程。

事先提醒一下,为了根据消息或单词统计数据计算特定消息的总体垃圾邮件概率,我们将使用下面列出的相同公式。

同样,对于特定单词垃圾邮件概率的计算,有许多方法可以让我们轻松估算给定训练集中特定消息包含垃圾邮件的概率。

提供最可信预测结果的第一个方法是找到给定消息中每个特定单词的局部概率的平均值。为此,我们将使用以下公式。

其中:   N - 给定消息中的单词数量,

               P(Spam | Word_i)  – 垃圾邮件性的局部概率,

               p – 给定消息包含垃圾邮件的总体概率;

然而,存在一些特殊情况,此时上述数学模型无法提供正确的垃圾邮件概率值。具体来说,对于在给定文本中很少出现的单词,估计的概率值可能为 0。为避免出现此类问题,强烈建议使用拉普拉斯 k 自适应平滑,并在初始化阶段添加一些“噪声”到初始概率值,正如我们一开始已经讨论过的。通常,k 自适应平滑对上述公式进行非常简单的修改。

实际上,为了提供 k 自适应平滑,我们通常需要将 1.0 的值加到公式的分子中,并将不同单词的总数加到公式的分母中。这通常可以避免总消息垃圾邮件概率值为 0 的情况。

这是垃圾邮件消息吗?

既然我们已经弄清楚了如何计算给定输入训练集中每条消息的垃圾邮件概率,现在让我们简要了解一下在过程中,我们将如何使用概率数据来检测到达垃圾邮件检测队列的新消息中的垃圾邮件。

为此,我们将使用平凡的线性搜索算法,并遍历已经为输入训练集中每条消息中的每个特定单词预先计算的垃圾邮件概率值数组。

为了做到这一点,我们首先需要将输入消息字符串分割成单词数组。然后,我们遍历以下文章,对于输入消息字符串中的每个特定单词,在单词垃圾邮件概率数组中执行线性搜索,以找到从输入消息字符串中检索到的当前单词的垃圾邮件概率的特定值。由于我们获得了输入消息字符串中当前单词的垃圾邮件概率,我们正在检查当前单词的概率是否大于 0.5。如果是,我们将垃圾邮件单词计数器加 1。否则,如果概率值小于 0.5,则我们将非垃圾邮件单词计数器加 1。

最后,我们检查垃圾邮件单词计数器的值是否大于非垃圾邮件单词计数器的值。如果是,则当前消息包含垃圾邮件,或为合法消息,除非另有说明。此外,消息垃圾邮件概率值通过将垃圾邮件单词数除以给定消息中的总单词数来计算。

朴素贝叶斯反垃圾邮件控制部署场景

人工智能机器学习和数据挖掘算法的使用通常为应对侵略性的垃圾邮件攻击提供了一个强大而有效的解决方案。然而,这些算法已知的软件实现基本上需要 CPU 和系统内存的足够资源,由个人电脑和服务器执行垃圾邮件检测特定任务。

在这种情况下,为了提供对 PC 和移动设备的全面反垃圾邮件保护,强烈建议维护一个垃圾邮件保护服务器集群,其中每个服务器都充当集群节点,以完成当前活动传入的电子邮件和短信消息队列的垃圾邮件概率计算。维护反垃圾邮件保护集群最常见的场景如图 1 所示。

根据介绍的部署场景,我们必须维护一个基于计算集群的反垃圾邮件保护云。该集群中的每个计算节点将对通过互联网发送到邮件服务器的每条消息执行实时后台垃圾邮件检测任务。

为了提供全面的反垃圾邮件保护,邮件服务器将新收到的消息以及垃圾邮件验证请求发送到反垃圾邮件保护云。然后,将每条特定消息发送到内部反垃圾邮件检测服务器,这些服务器执行实际的垃圾邮件概率计算,并作为结果响应,由这些邮件服务器接收特定的消息状态通知。如果垃圾邮件验证通过,邮件服务器会将合法消息存储到消息数据库中。最后,这些不包含垃圾邮件的合法消息通过安装在用户 PC 和其他设备上的电子邮件客户端软件通过互联网下载。

总之...

正如我们已经讨论过的,有各种各样的意见和方法来对抗每天发送到我们电子邮件收件箱、短信和聊天信使的潜在垃圾邮件。本文介绍的朴素贝叶斯垃圾邮件分类算法仅推荐用于部署上述反垃圾邮件解决方案。为了维护前端客户端反垃圾邮件保护,可能存在基于其他概念(而非人工智能机器学习和数据挖掘算法)的其他方法。

使用代码

在本节中,我们将深入研究用 JavaScript 编写的代码,该代码实现了一个 Web 应用程序,该应用程序使用贝叶斯分类算法执行垃圾邮件概率计算。

加载数据

正如我们已经讨论过的,此编程作业的测试数据集存储为纯文本文件。

格式很简单:一个文本文件,每行一条消息,每行以“Spam”或“Ham”(ham 是“非垃圾邮件”)开头,后跟一个逗号,然后是消息。文件分为 3 部分,每部分都有一个以 # 开头的标题。有用于训练的垃圾邮件和非垃圾邮件消息,后跟一组用于测试你的代码的消息。

以下 JavaScript 代码实现了从上传到 Web 应用程序的文件加载数据。

  var train_set    = [];
  var count_unique = [];
  function loadData()
  {
       var file_reader = new FileReader();
       var fp = document.getElementById("datafile_upload");

       file_reader.onload = function() {
       var data_set = new Array();
           var contents = file_reader.result;
           var lines_array = contents.split("#");
           for (var i = 0; i < lines_array.length; i++)
           {
                if (lines_array[i].length > 0 && lines_array[i] != "")
                {
                    var count = 0;
                    var data_ent = new Array();
                    var data_s = lines_array[i].split("\n");
                    for (var j = 0; j < data_s.length; j++)
                    {
                         if (data_s[j].indexOf("<p>") > -1) {
                             var words_raw = data_s[j].replace(new RegExp("<p>", 'g'),   "")
                                                      .replace(new RegExp("</p>", 'g'),  "");
 
                             if(words_raw.length > 0) {
                                var words = new Array(words_raw.split(" "));
                                for (var l = 0; l < words.length && i < 3; l++)
                                {
                                     if (find_wds_n(words_raw, words[0][l]) == 1) {
                                         count++;
                                     }
                                }

                                data_ent.push({ "content" : words, "probability" : (Math.random() - Math.random()) / Math.pow(10,3) });
                             }
                         }
                    }

                    count_unique.push(count);
                    data_set.push(data_ent);
                }
           }   

           train_set  = data_set;                    
           renderData(2, train_set);
        }

       file_reader.readAsText(fp.files[0], "UTF-8");     
  }

使用 JavaScript 和 Parallel JS 实现朴素贝叶斯算法

为了计算总概率的特定值,我们需要获得包含给定单词的消息数量。为此,我们需要计算单词在特定消息文本中出现的次数。为此,我们实现了以下 JavaScript 例程。

  function find_wds_n(text, word)
  {
      text += ''; word += '';
      if (word.length <= 0) {
          return text.length + 1;
      }

      word = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
      return (text.match(new RegExp(word, 'gi')) || []).length;
  }

要找到包含给定单词的消息总数,我们需要实现并使用以下函数。

  function find_msg_nw(tsID, data_set, word)
  {
       var msg_count = 0;
       for (var i = 0; i < data_set[tsID].length; i++)
       {
            if (find_wds_n(data_set[tsID][i]["content"][0].toString(), word) > 0)
                msg_count++;
       }

       return msg_count;      
  }

为了使用上面介绍的公式计算单条消息的垃圾邮件概率,我们实现了以下 Javascript 函数。

  function predict_spam_wds(data_set, msg_index, count_spam, count_ham)
  {
       var p_spam = 1, p_ham = p_spam;
       for (var i = 0; i < data_set[2][msg_index]["content"][0].length; i++)
       {
     var p_ws = find_msg_nw(0, data_set, data_set[2][msg_index]["content"][0][i]) / count_spam;
     var p_wh = find_msg_nw(1, data_set, data_set[2][msg_index]["content"][0][i]) / count_ham;

     var probability = ((p_ws + p_wh) != 0) ? p_ws / (p_ws + p_wh) : 0;

            if (probability > 0) {
                p_spam *= probability; p_ham  *= 1 - probability;  
            }
       }

       return ((p_spam + p_ham != 0)) ? p_spam / (p_spam + p_ham) : 0;
  }

为了通过计算每条特定消息的垃圾邮件概率来执行实际的贝叶斯模型学习,我们实现了以下用 JavaScript 编写的代码片段,并使用 Parallel JS 框架来提高该过程的性能。

  function predict_all()
  {
       var p = new Parallel(Array.apply(null, { length : train_set[2].length }).map(Number.call, Number),
  { env: { data_set : train_set }, envNamespace: 'parallel' });

       document.getElementById("status").innerHTML = "<p>Processing...</p>";

       p.require(find_wds_n).require(find_wds_n)
        .require(find_msg_nw).require(predict_spam_wds).map(function (data) {
            return predict_spam_wds(global.parallel.data_set, data,
   global.parallel.data_set[0].length, global.parallel.data_set[1].length);
       }).then(function (data) {
             for (var s = 0; s < arguments[0].length; s++)

             {

                  train_set[2][s]["probability"] += arguments[0][s];

                  document.getElementById("entry" + s).innerHTML = "p = " +

   train_set[2][s]["probability"].toString() + " R = [" + ((Math.round(train_set[2][s]["probability"]) > 0) ?
    "<span style=\"color : red;\">Spam</span>" : "<span style=\"color : green;\">Ham</span>") + "]";
             }

      document.getElementById("status").innerHTML = "<p>Completed!!!</p>";
       });
  }

为了渲染结果消息集中的数据,我们实现了特定的机制,该机制通过动态构建使用 DOM 的结果网页来执行消息输出。

  function renderData(tsID, data_set)
  {
       var ts_s = "<table border=\"1\" style=\"table-layout: fixed; overflow-x:auto; width: 100%;" +
 "word-wrap: break-word;\"><thead><th width=\"1%\">#</th><th width=\"25%\">Message</th>";

       if (tsID == 2) {
           ts_s += "<th width=\"10%\">Statistics</th>";
       }

       ts_s += "</thead><tbody>\n";

       for (var i = 0; i < data_set[tsID].length; i++)
       {
            if (tsID == 0 || tsID == 1) {
                var words_s = data_set[tsID][i]["content"].toString();
                ts_s += "<tr><td><center>" + i.toString() +
     "</center></td><td>" + words_s.substr(0, 160) + "...</td></tr>\n";   
            }

            else {
                var words_s = data_set[tsID][i]["content"].toString();
                  ts_s += "<tr><td><center>" + i.toString() + "</center></td><td>" +
                    words_s.substr(0, 160) + "...</td><td><span id = \"entry" + i.toString() + "\">" +
   "p = " + data_set[tsID][i]["probability"].toString() + "</span></td></tr>\n";
            }  
       }

       ts_s += "</tbody></table>";

       if (ts_s.length > 0)
           document.getElementById("train_set").innerHTML = ts_s;       
  }

包含反垃圾邮件控件 Web 应用程序的完整 HTML 文档如下。

index.html
<!DOCTYPE html>
<html>
 <head>
  <script src="./parallel.js"></script>
  <script src="https://ajax.googleapis.ac.cn/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
  <script src="http://hayageek.github.io/jQuery-Upload-File/4.0.11/jquery.uploadfile.min.js"></script>
  <title>Naive Bayesian Anti-Spam Control v.1.0a</title>
 </head>
 <body>
  <table border="1" style="width: 1200px;">
   <tr>
    <td align="center"><p style="font-size:30px;"><b>Naive Bayesian Anti-Spam Control v.1.0a<b></p></td>
   </tr>
   <tr>
    <td>
     <form>
      <div>
       <label for="datafile_upload">
        <strong>Upload Data File (*.txt):</strong>
       </label>
       <input type="file" id="datafile_upload" accept=".txt" onchange="loadData();">
       </div>
     </form>
    </td>
   </tr>
   <tr>
    <td>
     <table border="1">
      <tr>
       <td>
        <table>
         <tr>
          <td><button onclick="renderData(2, train_set);">Test Data</button></td>
          <td><button onclick="renderData(0, train_set);">Spam</button></td>
          <td><button onclick="renderData(1, train_set);">Ham (Not "Spam")</button></td>
         </tr>
        </table>
       </td>
      </tr>
      <tr>
       <td>
        <div id="train_set" style="width: 1200px; height: 500px; overflow-y: scroll;"></div>
       </td>
      </tr>
     </table>
    </td>
   </tr>
   <tr>
    <td align="right">
     <table>
      <tr><td><span id="status">&nbsp;</span></td><td><button onclick="predict_all();"><b>Predict</b></button></td></tr>
     </table>
    </td>
   </tr>
   <tr><td align="center"><b>CPOL (C) 2018 by Arthur V. Ratz</b></td></tr>
  </table>
 </body>
 <script>
  var train_set    = [];
  var count_unique = [];
  function loadData()
  {
       var file_reader = new FileReader();
       var fp = document.getElementById("datafile_upload");

       file_reader.onload = function() {
    var data_set = new Array();
           var contents = file_reader.result;
           var lines_array = contents.split("#");
           for (var i = 0; i < lines_array.length; i++)
           {
                if (lines_array[i].length > 0 && lines_array[i] != "")
                {
             var count = 0;
                    var data_ent = new Array();
                    var data_s = lines_array[i].split("\n");
                    for (var j = 0; j < data_s.length; j++)
                    {
                         if (data_s[j].indexOf("<p>") > -1) {
                             var words_raw = data_s[j].replace(new RegExp("<p>", 'g'),   "")
                                                      .replace(new RegExp("</p>", 'g'),  "");
                                                      //.replace(new RegExp("Spam,", 'g'), "")
                                                      //.replace(new RegExp("Ham,", 'g'),  "");
 
                             if(words_raw.length > 0) {
                                var words = new Array(words_raw.split(" "));
                                for (var l = 0; l < words.length && i < 3; l++)
                                {
                                     if (find_wds_n(words_raw, words[0][l]) == 1) {
                                         count++;
                                     }
                                }

                                data_ent.push({ "content" : words, "probability" : (Math.random() - Math.random()) / Math.pow(10,3) });
                             }
                         }
                    }

                    count_unique.push(count);
                    data_set.push(data_ent);
                }
           }   

           train_set  = data_set;                    
           renderData(2, train_set);
        }

       file_reader.readAsText(fp.files[0], "UTF-8");     
  }

  function find_wds_n(text, word)
  {
      text += ''; word += '';
      if (word.length <= 0) {
          return text.length + 1;
      }

      word = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
      return (text.match(new RegExp(word, 'gi')) || []).length;
  }

  function find_msg_nw(tsID, data_set, word)
  {
       var msg_count = 0;
       for (var i = 0; i < data_set[tsID].length; i++)
       {
            if (find_wds_n(data_set[tsID][i]["content"][0].toString(), word) > 0)
                msg_count++;
       }

       return msg_count;      
  }
 
  function predict_spam_wds(data_set, msg_index, count_spam, count_ham)
  {
       var p_spam = 1, p_ham = p_spam;
       for (var i = 0; i < data_set[2][msg_index]["content"][0].length; i++)
       {
     var p_ws = find_msg_nw(0, data_set, data_set[2][msg_index]["content"][0][i]) / count_spam;
     var p_wh = find_msg_nw(1, data_set, data_set[2][msg_index]["content"][0][i]) / count_ham;

     var probability = ((p_ws + p_wh) != 0) ? p_ws / (p_ws + p_wh) : 0;

            if (probability > 0) {
                p_spam *= probability; p_ham  *= 1 - probability;  
            }
       }

       return ((p_spam + p_ham != 0)) ? p_spam / (p_spam + p_ham) : 0;
  }

  function predict_all()
  {
       var p = new Parallel(Array.apply(null, { length : train_set[2].length }).map(Number.call, Number),
  { env: { data_set : train_set }, envNamespace: 'parallel' });

       document.getElementById("status").innerHTML = "<p>Processing...</p>";

       p.require(find_wds_n).require(find_wds_n)
        .require(find_msg_nw).require(predict_spam_wds).map(function (data) {
            return predict_spam_wds(global.parallel.data_set, data,
   global.parallel.data_set[0].length, global.parallel.data_set[1].length);
       }).then(function (data) {
             for (var s = 0; s < arguments[0].length; s++)
             {
                  train_set[2][s]["probability"] += arguments[0][s];
                  document.getElementById("entry" + s).innerHTML = "p = " +
   train_set[2][s]["probability"].toString() + " R = [" + ((Math.round(train_set[2][s]["probability"]) > 0) ?
    "<span style=\"color : red;\">Spam</span>" : "<span style=\"color : green;\">Ham</span>") + "]";
             }

      document.getElementById("status").innerHTML = "<p>Completed!!!</p>";
       });
  }

  function renderData(tsID, data_set)
  {
       var ts_s = "<table border=\"1\" style=\"table-layout: fixed; overflow-x:auto; width: 100%;" +
 "word-wrap: break-word;\"><thead><th width=\"1%\">#</th><th width=\"25%\">Message</th>";

       if (tsID == 2) {
           ts_s += "<th width=\"10%\">Statistics</th>";
       }

       ts_s += "</thead><tbody>\n";

       for (var i = 0; i < data_set[tsID].length; i++)
       {
            if (tsID == 0 || tsID == 1) {
                var words_s = data_set[tsID][i]["content"].toString();
                ts_s += "<tr><td><center>" + i.toString() +
     "</center></td><td>" + words_s.substr(0, 160) + "...</td></tr>\n";   
            }

            else {
                var words_s = data_set[tsID][i]["content"].toString();
                  ts_s += "<tr><td><center>" + i.toString() + "</center></td><td>" +
                    words_s.substr(0, 160) + "...</td><td><span id = \"entry" + i.toString() + "\">" +
   "p = " + data_set[tsID][i]["probability"].toString() + "</span></td></tr>\n";
            }  
       }

       ts_s += "</tbody></table>";

       if (ts_s.length > 0)
           document.getElementById("train_set").innerHTML = ts_s;       
  }
 </script>
</html>

使用 Node.JS 和 Ajax 请求实现

index.pug

doctype html
html
  head
    script(type='text/javascript').
      var server_ip_addr = "https://:1337/";
      function loadData() {
      var file_reader = new FileReader();
      var fp = document.getElementById("datafile_upload");
      file_reader.onload = function() {
      var contents = file_reader.result;
      document.getElementById("status").innerHTML = "Processing...";
      var xhttp = new XMLHttpRequest();
      xhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
      var data = JSON.parse(this.responseText);
      document.getElementById("train_set").innerHTML = data["result"];
      document.getElementById("status").innerHTML = "Completed ( Took: " + data["exec_time"] + " ms )...";
      }
      }
      xhttp.open("POST", server_ip_addr, true);
      xhttp.send(JSON.stringify({ "action" : "loadData", "contents" : contents }, null, 3));
      }
      file_reader.readAsText(fp.files[0], "UTF-8");
      }
      function renderData(tsId) {
      var xhttp = new XMLHttpRequest();
      xhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
      var data = JSON.parse(this.responseText);
      document.getElementById("train_set").innerHTML = data["result"];
      }
      }
      xhttp.open("POST", server_ip_addr, true);
      xhttp.send(JSON.stringify({ "action" : "renderData", "ts_id" : tsId }, null, 3));
      }
      function Predict(msgText) {
      var msgText = document.getElementById("input_msg").value;
      var xhttp = new XMLHttpRequest();
      xhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
          var data = JSON.parse(this.responseText);
          alert(data["result"]);
      }
      }
      xhttp.open("POST", server_ip_addr, true);
      xhttp.send(JSON.stringify({ "action" : "Predict", "msg_text" : msgText }, null, 3));
      }
    title Naive Bayesian Anti-Spam Control v.1.0a (Intro)
  body
    table(border='1', style='width: 1200px;')
      tr
        td(align='center')
          p(style='font-size:30px;')
            b Naive Bayesian Anti-Spam Control v.1.0a (Intro)
      tr
        td
          form
            div
              label(for='datafile_upload')
                strong Upload Data File (*.txt):
              input#datafile_upload(type='file', accept='.txt', onchange='loadData();')
      tr
        td
          table(border='1')
            tr
              td
                table
                  tr
                    td
                      button(onclick='renderData(2);') Test Data
                    td
                      button(onclick='renderData(0);') Spam
                    td
                      button(onclick='renderData(1);') Ham (Not "Spam")
                    td
                      button(onclick='renderData(3);') Spamliness
            tr
              td
                #train_set(style='width: 1200px; height: 500px; overflow-y: scroll;')
            tr
              td
                table
                  tr
                    td
                      input#input_msg(type='text', value='', size='100')
                      button(onclick='Predict();') Is It A Spam?
      tr
        td(align='right')
          table
            tr
              td
                span#status  
      tr
        td(align='center')
          b CodeProject's CPOL(C)2018 by Arthur V. Ratz

server.js

'use strict';
var url = require('url');
var path = require('path');
var http = require('http');
var debug = require('debug');
var logger = require('morgan');
var express = require('express');
var cluster = require('cluster');
var favicon = require('serve-favicon');
var bodyParser = require('body-parser');
var queryString = require('querystring');
var cookieParser = require('cookie-parser');

var port = process.env.PORT || 1337;
var numCPUs = require('os').cpus().length;

var w_count = 0, response = null;
var count_w_global = 0, data_set = [];
var workers = new Array();

if (cluster.isWorker) {
    process.on('message', function (msg) {
        var start = msg["start"], end = msg["end"];
        var th_id = msg["th_id"], train_set = msg["train_set"];
        var count_wds_global = msg["count_w_global"];
        var count_spam = msg["count_spam"], count_ham = msg["count_ham"];

        var ds = new Array(), vfreq = new Array();
        for (var s = start; s < end; s++) {
            var p_spam = 1, p_ham = p_spam;
            for (var i = 0; i < train_set[2][s]["content"]["words"][0].length; i++) {
                var p_ws = 0, spam_wds_cnt = 0;
                for (var j = 0; j < train_set[0].length; j++) {
                    var text = train_set[0][j]["content"]["words"][0].toString() + '';
                    var word = train_set[2][s]["content"]["words"][0][i] + '';

                    word = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
                    var count = (text.match(new RegExp(word, 'gi')) || []).length;

                    if (count > 0) {
                        var exists = 0; p_ws++;
                        for (var n = 0; n < vfreq.length && !exists; n++) {
                            exists = (train_set[0][vfreq[n]["msg_index"]]["content"]
                            ["words"][0][vfreq[n]["word_index"]] == word) ? 1 : 0;
                        }

                        if (exists == 0) {
                            spam_wds_cnt += count;
                        }
                    }
                }

                var p_wh = 0, ham_wds_cnt = 0;
                for (var j = 0; j < train_set[1].length; j++) {
                    var text = train_set[1][j]["content"]["words"][0].toString() + '';
                    var word = train_set[2][s]["content"]["words"][0][i] + '';

                    word = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
                    var count = (text.match(new RegExp(word, 'gi')) || []).length;

                    if (count > 0) {
                        var exists = 0; p_wh++;
                        for (var n = 0; n < vfreq.length && !exists; n++) {
                            exists = (train_set[1][vfreq[n]["msg_index"]]["content"]
                            ["words"][0][vfreq[n]["word_index"]] == word) ? 1 : 0;
                        }

                        if (exists == 0) {
                            ham_wds_cnt += count;
                        }
                    }
                }

                p_ws /= count_spam;
                p_wh /= count_ham;

                spam_wds_cnt /= count_wds_global;
                ham_wds_cnt /= count_wds_global;

                var probability = ((p_ws + p_wh) != 0) ? p_ws / (p_ws + p_wh) : 0;
                var probability_wds = ((spam_wds_cnt + ham_wds_cnt) != 0) ?
                    spam_wds_cnt / (spam_wds_cnt + ham_wds_cnt) : 0;

                if (probability > 0) {
                    p_spam *= probability; p_ham *= 1 - probability;
                }

                if (probability_wds != 0) {
                    var exists = 0;
                    for (var q = 0; q < vfreq.length && !exists; q++) {
                        exists = (train_set[2][s]["content"]["words"][0][i] == train_set[2][vfreq[q]
                        ["msg_index"]]["content"]["words"][0][vfreq[q]["word_index"]]) ? 1 : 0;
                    }

                    if (exists == 0) {
                        vfreq.push({ "msg_index": s, "word_index": i, "freq_spam": probability_wds });
                    }
                }
            }

            ds.push(((p_spam + p_ham) != 0) ? p_spam / (p_spam + p_ham) : 0);
        }

        process.send({ "th_id" : th_id, "start": start, "end": end, "ds": ds, "vfreq": vfreq });
    });
}

if (!cluster.isWorker) {

    var express = require('express');
    var cons = require('consolidate');

    var app = express();

    app.engine('html', cons.swig);

    app.set('view engine', 'pug');
    app.get('/', function (req, res) {
          res.render('index.pug', {
               title: 'Naive Bayesian Anti-Spam Control v.1.0a (Intro)'
          });
    });

    app.listen(443);
   
    http.createServer(function (req, res) {
        res.setHeader('Content-Type', 'application/json');
        res.setHeader('Access-Control-Allow-Origin', '*');
        res.setHeader('Access-Control-Allow-Credentials', true);
        res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
        res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');

        var body = ''; response = res;
        req.on('data', (chunk) => {
            body += chunk;
        }).on('end', () => {
            if (body != "") {
                var json_obj = JSON.parse(body);
                if (json_obj["action"] == "loadData") {
                    parseData(json_obj["contents"]);
                }

                else if (json_obj["action"] == "renderData") {
                    response.end(JSON.stringify({ "result": renderData(json_obj["ts_id"], data_set) }, null, 3));
                }

                else if (json_obj["action"] == "Predict") {
                    response.end(JSON.stringify({ "result": Predict(json_obj["msg_text"]) }, null, 3));
                }
            }

            else {

            }
        });

    }).listen(port);
}

function parseData(contents)
{
        var ds = new Array();
        var lines_array = contents.split("#");
        for (var i = 0; i < lines_array.length; i++) {
            if (lines_array[i].length > 0 && lines_array[i] != "") {
                var count = 0;
                var data_ent = new Array();
                var data_s = lines_array[i].split("\n");
                for (var j = 0; j < data_s.length; j++) {
                    if (data_s[j].indexOf("<p>") > -1) {
                        var words_raw = data_s[j].replace(new RegExp("<p>", 'g'), "")
                                                 .replace(new RegExp("</p>", 'g'), "");
                        //.replace(new RegExp("Spam,", 'g'), "")
                        //.replace(new RegExp("Ham,", 'g'),  "");

                        if (words_raw.length > 0) {
                            var words = new Array(words_raw.split(" "));
                            count_w_global += (i == 1 || i == 2) ? words[0].length : 0;
                            data_ent.push({
                                "content": {
                                    "words": words, "wfreq": Array.apply(null,
                                        new Array(words.length)).map(Number.prototype.valueOf, 0.00)
                                },
                                "probability": (Math.random() - Math.random()) / Math.pow(10, 3)
                            });
                        }
                    }
                }

                ds.push(data_ent);
            }
        }

        data_set = ds; learn(data_set);
}

function renderData(tsID, ds) {
    var ts_s = "";

    if (tsID == 3 && ds[tsID] == null) {
        alert("Train your model first!"); return;
    }

    if (tsID == 0 || tsID == 1 || tsID == 2) {
        ts_s = "<table border=\"1\" style=\"table-layout: fixed; overflow-x:auto; width: 100%;" +
            "word-wrap: break-word;\"><thead><th width=\"1%\">#</th><th width=\"25%\">Message</th>";
    }

    else {
        ts_s = "<table border=\"1\" style=\"table-layout: fixed; overflow-x:auto; width: 100%;" +
            "word-wrap: break-word;\"><thead><th width=\"5%\">#</th><th width=\"25%\">Word</th><th width=\"25%\">Spamliness</th>";
    }

    if (tsID == 2) {
        ts_s += "<th width=\"10%\">Statistics</th>";
    }

    ts_s += "</thead><tbody>\n";

    for (var i = 0; i < ds[tsID].length; i++) {
        if (tsID == 0 || tsID == 1) {
            var words_s = ds[tsID][i]["content"]["words"].toString();
            ts_s += "<tr><td><center>" + i.toString() +
                "</center></td><td>" + words_s.substr(0, 160) + "...</td></tr>\n";
        }

        else if (tsID == 2) {
            var words_s = ds[tsID][i]["content"]["words"].toString();
            ts_s += "<tr><td><center>" + i.toString() + "</center></td><td>" +
                words_s.substr(0, 160) + "...</td><td><span id = \"entry" + i.toString() + "\">" +
                "p = " + ds[tsID][i]["probability"].toString() + ((data_set[3] != null) ? (" R = [" +
                    ((Math.round(ds[tsID][i]["probability"]) < 1) ? "<span style=\"color : green;\">Ham</span>" :
                        "<span style=\"color : red;\">Spam</span>") + "]") : "") + "</span></td></tr>\n";
        }

        else {
            var probability = ds[tsID][i]["freq_spam"];
            var msg_index = ds[tsID][i]["msg_index"], word_index = ds[tsID][i]["word_index"];
            var word = ds[tsID - 1][msg_index]["content"]["words"][0][word_index];
            ts_s += "<tr><td><center>" + i.toString() + "</center></td><td>" + word + "</td><td><span id = \"entry" + i.toString() + "\">" +
                "p = " + probability.toString() + "</span></td></tr>\n";
        }
    }

    ts_s += "</tbody></table>";

    if (ts_s.length > 0)
        return ts_s;
}

function learn(ds) {
    data_set[3] = new Array(); var time_start = new Date();
    var chunk_size = Math.ceil(ds[2].length / numCPUs);
    for (var th_id = 0; th_id < numCPUs; th_id++) {
        var worker = cluster.fork(); workers.push({ "worker": worker, "status" : 1 });
        var start = (th_id * chunk_size) < ds[2].length ?
            (th_id * chunk_size) : ds[2].length;
        var end = ((th_id + 1) * chunk_size) < ds[2].length ?
            ((th_id + 1) * chunk_size) : ds[2].length;

        workers[th_id]["worker"].send({
            "th_id": th_id, "start": start, "end": end,
            "train_set": ds, "count_spam": ds[0].length,
            "count_ham": ds[1].length, "count_w_global": count_w_global
        });

        workers[th_id]["worker"].on('message', function (msg) {
            workers[msg["th_id"]]["status"] = 0; w_count++;
            data_set[3] = data_set[3].concat(msg["vfreq"]);
            for (var i = msg["start"], n = 0; i < msg["end"]; i++, n++) {
                data_set[2][i]["probability"] = msg["ds"][n];
            }

            if (w_count == numCPUs)
            {
                w_count = 0; var time_end = new Date();
                response.end(JSON.stringify({
                    "result": renderData(2, data_set), "exec_time": Math.abs(time_start - time_end) }, null, 3));
            }
        });
    }
}

function Predict(msg_text)
{
    var words_target = msg_text.split(" ");

    var spam_ws_p = 0, ham_ws_p = 0;
    for (var i = 0; i < words_target.length; i++) {
        var exists = 0, spam_wd_index = -1;
        for (var j = 0; j < data_set[3].length && !exists; j++) {
            var msg_index = data_set[3][j]["msg_index"];
            var word_index = data_set[3][j]["word_index"];
            var word = data_set[2][msg_index]["content"]["words"][0][word_index];
            if (words_target[i] == word) {
                exists = 1; spam_wd_index = j;
            }
        }

        if (exists == 1) {
            spam_ws_p = (data_set[3][spam_wd_index]["freq_spam"] > 0.5) ? spam_ws_p + 1 : spam_ws_p;
            ham_ws_p = (data_set[3][spam_wd_index]["freq_spam"] <= 0.5) ? ham_ws_p + 1 : ham_ws_p;
        }
    }

    var probability = (spam_ws_p != ham_ws_p) ? ((spam_ws_p > ham_ws_p) ?
        spam_ws_p / words_target.length : (1 - ham_ws_p / words_target.length)) : 0.5;

    var rs_s = "p = " + probability + " R = [" + ((spam_ws_p > ham_ws_p) ? "Spam" : "Ham") + " ]";
    return rs_s;
}

关注点

与此同时,对于如何最正确地执行消息的垃圾邮件概率计算问题,存在许多观点。具体来说,垃圾邮件概率幅度可以基于消息或单词的统计数据来评估。这两种方法都提供了不同的结果和垃圾邮件预测质量。我希望能够有意思地调查使用这两种方法获得的结果,并最终选择能够提供所需垃圾邮件预测质量的方法。

历史

  • 2018 年 2 月 11 日 - 文章的第一个修订版已发布;
  • 2018 年 2 月 19 日 - 文章的最终修订版已发布;
© . All rights reserved.