机器学习(6)——朴素贝叶斯(文本分类)

转载请注明作者和出处:https://blog.csdn.net/qq_28810395
Python版本: Python3.x
运行平台: Windows 10
IDE: Pycharm profession 2019
如有需要数据集或整个工程文件的可以关注Stefan0704回复朴素贝叶斯进行获取。

概率论相关

  学习贝叶斯要涉及一些数学中的概率与条件概率,下面具体讲解,如下图在一个盒子中有7个砖头,其中三块是灰色,四块是黑色,如果随机从中取一块砖头,取到(灰|黑)的概率,就可以通过(灰|黑)砖头的数目除以总的砖头数目来得到。
机器学习(6)——朴素贝叶斯(文本分类)_第1张图片
  上述概率分别为:

         P ( g r e y ) = n u m ( g r e y ) n u m ( a l l ) = 3 7 {P_{(grey)}} = \frac{{num(grey)}}{{num(all)}} = \frac{3}{7} P(grey)=num(all)num(grey)=73

         P ( b l a c k ) = n u m ( b l a c k ) n u m ( a l l ) = 4 7 {P_{(black)}} = \frac{{num(black)}}{{num(all)}} = \frac{4}{7} P(black)=num(all)num(black)=74
  如果放置结果改变如下图,在盒子A、B(左、右)中选择,那么我们随机取就存在A、B盒选择的条件,在这样的条件下,我们在计算取(灰|黑)的概率,这就叫条件概率,其条件概率的定义为:
         P ( g r e y ∣ b o x B ) = P ( g r e y a n d b o x B ) P ( b o x ) {P_{({\rm{grey|boxB}})}} = \frac{{P(grey{\rm{ and }}boxB)}}{{P(box)}} P(greyboxB)=P(box)P(greyandboxB)
机器学习(6)——朴素贝叶斯(文本分类)_第2张图片
  上述例子中我们计算在B盒中取灰砖的概率便为:
         P ( g r e y a n d b o x B ) = n u m ( g r e y i n B ) n u m ( A a n d B ) = 1 / 7 P ( b o x B ) = n u m ( B ) n u m ( a l l ) = 3 / 7 P ( g r e y ∣ b o x B ) = P ( g r e y a n d b o x B ) P ( b o x B ) = 1 / 7 3 / 7 = 1 3 \begin{array}{l} {P_{({\rm{grey and boxB}})}} = \frac{{num(grey{\rm{ in }}B)}}{{num(A{\rm{ and B}})}} = 1/7\\ {P_{({\rm{boxB}})}} = \frac{{num(B)}}{{num(all)}} = 3/7\\ {P_{({\rm{grey|boxB}})}} = \frac{{P(grey{\rm{ and }}boxB)}}{{P(boxB)}} = \frac{{1/7}}{{3/7}} = \frac{1}{3} \end{array} P(greyandboxB)=num(AandB)num(greyinB)=1/7P(boxB)=num(all)num(B)=3/7P(greyboxB)=P(boxB)P(greyandboxB)=3/71/7=31

  贝叶斯准则是将条件概率中的条件和结果进行交换,即已知P(X|C)求P(C|X),其公式定义如下:
         P ( c ∣ x ) = P ( x ∣ c ) P ( c ) P ( x ) {P_{(c|x)}} = \frac{{P(x|c)P(c)}}{{P(x)}} P(cx)=P(x)P(xc)P(c)
具体的公式推导请看贝叶斯推导

朴素贝叶斯简介

  朴素贝叶斯是贝叶斯决策理论的一部分,它是要求分类器给出一个最优的类别猜测结果,同时给出这个猜测的概率估计值的算法。相比于贝叶斯而言的“朴素”,是因为整个形式化过程只做最原始、最简单的假设,也就是特征之间相互独立。
  更简单来说,朴素贝叶斯的思想基础是对于给出的待分类项,求解在此项出现的条件下各个类别出现的概率,哪个最大,就认为此待分类项属于哪个类别。举个简单的例子,你在街上看到一个黑人,我问你你猜这哥们哪里来的,你十有八九猜非洲。为什么呢?因为黑人中非洲人的比率最高,当然人家也可能是美洲人或亚洲人,但在没有其它可用信息下,我们会选择条件概率最大的类别,这就是朴素贝叶斯的思想基础。
具体描述请看朴素贝叶斯分类器
  朴素贝叶斯的优缺点:
  优点:在数据较小的情况下仍然有效,可以处理多类别问题,适用标称型数据。
  缺点:对于输入数据的准备方式较为敏感。

朴素贝叶斯一般流程

  • 收集数据:可以使用任何方法(RSS源)。
  • 准备数据:需要数值型或者布尔型数据。
  • 分析数据:有大量特征时,绘制特征作用不大,这时候使用直方图效果较好。
  • 训练算法:计算不同的独立特征的条件概率。
  • 测试算法:计算错误率。
  • 使用算法:一个常见的朴素贝叶斯应用是文档分类。可以在任何的分类场景中使用朴素贝叶斯分类器,不一定非要是文本。

利用朴素贝叶斯进行文本分类

  以在线社区的留言板为例。为了使社区正能力的蓬勃发展,对于一些侮辱性的言论要进行屏蔽,为此需要一种快速的过滤器,如果某条留言使用了负面或者侮辱性的语句,我们就要标识语言不当,过滤掉相应留言。在此中问题建立两个类别:侮辱类和非侮辱类,使用1和0分别表示。

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

  首先要将处理的文本看成单词向量或者词条向量,也就是将句子转换成向量。

  1. 准备代码
    from numpy import *
    
    def loadDataSet(): #创建一些实验样本
    	#假设数据为最简单的6篇文章,每篇文章大概7~8个词汇左右,如下
        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]    #对应上述6篇文章的分类结果,1为侮辱性,0为非侮辱性
        return postingList,classVec
                     
    def createVocabList(dataSet):# 将所有文章中的词汇取并集汇总
        vocabSet = set([])  # 定义一个set空集(set存储的内容无重复)
        for document in dataSet:# 遍历导入的dataset数据,将所有词汇取并集存储至vocabSet中
            vocabSet = vocabSet | set(document) # | 符号为取并集,即获得所有文章的词汇表
        return list(vocabSet)
    
    def setOfWords2Vec(vocabList, inputSet):#该函数输入参数为词汇表及某篇文章,输出为文档向量,向量每个元素为1或0,分别表示词汇表中的单词在输入文档中是否出现;
        returnVec = [0]*len(vocabList)#构建一个0向量;
        for word in inputSet: # 遍历词汇表,如果文档中出现了词汇表中单词,则将输出的文档向量中对应值设为1,旨在计算各词汇出现的次数;
            if word in vocabList:
                returnVec[vocabList.index(word)] = 1  #因为上一段代码里,给的文章例子里的单词都是不重复的,如果有重复的单词的话,这段代码改写为:returnVec[vocabList.index(word)] += 1更为合适;
            else: print ("the word: %s is not in my Vocabulary!" % word)
        return returnVec #返回向量化后的某篇文章
    
  2. 检验代码
    import bayes
    
    if __name__ == '__main__':
        listOPosts,listClasses = bayes.loadDataSet()
        myVocaList = bayes.createVocabList(listOPosts)
        print(myVocaList)
    
  3. 结果
    ['garbage', 'help', 'posting', 'quit', 'worthless', 'maybe', 'park', 'is', 'food', 'I', 'has', 'love', 'cute', 'buying', 'take', 'stop', 'mr', 'dog', 'please', 'him', 'ate', 'steak', 'licks', 'so', 'to', 'flea', 'stupid', 'dalmation', 'how', 'my', 'problems', 'not']
    
    会发现我们现在已经可以把文本分割成不会重复的单词,现在我们检验一下词条是否在文档中,输出文档向量。
  4. 检验代码
    import bayes
    
    if __name__ == '__main__':
        listOPosts,listClasses = bayes.loadDataSet()
        myVocaList = bayes.createVocabList(listOPosts)
        print(myVocaList)
        print(listOPosts[0])
        print(bayes.setOfWords2Vec(myVocaList,listOPosts[0]))#检测文档1存在情况并输出文档向量
        print(listOPosts[2])
        print(bayes.setOfWords2Vec(myVocaList, listOPosts[2]))  # 检测文档3存在情况并输出文档向量
    
  5. 结果
    ['garbage', 'help', 'posting', 'quit', 'worthless', 'maybe', 'park', 'is', 'food', 'I', 'has', 'love', 'cute', 'buying', 'take', 'stop', 'mr', 'dog', 'please', 'him', 'ate', 'steak', 'licks', 'so', 'to', 'flea', 'stupid', 'dalmation', 'how', 'my', 'problems', 'not']
    ['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']
    [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0]
    ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him']
    [0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0]
    

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

  文本的预处理我们已经处理完成了,下步我们就要将训练集的每篇文章向量化,然后计算了训练集中各分类中各词汇出现的概率 p ( w i ∣ c 0 ) , p ( w i ∣ c 1 ) , p(wi_|c_0),p(w_i|c_1), p(wic0),p(wic1)和侮辱性文章的概率 p ( c 1 ) p(c_1) p(c1)、非侮辱性文章的概率 p ( c 0 ) p(c_0) p(c0)。现在我们回忆下朴素贝叶斯的公式:
             P ( c i ∣ w ) = P ( w ∣ c i ) ⋅ P ( c i ) P ( w ) P({c_i}|w) = \frac{{P(w|{c_i}) \cdot P({c_i})}}{{P(w)}} P(ciw)=P(w)P(wci)P(ci)
  这里 c i c_i ci可理解为文章的分类, c 0 c_0 c0代表不合格, c 1 c_1 c1代表合格。 w w w可以理解为文章的各个特征,即词汇,每个词汇都算是一篇文章的独立特征,假设有n个独立特征,所有词都相互独立,上式可以写成:
         P ( c i ∣ w ) = = P ( w 0 , w 1 , w 2 , . . . , w n ∣ c i ) ⋅ P ( c i ) P ( w 0 , w 1 , w 2 , . . . , w n ) = P ( w 0 ∣ c i ) P ( w 1 ∣ c i ) P ( w 2 ∣ c i ) ⋅ ⋅ ⋅ P ( w n ∣ c i ) ⋅ P ( c i ) P ( w 0 ) P ( w 1 ) ⋅ ⋅ ⋅ P ( w n ) P({c_i}|w) = = \frac{{P({w_0},{w_1},{w_2},...,{w_n}|{c_i}) \cdot P({c_i})}}{{P({w_0},{w_1},{w_2},...,{w_n})}} = \frac{{P({w_0}|{c_i})P({w_1}|{c_i})P({w_2}|{c_i}) \cdot \cdot \cdot P({w_n}|{c_i}) \cdot P({c_i})}}{{P({w_0})P({w_1}) \cdot \cdot \cdot P({w_n})}} P(ciw)==P(w0,w1,w2,...,wn)P(w0,w1,w2,...,wnci)P(ci)=P(w0)P(w1)P(wn)P(w0ci)P(w1ci)P(w2ci)P(wnci)P(ci)

  1. 理论已经回顾,下面便是计算概率的伪代码:
    计算每个类别中的文档数目  #求 P(c0),P(c1)
    对每篇训练文档:
        对每个类别:
            如果词条出现在文档中:增加该词条计数值
            增加所有词条计数值  #求 P(w) 
    对每个类别:
        对每个词条:
            将该词条的数目除以总词条数目得到条件概率 #求 P(w|ci)
    
  2. 训练算法代码
    #朴素贝叶斯分类器训练函数
    def trainNB0(trainMatrix,trainCategory):
        numTrainDocs = len(trainMatrix) #计算有多少篇文章
        numWords = len(trainMatrix[0]) #计算第一篇文档的词汇数
        pAbusive = sum(trainCategory)/float(numTrainDocs) #计算p(c1),p(c0)=1-p(c1)
        p0Num = np.zeros(numWords) #构建一个空矩阵,用来计算非侮辱性文章中词汇数
        p1Num = np.zeros(numWords) #构建一个空矩阵,用来计算侮辱性文章中词汇数
        p0Denom = 0.0; p1Denom = 0.0 
        for i in range(numTrainDocs): #遍历每一篇文章,来求P(w|c)
            if trainCategory[i] == 1: #判断该文章是否为侮辱性文章
                p1Num += trainMatrix[i] #累计每个词汇出现的次数
                p1Denom += sum(trainMatrix[i]) #计算所有该类别下不同词汇出现的总次数
            else:#如果该文章为非侮辱性文章
                p0Num += trainMatrix[i] 
                p0Denom += sum(trainMatrix[i])
        p1Vect = p1Num/p1Denom #计算每个词汇出现的概率P(wi|c1)
        p0Vect = p0Num/p0Denom #计算每个词汇出现的概率P(wi|c0)
        return p0Vect,p1Vect,pAbusive
    
  3. 检验代码
    import bayes
    
    if __name__ == '__main__':
        listOPosts,listClasses = bayes.loadDataSet()
        myVocaList = bayes.createVocabList(listOPosts)
        trainMat = []
        for postinDoc in listOPosts:
            trainMat.append(bayes.setOfWords2Vec(myVocaList, postinDoc))
        # 先文章转化为词条向量,并放入traninMat中
        p0V, p1V, pAb = bayes.trainNB0(trainMat, listClasses)  # 计算训练集的p(c1),p(wi|c0),p(wi|c1)
        print(p0V, p1V, pAb,sep='\n')
    
  4. 结果
    [0.         0.04166667 0.         0.         0.         0.
     0.         0.04166667 0.         0.04166667 0.04166667 0.04166667
     0.04166667 0.         0.         0.04166667 0.04166667 0.04166667
     0.04166667 0.08333333 0.04166667 0.04166667 0.04166667 0.04166667
     0.04166667 0.04166667 0.         0.04166667 0.04166667 0.125
     0.04166667 0.        ]
    [0.05263158 0.         0.05263158 0.05263158 0.10526316 0.05263158
     0.05263158 0.         0.05263158 0.         0.         0.
     0.         0.05263158 0.05263158 0.05263158 0.         0.10526316
     0.         0.05263158 0.         0.         0.         0.
     0.05263158 0.         0.15789474 0.         0.         0.
     0.         0.05263158]
    0.5
    

  我们计算的得出,属于侮辱类的概率是0.5,观察表中第一个单词 ‘garbage’,在类别1中出现一次,在类别0未出现,对应的条件概率是0.05263158和0.0,我们再看类别1中概率最大值 (0.15789474),我们在数组中找到其单词 ‘stupid’,说明 **‘stupid’**最能表征类别1(侮辱性文档类)的单词。
`

训练函数修正:根据现实情况修改分类器

  在上述的贝叶斯分类器进行文档分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算 p ( w 0 ∣ 1 ) p ( w 1 ∣ 1 ) p ( w 2 ∣ 1 ) p(w_0|1)p(w_1|1)p(w_2|1) p(w01)p(w11)p(w21),如果其中一个概率为0,那么最后的乘积也为0,与事实不符。为此进行下述改变:
在trainNB0()函数中进行修改

#p0Num = zeros(numWords); p1Num = zeros(numWords)     
p0Num = ones(numWords); p1Num = ones(numWords)  # change to ones()
#p0Denom = 0.0; p1Denom = 0.0                       
p0Denom = 2.0; p1Denom = 2.0  # change to 2.0

  还有在计算概率时候很多,由于每个概率都非常小,再多次相乘之后数值基本接近为0,不符合现实,为此选择取对数。机器学习(6)——朴素贝叶斯(文本分类)_第3张图片
  如图所示,采用对数可以避免下溢处或者浮点数舍入导致的错误,同时采用自然对数进行处理不会有任何损失,比较曲线,两者的极值点位置同样,仅仅是取值不同,但不影响最终结果,在代码中进行修改。

	#p1Vect = p1Num/p1Denom         
    #p0Vect = p0Num/p0Denom       
    p1Vect = log(p1Num / p1Denom)  # change to log()
    p0Vect = log(p0Num / p0Denom)  # change to log()

最终环节:实测分类

  现在我们的所有已经准备充分了,就缺个最终分类函数,根据 P ( c 0 ∣ w i ) > P ( c 1 ∣ w i ) P(c_0|w_i)>P(c_1|w_i) P(c0wi)>P(c1wi),则判断该文章为非侮辱性;如果 P ( c 0 ∣ w i ) < P ( c 1 ∣ w i ) P(c_0|w_i)P(c0wi)<P(c1wi),则判断该文章为侮辱性。代码如下:

def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):#vec2Classify为输入的一个向量化的新文章
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)# 由于是取对数,所以乘积变为直接求和即可,注意这里list和list是无法相乘,vec2Classify需要为array格式
    p0 = sum(vec2Classify * p0Vec) + log(1-pClass1)
    if(p1>p0):
        return 1
    if(p0>p1):
        return 0

  现在我们进行测试

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))

  主函数代码

import bayes
if __name__ == '__main__':
    print(bayes.testingNB())

  结果

['love', 'my', 'dalmation'] classified as:  0
['stupid', 'garbage'] classified as:  1

参考信息

https://blog.csdn.net/c369624808/article/details/78906630
http://www.ruanyifeng.com/blog/2013/12/naive_bayes_classifier.html

你可能感兴趣的:(机器学习)