朴素贝叶斯(Naive Bayes)(原理+Python实现)

创作背景

本菜鸡最近想学学 机器学习,这不,学到了 朴素贝叶斯
如果觉得我这篇文章写的好的话,能不能给我 点个赞评论 一波。如果要点个 关注 的话也不是不可以🤗

算法分类

对于 分类算法 来说,一般可分为 生成类算法判别类算法

生成类算法

通俗来讲,这类算法是在给定数据为 各个特征某一取值 的情况下,求出 属于标签某种取值 的概率,这类算法的典型是 朴素贝叶斯

判别类算法

通俗来讲,这类算法是 给定一些特征值,按照 各特征对标签值的影响程度判断属于哪一类数据,这类算法的典型是 决策树。下一篇文章会讲到。

区别

生成类算法 注重 特征值组合对标签值的影响;而 判别类算法 注重 单个特征对标签值的影响

知识补充

假设有两个事件 AB ,出现的概率分别为 P(A)P(B)两个事件同时出现 的概率为 P ( A , B ) P(A, B) P(A,B)。那么,在事件 A 发生的条件下,事件 B 发生的概率可以表示为 P(B|A) ,计算公式为
P ( B ∣ A ) = P ( A , B ) P ( A ) = P ( A ∣ B ) ⋅ P ( B ) P ( A ) P(B|A) = \frac{P(A, B)}{P(A)}=\frac{P(A|B) \cdot P(B)}{P(A)} P(BA)=P(A)P(A,B)=P(A)P(AB)P(B)
一般来说

  • P(B|A) ≠ P(A) ,也就是 AB 不是相互独立的,事件 B 的发生对事件 A 的发生 有影响
    • 如果 P(B|A) > P(A), 说明事件 B 的发生对事件 A 的发生有 促进 作用。
    • 反之,有 抑制 作用。
  • 如果 二者相等 ,则说明事件 B 的发生对事件 A 的发生 没有影响

朴素贝叶斯算法

朴素贝叶斯 中我们认为 特征之间相互独立 的,目的是在 各个特征值组合 的条件下,预测 标签各值 出现的概率。

假设数据集 T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) … ( x n , y n ) } T=\{(x_1, y_1), (x_2, y_2) \dots (x_n, y_n)\} T={(x1,y1),(x2,y2)(xn,yn)}P(X, Y) 独立同分布 产生,其中 XY 是两个特征,则

  • 先验概率 :某个特征各种取值的概率,eg: P ( Y = c k ) ,   k = 1 , 2 , … , K P(Y=c_k), \: k=1,2,\dots,K P(Y=ck),k=1,2,,K
  • 条件概率 :在 特征 Y 取值为 c k c_k ck 的条件下, 特征 X 取值为 x 的概率,表示为 P(X=x|Y=c_k) ,计算公式如下:

P ( X = x ∣ Y = c k ) = P ( X 1 = x 1 , X 2 = x 2 , … , X n = x n ∣ Y = c k ) ,   k = 1 , 2 , … , K P(X=x|Y=c_k) = P(X_1=x_1, X_2=x_2, \dots , X_n=x_n|Y=c_k), \: k=1,2,\dots,K P(X=xY=ck)=P(X1=x1,X2=x2,,Xn=xnY=ck),k=1,2,,K

条件独立性 假设下,可以合为
P ( X = x ∣ Y = c k ) = ∏ j = 1 n P ( X j = x j ∣ Y = c k ) P(X=x|Y=c_k) = \prod \limits_{j=1}^{n}{P(X_j=x_j|Y=c_k)} P(X=xY=ck)=j=1nP(Xj=xjY=ck)

  • 后验概率与先验概率相反,指的是 特征 X 取值为 x 的条件下,特征 Y 取值为 c k c_k ck 的概率,表示为 P(Y=c_k|X=x) ,计算公式如下:

P ( Y = c k ∣ X = x ) = P ( X = x , Y = c k ) P ( X = x ) = P ( Y = c k ) ⋅ P ( X = x ∣ Y = c k ) P ( X = x ) = P ( Y = c k ) ⋅ ∏ j = 1 n P ( X j = x j ∣ Y = c k ) ∑ k P ( Y = c k ) ⋅ ∏ j = 1 n P ( X j = x j ∣ Y = c k ) P(Y=c_k|X=x) = \frac{P(X=x, Y=c_k)}{P(X=x)}=\frac{P(Y=c_k) \cdot P(X=x|Y=c_k)}{P(X=x)} \\ =\frac{P(Y=c_k) \cdot \prod \limits_{j=1}^{n}{P(X_j=x_j|Y=c_k)}}{\displaystyle \sum _{k}{P(Y=c_k) \cdot \prod \limits_{j=1}^{n}{P(X_j=x_j|Y=c_k)}}} P(Y=ckX=x)=P(X=x)P(X=x,Y=ck)=P(X=x)P(Y=ck)P(X=xY=ck)=kP(Y=ck)j=1nP(Xj=xjY=ck)P(Y=ck)j=1nP(Xj=xjY=ck)

朴素贝叶斯 最后求得就是 每个标签值后验概率,将结果预测为 后验概率最大 的标签值。

举个栗子

假如我们有这样一个数据集(自己造的),如 下表:

序号x1x2 y
10
20
31
41
50
60
70
81
91
101
111
121
131
141
150

其中,

  • 特征 x1 表示 年龄 ,有三种取值,分别为 { 小 , 中 , 大 } \{小, 中, 大\} {,,}
  • 特征 x2 表示 收入 ,有三种取值,分别为 { 少 , 中 , 多 } \{少, 中, 多\} {,,}
  • 标签 y 表示 被诈骗过 ,有两种取值,分别为 { 0 , 1 } \{0, 1\} {0,1}

如果有一个人 年龄中等收入少,那他 是否被诈骗过

这就需要用到 贝叶斯 来判断。

求解思路

  1. 分别求出 被诈骗过没有被诈骗过 的人数占所有人数的 概率 ,即 P ( Y = 1 ) P(Y=1) P(Y=1) P ( Y = 0 ) P(Y=0) P(Y=0)
  2. 求出 年龄中等收入少 的数据分别占 被诈骗过没有被诈骗过 的数据的 概率 ,即 P ( x 1 = 中 ∣ Y = 0 ) , P ( x 1 = 中 ∣ Y = 1 ) P(x_1=中|Y=0), P(x_1=中|Y=1) P(x1=Y=0),P(x1=Y=1) P ( x 2 = 少 ∣ Y = 0 ) , P ( x 2 = 少 ∣ Y = 1 ) P(x_2=少|Y=0), P(x_2=少|Y=1) P(x2=Y=0),P(x2=Y=1)
  3. 求出以 年龄中等收入少 为前提的情况下,这个人 被诈骗过没被诈骗过 的概率,以 概率高的 作为最后分类结果。

求解过程(数学计算)

先验概率
P ( Y = 1 ) = 9 15 ,   P ( Y = 0 ) = 6 15 P ( x 1 = 中 ) = 5 15 ,   P ( x 2 = 少 ) = 4 15 P ( x 1 = 中 , x 2 = 少 ) = P ( x 1 = 中 ) × P ( x 2 = 少 ) = 4 45 P(Y=1)=\frac{9}{15},\: P(Y=0)=\frac{6}{15} \\ P(x_1=中)=\frac{5}{15},\: P(x_2=少)=\frac{4}{15} \\ P(x_1=中,x_2=少)=P(x_1=中) \times P(x_2=少)=\frac{4}{45} P(Y=1)=159,P(Y=0)=156P(x1=)=155,P(x2=)=154P(x1=,x2=)=P(x1=)×P(x2=)=454
条件概率(假设 特征之间相互独立
P ( x 1 = 中 ∣ Y = 0 ) = 2 6 ,   P ( x 1 = 中 ∣ Y = 1 ) = 3 9 P ( x 2 = 少 ∣ Y = 0 ) = 3 6 , P ( x 2 = 少 ∣ Y = 1 ) = 1 9 P ( x 1 = 中 , x 2 = 少 ∣ Y = 0 ) = P ( x 1 = 中 ∣ Y = 0 ) × P ( x 2 = 少 ∣ Y = 0 ) = 1 6 P ( x 1 = 中 , x 2 = 少 ∣ Y = 1 ) = P ( x 1 = 中 ∣ Y = 1 ) × P ( x 2 = 少 ∣ Y = 1 ) = 1 27 P(x_1=中|Y=0)=\frac{2}{6},\: P(x_1=中|Y=1)=\frac{3}{9} \\ P(x_2=少|Y=0)=\frac{3}{6}, P(x_2=少|Y=1)=\frac{1}{9} \\ P(x_1=中,x_2=少|Y=0)=P(x_1=中|Y=0) \times P(x_2=少|Y=0)=\frac{1}{6} \\ P(x_1=中,x_2=少|Y=1)=P(x_1=中|Y=1) \times P(x_2=少|Y=1)=\frac{1}{27} P(x1=Y=0)=62,P(x1=Y=1)=93P(x2=Y=0)=63,P(x2=Y=1)=91P(x1=,x2=Y=0)=P(x1=Y=0)×P(x2=Y=0)=61P(x1=,x2=Y=1)=P(x1=Y=1)×P(x2=Y=1)=271
后验概率
P ( Y = 0 ∣ x 1 = 中 , x 2 = 少 ) = P ( x 1 = 中 , x 2 = 少 ∣ Y = 0 ) × P ( Y = 0 ) P ( x 1 = 中 , x 2 = 少 ) = 1 6 × 6 15 4 45 = 3 4 P ( Y = 1 ∣ x 1 = 中 , x 2 = 少 ) = P ( x 1 = 中 , x 2 = 少 ∣ Y = 1 ) × P ( Y = 1 ) P ( x 1 = 中 , x 2 = 少 ) = 1 27 × 9 15 4 45 = 1 4 P(Y=0|x_1=中,x_2=少)=\frac{P(x_1=中,x_2=少|Y=0) \times P(Y=0)}{P(x_1=中,x_2=少)}=\frac{\frac{1}{6} \times \frac{6}{15}}{\frac{4}{45}}=\frac{3}{4} \\ P(Y=1|x_1=中,x_2=少)=\frac{P(x_1=中,x_2=少|Y=1) \times P(Y=1)}{P(x_1=中,x_2=少)}=\frac{\frac{1}{27} \times \frac{9}{15}}{\frac{4}{45}}=\frac{1}{4} P(Y=0x1=,x2=)=P(x1=,x2=)P(x1=,x2=Y=0)×P(Y=0)=45461×156=43P(Y=1x1=,x2=)=P(x1=,x2=)P(x1=,x2=Y=1)×P(Y=1)=454271×159=41

∵ P ( Y = 0 ∣ x 1 = 中 , x 2 = 少 ) > P ( Y = 1 ∣ x 1 = 中 , x 2 = 少 ) ∴ 这 个 人 很 大 概 率   没 有 被 骗 过   。 \because P(Y=0|x_1=中,x_2=少) > P(Y=1|x_1=中,x_2=少) \\ \therefore 这个人很大概率 \: 没有被骗过 \: 。 P(Y=0x1=,x2=)>P(Y=1x1=,x2=)

代码实现

自己实现
from collections import Iterable

class NaiveBayes:
    
    def __init__(self):
        # 先验概率
        self.pri = pd.DataFrame()
        # 条件概率
        self.cond = pd.DataFrame()
        # 标签列,默认为最后一列
        self.y_col = ''
        # 特征列名列表
        self.featrues = []
    
    def get_frequency(self, data, name=None, key=None):
        # 获得频率
        freq = data.value_counts(normalize=True)
        # 设置返回的 dataframe 的列名和索引
        name = freq.name if name is None else name
        key = freq.name if key is None else key
        
        freq = freq.to_frame(name=name)
        
        freq.index = pd.MultiIndex.from_product([[key], freq.index.tolist()])
        
        return freq
    
    def get_priori(self, data):
        # 初始化结果
        result = pd.DataFrame()
        # 遍历特征列
        for column in data.columns:
            # 获得频率
            p = self.get_frequency(data[column], 'value')
            # 保存结果
            result = result.append(p)
        # 返回先验概率
        return result
    
    def get_conditional(self, data):
        # 初始化结果
        result = None
        # 以标签列分类
        for cate, cate_index in data.groupby(self.y_col).groups.items():
            # 每个标签取值对应的数据
            x = data.loc[cate_index, self.featrues]
            # 初始化标签取值对应各特征取值频次,即 P(X=x|Y=y)
            cate_df = None
            # 遍历特征列
            for column in self.featrues:
                # 获得对应频次
                freq = self.get_frequency(x[column], name=cate, key=column)
                # 如果为 None,初始化
                if cate_df is None:
                    cate_df = freq
                # 否则,拼接结果
                else:
                    cate_df = pd.concat([cate_df, freq])
                    
            # 逻辑同上
            if result is None:
                result = cate_df
            else:
                result = pd.concat([result, cate_df], axis=1)
        # 返回条件概率
        return result
    
    def train(self, data, y_col='y'):
        
        if y_col not in data.columns:
            print(f'Column of y named {y_col} not in data. It will go by column {data.columns[-1]}')
            y_col = data.columns[-1]
        # 设置标签列列名
        self.y_col = y_col
        # 获得特征列名列表
        self.featrues = data.columns.tolist()
        self.featrues.remove(y_col)
        # 先验概率
        self.pri = self.get_priori(data)
        # 条件概率
        self.cond = self.get_conditional(data)
        
        return 
    
    def forward(self, pred_dict):
        # 预测结果,每一列,列名为标签各取值,值为各标签值在指定条件下的概率
        result = None
        # 遍历要预测的特征值字典,获得各标签值概率中 ΠP(X=x)
        for key, value in pred_dict.items():
            # 如果特征及值不存在,报错
            if (key, value) not in self.cond.index:
                raise ValueError(f'The key or value of input data not exists.')
            # 逻辑类似于 self.get_conditional
            if result is None:
                result = self.cond.loc[(key, value)].to_frame().T
            else:
                result *= self.cond.loc[(key, value)]
        # 每列乘上 P(Y=y)
        result = result.apply(lambda x: x * self.pri.loc[(self.y_col, x.name)].values[0])
        
        result /= result.sum(axis=1).values[0]
        
        return result.iloc[0]
    
    def predict(self, item):
        # 判断输入的用于预测的数据是否可迭代
        # 是的话就预测,不是的话就报错
        if isinstance(item, Iterable):
            # 输入特征值个数不对,报错
            if len(item) != len(self.featrues):
                raise ValueError(f'The number of input value is [{len(item)}], '
                                 f'which is not equal to the number of featrue [{len(self.featrues)}].')
            # 初始化特征值字典
            pred_dict = {}
            # 如果输入数据为字典,直接覆盖
            if isinstance(item, dict):
                
                pred_dict = item
            # 否则默认按照列名顺序传值
            else:
                
                for i, v in enumerate(item):
                    
                    if isinstance(i, dict):
                        pred_dict.update(v)
                    else:
                        pred_dict[self.featrues[i]] = v
            # 返回预测结果 
            return self.forward(pred_dict).idxmax()
        
        else:
            raise TypeError(f'The type of input data [{type(item)}] is not iterable.')

测试一下(data上表):

Input[]:	model = NaiveBayes()
    		model.train(data)
            x_pred = {'x1': '中', 'x2': '少'}
            prediction = model.predict(x_pred)
            print(f'Value: {", ".join([f"{key}: {value}" for key, value in x_pred.items()])}. Prediction: {prediction}')

-------------------------------------------------------------------

Output[]:	Value: x1:, x2:. Prediction: 0

预测结果正确!!!

使用 sklearn
Input[]:	import numpy as np
            from sklearn import preprocessing
            from sklearn.naive_bayes import GaussianNB

            encoders = {}
            for column in train_data:
                if train_data[column].dtype == object:
                    encoders[column] = preprocessing.LabelEncoder()
                    train_data[column] = encoders[column].fit_transform(train_data[column])

            model = GaussianNB()
            model.fit(train_data[train_data.columns[:-1]], train_data['y'])
            model.predict(np.array([encoders[key].transform([value])[0] for key, value in x_pred.items()]).reshape(1, -1))
            
-------------------------------------------------------------------

Output[]:	array([0], dtype=int64)

其中,train_data上表




结尾

上边的例子就是使用 朴素贝叶斯 算法进行 分类

有想要一起学习 python 的小伙伴可以 私信我 进群哦。

以上就是我要分享的内容,因为学识尚浅,会有不足,还请各位大佬指正。
有什么问题也可在评论区留言。
在这里插入图片描述

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值