朴素贝叶斯NaiveBayes以及python实现

朴素贝叶斯NaiveBayes以及python实现


本文主要是基于《机器学习》的一篇读书笔记。

1.贝叶斯决策论

(1)贝叶斯分类器的基本原理如下:
p ( c ∣ x ) = p ( c ) p ( x ∣ c ) p ( x ) p(c|x)=\frac{p(c)p(x|c)}{p(x)} p(cx)=p(x)p(c)p(xc)
其中, p ( c ) p(c) p(c)是先验概率, p ( x ∣ c ) p(x|c) p(xc)是样本x相对于类别c的类条件概率,也称为似然, p ( x ) p(x) p(x)是用于归一化的证据因子。

(2)贝叶斯分类器的表达式如下:
h ( x ) = a r g m a x c p ( c ∣ x ) h(x)=argmax_{c}p(c|x) h(x)=argmaxcp(cx)
即对于每个样本选择能使后验概率 p ( c ∣ x ) p(c|x) p(cx)最大的类别作为最后预测类别。

因为对于样本x而言 p ( x ) p(x) p(x)与类标记无关,所以估计 p ( c ∣ x ) p(c|x) p(cx)就变成了估计先验概率 p ( c ) p(c) p(c)和类条件概率 p ( x ∣ c ) p(x|c) p(xc)。对于先验概率 p ( c ) p(c) p(c),可以通过样本中给类别出现的频率进行估计;对于类条件概率 p ( x ∣ c ) p(x|c) p(xc),因为它是关于x所有属性的联合概率,直接根据样本出现频率进行估计会有严重的困难,例如样本x有d个属性都是二值的,那么就有 2 d 2^d 2d种可能,在实际情况中是可能大于样本数目的,因此直接统计样本出现频率就会导致某些样本取值的概率为0,然而“样本取值未被观测到”和“样本概率为0”是不同的,所以不能直接统计频率。

注:【基于有限的样本估计联合概率,在计算上与遭遇组合爆炸的问题,在数据上也会遇到样本稀疏问题,并且随着属性值的增加越严重。因为对于类条件概率,首先需要计算所有属性值的组合问题,这就是很耗费资源的一件事;并且在将属性值的所有组合计算完成后,会发现大部分的属性组合根本没有样本,因此会遇到样本稀疏问题,而“未被观测到”与“出现概率为零”通常是不同的。】

针对上面的问题因此有了朴素贝叶斯的做法。

2.朴素贝叶斯分类器

因为很难直接从有限的样本中直接估计所有属性的联合概率,因此朴素贝叶斯采用“属性条件独立性假设”:对于已知类别,假设所有属性相互独立。因此上面贝叶斯分类的基本原理可以写成如下形式:
p ( c ∣ x ) = p ( c ) p ( x ∣ c ) p ( x ) = p ( c ) p ( x ) ∏ i = 1 d p ( x i ∣ c ) p(c|x)=\frac{p(c)p(x|c)}{p(x)}=\frac{p(c)}{p(x)}\prod_{i=1}^dp(x_i|c) p(cx)=p(x)p(c)p(xc)=p(x)p(c)i=1dp(xic)
其中d为属性的个数, x i x_i xi为样本 x x x i i i个属性的取值。

因为 p ( x ) p(x) p(x)和类别无关,因此朴素贝叶斯分类器的表达式如下:
h ( x ) = a r g m a x c p ( c ) ∏ i = 1 d p ( x i ∣ c ) h(x)=argmax_{c}p(c)\prod_{i=1}^dp(x_i|c) h(x)=argmaxcp(c)i=1dp(xic)
因此朴素贝叶斯分类器的训练过程就是就是基于训练集D来估计先验概率 p ( c ) p(c) p(c),以及为每个属性估计条件概率 p ( x i ∣ c ) p(x_i|c) p(xic)。对于类条件概率 p ( x i ∣ c ) p(x_i|c) p(xic)可以通过极大似然似然进行估计,即通过假设其服从某种概率分布,然后求使得似然函数最大的参数值(假设的概率分布中的未知参数)。极大似然可以参考:极大似然估计及其应用

(1)对于先验概率,令 D c D_c Dc表示数据集 D D D中第c类样本组成的集合,若有充足的独立同分布样本,则可估计:
p ( c ) = ∣ D c ∣ ∣ D ∣ p(c)=\frac{|D_c|}{|D|} p(c)=DDc
(2)对于类条件概率我们将离散属性和连续属性的条件概率分开处理。首先对于离散属性,令 D c , x i D_{c,x_i} Dc,xi表示 D c D_c Dc中在第i个属性上取值为 x i x_i xi的样本集合,则其条件概率为:
p ( x i ∣ c ) = ∣ D c , x i ∣ ∣ D c ∣ p(x_i|c)=\frac{|D_{c,x_i}|}{|D_c|} p(xic)=DcDc,xi
然后对于连续属性而言可以考虑概率密度函数,假定 p ( x i ∣ c ) − N ( μ c , i , σ c , i 2 ) p(x_i|c)-N(\mu_{c,i},\sigma_{c,i}^2) p(xic)N(μc,i,σc,i2),其中 μ c , i 和 σ c , i 2 \mu_{c,i}和\sigma_{c,i}^2 μc,iσc,i2分别是第c类样本在第i个属性上的均值和方差(通过极大似然估计求得的,这也是sklearn中高斯贝叶斯分类器的实现),则条件概率为:
p ( x i ∣ c ) = 1 2 π σ c , i e x p ( − ( x i − μ c , i ) 2 2 σ c , i 2 ) p(x_i|c)=\frac{1}{\sqrt{2\pi}\sigma_{c,i}}exp(-\frac{(x_i-\mu_{c,i})^2}{2\sigma_{c,i}^2}) p(xic)=2π σc,i1exp(2σc,i2(xiμc,i)2)

这个是时候有个问题就是上面可能会出现某个属性的类条件概率为0,导致在最后预测的时候连乘的结果为0,显然这样不太合理,因此可以考虑加入拉普拉斯平滑:令 N N N为数据集D中的类别数, N i N_i Ni表示第i个属性可能取值的数目,那么先验概率和离散属性的类条件概率变为如下形式:
p ( c ) = ∣ D c ∣ + 1 ∣ D ∣ + N p ( x i ∣ c ) = ∣ D c , x i ∣ + 1 ∣ D c ∣ + N i p(c)=\frac{|D_c|+1}{|D|+N}\\p(x_i|c)=\frac{|D_{c,x_i}|+1}{|D_c|+N_i} p(c)=D+NDc+1p(xic)=Dc+NiDc,xi+1

总结:因为在朴素贝叶斯中我们对于连续性属性的概率分布是假设其服从正态分布,因此在探索数据分析部分如果发现连续值属性出现正偏或负偏的情况,那么我们可以考虑将其转换为正态分布。

3.python代码实现

数据集:

色泽,根蒂,敲声,纹理,脐部,触感,密度,含糖量,好瓜
青绿,蜷缩,浊响,清晰,凹陷,硬滑,0.697,0.46,是
乌黑,蜷缩,沉闷,清晰,凹陷,硬滑,0.774,0.376,是
乌黑,蜷缩,浊响,清晰,凹陷,硬滑,0.634,0.264,是
青绿,蜷缩,沉闷,清晰,凹陷,硬滑,0.608,0.318,是
浅白,蜷缩,浊响,清晰,凹陷,硬滑,0.556,0.215,是
青绿,稍蜷,浊响,清晰,稍凹,软粘,0.403,0.237,是
乌黑,稍蜷,浊响,稍糊,稍凹,软粘,0.481,0.149,是
乌黑,稍蜷,浊响,清晰,稍凹,硬滑,0.437,0.211,是
乌黑,稍蜷,沉闷,稍糊,稍凹,硬滑,0.666,0.091,否
青绿,硬挺,清脆,清晰,平坦,软粘,0.243,0.267,否
浅白,硬挺,清脆,模糊,平坦,硬滑,0.245,0.057,否
浅白,蜷缩,浊响,模糊,平坦,软粘,0.343,0.099,否
青绿,稍蜷,浊响,稍糊,凹陷,硬滑,0.639,0.161,否
浅白,稍蜷,沉闷,稍糊,凹陷,硬滑,0.657,0.198,否
乌黑,稍蜷,浊响,清晰,稍凹,软粘,0.36,0.37,否
浅白,蜷缩,浊响,模糊,平坦,硬滑,0.593,0.042,否
青绿,蜷缩,沉闷,稍糊,稍凹,硬滑,0.719,0.103,

代码:

import numpy as np
import pandas as pd
import math

def norm_pdf(val,mean, std):
    pdf = 1/(math.sqrt(2*math.pi)*std) * math.exp(-math.pow(val-mean,2)/(2*std*std))
    return pdf

def NaiveBayes(train_data:pd.DataFrame):
    parameters = {}
    features = train_data.columns.values[:-1]
    label = train_data.columns.values[-1]
    parameters[label] = {}
    label_values = train_data[label].unique()
    for val in label_values:
        D_c = len(train_data[train_data[label]==val])
        D = len(train_data)
        N = len(label_values)
        #计算概率时避免分母为0的情况,加入拉普拉斯平滑
        parameters[label][val] = (D_c+1)/(D+N)

    for feat in features:
        # print(feat)
        if feat not in parameters.keys():
            parameters[feat] = {}
        if ('object' in str(train_data[feat].dtype)) or ('int' in str(train_data[feat].dtype)):
            feat_values = train_data[feat].unique()
            N_i = len(feat_values)

            for feat_val in feat_values:

                parameters[feat][feat_val] = {}
                for label_val in label_values:
                    D_ci = len(train_data[train_data[label]==label_val][train_data[feat] == feat_val])
                    D_c = len(train_data[train_data[label] == label_val])
                    # print(feat_val,' ',label_val,'-> ',(D_ci + 1),'/', (D_c + N_i))
                    parameters[feat][feat_val][label_val] = (D_ci + 1) / (D_c + N_i)
        else:
            for label_val in label_values:
                parameters[feat][label_val] = {}
                mean_ci = train_data[train_data[label]==label_val][feat].mean()
                std_ci = train_data[train_data[label]==label_val][feat].std()
                parameters[feat][label_val]['mean'] = mean_ci
                parameters[feat][label_val]['std'] = std_ci

    return parameters

    return parameters

def predict(X, parameters:{}):
    features = X.columns.values
    label = '好瓜'
    label_values = ['是','否']

    res = ''
    max_p = -999

    for label_val in label_values:
        p = parameters[label][label_val]
        for feat in features:
            if ('object' in str(X[feat].dtype)) or ('int' in str(X[feat].dtype)):
                p *= parameters[feat][X.loc[0,feat]][label_val]
            else:
                p *= norm_pdf(X.loc[0,feat],parameters[feat][label_val]['mean'],parameters[feat][label_val]['std'])
        if p>max_p:
            res = label_val
            max_p = p

        print(res, ' ', p)
    return res, max_p


if __name__ == '__main__':
    df = pd.read_csv('../Data/watermelon.csv')
    parameters = NaiveBayes(df)
    print(parameters)

    X = pd.DataFrame()
    X['色泽'] = ['青绿']
    X['根蒂'] = ['蜷缩']
    X['敲声'] = ['浊响']
    X['纹理'] = ['清晰']
    X['脐部'] = ['凹陷']
    X['触感'] = ['硬滑']
    X['密度'] = [0.697]
    X['含糖量'] = [0.460]

    print(X)
    print(predict(X, parameters))

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值