贝叶斯定理
贝叶斯定理解决了现实生活里经常遇到的问题:已知某条件概率,如何得到两个事件交换后的概率,也就是在已知P(A|B)的情况下如何求得P(B|A)。这里先解释什么是条件概率:
表示事件B已经发生的前提下,事件A发生的概率,叫做事件B发生下事件A的条件概率。其基本求解公式为:。
贝叶斯定理之所以有用,是因为我们在生活中经常遇到这种情况:我们可以很容易直接得出P(A|B),P(B|A)则很难直接得出,但我们更关心P(B|A),贝叶斯定理就为我们打通从P(A|B)获得P(B|A)的道路。
下面不加证明地直接给出贝叶斯定理:
朴素贝叶斯分类的原理与流程
朴素贝叶斯分类是一种十分简单的分类算法,叫它朴素贝叶斯分类是因为这种方法的思想
真
的很朴素。
朴素贝叶斯的思想基础是这样的:对于给出的待分类项,求解在此项出现的条件下各个类别出现的概率,哪个最大,就认为此待分类项属于哪个类别。
通俗来说,就好比这么个道理,你在街上看到一个黑人,我问你你猜这哥们哪里来的,你十有八九猜非洲。为什么呢?因为黑人中非洲人的比率最高,当然人家也可能是美洲人或亚洲人,但在没有其它可用信息下,我们会选择条件概率最大的类别,这就是朴素贝叶斯的思想基础。
朴素贝叶斯分类器应用的学习任务中,每个实例x可由属性值的合取描述,而目标函数f(x)从某有限集合V中取值。学习器被提供一系列关于目标函数的训练样例,以及新实例(描述为属性值的元组)<a1,a2…an>,然后要求预测新实例的目标值(或分类)。
贝叶斯方法的新实例分类目标是在给定描述实例的属性值<a1,a2…an>下,得到最可能的目标值VMAP。
可使用贝叶斯公式将此表达式重写为
(1)
现在要做的是基于训练数据估计(1)式中两个数据项的值。估计每个P(vj)很容易,只要计算每个目标值vj出现在训练数据中的频率就可以。
然而,除非有一非常大的训练数据的集合,否则用这样方法估计不同的P(a1,a2…an|vj)项不太可行。
问题在于这些项的数量等于可能实例的数量乘以可能目标值的数量。因此为获得合理的估计,实例空间中每个实例必须出现多次。
朴素贝叶斯分类器基于一个简单的假定:在给定目标值时属性值之间相互条件独立。
换言之,该假定说明给定实例的目标值情况下,观察到联合的a1,a2…an的概率正好是对每个单独属性的概率乘积:
将其代入(1)式中,可得到朴素贝叶斯分类器所使用的方法:
朴素贝叶斯分类器:
其中vNB表示朴素贝叶斯分类器输出的目标值。
注意在朴素贝叶斯分类器中,须从训练数据中估计的不同P(ai|vj)项的数量只是不同的属性值数量乘以不同目标值数量——这比要估计P(a1,a2…an|vj)项所需的量小得多。
概括地讲,朴素贝叶斯学习方法需要估计不同的P(vj)和P(ai|vj)项,基于它们在训练数据上的频率。这些估计对应了待学习的假设。然后该假设使用上面式中的规则来分类新实例。只要所需的条件独立性能够被满足,朴素贝叶斯分类vNB等于MAP分类。
朴素贝叶斯学习方法和其他已介绍的学习方法之间有一有趣的差别:没有明确的搜索假设空间的过程(这里,可能假设的空间为可被赋予不同的P(vj)和P(ai|vj)项的可能值。相反,假设的形成不需要搜索,只是简单地计算训练样例中不同数据组合的出现频率)。
朴素贝叶斯分类的正式定义如下:
1、设为一个待分类项,而每个a为x的一个特征属性。
2、有类别集合。
3、计算。
4、如果,则。
那么现在的关键就是如何计算第3步中的各个条件概率。我们可以这么做:
1、找到一个已知分类的待分类项集合,这个集合叫做训练样本集。
2、统计得到在各类别下各个特征属性的条件概率估计。即。
3、如果各个特征属性是条件独立的,则根据贝叶斯定理有如下推导:
因为分母对于所有类别为常数,因为我们只要将分子最大化皆可。又因为各特征属性是条件独立的,所以有:
朴素贝叶斯分类实例:按照某人是否要打网球来划分天气
Day |
Outlook |
Temperature |
Humidity |
Wind |
PlayTennis |
D1 |
Sunny |
Hot |
High |
Weak |
No |
D2 |
Sunny |
Hot |
High |
Strong |
No |
D3 |
Overcast |
Hot |
High |
Weak |
Yes |
D4 |
Rain |
Mild |
High |
Weak |
Yes |
D5 |
Rain |
Cool |
Normal |
Weak |
Yes |
D6 |
Rain |
Cool |
Normal |
Strong |
No |
D7 |
Overcast |
Cool |
Normal |
Strong |
Yes |
D8 |
Sunny |
Mild |
High |
Weak |
No |
D9 |
Sunny |
Cool |
Normal |
Weak |
Yes |
D10 |
Rain |
Mild |
Normal |
Weak |
Yes |
D11 |
Sunny |
Mild |
Normal |
Strong |
Yes |
D12 |
Overcast |
Mild |
High |
Strong |
Yes |
D13 |
Overcast |
Hot |
Normal |
Weak |
Yes |
D14 |
Rain |
Mild |
High |
Strong |
No |
这里我们使用此表中的数据结合朴素贝叶斯分类器来分类下面的新实例:
<Outlook=sunny, Temperature=cool,Humidity=high,Wind=strong>
我们的任务是对此新实例预测目标概念PlayTennis 的目标值(yes 或no)。将上面式子应用到当前的任务,目标值vNB 由下式给出:
(2)
注意在最后一个表达式中ai已经用新实例的特定属性值实例化了。为计算vNB,现在需要10个概率,它们都可以训练数据中估计出。
首先不同目标值的概率可以基于这14个训练样例的频率很容易地估计出:
P(PlayTennis=yes)=9/14=0.64
P(PlayTennis=no)=5/14=0.36
相似地,可以估计出条件概率,例如对于Wind=Strong有:
P(Wind=strong|PlayTennis=yes)=3/9=0.33
P(Wind=strong|PlayTennis=no)=3/5=0.60
使用这些概率估计以及相似的对剩余属性的估计,可按照式(2)计算vNB如下(为简明起见忽略了属性名)。
P(yes)P(sunny|yes)P(cool|yes)P(high|yes)P(strong|yes)=0.0053
P(no)P(sunny|no)P(cool|no)P(high|no)P(strong|no)=0.0206
这样,基于从训练数据中学习到的概率估计,朴素贝叶斯分类器将此实例赋以目标值
PlayTennis=
no 。
更进一步,通过将上述的量归一化,可计算给定观察值下目标值为no 的条件概率。对于此例,概率为0.0206/(0.0206+0.0053)=0.795。
从数学角度来说,分类问题可做如下定义:
已知集合:和,确定映射规则,使得任意有且仅有一个使得成立。(不考虑模糊数学里的模糊集情况)
其中C叫做类别集合,其中每一个元素是一个类别,而I叫做项集合,其中每一个元素是一个待分类项,f叫做分类器。分类算法的任务就是构造分类器f。
这里要着重强调,分类问题往往采用经验性方法构造映射规则,即一般情况下的分类问题缺少足够的信息来构造100%正确的映射规则,而是通过对经验数据的学习从而实现一定概率意义上正确的分类,因此所训练出的分类器并不是一定能将每个待分类项准确映射到其分类,分类器的质量与分类器构造方法、待分类数据的特性以及训练样本数量等诸多因素有关。
朴素贝叶斯源码如下:
- #coding=utf-8
- '''
- Created on Oct 19, 2010
-
- @author: Peter
- 基于概率论的分类方法:朴素贝叶斯
-
- 分类器有时会产生错误结果,此时可以要求分类器给出一个最优的类别猜测结果,同时给出这个猜测的概率估计值
- 朴素贝叶斯
- 优点 在数据较少的情况下仍然有效,可以处理多类别问题
- 缺点 对于输入数据的准备方式较为敏感
- 适用数据类型 标称型数据
-
-
-
- 贝叶斯决策理论的核心思想:
- 我们现在用p1 (x,y)表示数据点(x,y)属于类别1的概率,用p2(x,y)表示数据点(x,y)属于类别2的概率
- 对于一个新的数据点(x,y)
- 如果p1(x,y)>p2(x,y),那么类别为1
- 如果p2(x,y)>p1(x,y),那么类别为2
-
-
- 先验条件
- 在调用该方法之前必须为真的条件。
- 后验条件
- 方法顺利执行完毕之后必须为真的条件。
- 条件概率
- 事件A在另外一个事件B已经发生条件下的发生概率。条件概率表示为P(A|B),读作“在B条件下A的概率”。
-
- 朴素贝叶斯的一般过程:
- (1)收集数据:可以使用任何方法。本章使用RSS源。
- (2)准备数据:需要数值型或者布尔型数据。
- (3)分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果更好。
- (4)训练算法:计算不同的独立特征的条件概率。
- (5)测试算法:计算错误率。
- (6)使用算法:一个常见的朴素贝叶斯应用是文档分类。可以在任意的分类场景中使用朴
- 素贝叶斯分类器,不一定非要是文本。
-
-
- 由统计学知,如果每个特征需要N个样本,那么对于10个特征将需要N^10个样本。多余包含1000个特征的词汇需要N^1000个样本
- 可以看出,所需要的样本数会随着特征数目增大而迅速增长
- 如果特征之间相互独立,那么样本数就可以从N^1000减少到1000*N。所谓独立independence,
- 指的是统计意义上的独立,即一个特征或者单词出现的可能性与它和其他单词相邻没有关系
-
-
- 总结:
- 对于分类而言,使用概率有时要比使用硬规则更为有效。贝叶斯概率及贝叶斯准则提供了一
- 种利用已知值来估计未知概率的有效方法。
- 可以通过特征之间的条件独立性假设,降低对数据量的需求。独立性假设是指一个词的出现
- 概率并不依赖于文档中的其他词。当然我们也知道这个假设过于简单。这就是之所以称为朴素贝
- 叶斯的原因。尽管条件独立性假设并不正确,但是朴素贝叶斯仍然是一种有效的分类器。
- 利用现代编程语言来实现朴素贝叶斯时需要考虑很多实际因素。下溢出就是其中一个问题,
- 它可以通过对概率取对数来解决。词袋模型在解决文档分类问题上比词集模型有所提高。还有其
- 他一些方面的改进,比如说移除停用词,当然也可以花大量时间对切分器进行优化。
-
-
- '''
- from numpy import *
- '''
- 将文本看成单词向量或者词条向量,将句子转换成向量考虑出现在所有文
- 档中的所有单词,再决定将哪些词纳人词汇表或者说所要的词汇集合,然后必须要将每一篇文档
- 转换为词汇表上的向量
-
- 第一个函数loadDataSet()创建了一些实验样本。该函数返回的第一个变量是进行词条切
- 分后的文档集合,这些文档来自斑点犬爱好者留言板。这些留言文本被切分成一系列的词条集合,
- 标点符号从文本中去掉,后面会探讨文本处理的细节。loadDataSet()函数返回的第二个变量
- 是一个类别标签的集合。这里有两类,侮辱性和非侮辱性。这些文本的类别由人工标注,这些标
- 注信息用于训练程序以便自动检测侮辱性留言。
-
-
- 词表到向量的转换函数
- '''
- def loadDataSet():
- postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
- ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
- ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
- ['stop', 'posting', 'stupid', 'worthless', 'garbage'],
- ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
- ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
- classVec = [0,1,0,1,0,1] #1 is abusive, 0 not 类别标签的集合
- return postingList,classVec
-
- '''
- 下一个函数createVocabList()会创建一个包含在所有文档中出现的不重复词的列表,为
- 此使用了Python的se七数据类型。将词条列表输给se七构造函数,
- 首先,创建一个空集合,然后将每篇文档返回的新词集合添加到该集合中。操作符}用于求
- 两个集合的并集,这也是一个按位或(OR)操作符(参见附录C)。在数学符号表示上,按位或
- 操作与集合求并操作使用相同记号。
-
-
- set返回一个不重复词表。
- '''
- def createVocabList(dataSet):
- vocabSet = set([]) #create empty set创建一个空集
- for document in dataSet:
- vocabSet = vocabSet | set(document) #union of the two sets 创建两个集合的并集 每篇文档返回的新词集合添加到该集合中
- return list(vocabSet)
-
- '''
-
-
- >>> import bayes
- >>> listOpoosts,listClasses = bayes.loadDataSet()
- >>> myVocabList = bayes.createVocabList(listOpoosts)
- >>> myVocabList
- ['cute', 'love', 'help', 'garbage', 'quit', 'I', 'problems', 'is', 'park', 'stop', 'flea', 'dalmation', 'licks', 'food', 'not', 'him', 'buying', 'posting', 'has', 'worthless', 'ate', 'to', 'maybe', 'please', 'dog', 'how', 'stupid', 'so', 'take', 'mr', 'steak', 'my']
- >>> bayes.setOfWords2Vec(myVocabList,listOpoosts[0])
- [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1]
- >>> bayes.setOfWords2Vec(myVocabList,listOpoosts[3])
- 该函数使用词汇表或者想要检查的所有单词作为输人,然后为其中每一个单词构建一个特
- 征。一旦给定一篇文档(斑点犬网站上的一条留言),该文档就会被转换为词向量。接下来检查
- 一下函数的有效性。
-
- 该函数的输入参数为词汇表及某个文档,输出的是文档向量,向量的每一元素为0或者1
- 分别表示词汇表中的单词在输入文档中是否出现
- '''
- def setOfWords2Vec(vocabList, inputSet):
- returnVec = [0]*len(vocabList) #创建一个其中元素均为0的向量 与词汇表等长
- for word in inputSet:
- if word in vocabList: #遍历文档中所有单词,如果出现了词汇表中的单词,则将输出的文档向量中的对应值设为1
- returnVec[vocabList.index(word)] = 1
- else: print "the word: %s is not in my Vocabulary!" % word
- return returnVec
-
-
- '''
- 训练算法:从词向量计算概率
- 伪代码如下:
- 计算每个类别中的文档数目
- 对每篇训练文档:
- 对每个类别:
- 如果词条出现文档中。增加该词条的计数值
- 增加所有词条的计数值
- 对每个类别:
- 对每个词条:
- 将该词条的数目除以总词条数目得到条件概率
- 返回每个类别的条件概率
-
- 代码函数中的输人参数为文档矩阵trainMatrix,以及由每篇文档类别标签所构成的向量
- 七rainCategory。首先,计算文档属于侮辱性文档(class=1)的概率,即P(1)。因为这是一个二
- 类分类问题,所以可以通过1-P(1)得到P(0)。对于多于两类的分类问题,则需要对代码稍加修改。
-
- 由于w中元素如此众多,因此可以使用NumPy数组快速计算这些值。上述程序中的分母变量是一个元素个数等于词
- 汇表大小的NumPy数组。在for循环中,要遍历训练集trainMatrix中的所有文档。一旦某个词
- 语(侮辱性或正常词语)在某一文档中出现,则该词对应的个数(p1Num或者pONum)就加1,
- 而且在所有的文档中,该文档的总词数也相应加1)。对于两个类别都要进行同样的计算处理。
- 最后,对每个元素除以该类别中的总词数。利用NumPy可以很好实现,用一个数组除以浮
- 点数即可,若使用常规的Python列表则难以完成这种任务,读者可以自己尝试一下。最后,函数
- 会返回两个向量和一个概率。
-
- >>> trainMat = []
- >>> for postinDoc in listOpoosts:
- trainMat.append(bayes.setOfWords2Vec(myVocabList,postinDoc))
-
-
- >>> p0V,p1V,pAb = bayes.trainNB0(trainMat,listClasses)
-
- 利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率
- 如果其中一个概率值为0,那么最后的乘积也为0。为降低这种影响,可以将所有词的出现数初始化为1,并将分母初始化为2
- 另一个遇到的问题是下溢出,这是由于太多很小的数相乘造成的。计算p(w0|c1)p(w1|c1)...p(wn|c1)时,
- 由于大部分因子都非常小,所以程序会下溢出或者得到不正确的答案。一种解决办法是对乘积取自然对数。在代数中有ln(a*b)=ln(a)+ln(b),于是通过求对数可以
- 避免下溢出或者浮点数舍人导致的错误。同时.采用自然对数进行处理不会有任何损失。
- 给出函数f (x)与ln(f (x) )的曲线。检查这两条曲线,就会发现它们在相同区域内同时增加或者
- 减少,并且在相同点上取到极值。它们的取值虽然不同,但不影响最终结果。通过修改return
- 前的两行代码,将上述做法用到分类器中:
- p1Vect = log(p1Num/p1Denom)
- p0Vect = log(p0Num/p0Denom)
-
- 使用NumPy的数组来计算两个向量相乘的结果.。这里的相乘是指对应元素
- 相乘,即先将两个向量中的第1个元素相乘,然后将第2个元素相乘,以此类推。接下来将词汇表
- 中所有词的对应值相加,然后将该值加到类别的对数概率上。最后,比较类别的概率返回大概率
- 对应的类别标签。
-
- trainMatrix
- 文档矩阵
- trainCategory
- 每篇文档类别标签构成的向量
- '''
- def trainNB0(trainMatrix,trainCategory):
- numTrainDocs = len(trainMatrix)
- numWords = len(trainMatrix[0])
- pAbusive = sum(trainCategory)/float(numTrainDocs)
- p0Num = ones(numWords); p1Num = ones(numWords) #change to ones() 初始化概率 初始化分子变量和分母变量
- p0Denom = 2.0; p1Denom = 2.0 #change to 2.0
- for i in range(numTrainDocs): #遍历训练集trainMatrix中的所有文档
- if trainCategory[i] == 1: #一旦某个词在某一文档中出现,则该词的个数+1 所有文档中,该文档的总词数也+1
- p1Num += trainMatrix[i] #向量相加
- p1Denom += sum(trainMatrix[i])
- else:
- p0Num += trainMatrix[i]
- p0Denom += sum(trainMatrix[i])
- p1Vect = log(p1Num/p1Denom) #change to log() 对每个元素做除法 对每个元素除以该类别中的总词数
- p0Vect = log(p0Num/p0Denom) #change to log()
- return p0Vect,p1Vect,pAbusive
-
- def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): #四个输入值 要分类的向量vec2Classify 使用trainNB0计算得到的三个概率 使用numpy的数组计算两个向量相乘的结果 比较类别的概率返回大概率对应的类别标签
- p1 = sum(vec2Classify * p1Vec) + log(pClass1) #element-wise mult 元素相乘
- p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
- if p1 > p0:
- return 1
- else:
- return 0
- '''
- 准备数据:文档词袋模型
- 目前为止,我们将每个词的出现与否作为一个特征,这可以被描述为词集模型(set-of-words
- model )。如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表
- 达的某种信息,这种方法被称为词袋模型(bag-of-words model )。在词袋中,每个单词可以出现
- 多次,而在词集中,每个词只能出现一次。为适应词袋模型,需要对函数se七OfWords2Vec()
- 稍加修改.修改后的函数称为baaOfWords2Vec()
-
- 使用朴素贝叶斯对电子邮件进行分类:
- (1)收集数据:提供文本文件。
- (2)准备数据:将文本文件解析成词条向量。
- (3)分析数据:检查词条确保解析的正确性。
- (4)训练算法:使用我们之前建立的trainNBO()函数。
- (5)测试算法:使用classifyNB(),并且构建一个新的测试函数来计算文档集的错误率。
- (6)使用算法:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。
-
-
- 朴素贝叶斯词袋模型
- '''
- def bagOfWords2VecMN(vocabList, inputSet):
- returnVec = [0]*len(vocabList)
- for word in inputSet:
- if word in vocabList:
- returnVec[vocabList.index(word)] += 1
- return returnVec
-
- def testingNB():
- listOPosts,listClasses = loadDataSet()
- myVocabList = createVocabList(listOPosts)
- trainMat=[]
- for postinDoc in listOPosts:
- trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
- p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses))
- testEntry = ['love', 'my', 'dalmation']
- thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
- print testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb)
- testEntry = ['stupid', 'garbage']
- thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
- print testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb)
-
-
- '''
- 第一个函数textParse()接受一个大字符串并将其解析为字符串列表。该函数去掉少于两
- 个字符的字符串,并将所有字符串转换为小写。你可以在函数中添加更多的解析操作,但是目前
- 的实现对于我们的应用足够了。
-
-
- 使用贝叶斯进行交叉验证
- 文件解析及完整的垃圾邮件测试函数
- '''
- def textParse(bigString): #input is big string, #output is word list
- import re
- listOfTokens = re.split(r'\W*', bigString) #接受一个大字符串并将其解析为字符串列表
- return [tok.lower() for tok in listOfTokens if len(tok) > 2] #去掉少于两个字符的的字符串,并将所有字符转换为小写
- '''
-
- 第二个函数spamTest()对则十斯垃圾邮件分类器进行自动化处理。导人文件夹spam与ham
- 下的文本文件,并将它们解析为词列表.。接下来构建一个测试集与一个训练集,两个集合中的
- 邮件都是随机选出的。本例中共有50封电子邮件,并不是很多,其中的10封电子邮件被随机选择
- 为测试集。分类器所需要的概率计算只利用训练集中的文档来完成。Python变量trainingSet
- 是一个整数列表,其中的值从。到49。接下来,随机选择其中10个文件O。选择出的数字所对应
- 的文档被添加到测试集,同时也将其从训练集中剔除。这种随机选择数据的一部分作为训练集,
- 而剩余部分作为测试集的过程称为留存交叉验证(hold-out cross validation )o假定现在只完成了
- 一次迭代,那么为了更精确地估计分类器的错误率,就应该进行多次迭代后求出平均错误率。
-
- 接下来的for循环遍历训练集的所有文档,对每封邮件基于词汇表并使用setOfWords2Vec()
- 函数来构建词向量。这些词在traindNB0()函数中用于计算分类所需的概率。然后遍历测试集,
- 对其中每封电子邮件进行分类。如果邮件分类错误,则错误数加1,最后给出总的错误百分比。
-
- 函数spamTest()会输出在10封随机选择的电子邮件上的分类错误率。既然这些电子邮件
- 是随机选择的,所以每次的输出结果可能有些差别。如果发现错误的话,函数会输出错分文
- 档的词表,这样就可以了解到底是哪篇文档发生了错误。如果想要更好地估计错误率,那么
- 就应该将上述过程重复多次,比如说10次,然后求平均值。我这么做了一下,获得的平均错
- 误率为6%
-
-
- '''
- def spamTest():
- docList=[]; classList = []; fullText =[]
- for i in range(1,26): #导入并解析文本文件
- wordList = textParse(open('email/spam/%d.txt' % i).read()) #导入Spam与ham下的文件 并将其解析为词列表
- docList.append(wordList)
- fullText.extend(wordList)
- classList.append(1)
- wordList = textParse(open('email/ham/%d.txt' % i).read())
- docList.append(wordList)
- fullText.extend(wordList)
- classList.append(0)
- vocabList = createVocabList(docList)#create vocabulary
- trainingSet = range(50); testSet=[] #create test set
- for i in range(10): #随机构建训练集 随机选取10个文件
- randIndex = int(random.uniform(0,len(trainingSet)))
- testSet.append(trainingSet[randIndex]) #选择出的数字及对应的文档被添加到测试集 同时 将其从训练集中剔除
- del(trainingSet[randIndex])
- trainMat=[]; trainClasses = []
- for docIndex in trainingSet:#train the classifier (get probs) trainNB0
- trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
- trainClasses.append(classList[docIndex])
- p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
- errorCount = 0
- for docIndex in testSet: #classify the remaining items 对测试集分类
- wordVector = bagOfWords2VecMN(vocabList, docList[docIndex]) #for循环遍历训练集的所有文档 对每封邮件基于词汇表并使用bagOfWords2VecMN函数来构建词向量
- if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]: #这些词在traindNB0()函数中用于计算分类所需的概率,遍历测试集,对每封电子邮件进行分类
- errorCount += 1 #如果邮件分类错误,错误数加1,最后给出总的错误百分比
- print "classification error",docList[docIndex]
- print 'the error rate is: ',float(errorCount)/len(testSet)
- #return vocabList,fullText
-
- '''
- 使用朴素贝叶斯来发现地域相关的用词
- (1)收集数据:从RSS源收集内容,这里需要对RSS源构建一个接口。
- (2)准备数据:将文本文件解析成词条向量。
- (3)分析数据:检查词条确保解析的正确性。
- (’)训练算法:使用我们之前建立的trainNBO()函数。
- (5)测试算法:观察错误率,确保分类器可用。可以修改切分程序,以降低错误率,提高
- 分类结果。
- (6)使用算法:构建一个完整的程序,封装所有内容。给定两个RSS源,该程序会显示最
- 常用的公共词。
-
- calcMostFreq该函数遍历词汇表中的每个词并统计它在文本中出现的次数,然后根据出现次数从高到低对词典进行排序,最后返回排序最高的100个单词。你很快就会明白这
- 个函数的重要性。
- 下一个函数localWords()使用两个RSS源作为参数。RSS源要在函数外导人,这样做的原
- 因是RSS源会随时间而改变。如果想通过改变代码来比较程序执行的差异,就应该使用相同的输
- 人。重新加载RSS源就会得到新的数据,但很难确定是代码原因还是输人原因导致输出结果的改
- 变。函数localWords()与程序清单4-5中的spamTest()函数几乎相同,区别在于这里访问的是
- RSS源.而不是文件。然后调用函数calcMostFreq()来获得排序最高的100个单词并随后将它
- 们移除O。函数的剩余部分与spamTest()基本类似,不同的是最后一行要返回下面要用到的值。
- 你可以注释掉用于移除高频词的三行代码,然后比较注释前后的分类性能O。我自己也尝试
- 了一下,去掉这几行代码之后,我发现错误率为54%,而保留这些代码得到的错误率为70%。这
- 里观察到的一个有趣现象是,这些留言中出现次数最多的前30个词涵盖了所有用词的30%。我在
- 进行测试的时候,vocabList的大小约为3000个词。也就是说,词汇表中的一小部分单词却占据
- 了所有文本用词的一大部分。产生这种现象的原因是因为语言中大部分都是冗余和结构辅助性内
- 容。另一个常用的方法是不仅移除高频词,同时从某个预定词表中移除结构上的辅助词。该词表
- 称为停用词表(stop word list ),目前可以找到许多停用词表
-
-
-
- RSS源分类器及高频词去除函数
- '''
-
- def calcMostFreq(vocabList,fullText):
- import operator
- freqDict = {}
- for token in vocabList: #计算出现频率 遍历词汇中的每个词并统计它在文本中出现的次数
- freqDict[token]=fullText.count(token)
- sortedFreq = sorted(freqDict.iteritems(), key=operator.itemgetter(1), reverse=True) #根据从高到低的顺序对词典进行排序,最后返回排序最高的100个单词
- return sortedFreq[:30]
-
- '''
- 类比spamtest函数
- '''
- def localWords(feed1,feed0): #使用两个RSS源作为参数 RSS源要在函数外导入 RSS源会随时间而改变
- import feedparser
- docList=[]; classList = []; fullText =[]
- minLen = min(len(feed1['entries']),len(feed0['entries']))
- for i in range(minLen):
- wordList = textParse(feed1['entries'][i]['summary']) #每次访问一条RSS源
- docList.append(wordList)
- fullText.extend(wordList)
- classList.append(1) #NY is class 1
- wordList = textParse(feed0['entries'][i]['summary'])
- docList.append(wordList)
- fullText.extend(wordList)
- classList.append(0)
- vocabList = createVocabList(docList)#create vocabulary
- top30Words = calcMostFreq(vocabList,fullText) #remove top 30 words 去掉出现次数最高的那些词
- for pairW in top30Words:
- if pairW[0] in vocabList: vocabList.remove(pairW[0])
- trainingSet = range(2*minLen); testSet=[] #create test set
- for i in range(20):
- randIndex = int(random.uniform(0,len(trainingSet)))
- testSet.append(trainingSet[randIndex])
- del(trainingSet[randIndex])
- trainMat=[]; trainClasses = []
- for docIndex in trainingSet:#train the classifier (get probs) trainNB0
- trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
- trainClasses.append(classList[docIndex])
- p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
- errorCount = 0
- for docIndex in testSet: #classify the remaining items
- wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
- if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
- errorCount += 1
- print 'the error rate is: ',float(errorCount)/len(testSet)
- return vocabList,p0V,p1V
- '''
- 分析数据:显示地域相关的用词
- 函数getTopWords()使用两个RSS源作为输人,然后训练并测试朴素贝叶
- 斯分类器,返回使用的概率值。然后创建两个列表用于元组的存储。与之前返回排名最高的X个
- 单词不同,这里可以返回大于某个I}值的所有词。这些元组会按照它们的条件概率进行排序。
-
-
- 最具表征性的词汇显示函数
- '''
- def getTopWords(ny,sf):
- import operator
- vocabList,p0V,p1V=localWords(ny,sf)
- topNY=[]; topSF=[]
- for i in range(len(p0V)):
- if p0V[i] > -6.0 : topSF.append((vocabList[i],p0V[i]))
- if p1V[i] > -6.0 : topNY.append((vocabList[i],p1V[i]))
- sortedSF = sorted(topSF, key=lambda pair: pair[1], reverse=True)
- print "SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**"
- for item in sortedSF:
- print item[0]
- sortedNY = sorted(topNY, key=lambda pair: pair[1], reverse=True)
- print "NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**"
- for item in sortedNY:
- print item[0]
本文缘起于最近在读的一本书-- Tom M.Mitchell的《机器学习》,书中第6章详细讲解了贝叶斯学习的理论知识,为了将其应用到实际中来,参考了网上许多资料,从而得此文。文章将分为两个部分,第一部分将介绍贝叶斯学习的相关理论(如果你对理论不感兴趣,请直接跳至第二部分<<基于朴素贝叶斯分类器的文本分类算法(下)>>)。第二部分讲如何将贝叶斯分类器应用到中文文本分类,随文附上示例代码。
Introduction
我们在《概率论和数理统计》这门课的第一章都学过贝叶斯公式和全概率公式,先来简单复习下:
条件概率
定义 设A, B是两个事件,且P(A)>0 称P(B∣A)=P(AB)/P(A)为在条件A下发生的条件事件B发生的条件概率。
乘法公式 设P(A)>0 则有P(AB)=P(B∣A)P(A)
全概率公式和贝叶斯公式
定义 设S为试验E的样本空间,B1, B2, …Bn为E的一组事件,若BiBj=Ф, i≠j, i, j=1, 2, …,n; B1∪B2∪…∪Bn=S则称B1, B2, …, Bn为样本空间的一个划分。
定理 设试验E的样本空间为,A为E的事件,B1, B2, …,Bn为的一个划分,且P(Bi)>0 (i=1, 2, …n),则P(A)=P(A∣B1)P(B1)+P(A∣B2)+ …+P(A∣Bn)P(Bn)称为全概率公式。
定理 设试验俄E的样本空间为S,A为E的事件,B1, B2, …,Bn为的一个划分,则
P(Bi∣A)=P(A∣Bi)P(Bi)/∑P(B|Aj)P(Aj)=P(B|Ai)P(Ai)/P(B)
称为贝叶斯公式。说明:i,j均为下标,求和均是1到n
下面我再举个简单的例子来说明下。
示例1
考虑一个医疗诊断问题,有两种可能的假设:(1)病人有癌症。(2)病人无癌症。样本数据来自某化验测试,它也有两种可能的结果:阳性和阴性。假设我们已经有先验知识:在所有人口中只有0.008的人患病。此外,化验测试对有病的患者有98%的可能返回阳性结果,对无病患者有97%的可能返回阴性结果。
上面的数据可以用以下概率式子表示:
P(cancer)=0.008,P(无cancer)=0.992
P(阳性|cancer)=0.98,P(阴性|cancer)=0.02
P(阳性|无cancer)=0.03,P(阴性|无cancer)=0.97
假设现在有一个新病人,化验测试返回阳性,是否将病人断定为有癌症呢?我们可以来计算极大后验假设:
P(阳性|cancer)p(cancer)=0.98*0.008 = 0.0078
P(阳性|无cancer)*p(无cancer)=0.03*0.992 = 0.0298
因此,应该判断为无癌症。
贝叶斯学习理论
贝叶斯是一种基于概率的学习算法,能够用来计算显式的假设概率,它基于假设的先验概率,给定假设下观察到不同数据的概率以及观察到的数据本身(后面我们可以看到,其实就这么三点东西,呵呵)。
我们用P(h)表示没有训练样本数据前假设h拥有的初始概率,也就称为h的先验概率,它反映了我们所拥有的关于h是一个正确假设的机会的背景知识。当然如果没有这个先验知识的话,在实际处理中,我们可以简单地将每一种假设都赋给一个相同的概率。类似,P(D)代表将要观察的训练样本数据D的先验概率(也就是说,在没有确定某一个假设成立时D的概率)。然后是P(D/h),它表示假设h成立时观察到数据D的概率。在机器学习中,我们感兴趣的是P(h/D),也就是给定了一个训练样本数据D,判断假设h成立的概率,这也称之为后验概率,它反映了在看到训练样本数据D后假设h成立的置信度。(注:后验概率p(h/D)反映了训练数据D的影响,而先验概率p(h)是独立于D的)。
P(h|D) = P(D|h)P(h)/p(D),从贝叶斯公式可以看出,后验概率p(h/D)取决于P(D|h)P(h)这个乘积,呵呵,这就是贝叶斯分类算法的核心思想。我们要做的就是要考虑候选假设集合H,并在其中寻找当给定训练数据D时可能性最大的假设h(h属于H)。
简单点说,就是给定了一个训练样本数据(样本数据已经人工分类好了),我们应该如何从这个样本数据集去学习,从而当我们碰到新的数据时,可以将新数据分类到某一个类别中去。那可以看到,上面的贝叶斯理论和这个任务是吻合的。
朴素贝叶斯分类
也许你觉得这理论还不是很懂,那我再举个简单的例子,让大家对这个算法的原理有个快速的认识。(注:这个示例摘抄自《机器学习》这本书的第三章的表3-2.)
假设给定了如下训练样本数据,我们学习的目标是根据给定的天气状况判断你对PlayTennis这个请求的回答是Yes还是No。
Day |
Outlook |
Temperature |
Humidity |
Wind |
PlayTennis |
D1 |
Sunny |
Hot |
High |
Weak |
No |
D2 |
Sunny |
Hot |
High |
Strong |
No |
D3 |
Overcast |
Hot |
High |
Weak |
Yes |
D4 |
Rain |
Mild |
High |
Weak |
Yes |
D5 |
Rain |
Cool |
Normal |
Weak |
Yes |
D6 |
Rain |
Cool |
Normal |
Strong |
No |
D7 |
Overcast |
Cool |
Normal |
Strong |
Yes |
D8 |
Sunny |
Mild |
High |
Weak |
No |
D9 |
Sunny |
Cool |
Normal |
Weak |
Yes |
D10 |
Rain |
Mild |
Normal |
Weak |
Yes |
D11 |
Sunny |
Mild |
Normal |
Strong |
Yes |
D12 |
Overcast |
Mild |
High |
Strong |
Yes |
D13 |
Overcast |
Hot |
Normal |
Weak |
Yes |
D14 |
Rain |
Mild |
High |
Strong |
No |
可以看到这里样本数据集提供了14个训练样本,我们将使用此表的数据,并结合朴素贝叶斯分类器来分类下面的新实例:
(Outlook = sunny,Temprature = cool,Humidity = high,Wind = strong)
我们的任务就是对此新实例预测目标概念PlayTennis的目标值(yes或no).
由上面的公式可以得到:
可以得到:
P(PlayTennis =yes) = 9/14 = 0.64,P(PlayTennis=no)=5/14 = 0.36
P(Wind=Stong| PlayTennis =yes)=3/9=0.33,p(Wind=Stong| PlayTennis =no)=3/5 = 0.6
其他数据类似可得,代入后得到:
P(yes)P(Sunny|yes)P(Cool|yes)P(high|yes)P(Strong|yes) = 0.0053
P(no)P(Sunny|no)P(Cool|no)P(high|no)P(Strong|no)=0.0206
因此应该分类到no这一类中。
贝叶斯文本分类算法
好了,现在开始进入本文的主旨部分:如何将贝叶斯分类器应用到中文文本的分类上来?
根据联合概率公式(全概率公式)
M——训练文本集合中经过踢出无用词去除文本预处理之后关键字的数量。
作者:洞庭散人
出处:http://phinecos.cnblogs.com/
本博客遵从
Creative Commons Attribution 3.0 License,若用于非商业目的,您可以自由转载,但请保留原作者信息和文章链接URL。