《机器学习实战》之四——朴素贝叶斯

《机器学习实战》之四——朴素贝叶斯

knn和决策树要求分类器对输入给出分类的结果。而『朴素贝叶斯』是给出一个最优的类别猜测结果,同时给出这个猜测的概率估计值,选择具有高概率的决策值。

一. 数学部分

朴素贝叶斯的核心还是贝叶斯公式,只不过加上了一些假设条件:

以二分类问题为背景:
要判断物体属于哪一类,我们可以通过比较p(c1|x)与p(c2|x)的大小,哪个大,我们就认为属于哪一类!
1. 根据贝叶斯公式

在这里插入图片描述
问题转换为比较:

在这里插入图片描述

对于p(c1), p(c2),利用训练样本,我们可以通过已知分类,或者手动分类来获得一个大的样本中,有多少输入C1,多少输入C2,从而计算出p(c1), p(c2),那么,现在的问题就是计算p(x|c1), p(x|c2)
注意:对于二分类问题来说p(c2)=1-p(c1)

2. 朴素贝叶斯
对于一个物品x,我们可以用x1,x2,…,xn特征来代表它。即
在这里插入图片描述
朴素贝叶斯的假设:假设每个特征都是相互独立的。于是有
在这里插入图片描述
那这样问题就简单多了,利用训练样本,我们只需要计算每个类别下每个特征的条件概率就可以了。
注意:朴素贝叶斯分类器有两种实现方法:一种是基于贝努力模型实现(该方法任务特征是同等重要的,所以在实现过程中并不考虑词在文档中出现的次数);另一种是基于多项式模型实现的(它考虑词在文档中的出现次数)

二. 准备数据阶段

依照机器学习的步骤,首先是准备输入数据。我们同样生成一个简单的训练数据。新建文件bayes.py,编辑代码如下:

  1. 转换为可接受的数据格式
##词表到向量的转换函数
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'],
                   ['qiut','buying','worthless','dog','food','stupid']]
    classVec = [0,1,0,1,0,1]  #1 代表侮辱性文字, 0 代表正常言论
    return postingList, classVec
  1. 接下来我们就要统计文档中的单词种类了,也就是数据集中的特征。编辑如下代码:
##创建一个不重复的词汇表
def createVocabList(dataSet):
    vocabSet = set([])  #创建空集
    for document in dataSet:
        vocabSet = vocabSet | set(document) #创建两个集合的并集
    return list(vocabSet)
  1. 有了词汇表,我们就可以通过词汇表对输入的数据进行分析了。我们构建一个函数分析输入数据。该函数的输入参数为:词汇表及数据词条。输出为何词汇条同样大小的向量,其中向量中的值非0即1,表示词条中是否出现该单词。
    处理过程:建立和等长的向量,遍历文档中的所有单词,如果文档在那个出现了词汇表中的单词,则将输出的文档向量中的对应值设为1,具体代码如下:
#将输入的一个文档生成词向量,当所输入的文章中的词在vocablist中出现时,
#就将其对应的向量设置为1,否则维持原来的不变
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

三. NB训练函数

##朴素贝叶斯分类器的训练函数
##输入参数:trainMatrix表示输入的文档矩阵,trainCategory表示每篇文档类别标签 所构成的向量
def trainNB(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix) #计算样本集的样本数
    numWords = len(trainMatrix[0]) #计算出词汇表中的总词数
    
    #计算p(c)的概率,这里指的是分类为1的概率,因为求和之后为0的不考虑
    pAbusive = sum(trainCategory)/float(numTrainDocs) 
    p0Num = np.ones(numWords) #创建包含numWords个0的向量
    p1Num = np.ones(numWords)
    p0Denom = 2.0
    p1Denom = 2.0
    for i in range(numTrainDocs):
        #统计每类单词的数目,注意我们这里讨论的是一个二分问题  
        #所以可以直接用一个if...else...即可,如果分类较多,则需要更改代码 
        if trainCategory[i] == 1:
            #如果说归为1类,那么把这个训练样本的向量和原先已有的向量求和,
            #保证了同一位置(表示同一单词)的数量的叠加,起到计数的作用
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = np.log(p1Num/p1Denom) #p1Num是一个向量,而怕p1Denom是一个数,除了之后计算得到的是每一个单词出现在不同类别的概率
    p0Vect = np.log(p0Num/p0Denom)
    return p0Vect, p1Vect, pAbusive

针对算法部分的改进

  1. 计算概率时,需要计算多个概率的乘积以获得文档属于某个类别的概率,即计算p(w0|ci)p(w1|ci)…p(wN|ci),然后当其中任意一项的值为0,那么最后的乘积也为0.为降低这种影响,
    采用拉普拉斯平滑,在分子上添加a(一般为1),分母上添加ka(k表示类别总数),即在这里将所有词的出现数初始化为1,并将分母初始化为2*1=2

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

  1. 解决下溢出问题

正如上面所述,由于有太多很小的数相乘。计算概率时,由于大部分因子都非常小,最后相乘的结果四舍五入为0,造成下溢出或者得不到准确的结果,所以,我们可以对成绩取自然对数,
即求解对数似然概率。这样,可以避免下溢出或者浮点数舍入导致的错误。同时采用自然对数处理不会有任何损失。
p0Vect=log(p0Num/p0Denom);p1Vect=log(p1Num/p1Denom)

四. NB分类函数

#朴素贝叶斯分类函数,对输入的待分类的文档词向量vec2Classify,给出分类结果
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + np.log(1.0-pClass1)
    if p1>p0:
        return 1
    else:
        return 0

五. 测试NB分类函数

#测试朴素贝叶斯分类函数
def testingNB():
    listOPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts) #生成词汇表
    trainMat = []
    for postingDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postingDoc))
    p0V, p1V, pAb = trainNB(np.array(trainMat),np.array(listClasses)) #训练NB模型,并且得到p0V,p1V,pAb
    
    testEntry = ['love','my','dalmation']
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, "classified as:", classifyNB(thisDoc,p0V,p1V,pAb))
    
    testEntry = ['stupid','garbage']
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, "classified as:", classifyNB(thisDoc,p0V,p1V,pAb))

之前的算法我们只考虑了单词出现与否,使用的是一种词集模型。贝叶斯有两种实现方式,另一种多项式模型,需要考虑每个单词出现的次数,就是所谓的词袋模型。为了适应这种词袋模型,我们需要对函数setOfWords2Vec作一下修改:

#构造词袋模型,每当遇到一个词,会增加词向量中的对应值,而不只是将对应的数值设为1
def bagOfWords2Vec(vocabList,inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec

好了,至此我们完成了整个分类器的实现.接下来我们同样适用一个实例,来测试我们的分类器。

六. 使用朴素贝叶斯进行垃圾邮件过滤

  1. 准备输入数据:我们读取两个邮件文件夹中的内容,并 把其中的每行都切分成可处理的单词。在文件中继续编辑如下代码,实现切分文本。
#文件解析
def textParse(bigString):
    import re
    listOfTokens = re.split(r'\\W*',bigString)
    return [tok.lower() for tok in listOfTokens if len(tok)>2]

  1. 有了可以使用的输入数据类型,接下来我们就可以利用我们之前的算法进行邮件过滤了,继续编辑代码:
#垃圾邮件测试函数
def spamTest():
    docList=[]
    classList=[]
    fullText=[]
    for i in range(1,26): #每个文件夹下都是有25个文本文档,循环读取25次
        wordList = textParse(open('email/spam/%d.txt' % i,encoding="ISO-8859-1").read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        
        wordList = textParse(open('email/ham/%d.txt' %i,encoding="ISO-8859-1").read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList = createVocabList(docList)
     #range(50)表示从0到50,不包括50 
    trainingSet = range(50)
    testSet=[]
    #随机选取10个作为测试集,剩下的作为训练集
    for i in range(10):
        randIndex = int(np.random.uniform(0, len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(list(trainingSet)[randIndex])
    #训练NB模型
    trainMat=[]
    trainClasses=[]
    for docIndex in trainingSet:
        trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V,p1V,pSpam=trainNB(np.array(trainMat),np.array(trainClasses))
    
    #测试
    errorCount = 0
    for docIndex in testSet:
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])
        if classifyNB(np.array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
            errorCount += 1
#            print("classification error:",docList[docIndex])
    print(errorCount)
    print("the error rate is:",float(errorCount)/len(testSet))
#    return vocabList, fullText
        

后记:

猜你喜欢

转载自blog.csdn.net/liujh845633242/article/details/84661106
今日推荐