【机器学习实战系列】读书笔记之朴素贝叶斯(一)

本文从以下几个方面来介绍:

一、贝叶斯和朴素贝叶斯

二、文本分类运用

三、文本分类算法思路

四、算法实现

五、总结

======================================================================

一、贝叶斯和朴素贝叶斯

1.基于贝叶斯的分类    


2.朴素贝叶斯法

二、文本分类应用

文本数据的一个典型特征就是其维度较大,比如一篇文档,会有几千甚至上万个词,但是不同类型或主题的文档所用词汇差距较大,可以不考虑词汇出现的顺序,即采用bag of words模型,假设文本中每个词的出现都是独立的。基于此类假设的文本分类问题,可以采用朴素贝叶斯方法进行求解。 
  以下例子取自《信息检索导论》一书,有训练集和测试集如下:

文档ID 文档中的词 属于c=Chinac=China
训练集 1 Chinese Beijing Chinese Yes
  2 Chinese Chinese Shanghai Yes
  3 Chinese Macao Yes
  4 Tokyo Japan Chinese No
测试集 5 Chinese Chinese Chinese Tokyo Japan ?

现在要判断测试集的数据是否属于China类。首先由训练集数据计算先验概率及类条件概率


于是,分类器会将测试集数据归于China类。

以上部分参考于https://blog.csdn.net/jteng/article/details/51499363

三、文本分类算法思路

1.准备数据,首先输入训练集,训练集包含多个文档,一个文档就是一个列表,一个训练集就是一个二维大列表,并且每一个文档对应自身的一个标签(比如是正常言论或者不是正常言论)。然后取出训练集中所有的词,去重,做成一个词汇表。

2.构建词向量,我们不关心这个词是什么意思,只关心这个词是否在词汇表中出现以及出现的次数。所以,对于每一个文档,首先构建一个和词汇表一样长的0值列表。然后遍历文档中的每一个词并与词汇表比较,假如这个词在词汇表中出现过,那么找出这个词在词汇表中的位置,比如说,这个词在词汇表的第i个位置,OK,将0值列表第i个位置的值加1(比如原来的值是0,加一后就变成1了)

3.将同标签的文档矩阵相加,最后成为一个1*n的矩阵,计算矩阵中所有值的和为t,然后得出:概率矩阵=矩阵/t,表示的是在相同标签下的文档中的每一个特征值出现的概率。即p(w/ci)

4.最后输入测试集,步骤如1,2,同时也计算出p(ci)即类标签的概率。再利用测试集的似然值p(w/ci),近似计算后验值p(ci/w)=p(w/ci)*p(ci)。选择概率最大的那个标签,即测试集属于的标签。

四、算法实现

#---------------------------从文本中构建词条向量-------------------------
#1 要从文本中获取特征,需要先拆分文本,这里特征是指来自文本的词条,每个词
#条是字符的任意组合。词条可以理解为单词,当然也可以是非单词词条,比如URL
#IP地址或者其他任意字符串
#  将文本拆分成词条向量后,将每一个文本片段表示为一个词条向量,值为1表示出现
#在文档中,值为0表示词条未出现


#导入numpy
import numpy as np
from numpy import *
import math

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 bagOfWords2Vec(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

#训练算法,从词向量计算概率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=ones(numWords);p1Num=ones(numWords)
    p0Denom=2.0;p1Denom=2.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=log(p1Num/p1Denom) #对每个类别的每个单词的数目除以该类别总数目得条件概率
    #利用NumPy数组计算p(wi|c0)

    p0Vect=log(p0Num/p0Denom)
    return p0Vect,p1Vect,pAbusive

#朴素贝叶斯分类函数
#@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)+np.log(pClass1)
    p0=sum(vec2Classify*p0Vec)+np.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((myVocabList,postinDoc))\
    #将文档矩阵和类标签向量转为NumPy的数组形式,方便接下来的概率计算
    #调用训练函数,得到相应概率值
    p0V,p1V,pAb=trainNB0(array(trainMat),array(listClasses))
    #测试文档
    testEntry=['love','my','dalmation']
    #将测试文档转为词条向量,并转为NumPy数组的形式
    thisDoc=array(bagOfWords2Vec(myVocabList,testEntry))
    #利用贝叶斯分类函数对测试文档进行分类并打印
    print(testEntry,'classified as:',classifyNB(thisDoc,p0V,p1V,pAb))
    #第二个测试文档
    testEntry1=['stupid','garbage']
    #同样转为词条向量,并转为NumPy数组的形式
    thisDoc1=array(bagOfWords2Vec(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 listOfTokens if len(tok)>2]

def spamTest():
    #新建三个列表
    docList=[];classList=[];fullText=[]
    #i 由1到26
    for i in range(1,26):
        #打开并读取指定目录下的本文中的长字符串,并进行处理返回
        wordList=testParse(open('F:/Naïve Bayes/spam/%d.txt' %i).read())
        #将得到的字符串列表添加到docList
        docList.append(wordList)
        #将字符串列表中的元素添加到fullTest
        fullText.extend(wordList)
        #类列表添加标签1
        classList.append(1)
        #打开并取得另外一个类别为0的文件,然后进行处理
        wordList=testParse(open('F:/Naïve Bayes/ham/%d.txt' %i,encoding='utf-8').read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    #将所有邮件中出现的字符串构建成字符串列表
    vocabList=createVocabList(docList)
    #构建一个大小为50的整数列表和一个空列表
    trainingSet=list(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(bagOfWords2Vec(vocabList,docList[docIndex]))
        #将该邮件的类标签存入训练类标签列表中
        trainClasses.append(classList[docIndex])
    #计算贝叶斯函数需要的概率值并返回
    p0V,p1V,pSpam=trainNB0(array(trainMat),array(trainClasses))
    errorCount=0
    #遍历测试集中的字符串列表
    for docIndex in testSet:
        #同样将测试集中的字符串列表转为词条向量
        wordVector=bagOfWords2Vec(vocabList,docList[docIndex])
        #对测试集中字符串向量进行预测分类,分类结果不等于实际结果
        if classifyNB(array(wordVector),p0V,p1V,pSpam)!=classList[docIndex]:
            errorCount+=1
        print('the error rate is:',float(errorCount)/len(testSet))

if __name__ == '__main__':
    spamTest()

在这里比较疑惑的点是将p0denom = 2.0,p1denom = 2.0,这里应该采用的是拉普拉斯平滑来防止出现0/0的情况出现,但是为什么只是加2?(希望探讨一下)下面介绍拉普拉斯平滑的一些处理。

拉普拉斯的理论支撑

  为了解决零概率的问题,法国数学家拉普拉斯最早提出用加1的方法估计没有出现过的现象的概率,所以加法平滑也叫做拉普拉斯平滑。
  假定训练样本很大时,每个分量x的计数加1造成的估计概率变化可以忽略不计,但可以方便有效的避免零概率问题。

应用举例

  假设在文本分类中,有3个类,C1、C2、C3,在指定的训练样本中,某个词语K1,在各个类中观测计数分别为0,990,10,K1的概率为0,0.99,0.01,对这三个量使用拉普拉斯平滑的计算方法如下:
  1/1003 = 0.001,991/1003=0.988,11/1003=0.011

  在实际的使用中也经常使用加 lambda(1≥lambda≥0)来代替简单加1。如果对N个计数都加上lambda,这时分母也要记得加上N*lambda

所以我对加2还是比较疑惑的,我觉得应该加上词典中特征的个数

搜了一下,看到有这样的解释,如下图:




下面贴上我用拉普拉斯平滑处理的代码:

#---------------------------从文本中构建词条向量-------------------------
#1 要从文本中获取特征,需要先拆分文本,这里特征是指来自文本的词条,每个词
#条是字符的任意组合。词条可以理解为单词,当然也可以是非单词词条,比如URL
#IP地址或者其他任意字符串
#  将文本拆分成词条向量后,将每一个文本片段表示为一个词条向量,值为1表示出现
#在文档中,值为0表示词条未出现


#导入numpy
import numpy as np
from numpy import *
import math

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)
    list_vocabSet = list(vocabSet)
    list_vocabSet_len = len(list_vocabSet)
    #再将集合转化为列表,便于接下来的处理
    return list_vocabSet

def createVocabList_len(dataSet):
    # 新建一个存放词条的集合
    vocabSet = set([])
    # 遍历文档集合中的每一篇文档
    for document in dataSet:
        # 将文档列表转为集合的形式,保证每个词条的唯一性
        # 然后与vocabSet取并集,向vocabSet中添加没有出现
        # 的新的词条
        vocabSet = vocabSet | set(document)
    list_vocabSet = list(vocabSet)
    #计算出词典特征数,为拉普拉斯平滑做准备
    list_vocabSet_len = len(list_vocabSet) 
    # 再将集合转化为列表,便于接下来的处理
    return list_vocabSet_len

#根据词条列表中的词条是否在文档中出现(出现1,未出现0),将文档转化为词条向量
def bagOfWords2Vec(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

#训练算法,从词向量计算概率p(w0|ci)...及p(ci)
#@trainMatrix:由每篇文档的词条向量组成的文档矩阵
#@trainCategory:每篇文档的类标签组成的向量
def trainNB0(trainMatrix,trainCategory,dataSet):
    #获取文档矩阵中文档的数目
    numTrainDocs=len(trainMatrix)
    #获取词条向量的长度
    numWords=len(trainMatrix[0])
    #所有文档中属于类1所占的比例p(c=1)
    pAbusive=sum(trainCategory)/float(numTrainDocs)
    #创建一个长度为词条向量等长的列表
    #拉普拉斯处理,分子初始化为1,分母初始化为词典的特征数
    p0Num=ones(numWords);p1Num=ones(numWords)
    p0Denom=createVocabList_len(dataSet);p1Denom=createVocabList_len(dataSet)
    #遍历每一篇文档的词条向量
    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=log(p1Num/p1Denom) #对每个类别的每个单词的数目除以该类别总数目得条件概率
    #利用NumPy数组计算p(wi|c0)

    p0Vect=log(p0Num/p0Denom)
    return p0Vect,p1Vect,pAbusive

#朴素贝叶斯分类函数
#@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)+np.log(pClass1)
    p0=sum(vec2Classify*p0Vec)+np.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((myVocabList,postinDoc))\
    #将文档矩阵和类标签向量转为NumPy的数组形式,方便接下来的概率计算
    #调用训练函数,得到相应概率值
    p0V,p1V,pAb=trainNB0(array(trainMat),array(listClasses))
    #测试文档
    testEntry=['love','my','dalmation']
    #将测试文档转为词条向量,并转为NumPy数组的形式
    thisDoc=array(bagOfWords2Vec(myVocabList,testEntry))
    #利用贝叶斯分类函数对测试文档进行分类并打印
    print(testEntry,'classified as:',classifyNB(thisDoc,p0V,p1V,pAb))
    #第二个测试文档
    testEntry1=['stupid','garbage']
    #同样转为词条向量,并转为NumPy数组的形式
    thisDoc1=array(bagOfWords2Vec(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 listOfTokens if len(tok)>2]

def spamTest():
    #新建三个列表
    docList=[];classList=[];fullText=[]
    #i 由1到26
    for i in range(1,26):
        #打开并读取指定目录下的本文中的长字符串,并进行处理返回
        wordList=testParse(open('F:/Naïve Bayes/spam/%d.txt' %i).read())
        #将得到的字符串列表添加到docList
        docList.append(wordList)
        #将字符串列表中的元素添加到fullTest
        fullText.extend(wordList)
        #类列表添加标签1
        classList.append(1)
        #打开并取得另外一个类别为0的文件,然后进行处理
        wordList=testParse(open('F:/Naïve Bayes/ham/%d.txt' %i,encoding='utf-8').read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    #将所有邮件中出现的字符串构建成字符串列表
    vocabList=createVocabList(docList)
    vocabList_len = len(vocabList)
    #构建一个大小为50的整数列表和一个空列表
    trainingSet=list(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(bagOfWords2Vec(vocabList,docList[docIndex]))
        #将该邮件的类标签存入训练类标签列表中
        trainClasses.append(classList[docIndex])
    #计算贝叶斯函数需要的概率值并返回
    p0V,p1V,pSpam=trainNB0(array(trainMat),array(trainClasses),docList)
    errorCount=0
    #遍历测试集中的字符串列表
    for docIndex in testSet:
        #同样将测试集中的字符串列表转为词条向量
        wordVector=bagOfWords2Vec(vocabList,docList[docIndex])
        #对测试集中字符串向量进行预测分类,分类结果不等于实际结果
        if classifyNB(array(wordVector),p0V,p1V,pSpam)!=classList[docIndex]:
            errorCount+=1
        print('the error rate is:',float(errorCount)/len(testSet))

if __name__ == '__main__':
    spamTest()

五、朴素贝叶斯总结

1.朴素贝叶斯=贝叶斯公式+条件独立假设

2.处理未在训练集中覆盖的词语——平滑技术(赋予一个校概率,从而降低整体的概率)

3.处理多分类问题,忽略被判断的文本的概率,即用似然函数

4.先验概率问题

贝叶斯方法,需要靠谱的先验概率,否则会在,最大似然法和基本的朴素贝叶斯得出不同地结果。建议是,在处理多份类问题时,知道先验概率具体数值且不相等的情况下,考虑删除部分数据使得先验概率相等,然后用最大似然法。如果不知道先验概率,就只能按等比例抽取样本,然后按先验概率相等的情况处理。

5.朴素贝叶斯的优点是对缺失数据不太敏感,常用于文本分类。

6.由于我们是通过先验和数据来决定后验的概率从而决定分类,所以分类决策存在一定的错误率。

7.使用数据类型,标称型数据。

猜你喜欢

转载自blog.csdn.net/qq_38150441/article/details/79815186