700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 基于朴素贝叶斯的垃圾邮件分类器Java实现和讲解

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

时间:2024-05-23 12:31:59

相关推荐

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

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

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

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

1 数据集

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

1.1 数据集的下载

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. (MIMEDefang) with ESMTP id j7CAoGvt023247

for <lu@>; Sun, 14 Aug 09:59:04 +0800 (CST)

Message-ID: <08121850.j7CAoGvt023247@spam-gw.>

From: "yan"<(8月27-28,上海)培训课程>

Reply-To: yan@"<b4a7r0h0@>

To: lu@

Subject: =?gb2312?B?t8eyxs7xvq3A7bXEssbO8bncwO0to6jJs8XMxKPE4qOp?=

Date: Tue, 30 Aug 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(正常邮件))的基础上,对贝叶斯算法的进一步推导。虽然在实际上,垃圾邮件的概率占多数,但这种方法的正确率仍保持非常高。具体推导过程/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链接:/aGreySky/bayesSpamMail

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。