朴素贝叶斯算法及实践

276 Views

参考书籍《机器学习实战》

KNN和决策树算法要求分类器做出艰难决策,给出“该数据实例属于哪一类”这类问题的明确答案,但有时分类器会产生错误结果。这是可以要求分类器给出一个最优的类别猜测结果,同时给出这个猜测的概率估计值。

基于贝叶斯决策理论的分类方法

朴素贝叶斯
优点:在数据较少的情况下仍然有效,可以处理多类别问题
缺点:对于输入数据的准备方式较为敏感
适用数据类型:标称型数据

假设我们有两类数据集,它由两类数据组成,数据分布如图4-1

我们现在用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

选择高概率对应的类别,这就是贝叶斯决策理论的核心。这类问题上,使用决策树效果不好,KNN计算量太大,贝叶斯是很好的选择。

条件概率:略
贝叶斯公式:

使用贝叶斯进行文档分类

机器学习的重要应用就是文档的自动分类。在文档分类中,整个文档(如一封电子邮件)是实例,而电子邮件中的某些元素则构成特征。虽然电子邮件是一种不断增加的文本,但我们同样也可以对新闻报道、用户留言、政府公文等其它任意类型的文本进行分类。我们可以观察文档中出现的词,并把每个词出现或者不出现作为一个特征,这样得到的特征数目就会和词汇表中的词目一样多。

朴素贝叶斯的一般过程
(1)收集数据:可以使用任何方法
(2)准备数据:需要数值型或者布尔型数据
(3)分析数据:有大量特征时,绘制特征作用不大,直方图效果更好
(4)训练方法:计算不同的独立特征的条件概率
(5)测试算法:计算错误率
(6)使用算法:一个常见的朴素贝叶斯应用是文档分类,可以在任意场景中使用朴素贝叶斯分类器

假设词汇表中有1000个单词,要得到好的概率分布,就需要足够多的数据样本。如果每个特征需要N个样本,对于包含1000个特征的词汇表将需要N^1000个样本。课件,所需样本会随着特征数目指数增大。

如果特征相互独立,则样本数可减少到1000xN。独立指一个特征或者单词出现的可能性与其他单词相邻没有关系。例如“计算机”和“程序员”一起出现的概率比“计算机”和“小人书”的概率大,但我们假设他们的概率是相同的,即“计算机”、“程序员”、“小人书”相互独立,这也是朴素贝叶斯中“朴素”的含义。朴素贝叶斯中另一个假设是每个特征同等重要。虽然这两个假设乍一看都很有问题,但是在实际操作中朴素贝叶斯的效果却很好。


使用Python进行文本分类

准备数据:从文本中构建词向量

我们将把文本看成单词向量或者词条向量,也就是将句子转换成向量。考虑出现在所有文档中的所有单词,再决定将哪些词纳入词汇表。

创建Bayes.py文件,添加下列代码:


#coding=utf-8   含有中文必须规定编码格式
def loadDataSet():#创建试验样本
    postingList = [['my','dog','has','flea','problem','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]#0代表正常言论,1表示侮辱性词汇
    return postingList,classVec

def createVocabList(dataSet):#统计所有文档中出现的不重复词汇
    vocabSet=set([])
    for document in dataSet:
        vocabSet = vocabSet | set(document)
    return list(vocabSet)

def setOfWords2Vec(vocabList,inputSet):
    returnVec = [0] * len(vocabList)#创建一个和输入文档等长的向量,初始全0
    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#得到单词出现次数的向量

为了查看函数的执行效果,输入测试:


import Bayes
listOPosts,listClasses = Bayes.loadDataSet()
myVocabList = Bayes.createVocabList(listOPosts)
print(myVocabList)
print(Bayes.setOfWords2Vec(myVocabList,listOPosts[0]))
print(Bayes.setOfWords2Vec(myVocabList,listOPosts[3]))

训练算法:从词向量计算概率

前面介绍了如何将一组单词转换为一组数字,接下来看看如何使用这些数字计算概率。现在已经知道了一个词是否出现在一篇文档中,也知道了该文档所属的类别。所以,我们重写之前的贝叶斯准则,将之前的x,y替换成w,w表示文档向量。这个例子中,数值个属于词汇表的词个数相同。

首先,通过类别i(侮辱性留言或正常留言)中文档数除以总的文档树来计算概率p(ci)。接下来计算p(w|ci),这就要用到朴素贝叶斯假设。如果将w展开为一个个独立的特征,那么就可以假设,它意味着可以使用p(w0|ci)p(w1|ci)p(w2|ci)...p(wn|ci)来计算上述概率。

打开Bayes.py,添加如下代码:


from numpy import *
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
    return p0Vect,p1Vect,pAbusive

代码输入参数为文档矩阵trainMatrix,以及由每篇文档类别标签所构成的向量trainCategory。计算侮辱性文档的概率p(1),再计算p(wi|c1)和p(wi|c0)。完成后输入测试代码:


import Bayes
listOPosts,listClasses = Bayes.loadDataSet()
myVocabList = Bayes.createVocabList(listOPosts)
trainMat = []
for postinDoc in listOPosts:
    trainMat.append(Bayes.setOfWords2Vec(myVocabList,postinDoc))
p0V,p1V,pAb = Bayes.trainNB0(trainMat,listClasses)
print(p0V)
print(p1V)
print(pAb)


发现文档属于侮辱性文档的概率为0.5.所有概率中最大的值出现在myVocabList第26下标位置,单词为stupid,意味着stupid最能表征类别1.

测试算法:根据现实情况修改分类器

利用贝叶斯分类器的时候,要计算多个概率的成绩以获得文档输入某个类别的概率。如果其中一个概率为0,那么最后的乘积也为0.为降低这种影响,可以将所有词的出现数初始化为1,并将分母初始化为2.将trainNB0对应位置改为:


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

由于大部分因子都非常小,所以程序会下溢出或者得不到正确答案,解决办法为对乘积取对数,即:


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

这样就构建了完整的分类器,再打开Bayes.py,将下列代码添加到Bayes.py


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

def testingNB():#测试分类器
    listOPosts,listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts)
    trainMat = []
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
        p0V,p1V,pAb = trainNB0(trainMat,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)

测试结果:

留下回复

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据