博客园  :: 首页  :: 新随笔  :: 管理

基于概率论的分类方法:朴素贝叶斯

Posted on 2020-08-07 15:43  wsg_blog  阅读(331)  评论(0编辑  收藏  举报
#基于概率论的分类方法:朴素贝叶斯
'''
优点:在数据较少的情况下任然有效,可以处理多类别问题。
缺点:对于输入数据的准备方式较为敏感。
适用数据类型:标称型数据

贝叶斯概率引入先验知识和逻辑推理来处理不确定命题。

朴素贝叶斯的一般过程:
    1.收集数据:可以使用任何方法。本章使用RSS源。
    2.准备数据:需要数值型或布尔型数据。
    3.分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果更好。
    4.训练算法:计算不同的独立特征的条件概率。
    5.测试算法:计算错误率。
    6.一个常见的朴素贝叶斯应用是文档分类。可以在任意的分类场景中使用朴素贝叶斯分类器,不一定非要是文本。
'''
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'],
                 ['mr', 'licks', 'ate', 'my', 'steak', 'how', \
                 'to', 'stop', 'him'],
                 ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
    classVec = [0,1,0,1,0,1]    #1 代表侮辱性文字, 0代表正常言论
    return postingList,classVec

def createVocabList(dataSet):
    #创建词表变量
    vocabSet = set([])
    for document in dataSet:
        #创建两个集合的并集
        vocabSet = vocabSet | set(document)
    return list(vocabSet)

def setOfWords2Vec(vocabList, inputSet):
    #创建一个其中所含元素都为0的向量
    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

#朴素贝叶斯训练算法伪代码
'''
计算每个类别中的文档数目
对每篇训练文档:
    对每个类别:
        如果词条出现在文档中->增加该词条的计数值
        增加所有词条的计数值
对每个类别:
    对每个词条:
        将该词条的数目除以总词条数目得到条件概率
返回每个类别的条件概率
'''
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    #侮辱性评论 概率
    pAbusive = sum(trainCategory)/float(numTrainDocs) #sum(list)= 0+1+0+1+0+1 
    #初始化概率
    #p0Num = zeros(numWords); p1Num = zeros(numWords)    #分子,词的出现次数
    #p0Denom = 0.0; p1Denom = 0.0                        #分母
    #利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,如果
    #其中一个概率值为0,那么左后的乘积也为0。为降低这种影响,可以将所有词的出现次数初始化为1,并将分母初始化为2
    p0Num = ones(numWords); p1Num = ones(numWords)    #分子,词的出现次数
    p0Denom = 2.0; p1Denom = 2.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
    
    p1Vect = log(p1Num/p1Denom)
    p0Vect = log(p0Num/p0Denom)
    return p0Vect,p1Vect,pAbusive

#根据实际情况修改分类器,主要修改上面接口trainNB0() 内的初始化概率大的分子分母和元素做除法
'''
1.利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,如果其中一个概率值为0,那么左后的乘积也为0。
    为降低这种影响,可以将所有词的出现次数初始化为1,并将分母初始化为2
2.另一个问题是 下溢出,由于太多很小的数相乘造成的。一种解决办法是对乘积取自然对数。ln(a*b) = ln(a)+ln(b)
'''

#朴素贝叶斯分类函数
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(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))


#文档词袋模型
'''
目前为止,我们将每个词测出现与否作为一个特征,这可以被描述为词集模型(set-of-words model)。
如果一个词在文档中出现不止一次,,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法
称为词袋模型(bag-of-words model).在词袋中,每个单词可以出现多次,而词集中,每个词智能出现一次。
我们对代码做些修改,使分类器工作得更好。
'''
def bagOfWords2vecMN(vocabList, inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec

#示例:使用朴素贝叶斯过滤垃圾邮件
def textParse(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=[]
    #导入并解析文本文件
    for i in range(1,26):
        '''
        #原始文件email/ham/6.txt及email/spam/17.txt有个字符存在乱码,删除乱码就好了
        try:
            wordList=textParse(open('email/spam/%d.txt' % i,encoding='UTF-8' ).read())
        except UnicodeDecodeError:
            print(i)    
        '''
        wordList = textParse(open('email/spam/%d.txt' % i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        wordList = textParse(open('email/ham/%d.txt' % i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList = createVocabList(docList)
    trainingSet = list(range(50)); testSet=[]
    for i in range(10):
        #随机构建训练集
        randIndex = int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    trainMat=[]; trainClasses=[]
    for docIndex in trainingSet:
        trainMat.append(setOfWords2Vec(vocabList, docList[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))


if __name__ == '__main__':
    '''
    #从文本中构建词向量
    listOPosts,listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts)
    print(myVocabList)
    retVec0 = setOfWords2Vec(myVocabList, listOPosts[0])
    print (retVec0)
    '''

    '''
    #从词向量计算概率
    listOPosts,listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts)
    trainMat=[]
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0V,p1V,pAb=trainNB0(trainMat,listClasses)
    print('词表:',myVocabList)
    print('正常评论各词出现的频率:',p0V)
    print('侮辱性评论各词出现的频率:',p1V)
    print('侮辱类评论出现的概率:',pAb)
    '''

    #示例1.测试 恶意留言 分类器效果
    #testingNB()

    示例2.测试 垃圾邮件 分类效果
    #spamTest()



'''
对于分类而言,使用概率有时比使用硬规则更为有效。贝叶斯概率及贝叶斯准则提供了一种利用已知值来
估计未知概率的有效方法。
可以通过特征之间的条件独立性假设,降低对数据量的需求。独立性假设是指一个词的出现概率并不依赖于
文档中的其他词。当然我们也知道这种假设过于简单。这就是之所以称为朴素贝叶斯的原因。尽管条件独立
性假设并不正确,但是朴素贝叶斯任然是一种有效的分类器。
'''

邮件训练测试链接:https://pan.baidu.com/s/1R8tXk4E_1RzoGowzZzKJ2g
提取码:af2o