机器学习笔记(五)——轻松看透朴素贝叶斯

1、算法概述

贝叶斯算法是基于统计学的一种几率分类方法,而朴素贝叶斯是其中最简单的一种;朴素贝叶斯属于监督学习的算法之一,通常用来解决分类问题,咱们之因此称之为"朴素",是由于整个形势化过程只作最原始、最简单的假设,即假设数据集全部的样本之间都是独立存在,互不影响的。python

用一个条件几率公式更好的理解这个假设条件,假设一个样本中有(a一、a二、a三、... an)共n个样本,如有P(a1,a2,a3,...,an) = P(a1) P(a2) P(a3)...P(an),则称该数据集中各个样本之间独立存在。算法

假设咱们有一个数据集,共有两个特征,分别为三角形和圆形,以下图所示:
在这里插入图片描述
若用P1(x,y)表示数据点(x,y)属于圆形的几率,用P2(x,y)表示数据点(x,y)属于三角形的几率,那么对于一个新数据点(x,y),能够用如下规则判断其类别:数组

  • 若P1(x,y)>P2(x,y),那么该点类别为1
  • 若P1(x,y)<P2(x,y),那么该点类别为2

总得来讲,未知属性的类别趋向几率高的。这就是贝叶斯决策理论的核心思想,即选择具备最高几率的决策。app

2、条件几率公式

条件几率公式是几率论中十分基础的一个公式,即在事件B发生的状况下,事件A也发生的几率,以下文氏图:
在这里插入图片描述
经过这幅文氏图,在在事件B发生的状况下,事件A也发生的几率以下less

在这里插入图片描述
同理可得ide

在这里插入图片描述
最后推得条件几率的计算公式以下函数

在这里插入图片描述
这个公式被称为贝叶斯准则,它告诉咱们如何交换条件几率中的条件和结果,例如已知P(B | A),如何计算P(A | B)。post

这里有几个概念须要了解:学习

  • P(A)称为"先验几率",即在事件B发生之间对事件A发生几率的判断。
  • P(A | B)称为"后验几率",即在事件B发生以后对事件A发生几率的再次判断。
  • P(B | A)/P(B)称为"可能性函数",这是一个调整因子,能够帮助预估几率更加接近真实几率。

因此条件几率也能够理解成:测试

*后验几率 = 先验几率 调整因子**

其中"调整因子"的值对条件几率的影响以下:

  • 当"调整因子"小于1时,"先验几率"被减弱,事件A的发生的几率变小
  • 当"调整因子"等于1时,"先验几率"不变,对事件A的发生几率无影响
  • 当"调整因子"大于1时,"先验几率"被加强,事件A的发生的几率变大

3、条件几率实例

再有一年半,偶也要面临考研or就业的抉择,向周围同窗询问了他们的选择,获得这么一份小数据集,偶也总结了一下自身条件,学习成绩通常、自学能力不错、家里的经济条件也容许,那我是选择考研仍是就业呢?

成绩 自学能力 家庭条件 选择
学霸 考研
通常 考研
学渣 考研
学霸 就业
通常 就业
学渣 就业

对于这个例子,按照贝叶斯公式进行求解,能够转化成P(考研 | 通常 强 好)和P(就业 | 通常 强 好)两类,由于贝叶斯的思想就是根据最高几率判断类别。

在这里插入图片描述
"先验几率"P(考研)很容易计算,可是"可能性函数"中的分母P(通常 强 好)殊不知如何计算,这里须要引入一个新的公式——全几率公式。
在这里插入图片描述
因此依据全几率公式P(通常 强 好)求值公式以下:

在这里插入图片描述
最后依据贝叶斯准则可计算两者的几率:
在这里插入图片描述
其中考研的几率为80%,就业的几率为20%,因此就我本身的条件而言,该算法将我分配至考研党中。咱们从小学就学过一个道理,分母相同的两个分数,分子大的分数大,由于朴素贝叶斯的思想是要依据几率判断类别,因此就能够省去计算全几率这一步,在编写程序的时候能够提升效率。

4、文本分类

从文本中获取特征,需先将文本拆分。这里的特征是来自文本的词条,一个词条是字符的任意组合。对于文本而言,能够将词条想象成单词;对于IP地址而言,又能够将词条想象成两个点间的数字组合,不一样类型的文本,词条的类型能够不一样。而后将每个文本片断表示为一个词条向量,其中值为1表示词条出如今文档中,0则表示词条未出现。

平时在刷微博的时候,无论事情好与坏,评论老是有好有坏,由于避免不了总有杠精的存在。构建一个快速过滤器,这个过滤器的功能就是分类好坏评论,若是某条评论使用了负面或者侮辱性的语言,则将该评论断定为侮辱类评论,反之则将其归为非侮辱类评论,其中侮辱类用1表示,非侮辱类用0表示。

4.1构建词向量

假设咱们已经获取到文本数据,先考虑出如今文本中的全部单词,决定将哪些词归入词汇表或者说所要的词聚集合,而后将文本中的句子转化为向量,以方便对文本中每句话的类别进行判断。

#设置文本数据集
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]                                                        
    return postingList,classVec

loadDataSet函数建立了实验文本,主要的操做是将每一句话切分红若干个单词,而且建立了一个类别标签列表,其中1表明侮辱类,0表明非侮辱类,是经过人的判断后进行标注。

#建立词汇表
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:
            #若词汇表包含该词汇,则将该位置的0变为1
            returnVec[vocabList.index(word)] = 1
    return returnVec
#将全部词条向量汇总  
def get_Mat(inputSet):
    trainMat = []  #建立空列表
    vocabList = createVocabList(inputSet)
    #遍历输入文本,将每一个词条向量加入列表中
    for Set in inputSet:
        returnVec = setOfWords2Vec(vocabList,Set)
        trainMat.append(returnVec)
    return trainMat

createVocabList函数的做用是经过set方法已经取并集的方式返回一个包含文本中全部出现的不重复词的集合;setOfWords2Vec函数的输入参数为词汇表和某个文本,输出的是文本向量,向量的元素包括1或0,分别表示词汇表中的单词是否出如今输入的文本中,思路是首先建立一个同词汇表等长的向量,并将其元素都设置为0,而后遍历输入文本的单词,若词汇表中出现了本文的单词,则将其对应位置上的0置换为1。

代码运行截图以下

在这里插入图片描述
例如词汇表中第四个单词has在第一个输入文本中出现,则向量中的第4个元素置为1;同理词汇表中最后一个单词not在第二个输入文本中出现,则向量中最后一个元素置为1。

4.2训练算法

这里若是重写上文提过的贝叶斯准则,W为一个向量,它由多个数值组成,Ci表明类别,即侮辱类or非侮辱类,公式以下:
在这里插入图片描述
若使用上述公式对一个未知类进行判断,咱们只需比较两个两个几率值的大小便可,首先经过类别i的文本数除以总文本数能够计算出P(Ci)的数值;而后计算P(W | Ci),由于W能够展开为一个个独立特征,那么P(W0,W1,W2...Wn | Ci) = P(W0 | Ci)P(W1 | Ci)P(W2 | Ci)...P(Wn | Ci),简化了计算的过程。

代码以下:

def trainNB(trainMatrix,trainCategory):
    #训练文本数量
    numTrainDocs = len(trainMatrix)
    #每篇文本的词条数
    numWords = len(trainMatrix[0])
    #文档属于侮辱类(1)的几率
    pAbusive = sum(trainCategory)/float(numTrainDocs)
    #建立两个长度为numWords的零数组
    p0Num = np.zeros(numWords)
    p1Num = np.zeros(numWords)
    #分母初始化
    p0Denom = 0.0
    p1Denom = 0.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            #统计侮辱类的条件几率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
            p1Num += trainMatrix[i]
            #print(p1Num)
            p1Denom += sum(trainMatrix[i])
            #print(p1Num)
        else:
            #统计非侮辱类的条件几率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    #计算词条出现的几率
    p1Vect = p1Num/p1Denom  
    p0Vect = p0Num/p0Denom 

    return p0Vect,p1Vect,pAbusive

trainNB函数的输入参数包括文本矩阵trainMatrix和每一个词条的类别标签所构成的向量trainCategory。首先文本属于侮辱类的几率只须要将侮辱性词条的个除以总词条个数便可;计算P(W | C1)P(W | C0)时,须要将其分子和分母初始化,遍历输入文本时,一旦某个词语(侮辱性or非侮辱性)在某一文档中出现,则该词对应的个数(p1Num或p0Num)就加1,而且在总文本中,该词条的总次数也相应加1。

代码运行截图以下
在这里插入图片描述
这里打印出属于侮辱性的词条,当不一样词条出现同一词语时,会在词条向量的同一位置上累加,最后每个词语出现的次数除以总数就获得了相应的几率,例如出现两次—>0.10五、出现三次—>0.15七、出现一次—>0.052。

4.3测试算法

def classifyNB(vec2Classify, p0V,P1V,PAb):
    #将对应元素相乘
    p1 = reduce(lambda x,y:x*y, vec2Classify * p1V) * pAb 
    p0 = reduce(lambda x,y:x*y, vec2Classify * p0V) * (1.0 - PAb)

    print('p0:',p0)
    print('p1:',p1)
    if p1 > p0:
        return 1
    else: 
        return 0

classifyNB函数传入的4个参数分别为测试文本的向量以及训练函数trainNB返回的三个参数,其做用是将文本的向量与p1V和p2V分别对应相乘,并乘以pAb和其二分类对应几率(1.0-pAb),而后比较p1与p2的大小判别出测试文本属于属于哪一类,这里举一个reduce方法的小例子,方便理解。

reduce(lambda x,y:x+y,[1,2,3,4])
'''
10
'''

reduce方法是将两个元素以某种操做运算符为条件归并成一个结果,而且它是一个迭代的过程,每次调用该方法直至获得最后一个结果,例如上面数组[1,2,3,4]以加法为操做运算实现1+2;3+3;6+4 = 10的操做过程。

下面经过调用前文的函数,对测试数据进行分类操做,代码以下:

def testingNB(testVec):
    #建立实验样本
    postingList,classVec = loadDataSet()
    #建立词汇表
    vocabSet = createVocabList(postingList)
    #将实验样本向量汇总
    trainMat = get_Mat(postingList)
    #训练算法
    p0V,P1V,PAb = trainNB(trainMat,classVec)
    #将测试文本向量化
    The_test = setOfWords2Vec(vocabSet,testVec)
    #判断类别
    if classifyNB(The_test,p0V,P1V,PAb):
        print(testVec,"侮辱类")
    else:
        print(testVec,"非侮辱类")

传入测试数据testVec,并返回分类结果以下图:
在这里插入图片描述
哎呀,这stupid怎么还能被判断成非侮辱类了呢?会不会是程序变蠢了?程序是正常的,可是须要对程序作一点改进,咱们都知道0是一个特别牛皮的数,由于不论什么数字乘以0结果都得0,因此只要p1V向量和测试向量有一个对应位置上同时都为0,那么最终结果必定为0。为了下降上述影响,能够将全部词的出现数初始化为1,并将分母初始化为2,这种方法被称为拉普拉斯平滑。

这部分对trainNB函数作如下更改:

p0Num = np.ones(numWords)
p1Num = np.ones(numWords)
p0Denom = 2.0
p1Denom = 2.0

除此以外,还有一个问题是下溢出,什么是下溢出呢?在许多很小的数相乘时,当计算乘积 P(W0 | Ci)P(W1 | Ci)P(W2 | Ci)...P(Wn | Ci)时,因为大部分因子都很是小,因此程序会下溢出或者得不到正确答案,好比程序会将乘积很是小的结果四舍五入后获得0,一种经典的解决办法是取乘积的天然对数。

在代数中有*ln(ab) = ln(a)+ln(b)**,由乘法转为加法后,就能够避免下溢出或者浮点数舍入致使的错误,有人可能会担忧,两者计算出的结果是有差别的,这是事实,可是对于咱们所须要的分类结果是无影响的。

f(x)与ln(x)的曲线以下图:
在这里插入图片描述
经过观察这两条曲线会发现它们在相同的区域同时增长或同时减小,而且在相同点取到极值,虽然两者的极值不一样,但不影响最终结果,由于咱们只需经过比较两者值的大小来判断测试数据的类别。

这部分对trainNB函数作如下更改:

p1Vect = np.log(p1Num/p1Denom)  
p0Vect = np.log(p0Num/p0Denom)

前面计算几率时作了取对数操做,因为*log(ab) = log(a)+log(b)**,因此能够对classifyNB函数进行改进,用sum方法代替reduce方法便可。

具体代码以下:

def classifyNB(ClassifyVec, p0V,p1V,pAb): 
    #p1 = reduce(lambda x,y:x*y, ClassifyVec * p1V) * pAb 
    #p0 = reduce(lambda x,y:x*y, ClassifyVec * p0V) * (1.0 - PAb)
    #将对应元素相乘
    p1 = sum(ClassifyVec * p1V) + np.log(pAb) 
    p0 = sum(ClassifyVec * p0V) + np.log(1.0 - pAb)
    print('p0:',p0)
    print('p1:',p1)
    if p1 > p0:
        return 1
    else: 
        return 0

最后测试总体代码运行截图以下:
在这里插入图片描述
经过p0与p1的比较,能够正确的将测试文本进行分类,stupid最后被断定为侮辱类,看来程序是不会变蠢的,会变蠢的是我。

4.4词袋模型拓展

前面程序中,咱们将每一个次的出现与否做为一个特征,这能够被描述为词集模型。若是一个词在文本中出现不止一次,不能将其单纯的做为特征同等看待,由于其中涉及到了权重不一样,这种方法被称为词袋模型。在词袋中,每一个单词能够出现若干次,而在词集中,每一个词只能出现一次。能够在词集模型的基础上加以修改,将其转换成词袋模型。

代码以下:

def setOfWords2Vec(vocabList, inputSet):
    #建立一个元素都为0的向量
    returnVec = [0] * len(vocabList)                                    
    for word in inputSet:                                               
        if word in vocabList:
            #若每当文本中出现词汇表中的单词一次,就将该位置的数字加1
            returnVec[vocabList.index(word)] += 1
    return returnVec

5、文末总结

朴素贝叶斯对应优势以下:

  • 能够处理样本较少的数据集
  • 能够处理多类别问题
  • 对缺失数据不太敏感
  • 适合进行文本分类

朴素贝叶斯对应缺点以下:

  • 对于输入数据的表达方式敏感
  • 须要假设数据中每一个特征之间须要独立
  • 先验模型创建不当可能致使预测结果不佳

本文就朴素贝叶斯该算法的原理进行简单介绍,下篇文章会介绍朴素贝叶斯的应用实例。

关注公众号【奶糖猫】后台回复“Bayes”可获取源码供参考,感谢阅读。

在这里插入图片描述

相关文章
相关标签/搜索