朴素贝叶斯原理及实现

本文参考自鲁东大学人工智能学院课程内容

百度百科解释:朴素贝叶斯法(Naive Bayes model)是基于贝叶斯定理与特征条件独立假设的分类方法。
最为广泛的两种分类模型是决策树模型(Decision Tree Model)和朴素贝叶斯模型(Naive Bayesian Model,NBM)。和决策树模型相比,朴素贝叶斯分类器(Naive Bayes Classifier 或 NBC)发源于古典数学理论,有着坚实的数学基础,以及稳定的分类效率。同时,NBC模型所需估计的参数很少,对缺失数据不太敏感,算法也比较简单。理论上,NBC模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为NBC模型假设属性之间相互独立,这个假设在实际应用中往往是不成立的,这给NBC模型的正确分类带来了一定影响。

理论

简单来说,朴素贝叶斯就是一种基于统计分类的方法,通过计算样本发生的概率,来实现分类。并且朴素贝叶斯要求各个事件之间发生的概率是独立的。

概率相关知识介绍

在这里插入图片描述
这是之前上两个博文,我在决策树算法中使用的是否放贷的数据集,基于这个数据集来简单介绍下朴素贝叶斯分类中所涉及的概率知识。

  • 独立分布

    在假设所有事件都独立的情况下,某一个事件发生的概率就称为独立概率。
    例如:

       P ( Y = y e s ) = 9 15 = 0.6 P(Y = yes) = \frac{9}{15} =0.6 P(Y=yes)=159=0.6         P ( Y = n o ) = 6 15 = 0.4 P(Y = no) = \frac{6}{15} =0.4 P(Y=no)=156=0.4

    表示在该数据集中,放贷条件为yes的概率为0.6(总样本15个,其中为yes的有9个),放贷条件为no的概率为0.4(总样本15个,其中为yes的有6个)

  • 联合分布

    联合概率表示某两个或多个事件同时发生的概率,通常表示为P(XY),即X和Y事件同时发生的概率。
    例如:P(x 3Y)

       P ( x 3 = 1 , Y = y e s ) = 6 15 = 0.4 P(x_3=1,Y=yes) = \frac{6}{15} =0.4 P(x3=1,Y=yes)=156=0.4         P ( x 3 = 1 , Y = n o ) = 0 15 = 0 P(x_3=1,Y=no) = \frac{0}{15} =0 P(x3=1,Y=no)=150=0

       P ( x 3 = 0 , Y = y e s ) = 3 15 = 0.2 P(x_3=0,Y=yes) = \frac{3}{15} =0.2 P(x3=0,Y=yes)=153=0.2         P ( x 3 = 0 , Y = n o ) = 6 15 = 0.4 P(x_3=0,Y=no) = \frac{6}{15} =0.4 P(x3=0,Y=no)=156=0.4

    表示在该数据集中,x3列值等于1同时放贷条件为yes的概率为0.4(总样本15个,其中为x3=1并于放贷等于yes的有6个),x3列值等于1同时放贷条件为no的概率为0.4(总样本15个,其中为x3=1并于放贷等于no的有0个)
    … …

  • 条件分布

    条件概率分布是指,在某一个条件发生的情况下另一个条件发生的概率,通常表示为P(X|Y),即在条件B发生的情况下条件X发生的概率。
    例如:P(x 3|Y) 和 P(Y|x 3)

       P ( x 3 = 1 ∣ Y = y e s ) = 6 9 = 0.67 P(x_3=1|Y=yes) = \frac{6}{9} =0.67 P(x3=1Y=yes)=96=0.67         P ( x 3 = 0 ∣ Y = y e s ) = 3 9 = 0.33 P(x_3=0|Y=yes) = \frac{3}{9} =0.33 P(x3=0Y=yes)=93=0.33

       P ( Y = y e s ∣ x 3 = 0 ) = 3 9 = 0.33 P(Y=yes|x_3=0) = \frac{3}{9} =0.33 P(Y=yesx3=0)=93=0.33         P ( Y = n o , x 3 = 0 ) =   6 9 = 0.67 P(Y=no,x_3=0) = \ \frac{6}{9} =0.67 P(Y=no,x3=0)= 96=0.67

    表示在该数据集中,在条件放贷等于yes的情况下x3列值等于1的概率为0.67(条件为yes的样本有9个,在这9个样本中x3=1的样本有6个),在条件放贷等于yes的情况下x3列值等于0的概率为0.33(条件为yes的样本有9个,在这9个样本中x3=0的样本有3个)

  • 几个概率之间的关系
    这几个概率之间存在可以相互转换的关系,关系公式如下:

       P ( X Y ) = P ( X ∣ Y ) ∗ P ( Y ) P(XY)=P(X|Y)*P(Y) P(XY)=P(XY)P(Y)

    同理也可以写为

       P ( X Y ) = P ( Y ∣ X ) ∗ P ( X ) P(XY)=P(Y|X)*P(X) P(XY)=P(YX)P(X)
    即:
       P ( X Y ) = P ( X ∣ Y ) ∗ P ( Y ) = P ( Y ∣ X ) ∗ P ( X ) P(XY)=P(X|Y)*P(Y)=P(Y|X)*P(X) P(XY)=P(XY)P(Y)=P(YX)P(X)

    去掉 P ( X Y ) P(XY) P(XY)可以得到

       P ( X ∣ Y ) = P ( Y ∣ X ) ∗ P ( X ) P ( Y ) P(X|Y)= \frac{P(Y|X)*P(X)}{P(Y)} P(XY)=P(Y)P(YX)P(X)   这就是著名的贝叶斯公式

在已知数据的情况下求取结果就可以转换为在已知X的情况下求Y。
在这里插入图片描述
求得的值越大,就说明属于该类别的可能性就越高。

你可能看起来好像很简单,前面已经算过了各个概率的值,直接带进去查表就可以得到相应的值,并根据大小就可以推断出它属于那种类别,但是这里有个问题,例如在[x1,x2,x3,x4] = [1,0,1,0] 的情况下,算得的

  P(Y = yes|x1,x2,x3,x4) = 0    P(Y = no|x1,x2,x3,x4) = 0

两个结果都为0,你能说它属于两个概率得情况相等吗?显然不行,所以需要将这个共识在做一个变形。

在假设条件独立得情况下有: P ( x 1 , x 2 , x 3 , x 4 ∣ Y = y e s ) = P ( x 1 ∣ Y ) P ( x 2 ∣ Y ) P ( x 3 ∣ Y ) P ( x 4 ∣ Y ) P(x_1,x_2,x_3,x_4|Y = yes)=P(x_1|Y)P(x_2|Y)P(x_3|Y)P(x_4|Y) P(x1,x2,x3,x4Y=yes)=P(x1Y)P(x2Y)P(x3Y)P(x4Y)
则原公式可以变形为:

   P ( Y = y e s ∣ x   1   , x   2   , x   3   , x   4   ) = P ( x 1 , x 2 , x 3 , x 4 ∣ Y = y e s ) P ( Y = y e s ) = P ( x 1 ∣ Y = y e s ) P ( x 2 ∣ Y = y e s ) P ( x 3 ∣ Y = y e s ) P ( x 4 ∣ Y = y e s ) P ( Y = y e s ) P(Y = yes|x~1~,x~2~,x~3~,x~4~)= \frac{P(x_1,x_2,x_3,x_4|Y = yes)}{P(Y = yes)}=\frac{P(x_1|Y = yes)P(x_2|Y = yes)P(x_3|Y = yes)P(x_4|Y = yes)}{P(Y = yes)} P(Y=yesx 1 ,x 2 ,x 3 ,x 4 )=P(Y=yes)P(x1,x2,x3,x4Y=yes)=P(Y=yes)P(x1Y=yes)P(x2Y=yes)P(x3Y=yes)P(x4Y=yes)

但是这里还有一个问题,由于分母上的概率是相乘起来的,当数据维数过多的情况下,分数相乘的式子就会很多,导致程序越界无法储存,为了解决这个问题,我们可以对等式两边取对数log

   L L K ( Y ∣ X ) = l o g ( P ( Y ) ) + ∑ i = 1 4 l o g ( P ( x i ∣ Y ) ) LLK(Y|X)=log(P(Y))+\sum^{4}_{i = 1}log(P(x_i|Y)) LLK(YX)=log(P(Y))+i=14log(P(xiY))   这就称为似然函数

分类问题就转换为了:
在这里插入图片描述

代码实现

导入numpy包

import numpy as np

加载数据集

#创建数据集
def createDataSet():
    """DateSet 基础数据集
    Args:
        无需传入参数
    Returns:
        返回数据集和对应的label标签
    """
    dataSet = [[0, 0, 0, 0, 'no'],
               [0, 0, 0, 1, 'no'],
               [0, 1, 0, 1, 'yes'],
               [0, 1, 1, 0, 'yes'],
               [0, 0, 0, 0, 'no'],
               [1, 0, 0, 0, 'no'],
               [1, 0, 0, 1, 'no'],
               [1, 1, 1, 1, 'yes'],
               [1, 0, 1, 2, 'yes'],
               [1, 0, 1, 2, 'yes'],
               [2, 0, 1, 2, 'yes'],
               [2, 0, 1, 1, 'yes'],
               [2, 1, 0, 1, 'yes'],
               [2, 1, 0, 2, 'yes'],
               [2, 0, 0, 0, 'no'],]
    labels = ['年龄', '有工作', '有自己的房子', '信贷情况']  #特征标签
    return dataSet, labels

获取概率模型, 输入feat,np.array格式,大小[N,D]
此函数是求取P(X|Y),传入的参数是在假设已经处理好了原始数据,只有满足Y条件的数据,然后根据统计求取在已经满足Y条件下的各个x的概率。

# 获取概率模型, 输入feat np.array格式 大小[N,D]
def trainPbmodel_X(feats):
    N,D = np.shape(feats)
   
    model = {}
    # 对每一维度的特征进行概率统计
    for d in range(D):
        data = feats[:,d].tolist()
        keys = set(data)
        N = len(data)
        model[d] ={}
        for key in keys:
            model[d][key] = float(data.count(key)/N)
    return model

整体模型训练函数

# datas: list格式 每个元素表示1个特征序列    
# labs:  list格式 每个元素表示一个标签
def trainPbmodel(datas,labs):
    # 定义模型
    model = {}
    # 获取分类的类别
    keys = set(labs)
    for key in keys:
        # 获得P(Y)
        Pbmodel_Y = labs.count(key)/len(labs)
        
        # 收集标签为Y的数据
        index = np.where(np.array(labs)==key)[0].tolist()
        feats = np.array(datas)[index]
        
        # 获得 P(X|Y)
        Pbmodel_X = trainPbmodel_X(feats)
        
        # 模型保存
        model[key]={}
        model[key]["PY"] = Pbmodel_Y
        model[key]["PX"] = Pbmodel_X
    return model

返回结果如下格式:
在这里插入图片描述
表示在yes的条件下,P(Y) = 0.6,其中在第0维也就是第一个条件中是0,1,2的,即P(x0=0,1,2|Y) = 0.22222, 0.33333, 0.4444。同理在1,2,3维的概率,以及在no条件下各维度的概率。

该函数用于在已经训练好的模式下预测结果

# feat : list格式 一条输入特征
# model: 训练的概率模型
# keys :考察标签的种类 
def getPbfromModel(feat,model,keys):
    results ={}
    eps = 0.00001  #这个值是防止在概率为0的情况下似然函数取log会溢出的问题,将概率为0替换为该数
    for key in keys:
        # 获取P(Y)
        PY = model.get(key,eps).get("PY")
        
        # 分别获取 P(X|Y)
        model_X = model.get(key,eps).get("PX")
        list_px=[]
        for d in range(len(feat)):
            pb = model_X.get(d,eps).get(feat[d],eps)
            list_px.append(pb)
        
        result = np.log(PY) + np.sum(np.log(list_px))
        results[key]= result
    return results

最后测试一下

if __name__ == '__main__':        
    # 获取数据集
    dataSet, labels = createDataSet()    
    # 截取数据和标签
    datas = [i[:-1] for i in dataSet]
    labs = [i[-1] for i in dataSet]     
    # 获取标签种类
    keys = set(labs)    
    # 进行模型训练
    model = trainPbmodel(datas,labs)
    # print(model)  
    # 根据输入数据获得预测结果
    feat = [0,1,0,1]
    result = getPbfromModel(feat,model,keys)
    print(result)  
    # 遍历结果找到概率最大值进行数据
    for key,value in result.items():
        if(value == max(result.values())):
            print("预测结果是",key)

运行结果为:

{'no': -14.220975666072437, 'yes': -4.512232190328822}
预测结果是 yes

上面是个二分类问题,下面看在多分类问题上适不适用

数据集为:

青年	近视	否	干涩	不配镜
青年	近视	否	正常	软镜片
青年	近视	是	干涩	不配镜
青年	远视	否	干涩	不配镜
青年	远视	是	干涩	不配镜
中年	近视	否	干涩	不配镜
中年	近视	否	正常	软镜片
中年	近视	是	干涩	不配镜
中年	近视	是	正常	硬镜片
中年	远视	否	干涩	不配镜
中年	远视	否	正常	软镜片
中年	远视	是	干涩	不配镜
中年	远视	是	正常	不配镜
老年	近视	否	正常	不配镜
老年	近视	是	干涩	不配镜
老年	近视	是	正常	硬镜片
老年	远视	否	干涩	不配镜
老年	远视	否	正常	软镜片
老年	远视	是	干涩	不配镜
老年	远视	是	正常	不配镜
青年	远视	是	正常	硬镜片

测试集为:

青年	远视	否	正常	软镜片
老年	近视	否	干涩	不配镜
青年	近视	是	正常	硬镜片

将上述两个数据集分别保存为train-lenses.txttest-lenses.txt
函数定义都不变,只是不需要createDataSet()函数

if __name__ == '__main__':        
# 读取数据文件 截取数据和标签
    with open("train-lenses.txt",'r',encoding="utf-8") as f:
        lines = f.read().splitlines()
    dataSet = [line.split('\t') for line in lines]
    
    datas = [i[:-1] for i in dataSet]
    labs = [i[-1] for i in dataSet] 
    
    # 获取标签种类
    keys = set(labs)
    # 进行模型训练
    model = trainPbmodel(datas,labs)
    print(model)
    # 测试
    # 读取测试文件
    with open("test-lenses.txt",'r',encoding="utf-8") as f:
        lines = f.read().splitlines()
    
    # 逐行读取数据并测试   
    for line in lines:
        data = line.split('\t')
        lab_true = data[-1]
        feat = data[:-1]
        result = getPbfromModel(feat,model,keys)
        
        key_out = ""
        for key,value in result.items():
            if(value == max(result.values())):
                key_out=key
        print("输入特征:")
        print(data)
        print(result)
        print("预测结果 %s  医生推荐 %s"%(key_out,lab_true))

运行结果为:
在这里插入图片描述
其中训练的模型为

{'不配镜': {'PY': 0.6666666666666666, 'PX': {0: {'老年': 0.35714285714285715, '中年': 0.35714285714285715, '青年': 0.2857142857142857}, 1: {'近视': 0.42857142857142855, '远视': 0.5714285714285714}, 2: {'否': 0.42857142857142855, '是': 0.5714285714285714}, 3: {'正常': 0.21428571428571427, '干涩': 0.7857142857142857}}}, '硬镜片': {'PY': 0.14285714285714285, 'PX': {0: {'老年': 0.3333333333333333, '中年': 0.3333333333333333, '青年': 0.3333333333333333}, 1: {'近视': 0.6666666666666666, '远视': 0.3333333333333333}, 2: {'是': 1.0}, 3: {'正常': 1.0}}}, '软镜片': {'PY': 0.19047619047619047, 'PX': {0: {'老年': 0.25, '中年': 0.5, '青年': 0.25}, 1: {'近视': 0.5, '远视': 0.5}, 2: {'否': 1.0}, 3: {'正常': 1.0}}}}

测试结果为:

输入特征:
['青年', '远视', '否', '正常', '软镜片']
{'不配镜': -4.605586765873308, '硬镜片': -15.656060191361762, '软镜片': -3.737669618283368}
预测结果 软镜片  医生推荐 软镜片
输入特征:
['老年', '近视', '否', '干涩', '不配镜']
{'不配镜': -3.370842302880618, '硬镜片': -26.475838475772044, '软镜片': -15.250595083253597}
预测结果 不配镜  医生推荐 不配镜
输入特征:
['青年', '近视', '是', '正常', '硬镜片']
{'不配镜': -4.605586765873308, '硬镜片': -3.4499875458315876, '软镜片': -15.250595083253597}
预测结果 硬镜片  医生推荐 硬镜片

以上就是朴素贝叶斯的相关理论和代码实现,希望对你的学习和理解有所帮助。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐