朴素贝叶斯

朴素贝叶斯是一个概率模型,在数学上能用概率解释的模型一般被认为是好模型。

朴素贝叶斯常用于文本分类。

先介绍几个基础概念。

1. 概率

设x为符合某种特征的样本,H为某个假设,比如假设x属于类别c,那分类就是求这个假设发生的概率,即P(H|x)的大小。

P(H|X)是后验概率,或者说在条件X下,H的后验概率,就是在已知样本符合某种特征时,H发生的概率

P(H)是先验概率,或者说H的先验概率,就是所有样本中H发生的概率

P(X|H),P(X)同理

2. 条件概率

P(A|B)=P(AB)/P(B)

P(B|A)=P(AB)/P(A)

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

经典案例

我们想计算含有单词drugs的邮件为垃圾邮件的概率

A为“这是封垃圾邮件”。P(A)也被称为先验信念(prior belief)。

计算方法是,统计训练集中垃圾邮件的比例。
如果我们的数据集每100封邮件有30封垃圾邮件,P(A)为30/100或0.3。

B表示“该封邮件含有单词drugs”。P(B)也被称为先验信念(prior belief)。

类似地,我们可以通过计算数据集中含有单词drugs的邮件数量得到P(B)。
如果每100封邮件中有10封邮件包含单词drugs,那么P(B)就为10/100或0.1。

P(B|A)指的是垃圾邮件中含有单词drugs的概率,计算起来也很容易,统计训练集中所有垃圾邮件的数量以及其中含有单词drugs的数量。
30封垃圾邮件中,如果有6封含有单词drugs,那么P(B|A)就为6/30或0.2。

现在,我们根据贝叶斯定理就能计算出P(A|B),得到含有drugs的邮件为垃圾邮件的概率。
把上面求出来的各项代入前面的贝叶斯公式,得到结果0.6。
这表明如果邮件中含有drugs这个词,那么该邮件为垃圾邮件的概率为60% 。

算法原理

1. 朴素贝叶斯之所以朴素,是其认为样本中属性间相互独立,体现在概率论上就是彼此是独立事件,故 P(AB)=P(A)P(B),所以文本就可以表示为P(X)=P(x1)P(x2)P(x3)...P(xk)

2. P(C|X)=P(X|C)P(C)/P(X)

P(X)表示样本集中含有某特征的样本的比例,是个常数,且对所有样本都一样,故可省略

P(C)其实也是个常数,但是要计算样本属于不同类别时要用不同类别的P(C),故不可省略

3. 计算出每个类别的概率,取概率最大对应的类别即可

粘张图吧,懒得写了

示例代码

import numpy as np

def loadDataSet():
    # 样本
    postingList=[['my','dog','has','flea','problem','help','please'],
                 ['maybe','not','take','him','to','dog','park','stupid'],
                 ['my','dalmation','is','so','cute','I','love','him'],
                 ['stop','posting','ate','my','steak','how','to','stop','him'],
                 ['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):
    # word2vec  只有0 1
    returnVec=[0]*len(vocabList)               # 每个文档的大小与词典保持一致,此时returnVec是空表
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)]=1 # 当前文档中有某个词条,则根据词典获取其位置并赋值1
        else:print "the word :%s is not in my vocabulary" %word
    return returnVec

def bagOfWords2Vec(vocabList,inputSet):
    # 词袋  出现次数
    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 trainNB(trainMatrix,trainCategory):
    # 训练模型 trainMatrix---X, trainCategory--->Y
    numTrainDocs=len(trainMatrix)       # 样本数量
    numWords=len(trainMatrix[0])        # 属性集,次向量长度
    pAbusive=sum(trainCategory)/float(numTrainDocs)     # 统计侮辱性文档的总个数,然后除以总文档个数     P(A)
    #p0Num=zeros(numWords);p1Num=zeros(numWords)
    #p0Denom=0.0;p1Denom=0.0
    p0Num=np.ones(numWords)
    p1Num=np.ones(numWords)
    p0Denom=2.0
    p1Denom=2.0
    for i in range(numTrainDocs):
        # 遍历样本
        if trainCategory[i]==1:
            # 标签为1, 用于计算该类别下词的概率,P(X|A)
            p1Num+=trainMatrix[i]           # 把属于同一类的文本向量相加,实质是统计某个词条在该类文本中出现频率
            p1Denom+=sum(trainMatrix[i])    # 把侮辱性文档向量的所有元素加起来
        else:
            # 标签为0, 用于计算该类别下词的概率,P(X|B)
            p0Num+=trainMatrix[i]
            p0Denom+=sum(trainMatrix[i])
    # p1Vec=p1Num/float(p1Denom)
    # p0Vec=p0Num/float(p0Denom)
    p1Vec=np.log(p1Num/p1Denom) # 统计词典中所有词条在侮辱性文档中出现的概率
    p0Vec=np.log(p0Num/p0Denom) # 统计词典中所有词条在正常文档中出现的概率
    return pAbusive,p1Vec,p0Vec

### 注意:被注释掉的代码代表不太好的初始化方式,在那种情况下某些词条的概率值可能会非常非常小,甚至约
### 等于0,那么在不同词条的概率在相乘时结果就近似于0

def classifyNB(vec2classify,p0Vec,p1Vec,pClass1):
    # 预测
    # 参数1是测试文档向量,参数2和参数3是词条在各个类别中出现的概率,参数4是P(C1)
    p1=sum(vec2classify*p1Vec)+np.log(pClass1)      # 这里没有直接计算P(x,y|C1)P(C1),而是取其对数这样做也是防止概率之积太小,以至于为0
    p0=sum(vec2classify*p0Vec)+np.log(1.0-pClass1)  # 取对数后虽然P(C1|x,y)和P(C0|x,y)的值变了,但是不影响它们的大小关系。
    if p1>p0:
        return 1
    else:
        return 0


if __name__ == '__main__':
    postingList,classVec = loadDataSet()
    vocab = createVocabList(postingList)
    setvec = [setOfWords2Vec(vocab, i) for i in postingList]
    bagvec = [bagOfWords2Vec(vocab, i) for i in postingList]

    pAbusive,p1Vec,p0Vec = trainNB(bagvec, classVec)

    for i in setvec:
        print classifyNB(i,p0Vec,p1Vec,pAbusive)

只是个示例,拿训练样本测试的。

注意在预测时,计算概率用了加法,本身应该是乘法的,但是因为取了log,乘法变加法,注意在计算P(xk)时直接取了log,所以在预测时直接sum了。

log 避免了因概率因子远小于1而连乘造成的下溢出。

还有一种避免0概率的方法叫拉普拉斯平滑,其实很简单,就是在每个类别的样本数上+1,那么样本最少是1,所以不会是0概率,注意每个类别加1,总样本要加类别数。

总结 

朴素贝叶斯对小规模数据集表现很好,适合多分类,但是如果属性间相关性较强,表现不好。

参考资料:

https://blog.csdn.net/amds123/article/details/70173402

https://blog.csdn.net/li8zi8fa/article/details/76176597

原文地址:https://www.cnblogs.com/yanshw/p/10647384.html