【机器学习算法】朴素贝叶斯算法总结
算法基础 . 2020/01/19发布 . shanyonggang_web . 我要评论 . 70阅读

朴素贝叶斯算法是有监督的学习算法,主要解决的是分类问题,其可用于垃圾邮件分类、客户流失分析、贷款信用评测等场景,该算法简单易懂、学习效率高,可以与决策树、神经网络相媲美,不过由于该算法以自变量之间的独立(条件特征独立)性和连续变量的正态性假设为前提,就会导致算法精度在某种程度上受影响,首先进行相关的理论知识学习,主要用到贝叶斯理论。本文源码放置在个人github上:https://github.com/ShanYonggang/Machine__Learning/tree/master/Naive_Bayes

贝叶斯理论

首先介绍贝叶斯决策理论,原理很简单,如下图所示,用P1代表红色概率(红类别)、P2代表蓝色区域概率(蓝类别)、P3代表绿色区域概率(绿类别),当出现一个新的样本(x,y),若P1(x,y)出现的概率最大,则该样本属于类别红类别、若P2(x,y)最大,则该样本属于蓝类别、若P3(x,y)出现的概率最大,则该样本属于绿类别,也就是说,我们会选择高概率对应的类别。

我们已经介绍了贝叶斯决策理论的原则,接下来就是求解各个类别对应出现的概率,在求解之前,我们先熟悉下统计学里的几个概念,如下:

条件概率:简单理解就是在事件A发生的条件下B发生的概率,用P(B|A)表示

P(B|A) = P(A交B)/P(A),因此P(A交B) = P(B|A)P(A),同理:P(A交B) = P(A|B)P(B),结合以上公式可得    P(A|B) = P(B|A)P(A)/P(B),这个即为条件概率公式。

全概率公式:如下所示,其中S事件为A事件和A'事件的和

此时P(B) = P(A交B) + P(A'交B),依据之前的条件概率公式可得:P(A交B) = P(B|A)P(A),P(A'交B) = P(B|A')P(A'),因此可得:P(B) = P(B|A)P(A) + P(B|A')P(A'),此公式即为全概率公式,将其与之前的条件概率公式结合即可得:P(A|B) = P(B|A)P(A)/P(B) =  P(B|A)P(A)/(P(B|A)P(A) + P(B|A')P(A'))。

贝叶斯推断

由上述可得P(A|B) = P(A)  * P(B|A)/P(B),其中:

  • P(A)称之为”先验概率“,即在B事件发生之前,求解的A概率;
  • P(A|B)称之为”后验概率“,即在B时间发生之后,对事件A概率的重新评估;
  • P(B|A)/P(B)称为”可能性函数“,这是一个调整因子,使得预估概率更接近真实概率。

因此条件概率可以表示为:先验概率 = 后验概率 * 可能性概率(调整因子),若调整因子大于1,则表示A事件发生的可能性变大,相反,若小于1则表示A事件发生的可能性变小,若A等于1则调整因子对其无影响。

朴素贝叶斯推断

朴素贝叶斯与贝叶斯相比,其差别主要在“朴素”两个字上,朴素贝叶斯对条件个概率分布做了条件独立性的假设。  比如下面的公式,假设有n个特征:

由于每个特征都是独立的,我们可以进一步拆分公式 :

这样我们就可以进行计算了。

简单案例说明

说了这么多理论,我们来看个简单的例子来具体说明下,使用知乎上比较多的一个例子,相亲问题,如下:

现在有一对男女朋友,男生想女生求婚,男生的四个特点分别是不帅性格好身高矮不上进,请你判断一下女生是嫁还是不嫁?

分析该问题,可以将其转化为求解P(嫁|不帅,性格好,身高矮,不上进)和P(不嫁|不帅,性格好,身高矮,不上进)的概率大小,这里就用到朴素贝叶斯:

P(嫁|不帅,性格好,身高矮,不上进) = P(不帅,性格好,身高矮,不上进|嫁)*P(嫁)/P(不帅,性格好,身高矮,不上进) 

其中:

  • P(不帅,性格好,身高矮,不上进|嫁) = P(不帅|嫁)*P(性格好|嫁)*P(身高矮|嫁)*P(不上进|嫁)
  • P(不帅,性格好,身高矮,不上进)  = P(不帅)*P(性格好)*P(身高矮)*P(不上进)

接下来求解每一项

分子部分:P(嫁人) = 6/12 = 1/2;P(不帅|嫁) = 3/6 = 1/2;P(性格好|嫁) = 5/6 ;P(身高矮|嫁) = 1/6;P(不上进|嫁) = 1/6;

分母部分:P(不帅) = 7/12;P(性格好) = 8/12 = 2/3;P(身高矮) = 7/12;P(不上进) = 5/12

P(嫁|不帅,性格好,身高矮,不上进) = (1/2*1/2*5/6*1/6*1/6)/(7/12*2/3*7/12*5/12) = 3/49 = 0.6112245

同理,我们可以计算:P(嫁|不帅,性格好,身高矮,不上进) ,其中:分子部分:P(嫁人) = 6/12 = 1/2;P(不帅|不嫁) = 2/6 = 1/3;P(性格好|不嫁) = 3/6 = 1/2 ;P(身高矮|不嫁) = 1;P(不上进|不嫁) = 4/6 = 2/3;分母与之前算的一样,因此:

P(不嫁|不帅,性格好,身高矮,不上进) = (1/2*1/3*1/2*1*2/3)/(7/12*2/3*7/12*5/12) = 144/245 = 0.5877551

我们比较计算的概率,所以我们根据朴素贝叶斯算法可以给这个女生答案,是嫁!!!

实战案例

以社区留言区为例子,构建一个过滤器,如果发现某条留言存在侮辱性语言,则将该留言标志为内容不当。过滤这类内容是一个很常见的需求。对此问题建立两个类型:侮辱类和非侮辱类,使用1和0分别表示。我们可以将留言内容文本看作单词向量,考虑出现所有文档中的单词,再决定将哪些单词纳入词汇表或者说所要的词汇集合,然后必须要将每一篇留言文档转换为词汇表上的向量。简单起见,我们先假设已经将文本切分完毕,存放到列表中,并对词汇向量进行分类标注。编写代码如下:

import numpy as np
from functools import reduce
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 setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)          #创建一个其中所含元素都为0的向量
    for word in inputSet:                                                #遍历每个词条
        if word in vocabList:                                            #如果词条存在于词汇表中,则置1
            returnVec[vocabList.index(word)] = 1
        else: 
            print("the word: %s is not in my Vocabulary!" % word)
    return returnVec                                                    #返回文档向量

def createVocabList(dataSet):
    vocabSet = set([])                      #创建一个空的不重复列表
    for document in dataSet:               
        vocabSet = vocabSet | set(document) #取并集
    return list(vocabSet)

def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)                            #计算训练的文档数目
    numWords = len(trainMatrix[0])                            #计算每篇文档的词条数
    pAbusive = sum(trainCategory)/float(numTrainDocs)        #文档属于侮辱类的概率
    p0Num = np.ones(numWords); p1Num = np.ones(numWords)    #创建numpy.ones数组,词条出现数初始化为1
    p0Denom = 2.0; p1Denom = 2.0                            #分母初始化为2
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:                            #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:                                                #统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = np.log(p1Num/p1Denom)                                      
    p0Vect = np.log(p0Num/p0Denom)         
    return p0Vect,p1Vect,pAbusive                            #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率
 
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
#     p1 = reduce(lambda x,y:x*y, vec2Classify * p1Vec) * pClass1                #对应元素相乘
#     p0 = reduce(lambda x,y:x*y, vec2Classify * p0Vec) * (1.0 - pClass1)
    p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)        #对应元素相乘。logA * B = logA + logB,所以这里加上log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
    print('p0:',p0)
    print('p1:',p1)
    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(np.array(trainMat),np.array(listClasses))        #训练朴素贝叶斯分类器
    testEntry = ['love', 'my', 'dalmation']                                    #测试样本1
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))                #测试样本向量化
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'属于侮辱类')                                        #执行分类并打印分类结果
    else:
        print(testEntry,'属于非侮辱类')                                        #执行分类并打印分类结果
    testEntry = ['stupid', 'garbage']                                        #测试样本2
 
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))                #测试样本向量化
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'属于侮辱类')                                        #执行分类并打印分类结果
    else:
        print(testEntry,'属于非侮辱类')                                        #执行分类并打印分类结果
 
if __name__ == '__main__':
    testingNB()

运行代码,最终结果如下:

代码说明:

loadDataSet()函数主要是加载数据,输出留言本文内容及其对应的分类标签,这个比较容易理解。

setOfWords2Vec()函数是创建词汇表,并且根据留言内容输出最终对应的文本向量,其中两个参数分别表示词汇表和输入的留言文本列表

createVocabList()函数是创建词汇表,循环所有内容文本, 然后将文本中出现的词存入列表中并且确保不重复

trainNB0()函数是训练分类器,其中:

  • 将所有词的出现数初始化为1,并将分母初始化为2。这种做法就叫做拉普拉斯平滑(Laplace Smoothing)又被称为加1平滑,是比较常用的平滑方法,它就是为了解决0概率问题。
  • 对乘积结果取自然对数,解决下溢出问题,两个小数相乘,越乘越小,这样就造成了下溢出。在程序中,在相应小数位置进行四舍五入,计算结果可能就变成0了。

classifyNB()函数是朴素贝叶斯分类函数

testingNB()判断函数,类似于主函数

朴素贝叶斯Sklearn

可以参考官方文档:朴素贝叶斯Sklearn实现

其包括以下方法:(一些概率分布知识可以参考:常用概率分布知识

高斯贝叶斯:

其主要用于分类问题,假定属性/特征是服从正态分布的。

from sklearn.naive_bayes import GaussianNB

其具体的属性和方法可以参考:高斯朴素贝叶斯sklearn文档

多项式贝叶斯:

用于离散值模型里。比如文本分类问题里面我们提到过,我们不光看词语是否在文本中出现,也得看出现的次数。如果总词数为n,出现词数为m的话,说起来有点像掷骰子n次出现m次这个词的场景。

from sklearn.naive_bayes import MultinomialNB
# 参数:alpha,其主要是为了解决概率为0时设置的平滑参数(默认为1)
MultinomialNB(alpha = 1.0,fit_prior = True,class_prior = None )

其具体的属性和方法可以参考:多项式朴素贝叶斯sklearn文档 

补充朴素贝叶斯:

CNB是标准多项式朴素贝叶斯(MNB)算法的改编,特别适合于不平衡数据集。具体来说,CNB使用来自每个类的补充的统计信息来计算模型的权重。

from sklearn.naive_bayes import ComplementNB

其具体的属性和方法可以参考:补充朴素贝叶斯sklearn文档 

伯努利贝叶斯:

其主要用于判断最后得到的特征只有0(没出现)和1(出现过),BernoulliNB是为二进制/布尔型功能而设计的。

from sklearn.naive_bayes import BernoulliNB

 其具体的属性和方法可以参考:伯努利朴素贝叶斯sklearn文档 

总结

  • 在训练朴素贝叶斯之前,需要对数据进行清洗处理;
  • 根据提取的分类特征需要进行向量化,然后训练朴素贝叶斯分类器;
  • 拉普拉斯平滑对于改善朴素贝叶斯分类器的分类效果有着积极的作用

 


  • 有疑问请在下方评论区留言,我会尽快回复。
  • Email私信我: 511248513@qq.com 或添加博主 微信
本文作者:shanyonggang_web
发布时间:2020年1月19日 14:43
许可协议: 署名-非商业性使用 4.0 国际许可协议
知识共享许可协议     转载请保留原文链接及作者
正在加载今日诗词....
您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请狠狠点击下面的


登录 后回复

当前暂无评论,点击登录来做第一个吃螃蟹的人吧!