zoukankan      html  css  js  c++  java
  • 朴素贝叶斯

    1 准备知识:条件概率公式

    相信学过概率论的同学对于概率论绝对不会陌生,如果一时觉得生疏,可以查阅相关资料,在这里主要是想贴出条件概率的计算公式:

      P(A|B)=P(A,B)/P(B)=P(B|A)*P(A)/P(B)

    2 如何使用条件概率进行分类

      假设这里要被分类的类别有两类,类c1和类c2,那么我们需要计算概率p(c1|x,y)和p(c2|x,y)的大小并进行比较:

    如果:p(c1|x,y)>p(c2|x,y),则(x,y)属于类c1

             p(c1|x,y)<p(c2|x,y),则(x,y)属于类c2

      我们知道p(x,y|c)的条件概率所表示的含义为:已知类别c1条件下,取到点(x,y)的概率;那么p(c1|x,y)所要表达的含义呢?显然,我们同样可以按照条件概率的方法来对概率含义进行描述,即在给定点(x,y)的条件下,求该点属于类c1的概率值。那么这样的概率该如何计算呢?显然,我们可以利用贝叶斯准则来进行变换计算:
      p(ci|x,y)=p(x,y|ci)*p(ci)/p(x,y)

    利用上面的公式,我们可以计算出在给定实例点的情况下,分类计算其属于各个类别的概率,然后比较概率值,选择具有最大概率的那么类作为点(x,y)的预测分类结果。

      以上我们知道了通过贝叶斯准则来计算属于各个分类的概率值,那么具体而言,就是计算贝叶斯公式中的三个概率,只要得到了这三个概率值,显然我们就能通过贝叶斯算法预测分类的结果了。因此,到了这里,我们就知道了朴树贝叶斯算法的核心所在了。

    3 朴素贝叶斯中朴素含义

       "朴素"含义:本章算法全称叫朴素贝叶斯算法,显然除了贝叶斯准备,朴素一词同样重要。这就是我们要说的条件独立性假设的概念。条件独立性假设是指特征之间的相互独立性假设,所谓独立,是指的是统计意义上的独立,即一个特征或者单词出现的可能性与它和其他单词相邻没有关系。举个例子来说,假设单词bacon出现在unhealthy后面是与delisious后面的概率相同。当然,我们知道其实并不正确,但这正是朴素一词的含义。同时,朴素贝叶斯另外一个含义是,这些特征同等重要。虽然这些假设都有一定的问题,但是朴素贝叶斯的实际效果却很好。

    二,朴素贝叶斯完成文档分类

          朴素贝叶斯的一个非常重要的应用就是文档分类。在文档分类中,整个文档(比如一封电子邮件)是实例,那么邮件中的单词就可以定义为特征。说到这里,我们有两种定义文档特征的方法。一种是词集模型,另外一种是词袋模型。顾名思义,词集模型就是对于一篇文档中出现的每个词,我们不考虑其出现的次数,而只考虑其在文档中是否出现,并将此作为特征;假设我们已经得到了所有文档中出现的词汇列表,那么根据每个词是否出现,就可以将文档转为一个与词汇列表等长的向量。而词袋模型,就是在词集模型的基础上,还要考虑单词在文档中出现的次数,从而考虑文档中某些单词出现多次所包含的信息。

         好了,讲了关于文档分类的特征描述之后,我们就可以开始编代码,实现具体的文本分类了

    1 拆分文本,准备数据

      要从文本中获取特征,显然我们需要先拆分文本,这里的文本指的是来自文本的词条,每个词条是字符的任意组合。词条可以为单词,当然也可以是URL,IP地址或者其他任意字符串。将文本按照词条进行拆分,根据词条是否在词汇列表中出现,将文档组成成词条向量,向量的每个值为1或者0,其中1表示出现,0表示未出现。

    接下来,以在线社区的留言为例。对于每一条留言进行预测分类,类别两种,侮辱性和非侮辱性,预测完成后,根据预测结果考虑屏蔽侮辱性言论,从而不影响社区发展。

         词表到向量的转换函数

    #---------------------------从文本中构建词条向量-------------------------
    #1 要从文本中获取特征,需要先拆分文本,这里特征是指来自文本的词条,每个词
    #条是字符的任意组合。词条可以理解为单词,当然也可以是非单词词条,比如URL
    #IP地址或者其他任意字符串 
    #  将文本拆分成词条向量后,将每一个文本片段表示为一个词条向量,值为1表示出现
    #在文档中,值为0表示词条未出现
    
    
    #导入numpy
    from numpy import *
    
    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'],
                     ['my','licks','ate','my','steak','how',
                      'to','stop','him'],
                     ['quit','buying','worthless','dog','food','stupid']]
        #由人工标注的每篇文档的类标签
        classVec=[0,1,0,1,0,1]
        return postingList,classVec
    
    #统计所有文档中出现的词条列表    
    def createVocabList(dataSet):
        #新建一个存放词条的集合
        vocabSet=set([])
        #遍历文档集合中的每一篇文档
        for document in dataSet:
            #将文档列表转为集合的形式,保证每个词条的唯一性
            #然后与vocabSet取并集,向vocabSet中添加没有出现
            #的新的词条        
            vocabSet=vocabSet|set(document)
        #再将集合转化为列表,便于接下来的处理
        return list(vocabSet)
    
    #根据词条列表中的词条是否在文档中出现(出现1,未出现0),将文档转化为词条向量    
    def setOfWords2Vec(vocabSet,inputSet):
        #新建一个长度为vocabSet的列表,并且各维度元素初始化为0
        returnVec=[0]*len(vocabSet)
        #遍历文档中的每一个词条
        for word in inputSet:
            #如果词条在词条列表中出现
            if word in vocabSet:
                #通过列表获取当前word的索引(下标)
                #将词条向量中的对应下标的项由0改为1
                returnVec[vocabSet.index(word)]=1
            else: print('the word: %s is not in my vocabulary! '%'word')
        #返回inputet转化后的词条向量
        return returnVec

    2 由词向量计算朴素贝叶斯用到的概率值

      这里,如果我们将之前的点(x,y)换成词条向量w(各维度的值由特征是否出现的0或1组成),在这里词条向量的维度和词汇表长度相同。

      p(ci|w)=p(w|ci)*p(ci)/p(w)

    我们将使用该公式计算文档词条向量属于各个类的概率,然后比较概率的大小,从而预测出分类结果。

      具体地,首先,可以通过统计各个类别的文档数目除以总得文档数目,计算出相应的p(ci);然后,基于条件独立性假设,将w展开为一个个的独立特征,那么就可以将上述公式写为p(w|ci)=p(w0|ci)*p(w1|ci)*...p(wN|ci),这样就很容易计算,从而极大地简化了计算过程。

      函数的伪代码为:

    计算每个类别文档的数目

    计算每个类别占总文档数目的比例

    对每一篇文档:

      对每一个类别:

        如果词条出现在文档中->增加该词条的计数值#统计每个类别中出现的词条的次数

        增加所有词条的计数值#统计每个类别的文档中出现的词条总数 

      对每个类别:

        将各个词条出现的次数除以类别中出现的总词条数目得到条件概率

    返回每个类别各个词条的条件概率和每个类别所占的比例

    代码如下:

    #训练算法,从词向量计算概率p(w0|ci)...及p(ci)
    #@trainMatrix:由每篇文档的词条向量组成的文档矩阵
    #@trainCategory:每篇文档的类标签组成的向量
    def trainNB0(trainMatrix,trainCategory):
        #获取文档矩阵中文档的数目
        numTrainDocs=len(trainMatrix)
        #获取词条向量的长度
        numWords=len(trainMatrix[0])
        #所有文档中属于类1所占的比例p(c=1)
        pAbusive=sum(trainCategory)/float(numTrainDocs)
        #创建一个长度为词条向量等长的列表
        p0Num=zeros(numWords);p1Num=zeros(numWords)
        p0Denom=0.0;p1Denom=0.0
        #遍历每一篇文档的词条向量
        for i in range(numTrainDocs):
            #如果该词条向量对应的标签为1
            if trainCategory[i]==1:
                #统计所有类别为1的词条向量中各个词条出现的次数
                p1Num+=trainMatrix[i]
                #统计类别为1的词条向量中出现的所有词条的总数
                #即统计类1所有文档中出现单词的数目
                p1Denom+=sum(trainMatrix[i])
            else:
                #统计所有类别为0的词条向量中各个词条出现的次数
                p0Num+=trainMatrix[i]
                #统计类别为0的词条向量中出现的所有词条的总数
                #即统计类0所有文档中出现单词的数目
                p0Denom+=sum(trainMatrix[i])
        #利用NumPy数组计算p(wi|c1)
        p1Vect=p1Num/p1Denom  #为避免下溢出问题,后面会改为log()
        #利用NumPy数组计算p(wi|c0)
        p0Vect=p0Num/p0Denom  #为避免下溢出问题,后面会改为log()
        return p0Vect,p1Vect,pAbusive

    3 针对算法的部分改进

    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

    2)解决下溢出问题

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

    #p0Vect=log(p0Num/p0Denom);p1Vect=log(p1Num/p1Denom)
    #朴素贝叶斯分类函数
    #@vec2Classify:待测试分类的词条向量
    #@p0Vec:类别0所有文档中各个词条出现的频数p(wi|c0)
    #@p0Vec:类别1所有文档中各个词条出现的频数p(wi|c1)
    #@pClass1:类别为1的文档占文档总数比例
    def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
        #根据朴素贝叶斯分类函数分别计算待分类文档属于类1和类0的概率
        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:
            #将每篇文档利用words2Vec函数转为词条向量,存入文档矩阵中
            trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
        #将文档矩阵和类标签向量转为NumPy的数组形式,方便接下来的概率计算
        #调用训练函数,得到相应概率值
        p0V,p1V,pAb=trainNB0(array(trainMat),array(listClasses))
        #测试文档
        testEntry=['love','my','dalmation']
        #将测试文档转为词条向量,并转为NumPy数组的形式
        thisDoc=array(setOfWords2Vec(myVocabList,testEntry))
        #利用贝叶斯分类函数对测试文档进行分类并打印
        print(testEntry,'classified as:',classifyNB(thisDoc,p0V,p1V,pAb))
        #第二个测试文档
        testEntry1=['stupid','garbage']
        #同样转为词条向量,并转为NumPy数组的形式
        thisDoc1=array(setOfWords2Vec(myVocabList,testEntry1))
        print(testEntry1,'classified as:',classifyNB(thisDoc1,p0V,p1V,pAb))
    #贝叶斯算法实例:过滤垃圾邮件
    
    #处理数据长字符串
    #1 对长字符串进行分割,分隔符为除单词和数字之外的任意符号串
    #2 将分割后的字符串中所有的大些字母变成小写lower(),并且只
    #保留单词长度大于3的单词
    def testParse(bigString):
        import re
        listOfTokens=re.split(r'W*',bigString)
        return [tok.lower() for tok in listOPosts if len(tok)>2]
    
    def spamTest():
        #新建三个列表
        docList=[];classList=[];fullTest=[]
        #i 由1到26
        for i in range(1,26):
            #打开并读取指定目录下的本文中的长字符串,并进行处理返回
            wordList=testParse(open('email/spam/%d.txt' %i).read())
            #将得到的字符串列表添加到docList
            docList.append(wordList)
            #将字符串列表中的元素添加到fullTest
            fullTest.extend(wordList)
            #类列表添加标签1
            classList.append(1)
            #打开并取得另外一个类别为0的文件,然后进行处理
            wordList=testParse(open('email/ham/&d.txt' %i).read())
            docList.append(wordList)
            fullTest.extend(wordList)
            classList.append(0)
        #将所有邮件中出现的字符串构建成字符串列表
        vocabList=createVocabList(docList)
        #构建一个大小为50的整数列表和一个空列表
        trainingSet=range(50);testSet=[]
        #随机选取1~50中的10个数,作为索引,构建测试集
        for i in range(10):
            #随机选取1~50中的一个整型数
            randIndex=int(random.uniform(0,len(trainingSet)))
            #将选出的数的列表索引值添加到testSet列表中
            testSet.append(trainingSet[randIndex])
            #从整数列表中删除选出的数,防止下次再次选出
            #同时将剩下的作为训练集
            del(trainingSet[randIndex])
        #新建两个列表
        trainMat=[];trainClasses=[]
        #遍历训练集中的吗每个字符串列表
        for docIndex in trainingSet:
            #将字符串列表转为词条向量,然后添加到训练矩阵中
            trainMat.append(setOfWords2Vec(vocabList,fullTest[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('the error rate is:',float(errorCount)/len(testSet))
  • 相关阅读:
    BigDecimal 的用法
    奇葩问题:Invalid bound statement (not found): cn.zss.zsdemo.mapper.RoleMapper.selectByPrimaryKey
    论文速览:MFQE 2.0: A New Approach for Multi-frame Quality Enhancement on Compressed Video
    博文目录
    【DEBUG】ImportError: No module named 'pygal'
    反直觉的三门问题
    一些卷积概念和图解
    MATLAB随手记
    Python3随手记
    论文写作随手记
  • 原文地址:https://www.cnblogs.com/echoboy/p/9842918.html
Copyright © 2011-2022 走看看