基于概率论的分类方法:朴素贝叶斯

来源:互联网 发布:python iteritems函数 编辑:程序博客网 时间:2024/05/01 11:47

在这里我们将完成两个过程:1.我们将充分利用Python的文本处理能力将文档切分为词向量,然后利用词向量对文档进行分类。2.我们将构建另一个分类器,观察其在真实的垃圾邮件数据集中的过滤效果。

1.概率知识:
1.1贝叶斯决策理论
朴素贝叶斯是贝叶斯决策理论的一部分,所以讲述朴素贝叶斯之前有必要快速了解一下贝叶斯决策理论。
假设我们有一个数据集,它由两类数据组成,数据分布如图1-1所示:
图1-1
我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中用圆点表示的类别)的概率,用p2(x,y)属于类别2(图中用三角形表示的类别)的概率,那么对于一个新数据点(x,y),可以用下面的规则来进行判断类别:
如果p1(x,y)>p2(x,y),那么类别为1
如果p2(x,y)>p1(x,y),那么类别为2
也就是说,我们会选择高概率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策。

1.2 条件概率
假设现在有一个装了7块石头的盒子,其中3块是灰色的,4块是黑色的,所以从盒子中随机取出一块石头,是灰色的概率为3/7,是黑色的概率是4/7。我们使用p(grey)来表示取到灰色石头的概率。
如果这7块石头放在A、B两个盒子里(A中有2个灰色,2个黑色;B中有1个灰色,2个黑色),那么上述概率该怎么求?
有两种方法求条件概率:1、假定计算的是从B盒取到灰色石头的概率,记作p(gray|bucketB)。条件概率的计算公式如下所示:p(gray|bucketB)=p(gray and bucketB)/p(bucketB).首先,用B盒中灰色的个数除以两个盒子的总数,得到p(gray and bucketB)=1/7,其次,由于B盒中有3块石头,而总数为7,于是p(bucketB)=3/7.不难得到,p(gray|bucketB)的值为1/3,p(gray|bucketA)的值为2/4;
2、另一种计算条件概率的方法为贝叶斯准则,通过交换条件概率中的条件与结果,即如果已知p(x|c),要求p(c|x),那么可以通过以下计算方法得到:p(c|x)=p(x|c)p(c)p(x) .讨论完条件概率,接下来的问题是如何将其应用到分类器中。

1.3 使用条件概率来分类
具体应用贝叶斯准则得到:p(ci|x,y)=p(x,y|ci)p(c)p(x,y)
使用这些定义,可以定义贝叶斯分类准则为:
如果p(c1|x,y)>p(c2|x,y),那么属于类别c1
如果p(c2|x,y)>p(c2|x,y),那么属于类别c2

2.使用Python进行文本分类
要从文本中获取特征,需要先拆分文本。如何做尼?这里的特征是来自文本的词条(token),一个词条是字符的任意组合。可以把词条想象为单词,也可以是非单词词条,如URL、IP地址或者任意其他字符串。然后将每一个文本片段表示为一个词条向量,其中值为1表示词条出现在文档中,0表示词条未出现。
接下来首先介绍将文本转换为数字向量的过程,然后介绍如何基于这些向量来计算条件概率,并在此基础上构建分类器。最后还要介绍一些利用Python实现朴素贝叶斯过程中需要考虑的问题。

2.1 准备数据:从文本中构建词向量
考虑出现在所有文档中的所有单词,再决定将哪些词纳入词汇表,然后必须要将每一篇文章转换为词汇表上的向量。

程序清单2-1:词表到向量的转换函数

def createVocabList(dataSet):     vocabSet = set([])     for document in dataSet:           vocabSet = vocab Set | set(document)      return list(vocabSet)

creatVocabList()会创建一个包含在所有文档中出现的不重复词的列表。将词条列表输给set构造函数,set就会返回一个不重复的词表。首先,创建一个空集合,然后将每篇文档返回的新词集合添加到该集合中。操作符|用于求两个集合的并集。

def setOfWords2Vec(vocabList,inputSet)       returnVec = [0]*len(vocabList)       for word in inputSet:             if word in vocabList:                  returnVec[vocabList.index(word)] = 1             else:print"the word: %s is not in my vocabulary!"%word       return returnVec 

获得词汇表后,便可以使用函数setOfWords2Vec(),该函数的输入参数为词汇表以及某个文档,输出的是文档向量,向量的每一元素为1或0,分别表示词汇表中的单词再输入文档中是否出现。函数首先创建一个和词汇表等长的向量,并将其元素设置为0,接着,遍历文档中的所有单词,如果出现了词汇表中的单词,则将输出的文档向量中的对应值设为1。

2.2 训练算法:从词向量计算概率
程序清单2-2: 朴素贝叶斯分类器训练函数

def trainNB0(trainMatrix , trainCategory)      numTrainDocs = len(trainMatrix)      numWords = len(trainMatrix[0])      pAbusive = sum(trainCategory)/float(numTrainDocs)      p0Num = zeros(numWords); p1Num = zeros(numWords)            p0Denom = 0.0; p1Denom = 0.0                            for i in range(numTrainDocs):           if trainCategory[i] == 1:                p1Num += trainMatrix[i] #每个单词出现的频次                p1Denom += sum(trainMatrix[i])#所有出现的单词总数            else:                p0Num += trainMatrix[i]                p0Denom += sum(trainMatrix[i])      p1Vect = p1Num/p1Denom   #每个单词出现的次数除以该类别中的总单词数        p0Vect = p0Num/p0Denom         

输入参数为所有文档的词向量trainMatrix,以及由每篇文档类别标签所构成的向量trainCategory。

2.3 测试算法:根据现实情况修改分类器
利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算p(w0|1)p(w1|1)p(w2|1)。如果其中一个概率值为0,那么最后乘积也为0。为避免这种影响,可以将所有词的出现次数初始化为1,并将分母初始化为2。将trainNB0()的第4行和第5行修改为:

 p0Num = ones(numWords); p1Num = ones(numWords)     p0Denom = 2.0; p1Denom = 2.0

另一个问题是下溢出。当计算乘积p(w0|ci)p(w1|ci)p(w2|ci)...时,由于大部分因子都很小,所以程序会下溢出或者得到不正确的答案,于是可以通过求对数避免下溢出或者浮点数舍入导致的错误。通过修改return前的两行代码:

p1Vect = log(p1Num/p1Denom)         p0Vect = log(p0Num/p0Denom)

程序清单2-3:朴素贝叶斯分类函数

def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):   p1 = sum(vec2Classify*p1Vec)+log(pClass1)   p0 = sum(vec2Classify*p0Vec)+log(1.0 - pClass1)   if p1>p0:      return 1   else:      return 0

有4个输入:要分类的向量vec2Classify以及使用函数trainNB0()计算得到的三个概率。

2.4 示例:使用朴素贝叶斯过滤垃圾邮件

2.4.1 准备数据:切分文本
程序清单2-4:

  def textParse(bigString):     import re     listOfTokens = re.split(r'\W*',bigString)     return [tok.lower() for tok in listOfTokens if len(tok)>2]

返回长度大于2并转换成小写的字符串。

2.4.2 测试算法:使用朴素贝叶斯进行交叉验证
程序清单2-5:

  def spamTest():     docList = []; classList = []; fullText = []     for i in range(1,26):        wordList = textParse(open('email/spam/%d.txt' %i).read())        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 = creatVocabList(docList)     trainingSet = range(50);testSet = []     for i in range(10):        randIndex = int(random.uniform(0,len(trainingSet)))        testSet.append(trainingSet[randIndex])        del(trainingSet[randIndex])     trainMat = []; trainClasses = []     for docIndex in trainingSet:        trainMat.append(setOfWords2Vec(vocabList,docList[docIndex]))        trainClasses.append(classList[docIndex])     p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))     errorCount = 0     for docIndex in testSet:        wordVector = setOfWords2Vec(vocabList,docList[docIndex])        if classifyNB(array(wordVector),p0V,p1V,pSpam) !=classList[docIndex]:        errorCount += 1        print 'classification error :',docList[docIndex]     print 'the error rate is: ',float(errorCount)/len(testSet)

导入文件夹spam和ham下的文本文件,接下来构建一个测试集与一个训练集,本例中共有50封电子邮件,其中10封被随机选择为测试集。变量trainingSet是一个整数列表,其中的值从0到49。接下来,随机选择其中10个文件。选择出的数字所对应的文档被添加到测试集,同时也将其从训练集中剔除。这种随机选择数据的一部分作为测试集,而剩余部分作为训练集的过程称为留存交叉验证。

0 0
原创粉丝点击