美文网首页
朴素贝叶斯算法

朴素贝叶斯算法

作者: LY豪 | 来源:发表于2018-12-10 00:22 被阅读0次

    简介

    贝叶斯学派很古老,但是从诞生到一百年前一直不是主流。主流是频率主义学派。频率主义学派的权威皮尔逊和费歇尔都对贝叶斯学派不屑一顾,但是贝叶斯学派硬是凭借在现代特定领域的出色应用表现为自己赢得了半壁江山。

    贝叶斯学派的思想可以概括为先验概率+数据=后验概率。也就是说我们在实际问题中需要得到的后验概率,可以通过先验概率和数据一起综合得到。数据大家好理解,被频率学派攻击的是先验概率,一般来说先验概率就是我们对于数据所在领域的历史经验,但是这个经验常常难以量化或者模型化,于是贝叶斯学派大胆的假设先验分布的模型,比如正态分布,beta分布等。这个假设一般没有特定的依据,因此一直被频率学派认为很荒谬。虽然难以从严密的数学逻辑里推出贝叶斯学派的逻辑,但是在很多实际应用中,贝叶斯理论很好用,比如垃圾邮件分类,文本分类。

    贝叶斯决策论

    贝叶斯决策论(Bayesian decision theory)是概率框架下实施决策的基本方法。对分类任务来说,在所有相关概率都已知的理想情形下,贝叶斯决策论考虑如何基于这些概率和误判损失来选择最优的类别标记。

    具体来说,若我们决策的目标是最小化分类错误率,贝叶斯最优分类器要对每个样本 x,选择能使后验概率 P( c | x )最大的类别 c 标记。在现实任务中后验概率通常难以直接获得。从这个角度来说,机器学习所要实现的是基于有限的训练样本集尽可能准确地估计出后验概率 P( c | x )。大体来说,主要有两种策略:给定x,可通过直接建模P( c | x )来预测c,这样得到的是“判别式模型”,例如,决策树、BP神经网络、支持向量机等等;也可先对联合概率分布P( x,c )建模,然后在由此获得P( c | x ),这样得到的是“生成式模型”。对于生成式模型来说,必然考虑 基于贝叶斯定理, P( c | x )可写为
    这就将求后验概率P(c|x)的问题转变为求类先验概率P(c)和条件概率P(x|c)。

    类先验概率 P(c) 表达了样本空间中各类样本所占的比例,根据大数定律,当训练集包含充足的独立同分布样本时,P(c) 可通过各类样本出现的频率来进行估计。

    因为对于类条件概率 P( x | c ) 来说,由于它涉及关于 x 所有属性的联合概率,直接根据样本出现的频率来估计将会遇到严重的困难。假如样本的d个属性都是二值的,则样本空间将有2的d次方种可能取值,在现实中,这个种类数往往大于训练样本,也就是说,很多样本取值在训练集中根本没有出现,直接使用频率来估计 P( x | c )显然不可行,因为“未被观测到”与“出现概率为零”通常是不同的。这可以通过极大似然估计来解决。

    朴素贝叶斯分类器

    基于贝叶斯公式来估计后验概率P( c | x )的主要困难在于:类条件概率P( x | c )是所有属性上的联合概率,难以从有限的训练样本直接估计而得。因此朴素贝叶斯分类器采用了“属性条件独立性假设”:对已知类别,假设所有属性相互独立。也就是说,假设每个属性独立的对分类结果发生影响。

    基于属性独立性假设,后验概率P( c | x )可写为

    显然朴素贝叶斯分类器的训练过程就是基于训练集D来估计类先验概率P( c ),并为每个属性估计条件概率P( x|c )。

    由于对所有类别来说P( x )相同,因此贝叶斯判定准则有

    下面是一个用西瓜数据集3.0训练朴素贝叶斯分类器,对测试例“测1”进行分类:



    注意:实践中常通过取对数的方式来将“连乘”转化为“连加”以避免数值下溢。

    拉普拉斯修正

    若某个属性值在训练集中没有与某个类同时出现过,则基于式(7.17)进行概率估计,再根据式(7.15)进行判别将出现问题。例如在使用西瓜数据集3.0训练朴素贝叶斯分类器时,对一个“敲声=清脆”的测试例,有

    由于式(7.15)的连乘式计算出的概率值为零,因此,无论该样本的属性如何,分类的结果都是“好瓜=否”,这明显不合理。

    这时候就要进行“平滑”,常用“拉普拉斯修正”。式(7.16)和(7.17)分别修正为

    显然,拉普拉斯修正避免了因训练集不充分而导致概率估值为零的问题,并且在训练集变大时,修正过程所引入的先验的影响也会逐渐变得可忽略,使得估值渐趋向于实际概率值。

    朴素贝叶斯的实现代码

    引入了拉普拉斯修正,数据集用的是西瓜数据集3.0


    西瓜数据集3.0
    import numpy as np
    import pandas as pd
    
    dataset = pd.read_csv('watermelon_3.csv', delimiter=",")
    del dataset['编号']
    print(dataset)
    X = dataset.values[:, :-1]
    m, n = np.shape(X)
    for i in range(m):
        X[i, n - 1] = round(X[i, n - 1], 3)
        X[i, n - 2] = round(X[i, n - 2], 3)
    y = dataset.values[:, -1]
    columnName = dataset.columns
    colIndex = {}
    for i in range(len(columnName)):
        colIndex[columnName[i]] = i
    
    Pmap = {}  # 函数P很耗时间,而且经常会求一样的东西,因此我加了个记忆化搜索,用map存一下,避免重复计算
    kindsOfAttribute = {}  # kindsOfAttribute[0] = 3,因为有3种不同的类型的"色泽"
    for i in range(n):
        kindsOfAttribute[i] = len(set(X[:, i]))
    continuousPara = {}  # 记忆一些参数的连续数据,以避免重复计算
    
    goodList = []
    badList = []
    for i in range(len(y)):
        if y[i] == '是':
            goodList.append(i)
        else:
            badList.append(i)
    
    import math
    
    
    def P(colID, attribute, C):  # P(colName=attribute|C) P(色泽=青绿|是)
        if (colID, attribute, C) in Pmap:
            return Pmap[(colID, attribute, C)]
        curJudgeList = []
        if C == '是':
            curJudgeList = goodList
        else:
            curJudgeList = badList
        ans = 0
        if colID >= 6:
            mean = 1
            std = 1
            if (colID, C) in continuousPara:
                curPara = continuousPara[(colID, C)]
                mean = curPara[0]
                std = curPara[1]
            else:
                curData = X[curJudgeList, colID]
                mean = curData.mean()
                std = curData.std()
                # print(mean,std)
                continuousPara[(colID, C)] = (mean, std)
            ans = 1 / (math.sqrt(math.pi * 2) * std) * math.exp((-(attribute - mean) ** 2) / (2 * std * std))
        else:
            for i in curJudgeList:
                if X[i, colID] == attribute:
                    ans += 1
            ans = (ans + 1) / (len(curJudgeList) + kindsOfAttribute[colID])
        Pmap[(colID, attribute, C)] = ans
        # print(ans)
        return ans
    
    
    def predictOne(single):
        ansYes = math.log2((len(goodList) + 1) / (len(y) + 2))  
        ansNo = math.log2((len(badList) + 1) / (len(y) + 2))
        for i in range(len(single)):  # 书上是连乘,但在实践中要把“连乘”通过取对数的方式转化为“连加”以避免数值下溢
            ansYes += math.log2(P(i, single[i], '是'))
            ansNo += math.log2(P(i, single[i], '否'))
        # print(ansYes,ansNo,math.pow(2,ansYes),math.pow(2,ansNo))
        if ansYes > ansNo:
            return '是'
        else:
            return '否'
    
    
    def predictAll(iX):
        predictY = []
        for i in range(m):
            predictY.append(predictOne(iX[i]))
        return predictY
    
    
    predictY = predictAll(X)
    print(y)
    print(np.array(predictAll(X)))
    
    confusionMatrix = np.zeros((2, 2))
    for i in range(len(y)):
        if predictY[i] == y[i]:
            if y[i] == '否':
                confusionMatrix[0, 0] += 1
            else:
                confusionMatrix[1, 1] += 1
        else:
            if y[i] == '否':
                confusionMatrix[0, 1] += 1
            else:
                confusionMatrix[1, 0] += 1
    print(confusionMatrix)
    
    
    输出结果

    输出结果说明:

    [7,2]表示预测的结果有7个坏瓜预测成功,两个预测成好瓜
    [1,7]表示预测的结果有一个好瓜预测成坏瓜,7个好瓜预测成好瓜
    

    代码以及数据集可以到我码云下载

    相关文章

      网友评论

          本文标题:朴素贝叶斯算法

          本文链接:https://www.haomeiwen.com/subject/bjahhqtx.html