2.1、什么是贝叶斯分类
据维基百科上的介绍,贝叶斯定理是关于随机事件A 和B 的条件概率和边缘概率的一则定理。
如上所示,其中P(A|B)是在B 发生的情况下A 发生的可能性。在贝叶斯定理中,每个名词都有约定俗成的名称:
∙ P(A)是A 的先验概率或边缘概率。之所以称为" 先验" 是因為它不考虑任何B 方面的因素。 ∙ P(A|B)是已知B 发生后A 的条件概率(直白来讲,就是先有B 而后=>才有A ),也由于得自B 的取值而被称作A 的后验概率。
∙ P(B|A)是已知A 发生后B 的条件概率(直白来讲,就是先有A 而后=>才有B ),也由于得自A 的取值而被称作B 的后验概率。
∙ P(B)是B 的先验概率或边缘概率,也作标准化常量(normalized constant)。
按这些术语,Bayes 定理可表述为:后验概率 = (相似度*先验概率)/标准化常量,也就是說,后验概率与先验概率和相似度的乘积成正比。另外,比例P(B|A)/P(B)也有时被称作标准相似度
(standardised likelihood),Bayes 定理可表述为:后验概率 = 标准相似度*先验概率。
2.2 贝叶斯公式如何而来
贝叶斯公式是怎么来的?下面再举wikipedia 上的一个例子:
一所学校里面有 60% 的男生,40% 的女生。男生总是穿长裤,女生则一半穿长裤一半穿裙子。有了这些信息之后我们可以容易地计算―随机选取一个学生,他(她)穿长裤的概率和穿裙子的概率是多大‖,这个就是前面说的―正向概率‖的计算。然而,假设你走在校园中,迎面走来一个穿长裤的学生(很不幸的是你高度近似,你只看得见他(她)穿的是否长裤,而无法确定他(她)的性别),你能够推断出他(她)是男生的概率是多大吗?
一些认知科学的研究表明(《决策与判断》以及《》第12章:小孩也可以解决贝叶斯问题),我们对形式化的贝叶斯问题不擅长,但对于以频率形式呈现的等价问题却很擅长。在这里,我们不妨把问题重新叙述成:你在校园里面随机游走,遇到了 N 个穿长裤的人(仍然假设你无法直接观察到他们的性别),问这 N 个人里面有多少个女生多少个男生。
你说,这还不简单:算出学校里面有多少穿长裤的,然后在这些人里面再算出有多少女生,不就行了? 我们来算一算:假设学校里面人的总数是 U 个。60% 的男生都穿长裤,于是我们得到了 U * P(Boy) * P(Pants|Boy) 个穿长裤的(男生)(其中 P(Boy) 是男生的概率 = 60%,这里可以简单的理解为男生的比例;P(Pants|Boy) 是条件概率,即在 Boy 这个条件下穿长裤的概率是多大,这里是 100% ,因为所有男生都穿长裤)。40% 的女生里面又有一半(50%)是穿长裤的,于是我们又得到了 U * P(Girl) *
P(Pants|Girl) 个穿长裤的(女生)。加起来一共是 U * P(Boy) * P(Pants|Boy) + U * P(Girl) * P(Pants|Girl) 个穿长裤的,其中有 U * P(Girl) * P(Pants|Girl) 个女生。两者一比就是你要求的答案。
下面我们把这个答案形式化一下:我们要求的是 P(Girl|Pants) (穿长裤的人里面有多少女生),我们计算的结果是 U * P(Girl) * P(Pants|Girl) / [U * P(Boy) * P(Pants|Boy) + U * P(Girl) * P(Pants|Girl)] 。容易发现这里校园内人的总数是无关的,两边同时消去U ,于是得到
P(Girl|Pants) = P(Girl) * P(Pants|Girl) / [P(Boy) * P(Pants|Boy) + P(Girl) *
P(Pants|Girl)]
注意,如果把上式收缩起来,分母其实就是 P(Pants) ,分子其实就是 P(Pants, Girl) 。而这个比例很自然地就读作:在穿长裤的人( P(Pants) )里面有多少(穿长裤)的女孩( P(Pants, Girl) )。 上式中的 Pants 和 Boy/Girl 可以指代一切东西,So ,其一般形式就是:
P(B|A) = P(A|B) * P(B) / [P(A|B) * P(B) + P(A|~B) * P(~B) ]
收缩起来就是:
P(B|A) = P(AB) / P(A)
其实这个就等于:
P(B|A) * P(A) = P(AB)
更进一步阐述,P(B|A)便是在条件A 的情况下,B 出现的概率是多大。然看似这么平凡的贝叶斯公式,背后却隐含着非常深刻的原理。
2.3、拼写纠正
经典著作《人工智能:现代方法》的作者之一 Peter Norvig 曾经写过一篇介绍如何写一个拼写检查/纠正器的文章,里面用到的就是贝叶斯方法,下面,将其核心思想简单描述下。
首先,我们需要询问的是:―问题是什么?‖
问题是我们看到用户输入了一个不在字典中的单词,我们需要去猜测:―这个家伙到底真正想输入的单词是什么呢?‖用刚才我们形式化的语言来叙述就是,我们需要求:
P(我们猜测他想输入的单词 | 他实际输入的单词)
这个概率。并找出那个使得这个概率最大的猜测单词。显然,我们的猜测未必是唯一的,就像前面举的那个自然语言的歧义性的例子一样;这里,比如用户输入: thew ,那么他到底是想输入 the ,还是想输入 thaw ?到底哪个猜测可能性更大呢?幸运的是我们可以用贝叶斯公式来直接出它们各自的概率,我们
不妨将我们的多个猜测记为 h1 h2 .. ( h 代表 hypothesis ),它们都属于一个有限且离散的猜测空间 H (单词总共就那么多而已),将用户实际输入的单词记为 D ( D 代表 Data ,即观测数据),于是 P(我们的猜测1 | 他实际输入的单词)
可以抽象地记为:
P(h1 | D)
类似地,对于我们的猜测2,则是 P(h2 | D)。不妨统一记为:
P(h | D)
运用一次贝叶斯公式,我们得到:
P(h | D) = P(h) * P(D | h) / P(D)
对于不同的具体猜测 h1 h2 h3 .. ,P(D) 都是一样的,所以在比较 P(h1 | D) 和 P(h2 | D) 的时候我们可以忽略这个常数。即我们只需要知道:
P(h | D) ∝ P(h) * P(D | h) (注:那个符号的意思是―正比例于‖,不是无穷大,注意符号右端是有一个小缺口的。)
这个式子的抽象含义是:对于给定观测数据,一个猜测是好是坏,取决于―这个猜测本身独立的可能性大小(先验概率,Prior )‖和―这个猜测生成我们观测到的数据的可能性大小‖(似然,Likelihood )的乘积。具体到我们的那个 thew 例子上,含义就是,用户实际是想输入 the 的可能性大小取决于 the 本身在词汇表中被使用的可能性(频繁程度)大小(先验概率)和 想打 the 却打成 thew 的可能性大小(似然)的乘积。
剩下的事情就很简单了,对于我们猜测为可能的每个单词计算一下 P(h) * P(D | h) 这个值,然后取最大的,得到的就是最靠谱的猜测。更多细节请参看未鹏兄之原文。
2.4、贝叶斯的应用
2.4.1、中文分词
贝叶斯是机器学习的核心方法之一。比如中文分词领域就用到了贝叶斯。浪潮之巅的作者吴军在《数学之美》系列中就有一篇是介绍中文分词的。这里介绍一下核心的思想,不做赘述,详细请参考吴军的原文。 分词问题的描述为:给定一个句子(字串),如:
南京市长江大桥
如何对这个句子进行分词(词串)才是最靠谱的。例如:
1. 南京市/长江大桥
2. 南京/市长/江大桥
这两个分词,到底哪个更靠谱呢?
我们用贝叶斯公式来形式化地描述这个问题,令 X 为字串(句子),Y 为词串(一种特定的分词假设)。我们就是需要寻找使得 P(Y|X) 最大的 Y ,使用一次贝叶斯可得:
P(Y|X) ∝ P(Y)*P(X|Y)
用自然语言来说就是 这种分词方式(词串)的可能性 乘以 这个词串生成我们的句子的可能性。我们进一步容易看到:可以近似地将 P(X|Y) 看作是恒等于 1 的,因为任意假想的一种分词方式之下生成我们的句子总是精准地生成的(只需把分词之间的分界符号扔掉即可)。于是,我们就变成了去最大化 P(Y) ,也就是寻找一种分词使得这个词串(句子)的概率最大化。而如何计算一个词串:
W1, W2, W3, W4 ..
的可能性呢?我们知道,根据联合概率的公式展开:P(W1, W2, W3, W4 ..) = P(W1) * P(W2|W1) * P(W3|W2, W1) * P(W4|W1,W2,W3) * .. 于是我们可以通过一系列的条件概率(右式)的乘积来求整个联合概率。然而不幸的是随着条件数目的增加(P(Wn|Wn-1,Wn-2,..,W1) 的条件有 n-1 个),数据稀疏问题也会越来越严重,即便语料库再大也无法统计出一个靠谱的 P(Wn|Wn-1,Wn-2,..,W1) 来。为了缓解这个问题,计算机科学家们一如既往地使用了―天真‖假设:我们假设句子中一个词的出现概率只依赖于它前面的有限的 k 个词(k 一般不超过 3,如果只依赖于前面的一个词,就是2元语言模型(2-gram ),同理有 3-gram 、 4-gram 等),这个就是所谓的―有限地平线‖假设。
虽然上面这个假设很傻很天真,但结果却表明它的结果往往是很好很强大的,后面要提到的朴素贝叶斯方法使用的假设跟这个精神上是完全一致的,我们会解释为什么像这样一个天真的假设能够得到强大的结果。目前我们只要知道,有了这个假设,刚才那个乘积就可以改写成: P(W1) * P(W2|W1) * P(W3|W2) * P(W4|W3) .. (假设每个词只依赖于它前面的一个词)。而统计 P(W2|W1) 就不再受到数据稀疏问题的困扰了。对于我们上面提到的例子―南京市长江大桥‖,如果按照自左到右的贪婪方法分词的话,结果就成了―南京市长/江大桥‖。但如果按照贝叶斯分词的话(假设使用 3-gram ),由于―南京市长‖和―江大桥‖在语料库中一起出现的频率为 0 ,这个整句的概率便会被判定为 0 。 从而使得―南京市/长江大桥‖这一分词方式胜出。
2.4.2、贝叶斯图像识别,Analysis by Synthesis
贝叶斯方法是一个非常 general 的推理框架。其核心理念可以描述成:Analysis by Synthesis (通过合成来分析)。06 年的认知科学新进展上有一篇 paper 就是讲用贝叶斯推理来解释视觉识别的,一图胜千言,下图就是摘自这篇 paper :
首先是视觉系统提取图形的边角特征,然后使用这些特征自底向上地激活高层的抽象概念(比如是 E 还是 F 还是等号),然后使用一个自顶向下的验证来比较到底哪个概念最佳地解释了观察到的图像。
2.4.3、最大似然与最小二乘
学过线性代数的大概都知道经典的最小二乘方法来做线性回归。问题描述是:给定平面上 N 个点,(这里不妨假设我们想用一条直线来拟合这些点——回归可以看作是拟合的特例,即允许误差的拟合),找出一条最佳描述了这些点的直线。
一个接踵而来的问题就是,我们如何定义最佳?我们设每个点的坐标为 (Xi, Yi) 。如果直线为 y = f(x) 。那么 (Xi, Yi) 跟直线对这个点的―预测‖:(Xi, f(Xi)) 就相差了一个 ΔYi = |Yi – f(Xi)| 。最小二乘就是说寻找直线使得 (ΔY1)^2 + (ΔY2)^2 + .. (即误差的平方和)最小,至于为什么是误差的平方和而不是误差的绝对值和,统计学上也没有什么好的解释。然而贝叶斯方法却能对此提供一个完美的解释。
我们假设直线对于坐标 Xi 给出的预测 f(Xi) 是最靠谱的预测,所有纵坐标偏离 f(Xi) 的那些数据点都含有噪音,是噪音使得它们偏离了完美的一条直线,一个合理的假设就是偏离路线越远的概率越小,具体小多少,可以用一个正态分布曲线来模拟,这个分布曲线以直线对 Xi 给出的预测 f(Xi) 为中心,实际纵坐标为 Yi 的点 (Xi, Yi) 发生的概率就正比于 EXP[-(ΔYi)^2]。(EXP(..) 代表以常数 e 为底的多少次方)。
现在我们回到问题的贝叶斯方面,我们要想最大化的后验概率是:
P(h|D) ∝ P(h) * P(D|h)
又见贝叶斯!这里 h 就是指一条特定的直线,D 就是指这 N 个数据点。我们需要寻找一条直线 h 使得 P(h) * P(D|h) 最大。很显然,P(h) 这个先验概率是均匀的,因为哪条直线也不比另一条更优越。所以我们只需要看 P(D|h) 这一项,这一项是指这条直线生成这些数据点的概率,刚才说过了,生成数据点 (Xi, Yi) 的概率为 EXP[-(ΔYi)^2] 乘以一个常数。而 P(D|h) = P(d1|h) * P(d2|h) * .. 即假设各个数据点是独立生成的,所以可以把每个概率乘起来。于是生成 N 个数据点的概率为 EXP[-(ΔY1)^2] * EXP[-(ΔY2)^2] * EXP[-(ΔY3)^2] * .. = EXP{-[(ΔY1)^2 + (ΔY2)^2 + (ΔY3)^2 + ..]} 最大化这个概率就是要最小化 (ΔY1)^2 + (ΔY2)^2 + (ΔY3)^2 + .. 。 熟悉这个式子吗?
除了以上所介绍的之外,贝叶斯还在词义消岐,语言模型的平滑方法中都有一定应用。下节,咱们再来简单看下朴素贝叶斯方法。
2.5、朴素贝叶斯方法
朴素贝叶斯方法是一个很特别的方法,所以值得介绍一下。在众多的分类模型中,应用最为广泛的两种分类模型是决策树模型(Decision Tree Model)和朴素贝叶斯模型(Naive Bayesian Model,NBC )。 朴素贝叶斯模型发源于古典数学理论,有着坚实的数学基础,以及稳定的分类效率。
同时,NBC 模型所需估计的参数很少,对缺失数据不太敏感,算法也比较简单。理论上,NBC 模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为NBC 模型假设属性之间相互独立,这个假设在实际应用中往往是不成立的,这给NBC 模型的正确分类带来了一定影响。在属性个数比较多或者属性之间相关性较大时,NBC 模型的分类效率比不上决策树模型。而在属性相关性较小时,NBC 模型的性能最为良好。
接下来,我们用朴素贝叶斯在垃圾邮件过滤中的应用来举例说明。
2.5.1、贝叶斯垃圾邮件过滤器
问题是什么?问题是,给定一封邮件,判定它是否属于垃圾邮件。按照先例,我们还是用 D 来表示这封邮件,注意 D 由 N 个单词组成。我们用 h+ 来表示垃圾邮件,h- 表示正常邮件。问题可以形式化地描述为求:
P(h+|D) = P(h+) * P(D|h+) / P(D)
P(h-|D) = P(h-) * P(D|h-) / P(D)
其中 P(h+) 和 P(h-) 这两个先验概率都是很容易求出来的,只需要计算一个邮件库里面垃圾邮件和正常邮件的比例就行了。然而 P(D|h+) 却不容易求,因为 D 里面含有 N 个单词 d1, d2, d3, .. ,所以P(D|h+) = P(d1,d2,..,dn|h+) 。我们又一次遇到了数据稀疏性,为什么这么说呢?P(d1,d2,..,dn|h+) 就是说在垃圾邮件当中出现跟我们目前这封邮件一模一样的一封邮件的概率是多大!开玩笑,每封邮件都是不同的,世界上有无穷多封邮件。瞧,这就是数据稀疏性,因为可以肯定地说,你收集的训练数据库不管里面含了多
少封邮件,也不可能找出一封跟目前这封一模一样的。结果呢?我们又该如何来计算 P(d1,d2,..,dn|h+) 呢?
我们将 P(d1,d2,..,dn|h+) 扩展为: P(d1|h+) * P(d2|d1, h+) * P(d3|d2,d1, h+) * .. 。熟悉这个式子吗?这里我们会使用一个更激进的假设,我们假设 di 与 di-1 是完全条件无关的,于是式子就简化为 P(d1|h+) * P(d2|h+) * P(d3|h+) * .. 。这个就是所谓的条件独立假设,也正是朴素贝叶斯方法的朴素之处。而计算 P(d1|h+) * P(d2|h+) * P(d3|h+) * .. 就太简单了,只要统计 di 这个单词在垃圾邮件中出现的频率即可。关于贝叶斯垃圾邮件过滤更多的内容可以参考这个条目,注意其中提到的其他资料。
2.6、层级贝叶斯模型
层级贝叶斯模型是现代贝叶斯方法的标志性建筑之一。前面讲的贝叶斯,都是在同一个事物层次上的各个因素之间进行统计推理,然而层次贝叶斯模型在哲学上更深入了一层,将这些因素背后的因素(原因的原因,原因的原因,以此类推)囊括进来。一个教科书例子是:如果你手头有 N 枚硬币,它们是同一个工厂铸出来的,你把每一枚硬币掷出一个结果,然后基于这 N 个结果对这 N 个硬币的 θ (出现正面的比例)进行推理。如果根据最大似然,每个硬币的 θ 不是 1 就是 0 (这个前面提到过的),然而我们又知道每个硬币的 p(θ) 是有一个先验概率的,也许是一个 beta 分布。也就是说,每个硬币的实际投掷结果 Xi 服从以 θ 为中心的正态分布,而 θ 又服从另一个以 Ψ 为中心的 beta 分布。层层因果关系就体现出来了。进而 Ψ 还可能依赖于因果链上更上层的因素,以此类推。
2.7、基于newsgroup 文档集的贝叶斯算法实现
2.7.1、newsgroup 文档集介绍与预处理
Newsgroups 最早由Lang 于1995收集并在[Lang 1995]中使用。它含有20000篇左右的Usenet 文档,几乎平均分配20个不同的新闻组。除了其中4.5%的文档属于两个或两个以上的新闻组以外,其余文档仅属于一个新闻组,因此它通常被作为单标注分类问题来处理。Newsgroups 已经成为文本分类聚类中常用的文档集。美国MIT 大学Jason Rennie对Newsgroups 作了必要的处理,使得每个文档只属于一个新闻组,形成Newsgroups-18828。
(注:本2.7节内容主要援引自参考文献条目8的内容,有任何不妥之处,还望原作者及众读者海涵,谢谢)
要做文本分类首先得完成文本的预处理,预处理的主要步骤如下:
1. 英文词法分析,去除数字、连字符、标点符号、特殊 字符,所有大写字母转换成小写,可以用正
则表达式:String res[] = line.split("[^a-zA-Z]");
2. 去停用词,过滤对分类无价值的词;
3. 词根还原stemming ,基于。 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16. private static String lineProcess(String line, ArrayList stopWordsArray) throws IOException { // TODO Auto-generated method stub //step1 英文词法分析,去除数字、连字符、标点符号、特殊字符,所有大写字母转换成小写,可以考虑用正则表达式 String res[] = line.split("[^a-zA-Z]"); //这里要小心,防止把有单词中间有数字和连字符的单词 截断了,但是截断也没事 String resString = new String(); //step2去停用词 //step3stemming,返回后一起做 for (int i = 0; i
2.7.2、特征词的选取
首先统计经过预处理后在所有文档中出现不重复的单词一共有87554个,对这些词进行统计发现: 出现次数大于等于1次的词有87554个
出现次数大于等于3次的词有36456个
出现次数大于等于4次的词有30095个
特征词的选取策略:
策略一:保留所有词作为特征词 共计87554个
策略二:选取出现次数大于等于4次的词作为特征词共计30095个
特征词的选取策略:采用策略一,后面将对两种特征词选取策略的计算时间和平均准确率做对比
2.7.3、贝叶斯算法描述及实现
根据朴素贝叶斯公式,每个测试样例属于某个类别的概率 = 所有测试样例包含特征词类条件概率P(tk|c)之积 * 先验概率P(c)
在具体计算类条件概率和先验概率时,朴素贝叶斯分类器有两种模型:
(1) 多项式模型( multinomial model ) –以单词为粒度
类条件概率P(tk|c)=(类c 下单词tk 在各个文档中出现过的次数之和+1)/(类c 下单词总数+训练样本中不重复特征词总数)
先验概率P(c)=类c 下的单词总数/整个训练样本的单词总数
(2) 伯努利模型(Bernoulli model) –以文件为粒度
类条件概率P(tk|c)=(类c 下包含单词tk 的文件数+1)/(类c 下文件总数+2)
先验概率P(c)=类c 下文件总数/整个训练样本的文件总数
本分类器选用多项式模型计算,根据斯坦福的《Introduction to Information Retrieval 》课件上所说,多项式模型计算准确率更高。
贝叶斯算法的实现有以下注意点:
1. 计算概率用到了BigDecimal 类实现任意精度计算;
2. 用交叉验证法做十次分类实验,对准确率取平均值;
3. 根据正确类目文件和分类结果文计算混淆矩阵并且输出;
4. Map cateWordsProb key为―类目_单词‖, value为该类目下该单词的出现次数,
避免重复计算。 贝叶斯算法实现类如下NaiveBayesianClassifier.java (author :yangliu )
1. package com.pku.yangliu;
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17. import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.math.BigDecimal; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.Vector; /**利用朴素贝叶斯算法对newsgroup 文档集做分类,采用十组交叉测试取平均值 * 采用多项式模型,stanford 信息检索导论课件上面言多项式模型比伯努利模型准确度高
18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55.
56. * 类条件概率P(tk|c)=(类c 下单词tk 在各个文档中出现过的次数之和+1)/(类c 下单词总数+|V|) */ public class NaiveBayesianClassifier { /**用贝叶斯法对测试文档集分类 * @param trainDir 训练文档集目录 * @param testDir 测试文档集目录 * @param classifyResultFileNew 分类结果文件路径 * @throws Exception */ private void doProcess(String trainDir, String testDir, String classifyResultFileNew) throws Exception { // TODO Auto-generated method stub Map cateWordsNum = new TreeMap();//保存训练集每个类别的总词数 Map cateWordsProb = new TreeMap();//保存训练样本每个类别中每个属性词的出现词数 cateWordsProb = getCateWordsProb(trainDir); cateWordsNum = getCateWordsNum(trainDir); double totalWordsNum = 0.0; //记录所有训练集的总词数 Set> cateWordsNumSet = cateWordsNum.entrySet(); for (Iterator> it = cateWordsNumSet.iterator(); it.hasNext();){ Map.Entry me = it.next(); totalWordsNum += me.getValue(); } //下面开始读取测试样例做分类 Vector testFileWords = new Vector(); String word; File[] testDirFiles = new File(testDir).listFiles(); FileWriter crWriter = new FileWriter(classifyResultFileNew); for (int i = 0; i
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96. String bestCate = null ; for (int k = 0; k cateWordsProb 用" 类目_单词" 对来索引的map, 保存的val 就是该类目下该单词的出现次数 * @throws IOException */ public Map getCateWordsProb(String strDir) throws IOException{ Map cateWordsProb = new TreeMap(); File sampleFile = new File(strDir); File [] sampleDir = sampleFile.listFiles(); String word; for (int i = 0;i
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
数
110.
111.
112.
113.
114.
115.
116.
117.
118. } else { cateWordsProb.put(key, 1.0); } } } } return cateWordsProb; } /**计算某一个测试样本属于某个类别的概率 * @param Map cateWordsProb 记录每个目录中出现的单词及次 * @param File trainFile 该类别所有的训练样本所在目录 * @param Vector testFileWords 该测试样本中的所有词构成的容器 * @param double totalWordsNum 记录所有训练样本的单词总数 * @param Map cateWordsNum 记录每个类别的单词总数 * @return BigDecimal 返回该测试样本在该类别中的概率 * @throws Exception * @throws IOException */ private BigDecimal computeCateProb(File trainFile, Vector testFi
leWords, Map cateWordsNum, double totalWordsNum, Map cateWordsProb) throws Exception {
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
;
133. probability = probability.multiply(xcProb); String me = it.next(); String key = trainFile.getName()+"_"+me; double testFileWordNumInCate; if (cateWordsProb.containsKey(key)){ testFileWordNumInCate = cateWordsProb.get(key); }else testFileWordNumInCate = 0.0; BigDecimal testFileWordNumInCateBD = new BigDecimal(testFileWord BigDecimal xcProb = (testFileWordNumInCateBD.add(new BigDecimal( // TODO Auto-generated method stub BigDecimal probability = new BigDecimal(1); double wordNumInCate = cateWordsNum.get(trainFile.getName()); BigDecimal wordNumInCateBD = new BigDecimal(wordNumInCate); BigDecimal totalWordsNumBD = new BigDecimal(totalWordsNum); for (Iterator it = testFileWords.iterator(); it.hasNext();){ NumInCate); 0.0001))).divide(totalWordsNumBD.add(wordNumInCateBD),10, BigDecimal.ROUND_CEILING)
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175. BigDecimal res = probability.multiply(wordNumInCateBD.divide(totalWo return res; } /**获得每个类目下的单词总数 * @param trainDir 训练文档集目录 * @return Map 的map * @throws IOException */ private Map getCateWordsNum(String trainDir) throws IOEx // TODO Auto-generated method stub Map cateWordsNum = new TreeMap(); File[] sampleDir = new File(trainDir).listFiles(); for (int i = 0; i rightCate = new TreeMap(); Map resultCate = new TreeMap(); rightCate = getMapFromResultFile(classifyResultFile); resultCate = getMapFromResultFile(classifyResultFileNew); rdsNumBD, 10, BigDecimal.ROUND_CEILING)); ception {
177.
178.
179.
180.
181.
182.
183.
184.
185.
186. double rightCount = 0.0; for (Iterator> it = resCateSet.iterator(); Map.Entry me = it.next(); if (me.getValue().equals(rightCate.get(me.getKey()))){ rightCount ++; } } computerConfusionMatrix(rightCate,resultCate); return rightCount / resultCate.size(); } it.hasNext();){
2.1、什么是贝叶斯分类
据维基百科上的介绍,贝叶斯定理是关于随机事件A 和B 的条件概率和边缘概率的一则定理。
如上所示,其中P(A|B)是在B 发生的情况下A 发生的可能性。在贝叶斯定理中,每个名词都有约定俗成的名称:
∙ P(A)是A 的先验概率或边缘概率。之所以称为" 先验" 是因為它不考虑任何B 方面的因素。 ∙ P(A|B)是已知B 发生后A 的条件概率(直白来讲,就是先有B 而后=>才有A ),也由于得自B 的取值而被称作A 的后验概率。
∙ P(B|A)是已知A 发生后B 的条件概率(直白来讲,就是先有A 而后=>才有B ),也由于得自A 的取值而被称作B 的后验概率。
∙ P(B)是B 的先验概率或边缘概率,也作标准化常量(normalized constant)。
按这些术语,Bayes 定理可表述为:后验概率 = (相似度*先验概率)/标准化常量,也就是說,后验概率与先验概率和相似度的乘积成正比。另外,比例P(B|A)/P(B)也有时被称作标准相似度
(standardised likelihood),Bayes 定理可表述为:后验概率 = 标准相似度*先验概率。
2.2 贝叶斯公式如何而来
贝叶斯公式是怎么来的?下面再举wikipedia 上的一个例子:
一所学校里面有 60% 的男生,40% 的女生。男生总是穿长裤,女生则一半穿长裤一半穿裙子。有了这些信息之后我们可以容易地计算―随机选取一个学生,他(她)穿长裤的概率和穿裙子的概率是多大‖,这个就是前面说的―正向概率‖的计算。然而,假设你走在校园中,迎面走来一个穿长裤的学生(很不幸的是你高度近似,你只看得见他(她)穿的是否长裤,而无法确定他(她)的性别),你能够推断出他(她)是男生的概率是多大吗?
一些认知科学的研究表明(《决策与判断》以及《》第12章:小孩也可以解决贝叶斯问题),我们对形式化的贝叶斯问题不擅长,但对于以频率形式呈现的等价问题却很擅长。在这里,我们不妨把问题重新叙述成:你在校园里面随机游走,遇到了 N 个穿长裤的人(仍然假设你无法直接观察到他们的性别),问这 N 个人里面有多少个女生多少个男生。
你说,这还不简单:算出学校里面有多少穿长裤的,然后在这些人里面再算出有多少女生,不就行了? 我们来算一算:假设学校里面人的总数是 U 个。60% 的男生都穿长裤,于是我们得到了 U * P(Boy) * P(Pants|Boy) 个穿长裤的(男生)(其中 P(Boy) 是男生的概率 = 60%,这里可以简单的理解为男生的比例;P(Pants|Boy) 是条件概率,即在 Boy 这个条件下穿长裤的概率是多大,这里是 100% ,因为所有男生都穿长裤)。40% 的女生里面又有一半(50%)是穿长裤的,于是我们又得到了 U * P(Girl) *
P(Pants|Girl) 个穿长裤的(女生)。加起来一共是 U * P(Boy) * P(Pants|Boy) + U * P(Girl) * P(Pants|Girl) 个穿长裤的,其中有 U * P(Girl) * P(Pants|Girl) 个女生。两者一比就是你要求的答案。
下面我们把这个答案形式化一下:我们要求的是 P(Girl|Pants) (穿长裤的人里面有多少女生),我们计算的结果是 U * P(Girl) * P(Pants|Girl) / [U * P(Boy) * P(Pants|Boy) + U * P(Girl) * P(Pants|Girl)] 。容易发现这里校园内人的总数是无关的,两边同时消去U ,于是得到
P(Girl|Pants) = P(Girl) * P(Pants|Girl) / [P(Boy) * P(Pants|Boy) + P(Girl) *
P(Pants|Girl)]
注意,如果把上式收缩起来,分母其实就是 P(Pants) ,分子其实就是 P(Pants, Girl) 。而这个比例很自然地就读作:在穿长裤的人( P(Pants) )里面有多少(穿长裤)的女孩( P(Pants, Girl) )。 上式中的 Pants 和 Boy/Girl 可以指代一切东西,So ,其一般形式就是:
P(B|A) = P(A|B) * P(B) / [P(A|B) * P(B) + P(A|~B) * P(~B) ]
收缩起来就是:
P(B|A) = P(AB) / P(A)
其实这个就等于:
P(B|A) * P(A) = P(AB)
更进一步阐述,P(B|A)便是在条件A 的情况下,B 出现的概率是多大。然看似这么平凡的贝叶斯公式,背后却隐含着非常深刻的原理。
2.3、拼写纠正
经典著作《人工智能:现代方法》的作者之一 Peter Norvig 曾经写过一篇介绍如何写一个拼写检查/纠正器的文章,里面用到的就是贝叶斯方法,下面,将其核心思想简单描述下。
首先,我们需要询问的是:―问题是什么?‖
问题是我们看到用户输入了一个不在字典中的单词,我们需要去猜测:―这个家伙到底真正想输入的单词是什么呢?‖用刚才我们形式化的语言来叙述就是,我们需要求:
P(我们猜测他想输入的单词 | 他实际输入的单词)
这个概率。并找出那个使得这个概率最大的猜测单词。显然,我们的猜测未必是唯一的,就像前面举的那个自然语言的歧义性的例子一样;这里,比如用户输入: thew ,那么他到底是想输入 the ,还是想输入 thaw ?到底哪个猜测可能性更大呢?幸运的是我们可以用贝叶斯公式来直接出它们各自的概率,我们
不妨将我们的多个猜测记为 h1 h2 .. ( h 代表 hypothesis ),它们都属于一个有限且离散的猜测空间 H (单词总共就那么多而已),将用户实际输入的单词记为 D ( D 代表 Data ,即观测数据),于是 P(我们的猜测1 | 他实际输入的单词)
可以抽象地记为:
P(h1 | D)
类似地,对于我们的猜测2,则是 P(h2 | D)。不妨统一记为:
P(h | D)
运用一次贝叶斯公式,我们得到:
P(h | D) = P(h) * P(D | h) / P(D)
对于不同的具体猜测 h1 h2 h3 .. ,P(D) 都是一样的,所以在比较 P(h1 | D) 和 P(h2 | D) 的时候我们可以忽略这个常数。即我们只需要知道:
P(h | D) ∝ P(h) * P(D | h) (注:那个符号的意思是―正比例于‖,不是无穷大,注意符号右端是有一个小缺口的。)
这个式子的抽象含义是:对于给定观测数据,一个猜测是好是坏,取决于―这个猜测本身独立的可能性大小(先验概率,Prior )‖和―这个猜测生成我们观测到的数据的可能性大小‖(似然,Likelihood )的乘积。具体到我们的那个 thew 例子上,含义就是,用户实际是想输入 the 的可能性大小取决于 the 本身在词汇表中被使用的可能性(频繁程度)大小(先验概率)和 想打 the 却打成 thew 的可能性大小(似然)的乘积。
剩下的事情就很简单了,对于我们猜测为可能的每个单词计算一下 P(h) * P(D | h) 这个值,然后取最大的,得到的就是最靠谱的猜测。更多细节请参看未鹏兄之原文。
2.4、贝叶斯的应用
2.4.1、中文分词
贝叶斯是机器学习的核心方法之一。比如中文分词领域就用到了贝叶斯。浪潮之巅的作者吴军在《数学之美》系列中就有一篇是介绍中文分词的。这里介绍一下核心的思想,不做赘述,详细请参考吴军的原文。 分词问题的描述为:给定一个句子(字串),如:
南京市长江大桥
如何对这个句子进行分词(词串)才是最靠谱的。例如:
1. 南京市/长江大桥
2. 南京/市长/江大桥
这两个分词,到底哪个更靠谱呢?
我们用贝叶斯公式来形式化地描述这个问题,令 X 为字串(句子),Y 为词串(一种特定的分词假设)。我们就是需要寻找使得 P(Y|X) 最大的 Y ,使用一次贝叶斯可得:
P(Y|X) ∝ P(Y)*P(X|Y)
用自然语言来说就是 这种分词方式(词串)的可能性 乘以 这个词串生成我们的句子的可能性。我们进一步容易看到:可以近似地将 P(X|Y) 看作是恒等于 1 的,因为任意假想的一种分词方式之下生成我们的句子总是精准地生成的(只需把分词之间的分界符号扔掉即可)。于是,我们就变成了去最大化 P(Y) ,也就是寻找一种分词使得这个词串(句子)的概率最大化。而如何计算一个词串:
W1, W2, W3, W4 ..
的可能性呢?我们知道,根据联合概率的公式展开:P(W1, W2, W3, W4 ..) = P(W1) * P(W2|W1) * P(W3|W2, W1) * P(W4|W1,W2,W3) * .. 于是我们可以通过一系列的条件概率(右式)的乘积来求整个联合概率。然而不幸的是随着条件数目的增加(P(Wn|Wn-1,Wn-2,..,W1) 的条件有 n-1 个),数据稀疏问题也会越来越严重,即便语料库再大也无法统计出一个靠谱的 P(Wn|Wn-1,Wn-2,..,W1) 来。为了缓解这个问题,计算机科学家们一如既往地使用了―天真‖假设:我们假设句子中一个词的出现概率只依赖于它前面的有限的 k 个词(k 一般不超过 3,如果只依赖于前面的一个词,就是2元语言模型(2-gram ),同理有 3-gram 、 4-gram 等),这个就是所谓的―有限地平线‖假设。
虽然上面这个假设很傻很天真,但结果却表明它的结果往往是很好很强大的,后面要提到的朴素贝叶斯方法使用的假设跟这个精神上是完全一致的,我们会解释为什么像这样一个天真的假设能够得到强大的结果。目前我们只要知道,有了这个假设,刚才那个乘积就可以改写成: P(W1) * P(W2|W1) * P(W3|W2) * P(W4|W3) .. (假设每个词只依赖于它前面的一个词)。而统计 P(W2|W1) 就不再受到数据稀疏问题的困扰了。对于我们上面提到的例子―南京市长江大桥‖,如果按照自左到右的贪婪方法分词的话,结果就成了―南京市长/江大桥‖。但如果按照贝叶斯分词的话(假设使用 3-gram ),由于―南京市长‖和―江大桥‖在语料库中一起出现的频率为 0 ,这个整句的概率便会被判定为 0 。 从而使得―南京市/长江大桥‖这一分词方式胜出。
2.4.2、贝叶斯图像识别,Analysis by Synthesis
贝叶斯方法是一个非常 general 的推理框架。其核心理念可以描述成:Analysis by Synthesis (通过合成来分析)。06 年的认知科学新进展上有一篇 paper 就是讲用贝叶斯推理来解释视觉识别的,一图胜千言,下图就是摘自这篇 paper :
首先是视觉系统提取图形的边角特征,然后使用这些特征自底向上地激活高层的抽象概念(比如是 E 还是 F 还是等号),然后使用一个自顶向下的验证来比较到底哪个概念最佳地解释了观察到的图像。
2.4.3、最大似然与最小二乘
学过线性代数的大概都知道经典的最小二乘方法来做线性回归。问题描述是:给定平面上 N 个点,(这里不妨假设我们想用一条直线来拟合这些点——回归可以看作是拟合的特例,即允许误差的拟合),找出一条最佳描述了这些点的直线。
一个接踵而来的问题就是,我们如何定义最佳?我们设每个点的坐标为 (Xi, Yi) 。如果直线为 y = f(x) 。那么 (Xi, Yi) 跟直线对这个点的―预测‖:(Xi, f(Xi)) 就相差了一个 ΔYi = |Yi – f(Xi)| 。最小二乘就是说寻找直线使得 (ΔY1)^2 + (ΔY2)^2 + .. (即误差的平方和)最小,至于为什么是误差的平方和而不是误差的绝对值和,统计学上也没有什么好的解释。然而贝叶斯方法却能对此提供一个完美的解释。
我们假设直线对于坐标 Xi 给出的预测 f(Xi) 是最靠谱的预测,所有纵坐标偏离 f(Xi) 的那些数据点都含有噪音,是噪音使得它们偏离了完美的一条直线,一个合理的假设就是偏离路线越远的概率越小,具体小多少,可以用一个正态分布曲线来模拟,这个分布曲线以直线对 Xi 给出的预测 f(Xi) 为中心,实际纵坐标为 Yi 的点 (Xi, Yi) 发生的概率就正比于 EXP[-(ΔYi)^2]。(EXP(..) 代表以常数 e 为底的多少次方)。
现在我们回到问题的贝叶斯方面,我们要想最大化的后验概率是:
P(h|D) ∝ P(h) * P(D|h)
又见贝叶斯!这里 h 就是指一条特定的直线,D 就是指这 N 个数据点。我们需要寻找一条直线 h 使得 P(h) * P(D|h) 最大。很显然,P(h) 这个先验概率是均匀的,因为哪条直线也不比另一条更优越。所以我们只需要看 P(D|h) 这一项,这一项是指这条直线生成这些数据点的概率,刚才说过了,生成数据点 (Xi, Yi) 的概率为 EXP[-(ΔYi)^2] 乘以一个常数。而 P(D|h) = P(d1|h) * P(d2|h) * .. 即假设各个数据点是独立生成的,所以可以把每个概率乘起来。于是生成 N 个数据点的概率为 EXP[-(ΔY1)^2] * EXP[-(ΔY2)^2] * EXP[-(ΔY3)^2] * .. = EXP{-[(ΔY1)^2 + (ΔY2)^2 + (ΔY3)^2 + ..]} 最大化这个概率就是要最小化 (ΔY1)^2 + (ΔY2)^2 + (ΔY3)^2 + .. 。 熟悉这个式子吗?
除了以上所介绍的之外,贝叶斯还在词义消岐,语言模型的平滑方法中都有一定应用。下节,咱们再来简单看下朴素贝叶斯方法。
2.5、朴素贝叶斯方法
朴素贝叶斯方法是一个很特别的方法,所以值得介绍一下。在众多的分类模型中,应用最为广泛的两种分类模型是决策树模型(Decision Tree Model)和朴素贝叶斯模型(Naive Bayesian Model,NBC )。 朴素贝叶斯模型发源于古典数学理论,有着坚实的数学基础,以及稳定的分类效率。
同时,NBC 模型所需估计的参数很少,对缺失数据不太敏感,算法也比较简单。理论上,NBC 模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为NBC 模型假设属性之间相互独立,这个假设在实际应用中往往是不成立的,这给NBC 模型的正确分类带来了一定影响。在属性个数比较多或者属性之间相关性较大时,NBC 模型的分类效率比不上决策树模型。而在属性相关性较小时,NBC 模型的性能最为良好。
接下来,我们用朴素贝叶斯在垃圾邮件过滤中的应用来举例说明。
2.5.1、贝叶斯垃圾邮件过滤器
问题是什么?问题是,给定一封邮件,判定它是否属于垃圾邮件。按照先例,我们还是用 D 来表示这封邮件,注意 D 由 N 个单词组成。我们用 h+ 来表示垃圾邮件,h- 表示正常邮件。问题可以形式化地描述为求:
P(h+|D) = P(h+) * P(D|h+) / P(D)
P(h-|D) = P(h-) * P(D|h-) / P(D)
其中 P(h+) 和 P(h-) 这两个先验概率都是很容易求出来的,只需要计算一个邮件库里面垃圾邮件和正常邮件的比例就行了。然而 P(D|h+) 却不容易求,因为 D 里面含有 N 个单词 d1, d2, d3, .. ,所以P(D|h+) = P(d1,d2,..,dn|h+) 。我们又一次遇到了数据稀疏性,为什么这么说呢?P(d1,d2,..,dn|h+) 就是说在垃圾邮件当中出现跟我们目前这封邮件一模一样的一封邮件的概率是多大!开玩笑,每封邮件都是不同的,世界上有无穷多封邮件。瞧,这就是数据稀疏性,因为可以肯定地说,你收集的训练数据库不管里面含了多
少封邮件,也不可能找出一封跟目前这封一模一样的。结果呢?我们又该如何来计算 P(d1,d2,..,dn|h+) 呢?
我们将 P(d1,d2,..,dn|h+) 扩展为: P(d1|h+) * P(d2|d1, h+) * P(d3|d2,d1, h+) * .. 。熟悉这个式子吗?这里我们会使用一个更激进的假设,我们假设 di 与 di-1 是完全条件无关的,于是式子就简化为 P(d1|h+) * P(d2|h+) * P(d3|h+) * .. 。这个就是所谓的条件独立假设,也正是朴素贝叶斯方法的朴素之处。而计算 P(d1|h+) * P(d2|h+) * P(d3|h+) * .. 就太简单了,只要统计 di 这个单词在垃圾邮件中出现的频率即可。关于贝叶斯垃圾邮件过滤更多的内容可以参考这个条目,注意其中提到的其他资料。
2.6、层级贝叶斯模型
层级贝叶斯模型是现代贝叶斯方法的标志性建筑之一。前面讲的贝叶斯,都是在同一个事物层次上的各个因素之间进行统计推理,然而层次贝叶斯模型在哲学上更深入了一层,将这些因素背后的因素(原因的原因,原因的原因,以此类推)囊括进来。一个教科书例子是:如果你手头有 N 枚硬币,它们是同一个工厂铸出来的,你把每一枚硬币掷出一个结果,然后基于这 N 个结果对这 N 个硬币的 θ (出现正面的比例)进行推理。如果根据最大似然,每个硬币的 θ 不是 1 就是 0 (这个前面提到过的),然而我们又知道每个硬币的 p(θ) 是有一个先验概率的,也许是一个 beta 分布。也就是说,每个硬币的实际投掷结果 Xi 服从以 θ 为中心的正态分布,而 θ 又服从另一个以 Ψ 为中心的 beta 分布。层层因果关系就体现出来了。进而 Ψ 还可能依赖于因果链上更上层的因素,以此类推。
2.7、基于newsgroup 文档集的贝叶斯算法实现
2.7.1、newsgroup 文档集介绍与预处理
Newsgroups 最早由Lang 于1995收集并在[Lang 1995]中使用。它含有20000篇左右的Usenet 文档,几乎平均分配20个不同的新闻组。除了其中4.5%的文档属于两个或两个以上的新闻组以外,其余文档仅属于一个新闻组,因此它通常被作为单标注分类问题来处理。Newsgroups 已经成为文本分类聚类中常用的文档集。美国MIT 大学Jason Rennie对Newsgroups 作了必要的处理,使得每个文档只属于一个新闻组,形成Newsgroups-18828。
(注:本2.7节内容主要援引自参考文献条目8的内容,有任何不妥之处,还望原作者及众读者海涵,谢谢)
要做文本分类首先得完成文本的预处理,预处理的主要步骤如下:
1. 英文词法分析,去除数字、连字符、标点符号、特殊 字符,所有大写字母转换成小写,可以用正
则表达式:String res[] = line.split("[^a-zA-Z]");
2. 去停用词,过滤对分类无价值的词;
3. 词根还原stemming ,基于。 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16. private static String lineProcess(String line, ArrayList stopWordsArray) throws IOException { // TODO Auto-generated method stub //step1 英文词法分析,去除数字、连字符、标点符号、特殊字符,所有大写字母转换成小写,可以考虑用正则表达式 String res[] = line.split("[^a-zA-Z]"); //这里要小心,防止把有单词中间有数字和连字符的单词 截断了,但是截断也没事 String resString = new String(); //step2去停用词 //step3stemming,返回后一起做 for (int i = 0; i
2.7.2、特征词的选取
首先统计经过预处理后在所有文档中出现不重复的单词一共有87554个,对这些词进行统计发现: 出现次数大于等于1次的词有87554个
出现次数大于等于3次的词有36456个
出现次数大于等于4次的词有30095个
特征词的选取策略:
策略一:保留所有词作为特征词 共计87554个
策略二:选取出现次数大于等于4次的词作为特征词共计30095个
特征词的选取策略:采用策略一,后面将对两种特征词选取策略的计算时间和平均准确率做对比
2.7.3、贝叶斯算法描述及实现
根据朴素贝叶斯公式,每个测试样例属于某个类别的概率 = 所有测试样例包含特征词类条件概率P(tk|c)之积 * 先验概率P(c)
在具体计算类条件概率和先验概率时,朴素贝叶斯分类器有两种模型:
(1) 多项式模型( multinomial model ) –以单词为粒度
类条件概率P(tk|c)=(类c 下单词tk 在各个文档中出现过的次数之和+1)/(类c 下单词总数+训练样本中不重复特征词总数)
先验概率P(c)=类c 下的单词总数/整个训练样本的单词总数
(2) 伯努利模型(Bernoulli model) –以文件为粒度
类条件概率P(tk|c)=(类c 下包含单词tk 的文件数+1)/(类c 下文件总数+2)
先验概率P(c)=类c 下文件总数/整个训练样本的文件总数
本分类器选用多项式模型计算,根据斯坦福的《Introduction to Information Retrieval 》课件上所说,多项式模型计算准确率更高。
贝叶斯算法的实现有以下注意点:
1. 计算概率用到了BigDecimal 类实现任意精度计算;
2. 用交叉验证法做十次分类实验,对准确率取平均值;
3. 根据正确类目文件和分类结果文计算混淆矩阵并且输出;
4. Map cateWordsProb key为―类目_单词‖, value为该类目下该单词的出现次数,
避免重复计算。 贝叶斯算法实现类如下NaiveBayesianClassifier.java (author :yangliu )
1. package com.pku.yangliu;
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17. import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.math.BigDecimal; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.Vector; /**利用朴素贝叶斯算法对newsgroup 文档集做分类,采用十组交叉测试取平均值 * 采用多项式模型,stanford 信息检索导论课件上面言多项式模型比伯努利模型准确度高
18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55.
56. * 类条件概率P(tk|c)=(类c 下单词tk 在各个文档中出现过的次数之和+1)/(类c 下单词总数+|V|) */ public class NaiveBayesianClassifier { /**用贝叶斯法对测试文档集分类 * @param trainDir 训练文档集目录 * @param testDir 测试文档集目录 * @param classifyResultFileNew 分类结果文件路径 * @throws Exception */ private void doProcess(String trainDir, String testDir, String classifyResultFileNew) throws Exception { // TODO Auto-generated method stub Map cateWordsNum = new TreeMap();//保存训练集每个类别的总词数 Map cateWordsProb = new TreeMap();//保存训练样本每个类别中每个属性词的出现词数 cateWordsProb = getCateWordsProb(trainDir); cateWordsNum = getCateWordsNum(trainDir); double totalWordsNum = 0.0; //记录所有训练集的总词数 Set> cateWordsNumSet = cateWordsNum.entrySet(); for (Iterator> it = cateWordsNumSet.iterator(); it.hasNext();){ Map.Entry me = it.next(); totalWordsNum += me.getValue(); } //下面开始读取测试样例做分类 Vector testFileWords = new Vector(); String word; File[] testDirFiles = new File(testDir).listFiles(); FileWriter crWriter = new FileWriter(classifyResultFileNew); for (int i = 0; i
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96. String bestCate = null ; for (int k = 0; k cateWordsProb 用" 类目_单词" 对来索引的map, 保存的val 就是该类目下该单词的出现次数 * @throws IOException */ public Map getCateWordsProb(String strDir) throws IOException{ Map cateWordsProb = new TreeMap(); File sampleFile = new File(strDir); File [] sampleDir = sampleFile.listFiles(); String word; for (int i = 0;i
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
数
110.
111.
112.
113.
114.
115.
116.
117.
118. } else { cateWordsProb.put(key, 1.0); } } } } return cateWordsProb; } /**计算某一个测试样本属于某个类别的概率 * @param Map cateWordsProb 记录每个目录中出现的单词及次 * @param File trainFile 该类别所有的训练样本所在目录 * @param Vector testFileWords 该测试样本中的所有词构成的容器 * @param double totalWordsNum 记录所有训练样本的单词总数 * @param Map cateWordsNum 记录每个类别的单词总数 * @return BigDecimal 返回该测试样本在该类别中的概率 * @throws Exception * @throws IOException */ private BigDecimal computeCateProb(File trainFile, Vector testFi
leWords, Map cateWordsNum, double totalWordsNum, Map cateWordsProb) throws Exception {
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
;
133. probability = probability.multiply(xcProb); String me = it.next(); String key = trainFile.getName()+"_"+me; double testFileWordNumInCate; if (cateWordsProb.containsKey(key)){ testFileWordNumInCate = cateWordsProb.get(key); }else testFileWordNumInCate = 0.0; BigDecimal testFileWordNumInCateBD = new BigDecimal(testFileWord BigDecimal xcProb = (testFileWordNumInCateBD.add(new BigDecimal( // TODO Auto-generated method stub BigDecimal probability = new BigDecimal(1); double wordNumInCate = cateWordsNum.get(trainFile.getName()); BigDecimal wordNumInCateBD = new BigDecimal(wordNumInCate); BigDecimal totalWordsNumBD = new BigDecimal(totalWordsNum); for (Iterator it = testFileWords.iterator(); it.hasNext();){ NumInCate); 0.0001))).divide(totalWordsNumBD.add(wordNumInCateBD),10, BigDecimal.ROUND_CEILING)
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175. BigDecimal res = probability.multiply(wordNumInCateBD.divide(totalWo return res; } /**获得每个类目下的单词总数 * @param trainDir 训练文档集目录 * @return Map 的map * @throws IOException */ private Map getCateWordsNum(String trainDir) throws IOEx // TODO Auto-generated method stub Map cateWordsNum = new TreeMap(); File[] sampleDir = new File(trainDir).listFiles(); for (int i = 0; i rightCate = new TreeMap(); Map resultCate = new TreeMap(); rightCate = getMapFromResultFile(classifyResultFile); resultCate = getMapFromResultFile(classifyResultFileNew); rdsNumBD, 10, BigDecimal.ROUND_CEILING)); ception {
177.
178.
179.
180.
181.
182.
183.
184.
185.
186. double rightCount = 0.0; for (Iterator> it = resCateSet.iterator(); Map.Entry me = it.next(); if (me.getValue().equals(rightCate.get(me.getKey()))){ rightCount ++; } } computerConfusionMatrix(rightCate,resultCate); return rightCount / resultCate.size(); } it.hasNext();){