朴素贝叶斯算法最典型的应用就是垃圾邮件的识别,在数据量非常大的情况下,识别的正确率可以达到接近100%,同时实现起来思路并不复杂。本文介绍的就是基于朴素贝叶斯算法的垃圾邮件识别的实现。如果之前对贝叶斯算法不了解的同学可以先阅读这篇文章,非常好懂!https://blog.csdn.net/fisherming/article/details/79509025

这篇文章最后得到一个非常通俗的公式:

第一个“=”代表的就是贝叶斯公式,第二个“=”是在此基础上使用全概率公式进行展开。通过这个公式,使对未知概率的预测转换成了对已知概率的运算。贝叶斯算法的实现其实就是在统计已知数据的概率,最后套入公式计算。

1 数据集

贝叶斯算法实现并不复杂,主要难点在于对数据集的处理,使算法的正确率提高。因此编码之前需要先选择好合适的数据集。

1.1 数据集的下载

2006 TREC Public Spam Corpora

这是我在网上找到的非常不错的一个垃圾邮件数据集,里面有中英文两种垃圾邮件数据集下载。我下载的是中文垃圾邮件,里面包含了几万封正常邮件和垃圾邮件。

1.2 数据集的索引

这份数据集里面包含了3个文件夹,data里面包含的就是邮件文件夹,包含了60000+封邮件,正常、垃圾邮件都有,没有进行分类和标识。

delay和full里面包含的都是索引文件,索引文件里面每个索引对应一封邮件。如

spam ../data/000/000

ham ../data/000/001

代表的是data中000文件下的000文件为一封垃圾(spam为垃圾)邮件;001文件为一封正常(ham)邮件。

1.3 邮件信息的处理

每一封邮件的格式都是邮件传输信息+邮件主体。

Received: from hp-5e1fe6310264 ([218.79.188.136])
    by spam-gw.ccert.edu.cn (MIMEDefang) with ESMTP id j7CAoGvt023247
    for <lu@ccert.edu.cn>; Sun, 14 Aug 2005 09:59:04 +0800 (CST)
Message-ID: <200508121850.j7CAoGvt023247@spam-gw.ccert.edu.cn>
From: "yan"<(8月27-28,上海)培训课程>
Reply-To: yan@vip.163.com"<b4a7r0h0@vip.163.com>
To: lu@ccert.edu.cn
Subject: =?gb2312?B?t8eyxs7xvq3A7bXEssbO8bncwO0to6jJs8XMxKPE4qOp?=
Date: Tue, 30 Aug 2005 10:08:15 +0800
MIME-Version: 1.0
Content-type: multipart/related;
    type="multipart/alternative";
    boundary="----=_NextPart_000_004A_2531AAAC.6F950005"
X-Priority: 3
X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook Express 6.00.2800.1158
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2800.1441

非财务纠淼牟莆窆芾-(沙盘模拟)

------如何运用财务岳硖岣吖芾砑ㄐ

 

[课 程 背 景]

 
   每一位管理和技术人员都清楚地懂得,单纯从技术角度衡量为合算的方案,也许
   却是一个财务陷阱,表面赢利而暗地里亏损,使经
   营者无法接受。如何将技术手段与财务运作相结合,使每位管理和技术人员都从
   老板的角度进行思考,有效地规避财务陷阱,实现管理决策与居勘甑囊恢滦裕
   本课程通过沙盘模拟和案例分析,使企业各级管理和技术人员掌握财务管理知识
   ,利用财务信息改进管理决策,实现管理效益最大化。通过学习本课程,您将:
   ★ 对会计与财务管理有基本了解,提高日常管理活动的财务可行性;
   ★ 掌握业绩评价的依据和方法,评估居导,实施科学的业绩考核;
   ★ 掌握合乎财务栽虻墓芾砭霾叻椒,与老板的思维同步;
   ★ 通过分析关键业绩指标,形成战略规划与全面预算;
   ★ 突出企业管理的重心,形成管理的系统性。

而邮件的传输信息会影响到我们对于邮件的分词,导致正确率下降。因此在实现时需要跳过传输信息这部分。每封邮件的传输信息和主体信息之间会有一行空行,我是通过这个进行分离,只读取空行之后的邮件内容。

2 训练数据集

了解数据集结构后,我们便可以对数据集进行处理。首先需要对一部分数据集进行训练。由于数据集中有60000+邮件,于是我们采用50000封作为训练集,10000封作为测试集。

public static final int TRAIN_MAX_NUM = 50000;//训练邮件最大数
public static final int TEST_MAX_NUM = 10000;//测试邮件最大数

2.1 索引文件读取

训练数直接作用于读取索引的索引数。

    while((indexLine = bufferedReader.readLine())!=null && train_num < TRAIN_MAX_NUM){train_num++;System.out.println(indexLine);String[] typeAndIndex = indexLine.split(" ");String type = typeAndIndex[0];String index = typeAndIndex[1].replace("..","mail/trec06c");String str = readFile(index);

读取一个索引,就需要分离出它所代表的的邮件类型(垃圾或正常)、文件位置。再根据文件位置读取邮件。

2.2 邮件读取、分词

我们需要将邮件读取成一个String字符串,并且跳过传输信息。只有当读取到空行之后才进行字符串拼接。

            String tmp = "";int flag = 0;while ((tmp = br.readLine()) != null){if (flag == 1){str.append(tmp);}if (tmp.equals("") || tmp.length() == 0){flag = 1;}}

读取之后得到的字符串需要进行分词,得到一个个独立语义的词语,并保存在集合中。

这里使用的“结巴分词Java版”

JiebaSegmenter jb = new JiebaSegmenter();
List<String> tempList = jb.sentenceProcess(str);

得到的分词并不是十分完美,里面包含了标点符号这类无意义的字符,因此需要对集合进行清理。这里我只对单个字符或字进行清理,想要得到更完美的集合还需要清理类似多个符号组成的分词。

            List<String> rightList = new ArrayList<>();for (String s : tempList){if(s.length() > 1)rightList.add(s);}

那么,在得到一封邮件的所有分词之后,就可以根据这封邮件的类别(垃圾或正常),对这些分词进行归类。单纯的归入集合中并不能体现出这个词与“垃圾邮件”之间的关联度,为此我们需要使用Map进行存储,存储这个分词在所有“垃圾邮件”中出现的次数。同时,我们也需要对我们所读取的所有分词量进行记录。有了这些才能对上文中的概率公式进行计算。

    public static Map<String, Integer> spamMailMap = new HashMap<>();//垃圾邮件分词表public static Map<String, Integer> hamMailMap = new HashMap<>();//正常邮件分词表public static Integer spamMailSegNum = 0;//垃圾邮件分词总数量public static Integer hamMailSegNum = 0;//正常邮件分词总数量
          if (type.equals("spam")) {spamMailSegNum += rightList.size();for (String s : rightList) {spamMailMap.put(s, spamMailMap.containsKey(s) ? spamMailMap.get(s) + 1 : 1);}}else {hamMailSegNum += rightList.size();for (String s : rightList) {hamMailMap.put(s, hamMailMap.containsKey(s) ? hamMailMap.get(s) + 1 : 1);}}

2.3 分词概率表的统计

这一步我们计算的是对于每个分词,“邮件中出现分词时,该邮件为垃圾邮件的概率”,公式:

P(Spam|ti) =P1(ti)/((P1(ti) +P2(ti));

P1(ti )表示ti出现在垃圾邮件中的次数 / 垃圾邮件分词总个数

P2(ti )表示ti出现在正常邮件中的次数 / 正常邮件分词总个数

这儿如果直接用“ti在垃圾邮件中出现的次数/ti出现的总次数”得到结果的话,在垃圾邮件占多数的情况下,会影响结果。 因此需要用“ti出现的概率代替ti出现的次数”进行计算

    for (Iterator iter = spamMailMap.keySet().iterator(); iter.hasNext();) {String key = (String) iter.next();double rate = (double) spamMailMap.get(key) / (double) spamMailSegNum;double allRate = rate;if (hamMailMap.containsKey(key)) {allRate += (double) hamMailMap.get(key) / (double) hamMailSegNum;}else{ //如果正常邮件中未出现该词,则默认在正常邮件中出现过1次,防止判断过于绝对allRate += 1d / (double) hamMailSegNum;}spamMailRateMap.put(key, rate / allRate);}

特殊情况:当该分词未出现在正常邮件集中,则rate = 1,在之后的计算中会导致垃圾邮件的概率直接为100%,否定了其它分词的作用。这是不合理的,因此引入Laplace校准,默认在正常邮件中出现1次。

3 测试数据集

测试的目标是得到每一封邮件为“垃圾邮件”的概率。在此之前,我们已经得到了训练完成的已知概率集合。只要测试邮件的分词出现在我们的已知概率集合中,我们就能通过概率计算(联合概率公式、贝叶斯公式、全概率公式)计算出它为垃圾邮件的概率。

3.1 读取测试集

和训练数据集类似,测试集的读取仍然依靠着full文件夹下的索引文件进行读取。

因为与训练集读取的是同一份文件,我们需要跳过训练集所对应的索引。start_count为训练集的数量。

            if (nowIndex < start_count) {nowIndex++;continue;}

3.2 计算概率

在垃圾邮件这类二分类问题的概率计算上,可以直接使用贝叶斯公式进行代入计算,也可使用联合概率简化计算。联合概率法是在先验概率=1/2(P(垃圾邮件) = P(正常邮件))的基础上,对贝叶斯算法的进一步推导。虽然在实际上,垃圾邮件的概率占多数,但这种方法的正确率仍保持非常高。具体推导过程https://blog.csdn.net/wangyao_bupt/article/details/55002483

3.2.1 联合概率法

P(S|W1 W2) = P(W1 W2|S)P(S) / (P(W1 W2|S)P(S) + P(W1 W2|~S)P(~S))

W1,W2独立:P(W1 W2) = P(W1)P(W2), P(W1 W2|S) = P(W1|S)P(W2|S) (?)
上式 = P(W1|S)P(W2|S)P(S) / (P(W1|S)P(W2|S)P(S) + P(W1|~S)P(W2|~S)P(~S))

应用Bayesian 原理,将 P(Wi|S) 用 P(S|Wi) 表示:
上式 = (P(S|W1)P(S|W2)P(S) * P(W1)P(W2) / P(S)^2) / ((P(S|W1)P(S|W2)P(S) * P(W1)P(W2) / P(S)^2) + (P(~S|W1)P(~S|W2)P(~S) * P(W1)P(W2) / P(~S)^2))

在 P(S) = P(~S) = 50% 的条件下:
上式 = P(S|W1)P(S|W2) / (P(S|W1)P(S|W2) + P(~S|W1)P(~S|W2))
= P1P2 / (P1P2 + (1-P1)(1-P2));

这是我在网上找到的简单推导方法,由此可以得到一个公式

P(Spam|t1,t2,t3……tn)
=[P(Spam|t1)*P(Spam|t2)*……P( Spam|t1)]
/(P(Spam|t1)*P(Spam|t2)*……P( Spam|tn)+[1-P(Spam|t1)]*(1-P(Spam|t2))*……(1-P(Spam|tn)))

其中, P(Spam|ti)我们已经在2.3 分词概率表中求得。

所以使用联合概率法,我们只需要找到测试邮件的所有分词在分词概率表中的概率,代入公式就能得到结果,非常方便。

具体代码:

                //该邮件是否手动设置概率的标志boolean flag = false;double rate = 1d;double tempRate = 1d;for (String s : wordList) {if (spamMailRateMap.containsKey(s)) {double tmp = spamMailRateMap.get(s);tempRate *= 1d - tmp;rate *= tmp;//当有一个概率非常趋近于0时,需要进行判断// 判断该邮件更接近垃圾邮件还是正常邮件if (tempRate == 0d && rate == 0d){//同时趋于0,则概率设为0.5probability[testNum] = 0.5d;}else if (tempRate == 0d){//正常邮件概率趋于0probability[testNum] = 1d;}else{//垃圾邮件趋于0probability[testNum] = 0d;}if (tempRate == 0d || rate == 0d){//有趋近,则后续不用进行设置概率flag = true;break;}}}if (!flag) //flag=true代表已经手动设置过概率probability[testNum] = rate / (rate + tempRate);

其中rate的结果对应着公式的分子部分,(rate + tempRate)的结果对应着公式的分母部分。probability为邮件的垃圾概率。这一步先存在数组中,最后再与阈值比较。在公式的计算中,rate和tempRate都又可能趋于0。而double型最多只能存储10^-325的数值,小于此数值时会直接即为0。如果不进行处理,后续的probability计算将得到NaN,因此需要进行判断更接近垃圾邮件还是正常邮件。

3.2.2 贝叶斯公式法

贝叶斯公式法相对联合概率法,更容易理解,但需要求更多参数。

  注:P1(ti)为ti出现在垃圾邮件中的次数/垃圾邮件分词总数P2(ti)为ti出现在正常邮件中的次数/正常邮件分词总数P(ti)为ti出现在所有邮件中的次数/邮件分词总数1 计算邮件中出现ti时,该邮件为垃圾邮件的概率P(Spam|ti)=P1(ti)/((P1(ti)+P2(ti))P(ham|ti)=P2(ti)/((P1(ti)+P2(ti))2 贝叶斯公式计算垃圾邮件中ti的概率P(ti|Spam)=P(Spam|ti)*P(ti)/P(Spam)P(ti|ham)=P(ham|ti)*P(ti)/P(ham)=[1-P(Spam|ti)]*P(ti)/[1-P(Spam)]3 贝叶斯公式计算最后概率P(Spam|t1,t2,...,tn)
=P(Spam)*P(t1|Spam)P(t2|Spam)*...*P(tn|Spam)
/[P(Spam)*P(t1|Spam)P(t2|Spam)*...*P(tn|Spam)+ P(ham)*P(t1|ham)P(t2|ham)*...*P(tn|ham)]3.1 分子展开P(Spam)*P(t1|Spam)*P(t2|Spam)*...*P(tn|Spam)
=P(Spam)*[P(Spam|t1)*P(t1)/P(Spam)]*...*[P(Spam|tn)*P(tn)/P(Spam)]3.2 分母后一项展开P(ham)*P(t1|ham)*P(t2|ham)*...*P(tn|ham)
=[1-P(Spam)]*[1-P(Spam|t1)]*P(t1)/[1-P(Spam)]*...*[1-P(Spam|tn)]*P(tn)/[1-P(Spam)]

P(Spam)就是所有邮件中垃圾邮件的概率(先验概率),P(ham) = 1 - P(Spam)

我们在训练后就可以得到:

        //垃圾邮件率spamTrainRate = (double) spamTrainMailNum / (double) train_num;

同时,P(Spam|ti)我们已经在2.3 分词概率表中求得。

P(ti)为ti出现在所有邮件中的次数/邮件分词总数,我们也可以通过计算求得:

((double)(spamMailMap.get(s) + (hamMailMap.containsKey(s)? hamMailMap.get(s): 0)))/ (double) (spamMailSegNum + hamMailSegNum)

至此,分子和分母中所有参数均可以计算。贴上代码:

//该邮件是否手动设置概率的标志boolean flag = false;//P(Spam)*  乘积P(Spam|ti)*P(ti)/P(Spam) (i从1-n)//spamTrainRate = P(Spam)double rate = spamTrainRate;//[1-P(Spam)]*  乘积[1-P(Spam|ti)]*P(ti)/[1-P(Spam)] (i从1-n)// 1 - spamTrainRate = [1-P(Spam)]double tempRate = 1 - spamTrainRate;for (String s : wordList) {if (spamMailRateMap.containsKey(s)) {double spamTmp = spamMailRateMap.get(s);rate *= spamTmp * (((double)(spamMailMap.get(s) + (hamMailMap.containsKey(s)? hamMailMap.get(s): 0))) / (double) (spamMailSegNum + hamMailSegNum)) / spamTrainRate;tempRate *= (1d - spamTmp) * (((double)spamMailMap.get(s)+(hamMailMap.containsKey(s)? hamMailMap.get(s): 0)) / (double) (spamMailSegNum + hamMailSegNum)) / spamTrainRate;;//当有一个概率非常趋近于0时,需要进行判断// 判断该邮件更接近垃圾邮件还是正常邮件if (tempRate == 0d && rate == 0d){//同时趋于0,则概率设为0.5probability[test_num] = 0.5d;}else if (tempRate == 0d){//正常邮件概率趋于0probability[test_num] = 1d;}else{//垃圾邮件趋于0probability[test_num] = 0d;}if (tempRate == 0d || rate == 0d){//有趋近,则后续不用进行设置概率flag = true;break;}}}if (!flag) { //flag=true代表已经手动设置过概率probability[test_num] = rate / (rate + tempRate);}

与联合概率法的明显区别在于rate,tempRate初值的设定(贝叶斯法需要将先验概率作为初值)以及复杂计算公式。

后续

1.优化了联合概率法手动判定邮件类型语句,减少了误判率,消除了0.5概率处样本聚集情况。

                //该邮件是否手动设置概率的标志int flag = 0;double rate = 1d;double tempRate = 1d;for (String s : wordList) {if (spamMailRateMap.containsKey(s)) {double tmp = spamMailRateMap.get(s);tempRate *= 1d - tmp;rate *= tmp;//当有一个概率非常趋近于0时,需要进行判断// 判断该邮件更接近垃圾邮件还是正常邮件if (tempRate == 0d && rate == 0d){//同时趋于0,则概率设为0.5flag = 1;//同时趋于0}else if (tempRate == 0d){//正常邮件概率趋于0probability[testNum] = 1d;flag = 2;//正常趋于0if (rate > Math.pow(10, -200)){//差125量级,直接判定break;}}else if(rate == 0d){//垃圾邮件趋于0probability[testNum] = 0d;flag = 3;//垃圾趋于0if (tempRate > Math.pow(10, -200)){//差125量级,直接判定break;}}if (flag == 1){//已经同时趋于0,不需要继续遍历probability[testNum] = 0.5d;break;}}}if (flag == 0) { //flag!=0代表已经手动设置过概率probability[testNum] = rate / (rate + tempRate);}

2.贝叶斯公式在处理大量分词时,会导致正常邮件概率(tempRate)和垃圾邮件概率(rate)都趋于0, 导致程序判定为概率为0.5

于是优化了贝叶斯公式法,当正常邮件概率(tempRate)和垃圾邮件概率(rate)有一个快趋于0时,将二者都进行放大,不会影响结果,且保证了概率不会趋于0。

if (rate < Math.pow(10, -300) || tempRate < Math.pow(10, -300)){if (rate > 1){probability[testNum] = 1d;flag = 2;break;}else if (tempRate > 1){probability[testNum] = 0d;flag = 3;break;}else {rate *= Math.pow(10, 7);tempRate *= Math.pow(10, 7);}
}

分布恢复正常:

结语

得到probability数组后,我们就可以通过它与阈值的比较,判断出所有邮件的类别(垃圾或正常),在50000条训练集下,两种计算公式均能达到95%以上的正确率。而垃圾邮件分类器,不仅对正确率有要求,还得尽量不能“错杀”任何一封正常邮件。我将在下一篇文章对这个垃圾邮件分类器进行指标评价,欢迎阅读。后续也会将项目源码放在GitHub上。

GitHub链接:https://github.com/aGreySky/bayesSpamMail

基于朴素贝叶斯的垃圾邮件分类器Java实现和讲解相关推荐

  1. Python实现基于朴素贝叶斯的垃圾邮件分类 标签: python朴素贝叶斯垃圾邮件分类 2016-04-20 15:09 2750人阅读 评论(1) 收藏 举报 分类: 机器学习(19) 听说

    Python实现基于朴素贝叶斯的垃圾邮件分类 标签: python朴素贝叶斯垃圾邮件分类 2016-04-20 15:09 2750人阅读 评论(1) 收藏 举报  分类: 机器学习(19)  听说朴 ...

  2. 基于朴素贝叶斯的垃圾邮件分类-着重理解拉普拉斯变换

    1. 引言 在正式学习朴素贝叶斯之前,需要明确的是机器学习所要实现的是基于有限的训练样本集尽可能准确地估计出后验概率P(c|x),即根据特征得到所属类别的概率,首先引入两个概念. 判别式模型(disc ...

  3. 基于朴素贝叶斯的垃圾邮件识别

    在网上看到很多用朴素贝叶斯算法来实现垃圾邮件分类的,有直接调用库的,也有自己写的.出于对贝叶斯算法的复习,我也想用贝叶斯算法写写邮件识别,做一个简单的识别系统. 一.开发环境 Python3.6,邮件 ...

  4. 基于朴素贝叶斯的垃圾邮件分类系统(包含黑白名单和特征库功能)

    目录 一.数据预处理 二.训练 三.测试 四.全部代码及数据集 一.数据预处理 经过常用的数据预处理,如删除停用词等后,统计所有训练集中垃圾邮件的内容,经分词后输出频次,得到垃圾邮件的特征库并画出词云 ...

  5. 基于朴素贝叶斯的垃圾邮件过滤实现

    重点 1 目的是计算P(内容|正常)与P(内容|垃圾)的概率,所以需要计算出字典中所有此在正常.垃圾条件下出现的概率 2 如果词典中的某个词未在正常/垃圾邮件中出现会导致P(内容|正常/垃圾)=0,所 ...

  6. 朴素贝叶斯法 - 垃圾邮件分类

    本文基于朴素贝叶斯构建一个分类垃圾邮件的模型,研究对象是英文的垃圾邮件. 邮件内容保存在txt文件中,其中分为训练样本train和测试样本test. 在训练样本中正常邮件命名为:pos:垃圾邮件命名为 ...

  7. AI基础:朴素贝叶斯与垃圾邮件分类

    来,继续回顾基础算法 文章目录 背景&贝叶斯原理 贝叶斯分类器 朴素贝叶斯分类器 西瓜数据集下的朴素贝叶斯示例 朴素贝叶斯分类的优缺点 朴素贝叶斯关键问题 朴素贝叶斯企业中的应用案例 基于朴素 ...

  8. 机器学习之朴素贝叶斯实现垃圾邮件过滤

    一.朴素贝叶斯概述 朴素贝叶斯法是基于贝叶斯定理与特征条件独立性假设的分类方法.对于给定的训练集,首先基于特征条件独立假设学习输入输出的联合概率分布(朴素贝叶斯法这种通过学习得到模型的机制,显然属于生 ...

  9. 朴素贝叶斯(垃圾邮件分类)

    一.基于贝叶斯决策理论的分类方法 朴素贝叶斯是经典的机器学习算法之一,也是为数不多的基于概率论的分类算法.对于大多数的分类算法,在所有的机器学习分类算法中,朴素贝叶斯和其他绝大多数的分类算法都不同.比 ...

最新文章

  1. 说说CSRF的***
  2. VS2010中的调试技巧
  3. linux 启动后台服务 nohup
  4. 两列布局左边定宽,右边自适应
  5. sap.m.list render initialization process
  6. Android adb 启动APP
  7. webpack配置:css文件打包、JS压缩打包和HTML文件发布
  8. 针对不同pandas版本进行列名的修改
  9. ajax跨越html,ajax跨域的解决方案
  10. 老男孩之《生日快乐》
  11. 从administrator到system权限的几种方式
  12. 大魔王程序员生成记#06.2#C语言习题
  13. java ftp 上传下载
  14. Python 基于sicpy求解定积分 ,不定积分以及多重积分
  15. SourceInsight | source insight使用技巧
  16. BLE蓝牙芯片KT1025A程序代码和调试总结
  17. python基础教程视频纸飞机_叠飞机怎么叠
  18. python之百度贴吧自动签到
  19. 【Unity】从零使用Amplify Shader - 超简单2D外轮廓
  20. 汽车标志品牌查询介绍小程序源码

热门文章

  1. Ubuntu16.04-64位操作系统上安装Cadence Conformal10.1教程
  2. 【标准全文】GB 18384-2020 电动汽车安全要求
  3. 递归问题之小白上楼梯
  4. 微信小程序学习笔记 ( 小程序主体学习 二 逻辑层 app.js 扩展 高级食用方法)
  5. 关于脑电弱形式分部积分的推导
  6. ## 配置项props
  7. 【工业现场】核心工段机器代人,维视智造助力环保行业智能升级
  8. Java-受检异常与非受检异常
  9. Java中的test
  10. restTemplate请求重发的相关设置-通过配置