朴素贝叶斯是一种简单的分类算法,称它“朴素”是因为,整个形式化过程只做最原始 最简单的假设。朴素贝叶斯的核心思想是:对于待分类项,求解此待分类项在各个类别中出现的概率,哪个类别概率最大,则认为此待分类项就属于那个类别。

朴素贝叶斯是贝叶斯决策理论的一部分

1 朴素贝叶斯原理

1.1 概率论知识

既然朴素贝叶斯是求概率,首先介绍概率论的知识。假设\(X\)和\(Y\)相互独立,则有条件独立公式:

\[P(X,Y)=P(X)P(Y) \]

条件概率公式:

\[P(Y|X)=\frac{P(X,Y)}{P(X)} \]

\[P(X|Y)=\frac{P(X,Y)}{P(Y)} \]

全概率公式:

\[P(X)=\sum_{k}P(X|Y=Y_{k})P(Y_{k}) \]

其中,\(\sum_{k}P(Y_{k})=1\)。

1.2 贝叶斯定理

朴素贝叶斯是贝叶斯决策理论的一部分,讲朴素贝叶斯前,有必要了解一下贝叶斯决策理论。贝叶斯理论解决的是这样的问题:已知某条件概率,如何得到两个事件交换后的概率呢?也就是,已知\(P(X|Y)\)的情况下如何求得\(P(Y|X)\)?于是,提出贝叶斯定理:

\[P(Y|X)=\frac{P(X|Y)P(Y)}{P(X)} \]

\[P(Y_{k}|X)=\frac{P(X|Y_{k})P(Y_{k})}{\sum_{k}P(X|Y=Y_{k})P(Y_{k})} \]

2 朴素贝叶斯算法

输入:训练集有\(m\)个样本,每个样本有\(n\)个特征,共有\(K\)个特征输出类别,训练集表示为:\(T=\left \{ \left ( x_{1},y_{1} \right ),\left ( x_{2},y_{2} \right ),\cdots,\left ( x_{n},y_{n} \right ) \right \},其中\)x_{i}=\left ( x_{i}{(1)},x_{i},\cdots,x_{i}^{n} \right ){T}$,$x_{i}\(是第\)i\(个样本的第\)j\(个特征,\)y\epsilon \left { c_{1},c_{2},\cdots,c_{K} \right }\( **输出:**待测试实例\)x_{(test)}$的分类

算法流程:
step1 计算\(Y\)的\(K\)个先验概率

\[P(Y=c_{k}) \]

step2 计算条件概率

\[P(X=x|Y=c_{k})=P(X^{(1)}=x^{(1)},\cdots, X^{(n)}=x^{(n)}|Y=c_{k}) \]

上式的参数是指数级别,无法计算。所以根据特征条件独立假设,可以化简为下式

\[P(X=x|Y=c_{k})=\prod_{j=1}^{n}P(X^{(j)}=x^{(j)}|Y=c_{k}) \]

step3 根据贝叶斯定理,计算后验概率

\[P(Y=c_{k}|X=x)=\frac{P(X=x|Y=c_{k})P(Y=c_{k})}{\sum_{k}P(X=x|Y=c_{k})P(Y=c_{k})} \]

把\(P(X=x|Y=c_{k})=\prod _{j=1}^{n}P(X^{(j)}=x^{(j)}|Y=c_{k})\)得到

\[P(Y=c_{k}|X=x)=\frac{\prod_{j=1}^{n}P(X^{(j)}=x^{(j)}|Y=c_{k})P(Y=c_{k})}{\sum_{k}\prod_{j=1}^{n}P(X^{(j)}=x^{(j)}|Y=c_{k})P(Y=c_{k})} \]

由于分母相同,上式简化为

\[P(Y=c_{k}|X=x)=\prod_{j=1}^{n} P(X^{(j)}=x^{(j)}|Y=c_{k})P(Y=c_{k}) \]

step4 计算\(X_{(test)}\)的类别

\[y_{(test)}=arg max_{c_{k}}\prod_{j=1}^{n} P(X^{(j)}=x^{(j)}_{(test)}|Y=c_{k})P(Y=c_{k}) \]

3 朴素贝叶斯代码实现

#bayes.py
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 #postingList表示进行词条切分后的文档集合 classVec表示类别标签集合

def createVocabList(dataSet): #创建一个包含在所有文档中出现的不重复词的列表(获取词表)
    vocabSet = set([])  #创建一个空集 set返回一个不重复词表
    for document in dataSet: #将每篇文档返回的新词集合添加到该集合vocabSet中
        vocabSet = vocabSet | set(document)  #创建两个集合的并集
    return list(vocabSet)

def setOfWords2Vec(vocabList, inputSet): #参数vocabList代表词汇表 inputSet代表输入文档 函数最后输出文档向量 向量每个元素为1或0 表示词汇表中的单词在输入文档中是否出现
    returnVec = [0]*len(vocabList)  #创建一个其中所含元素都为0的向量(该向量与词汇表等长)
    for word in inputSet: #遍历文档中的所有单词 如果出现词汇表中的单词则将输出的文档向量中的对应值设为1
        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): #trainMatrix代表文档矩阵 trainCategory代表每篇文档类别标签所构成的向量
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pAbusive = sum(trainCategory)/float(numTrainDocs)  #计算文档属于侮辱性文档的概率
    #p0Num = zeros(numWords)  #初始化概率
    #p1Num = zeros(numWords)
    #p0Denom = 0.0
    #p1Denom = 0.0
    p0Num = ones(numWords)
    p1Num = ones(numWords)
    p0Denom = 2.0
    p1Denom = 2.0

    for i in range(numTrainDocs): #遍历训练集trainMatrix中的所有文档
        if trainCategory[i] == 1:  #向量相加 某个词语在文档中出现 则该词对应个数p1Num或p0Num加1 同时在所有文档中 该文档的总词数也对应加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  #返回两个向量 一个概率

#测试算法:朴素贝叶斯分类函数
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 bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec

#封装函数所有操作
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))

#文件解析及垃圾邮件测试函数
def textParse(bigSting):  #接受一个大字符串并将其解析为字符串列表
    import re
    listOfTokens = re.split(r'\W*', bigSting)
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]  #去掉少于两个字符的字符串 并将所有字符串转换为小写

def spamTest():  #对贝叶斯垃圾邮件分类器进行自动化处理
    docList=[]; classList = []; fullText =[]
    for i in range(1,26): #导入文本文件并将其解析为词列表
        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())
        #print(i)
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList = createVocabList(docList)#create vocabulary
    trainingSet = list(range(50)); testSet=[]           #create test set
    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:#train the classifier (get probs) trainNB0
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
    errorCount = 0
    for docIndex in testSet:        #classify the remaining items
        wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
        if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
            errorCount += 1
            print("classification error",docList[docIndex])
    print('the error rate is: ',float(errorCount)/len(testSet))
#运行效果
>>> import bayes
>>> listOPosts, listClasses = bayes.loadDataSet()
>>> myVocaList = bayes.createVocabList(listOPosts)
>>> myVocaList
['garbage', 'ate', 'park', 'stupid', 'dalmation', 'him', 'not', 'how', 'cute', 'posting', 'food', 'has', 'so', 'please', 'love', 'my', 'to', 'steak', 'dog', 'quit', 'licks', 'buying', 'stop', 'take', 'mr', 'I', 'worthless', 'maybe', 'is', 'flea', 'problems', 'help']
>>>bayes.setOfWords2Vec(myVocaList, listOPosts[0])
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1]
>>>from numpy import *
>>>import imp
>>>import bayes
>>>imp.reload(bayes)
>>> myVocabList = bayes.createVocabList(listOPosts)
>>> trainMat = []
for postinDoc in listOPosts:
...  trainMat.append(bayes.setOfWords2Vec(myVocabList, postinDoc))
...
>>> p0V,p1V,pAb = bayes.trainNB0(trainMat, listClasses)
>>> pAb
0.5
>>> p0V
array([ 0.        ,  0.04166667,  0.        ,  0.        ,  0.04166667,
        0.08333333,  0.        ,  0.04166667,  0.04166667,  0.        ,
        0.        ,  0.04166667,  0.04166667,  0.04166667,  0.04166667,
        0.125     ,  0.04166667,  0.04166667,  0.04166667,  0.        ,
        0.04166667,  0.        ,  0.04166667,  0.        ,  0.04166667,
        0.04166667,  0.        ,  0.        ,  0.04166667,  0.04166667,
        0.04166667,  0.04166667])
>>> p1V
array([ 0.05263158,  0.        ,  0.05263158,  0.15789474,  0.        ,
        0.05263158,  0.05263158,  0.        ,  0.        ,  0.05263158,
        0.05263158,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.05263158,  0.        ,  0.10526316,  0.05263158,
        0.        ,  0.05263158,  0.05263158,  0.05263158,  0.        ,
        0.        ,  0.10526316,  0.05263158,  0.        ,  0.        ,
        0.        ,  0.        ])
myVocabList
['garbage', 'ate', 'park', 'stupid', 'dalmation', 'him', 'not', 'how', 'cute', 'posting', 'food', 'has', 'so', 'please', 'love', 'my', 'to', 'steak', 'dog', 'quit', 'licks', 'buying', 'stop', 'take', 'mr', 'I', 'worthless', 'maybe', 'is', 'flea', 'problems', 'help']
imp.reload(bayes)
<module 'bayes' from 'D:\\Python\\Mechine_learning\\Bayes\\bayes.py'>
>>> bayes.testingNB()
['love', 'my', 'dalmation'] classified as:  0
['stupid', 'garbage'] classified as:  1

4 朴素贝叶斯小结

贝叶斯优缺点:
优点
朴素贝叶斯模型有稳定的分类效率。
对小规模的数据表现很好,能个处理多分类任务,适合增量式训练,尤其是数据量超出内存时,可以一批批的去增量训练。
对缺失数据不太敏感,算也比较简单,常用于文本分类。
缺点
朴素贝叶斯模型的特征条件独立假设在实际应用中往往是不成立的。
如果样本数据分布不能很好的代表样本空间分布,那先验概率容易测不准。对输入数据的表达形式很敏感。