机器学习之朴素贝叶斯分类器原理详解、公式推导(手推)、面试问题、简单实例(python实现,sklearn调包)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

在这里插入图片描述

目录

1. 朴素贝叶斯原理

1.1. 特性

朴素贝叶斯是一种有监督学习算法这种算法基于贝叶斯的一个朴素的假设——每对特征和样本数据都是独立同分布的。最终可以推出朴素贝叶斯分类器的判定准则

h n b ( x ) = a r g   m a x c ∈ Υ   P ( c ) ∏ i = 1 d P ( x i   ∣   c ) h_{nb}(x)=\mathop{arg\ max}\limits_{c\in \varUpsilon}\ P(c) \prod_{i=1}^dP(x_i\ | \ c) hnb(x)=cΥarg max P(c)i=1dP(xi  c)

我们之前说朴素贝叶斯是一系列算法这是因为基于不同的条件独立性假设朴素贝叶斯的分类器会有所不同。

虽然朴素贝叶斯看起来很简单实际上推起来也很简单但是它们在现实中的运用还是很不错的主要是它们仅仅只需要少量训练数据就能估计几个必要的参数这非常重要。

下面说一下朴素贝叶斯的优缺点

优点

  1. 朴素贝叶斯模型发源于古典数学理论有稳定的分类效率。
  2. 对小规模的数据表现很好能个处理多分类任务适合增量式训练尤其是数据量超出内存时我们可以一批批的去增量训练。
  3. 对缺失数据不太敏感算法也比较简单常用于文本分类。
  4. 快。和SVM等复杂的方法相比朴素贝叶斯分类器可以非常快因为类条件特征分布的独立性意味着每个分布可以独立估计为一维分布这有助于缓解维度数量过大带来的问题。

缺点

  1. 理论上朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此这是因为朴素贝叶斯模型假设属性之间相互独立这个假设在实际应用中往往是不成立的在属性个数比较多或者属性之间相关性较大时分类效果不好。而在属性相关性较小时朴素贝叶斯性能最为良好。对于这一点有半朴素贝叶斯之类的算法通过考虑部分关联性适度改进。
  2. 需要知道先验概率且先验概率很多时候取决于假设假设的模型可以有很多种因此在某些时候会由于假设的先验模型的原因导致预测效果不佳。所以在使用sklearn等包的时候predict_proba仅仅做参考就好了一般都不太靠谱。
  3. 由于我们是通过先验和数据来决定后验的概率从而决定分类所以分类决策存在一定的错误率。
  4. 对输入数据的表达形式很敏感。

1.2. 思路

贝叶斯的主要思路就是数数统计每个条件组合下各种结果的概率并根据这个概率做出分类。

但是单纯的贝叶斯在计算机计算的时候会出现计算规模过大的情况这里就需要 引入朴素条件化简式子。

最终结果就是在计算时我们只需要统计单个条件下的结果并据此计算得结果就好了区别于先自由组合条件在统计结果。

2. 公式推导

朴素贝叶斯分类器的推导离不开概率论这边顺便复习下概率论最基础的知识

已知有一种疾病在城市中的患病率为1%在检查时有银兴阳性两种结果在检查结果为阴性时有10%概率患病在检查结果为阳性时有90%概率患病。

城市中有10000个人参与检查。

在这里插入图片描述

我们将这个问题带入到 0-1 评分下的脱单数据集中

在这里插入图片描述

不难发现直接使用贝叶斯公式时我们遇到了麻烦。我们判断交往R的特征有5个在计算时条件的结果有 2 5 2^5 25 种组合方式。如果我们有50个特征那需要计算 2 50 2^{50} 250 种特征组合如果特征还不是 0-1 变量而是另一个脱单数据集那样的 0-10 评分数据则需要计算 1 1 50 11^{50} 1150 种特征组合甚至在连续数据下这种方法是不可用的。

这个时候就体现出朴素贝叶斯下那个朴素的假设效果不朴素了。在此假设下问题转化为

在这里插入图片描述

需要注意的是 朴素贝叶斯在加入朴素条件后变换式子还能提高算法的可靠性。在样本量不够大的时候很可能出现 P (   A 0   B 0   C 0   D 0   E 0   ∣   R 1 ) P(\ A_0\ B_0\ C_0\ D_0\ E_0\ |\ R_1) P( A0 B0 C0 D0 E0  R1) 为 0 和 P (   A 0   B 0   C 0   D 0   E 0 ) P(\ A_0\ B_0\ C_0\ D_0\ E_0) P( A0 B0 C0 D0 E0) 为 0 的情况毕竟总有一类人的条件在相亲市场中不受欢迎但是因为这是在小样本数据中的结果并不能真实代表这个概率直接判定 0 显然不可靠。
在朴素贝叶斯公式中 P (   A 0   ∣   R 1 ) P(\ A_0\ |\ R_1) P( A0  R1) 等条件累乘的结果显然大于等于 P (   A 0   B 0   C 0   D 0   E 0   ∣   R 1 ) P(\ A_0\ B_0\ C_0\ D_0\ E_0\ |\ R_1) P( A0 B0 C0 D0 E0  R1) 这意味着变换后更不容易为0。
换一种说法 P (   A 0   ∣   R 1 ) P(\ A_0\ |\ R_1) P( A0  R1) 每个条件相较于 P (   A 0   B 0   C 0   D 0   E 0   ∣   R 1 ) P(\ A_0\ B_0\ C_0\ D_0\ E_0\ |\ R_1) P( A0 B0 C0 D0 E0  R1) 的可能性是更高的他们每个不等于0的概率都比 P (   A 0   B 0   C 0   D 0   E 0   ∣   R 1 ) P(\ A_0\ B_0\ C_0\ D_0\ E_0\ |\ R_1) P( A0 B0 C0 D0 E0  R1) 大。
因此在变换后朴素贝叶斯公式更加稳定在小样本数据中发挥更好。

为了模拟计算过程我们回到 0-1 脱单数据集中

在这里插入图片描述
然后对照着计算。

我们之前提过一嘴这样的方法只是最简单的数量统计面对连续型数据时简单的数量统计势必会使算法陷入维度灾难中此外简单的统计数量也不能体现样本数据的连续性。因此我们需要一种方法处理连续型数据当然如果这种方法对连续型数据有效那对离散型数据也同样适用。

3. 简单实例

3.1. 数据集

脱单数据集2.0

我们这里根据身边朋友的相亲意向对如下脱单样本打标签完成如下交往意向数据集。我们将这个数据集称为脱单数据集2.0

样本颜值A身材B性格C收入D学历E交往R
1647781
210102420
3796861
48210260
5661940
6395760
7973440
8856761
94168100
10667671
11749520
121591060
13677581
14656861
158776101

lovedata_2.csv

颜值A,身材B,性格C,收入D,学历E,交往R
6,4,7,7,8,1
10,10,2,4,2,0
7,9,6,8,6,1
8,2,10,2,6,0
6,6,1,9,4,0
3,9,5,7,6,0
9,7,3,4,4,0
8,5,6,7,6,1
4,1,6,8,10,0
6,6,7,6,7,1
7,4,9,5,2,0
1,5,9,10,6,0
6,7,7,5,8,1
6,5,6,8,6,1
8,7,7,6,10,1

脱单数据集1.0

楼上的数据集不是0-1数据不太适合说明问题我们这里退化下交往意向数据集将1-5的评分标记为06-10的评分标记为1完成0-1交往意向数据集。暂时忽略重复数据毕竟现实中也可能出现样本特性一样的情况。我们将这个数据集称为脱单数据集1.0

样本颜值A身材B性格C收入D学历E交往R
1101111
2110000
3111111
4101010
5110100
6010110
7110000
8101111
9001110
10111111
11101000
12001110
13111011
14101111
15111111

lovedata_1.csv

颜值A,身材B,性格C,收入D,学历E,交往R
1,0,1,1,1,1
1,1,0,0,0,0
1,1,1,1,1,1
1,0,1,0,1,0
1,1,0,1,0,0
0,1,0,1,1,0
1,1,0,0,0,0
1,0,1,1,1,1
0,0,1,1,1,0
1,1,1,1,1,1
1,0,1,0,0,0
0,0,1,1,1,0
1,1,1,0,1,1
1,0,1,1,1,1
1,1,1,1,1,1

西瓜数据集

参考西瓜书上的数据转化为整型变量。

watermelon.csv

编号,色泽,根蒂,敲声,纹理,脐部,触感,密度,含糖率,好瓜
1,0,0,0,0,0,0,0.697,0.46,1
2,1,0,1,0,0,0,0.774,0.376,1
3,1,0,0,0,0,0,0.634,0.264,1
4,0,0,1,0,0,0,0.608,0.318,1
5,2,0,0,0,0,0,0.556,0.215,1
6,0,1,0,0,1,1,0.403,0.237,1
7,1,1,0,1,1,1,0.481,0.149,1
8,1,1,0,0,1,0,0.437,0.211,1
9,1,1,1,1,1,0,0.666,0.091,0
10,0,2,2,0,2,1,0.243,0.267,0
11,2,2,2,2,2,0,0.245,0.057,0
12,2,0,0,2,2,1,0.343,0.099,0
13,0,1,0,1,0,0,0.639,0.161,0
14,2,1,1,1,0,0,0.657,0.198,0
15,1,1,0,0,1,1,0.36,0.37,0
16,2,0,0,2,2,0,0.593,0.042,0
17,0,0,1,1,1,0,0.719,0.103,0

3.2. python实现

def love1(filename, coiled=False):
    # 计算p(a|b)
    def calculate_frq_ab(data, label, coiled):
        # 存储各个label对应的概率
        label_p = {}
        # unique就series能用
        for label_value in label.unique():
            # 存储这个label值下这个特征值组合的概率
            feature_p = {}
            # 筛选label后的数据
            data_filter = data.loc[label[label == label_value].index.values]
            # 每个feature一个个数过去
            for feature in data_filter.columns:
                feature_p[feature] = data_filter.value_counts(feature) / data_filter.shape[0]
            label_p[label_value] = feature_p
        return label_p
        # label = {
        #    label_value1: {feature1: df, feature2: df},
        #    label_value2: {feature1: df, feature2: df},
        # }


    # 计算p(a),p(b)
    def calculate_frq_a_b(data, label, coiled):
        p = {}
        p[label.name] = label.value_counts() / label.shape[0]
        for feature in data.columns:
            p[feature] = data.value_counts(feature) / data.shape[0]
        return p

    # 分类器,a为条件b为label
    def clf(data, label, p_ab, p_a_b):
        # label = label.unique()
        ret = []
        for sample in data.T.iteritems():
            r = []
            for label_i in label:
                p_b_i = p_a_b[label.name][label_i]
                p_a_i = 1
                p_ab_i = 1
                for index, feature_name in enumerate(data.columns):
                    try:
                        p_a_i *= p_a_b[feature_name][sample[1][index]]
                        p_ab_i *= p_ab[label_i][feature_name][sample[1][index]]
                    except KeyError as e:
                        # 出现概率为0的情况
                        p_a_i = 0
                        p_ab_i = 1
                        break
                r.append(p_b_i * p_a_i)
                # / p_ab_i
            r = label[r.index(max(r))]
            ret.append(r)
        return ret

    data_train, data_test, label_train, label_test = get_data(filename)
    p_ab = calculate_frq_ab(data_train, label_train, coiled)
    p_a_b = calculate_frq_a_b(data_train, label_train, coiled)
    print("训练准确率为")
    eveluate(clf(data_train, label_train, p_ab, p_a_b), label_train)
    print("测试准确率为")
    eveluate(clf(data_test, label_train, p_ab, p_a_b), label_test)

3.3. sklearn实现

# 高斯朴素贝叶斯
def sk(files):
    while 1:
        for index, name in enumerate(files):
            print(index, ". ", name)
        print(len(files), ". 退出")
        try:
            choice = int(input())
            file = files[choice]
        except:
            return 0
        data_train, data_test, label_train, label_test = get_data(file)
        clf = GaussianNB()
        clf = clf.fit(data_train, label_train)
        predict = clf.predict(data_test)
        eveluate(predict, label_test)

3.4. 实验结果

一、脱单数据集1.0

在这里插入图片描述

二、脱单数据集2.0

在这里插入图片描述

三、西瓜数据集

在这里插入图片描述

四、sklearn

在这里插入图片描述

4. 几个注意点(面试问题)

  朴素贝叶斯有几个注意点可能会在面试中被提到还是比较能体现被面试者对这个算法的理解的。当然我们也不一定就是为了面试搞清楚这些问题对帮助我们理解这个算法还是很有好处的。其中部分答案是博主自己的理解如果有问题麻烦路过的大佬评论区指正。

  1. 证明条件独立性假设不成立时朴素贝叶斯分类器仍有可能产生最优贝叶斯分类器。
    **答**这篇论文给出了详细的解答《On the Optimality of the Simple Bayesian Classifier under Zero-One Loss》我们浅浅尝试复述下。

  2. 实践中使用 h n b ( x ) = a r g   m a x c ∈ Υ   P ( c ) ∏ i = 1 d P ( x i   ∣   c ) h_{nb}(x)=\mathop{arg\ max}\limits_{c\in \varUpsilon}\ P(c) \prod_{i=1}^dP(x_i\ | \ c) hnb(x)=cΥarg max P(c)i=1dP(xi  c) 决定分类类别时如果数据维度很高则会导致 ∏ i = 1 d P ( x i   ∣   c ) \prod_{i=1}^dP(x_i\ | \ c) i=1dP(xi  c) 的结果非常接近0从而导致下溢。试述防止下溢的方法。
    这个问题其实西瓜书第153页有提及方法跟很多的数学式子的思路相似对原式取对数将连乘转化为连加。 h n b ( x ) = a r g   m a x c ∈ Υ   P ( c ) ∏ i = 1 d P ( x i   ∣   c ) h_{nb}(x)=\mathop{arg\ max}\limits_{c\in \varUpsilon}\ P(c) \prod_{i=1}^dP(x_i\ | \ c) hnb(x)=cΥarg max P(c)i=1dP(xi  c) 因此转化为 h n b ( x ) = a r g   m a x θ   l o g ( P ( c ) ) + ∑ i = 1 d l o g ( P ( x i   ∣   c ) ) h_{nb}(x)=\mathop{arg\ max}\limits_{\theta}\ log(P(c)) + \sum_{i=1}^dlog(P(x_i\ | \ c)) hnb(x)=θarg max log(P(c))+i=1dlog(P(xi  c))

  3. 证明二分类任务种两类数据满足高斯分布且方差相同时线性判别分析产生贝叶斯最优分类器。
    转其他博主答案
    在这里插入图片描述

5. 运行可直接食用

import random
from sklearn import preprocessing
import numpy as np
from sklearn.naive_bayes import GaussianNB
import pandas as pd


def get_data(filename, to_one=False, train_size=0.5):
    data = pd.read_csv(filename).sample(frac=1, random_state=1129)
    labels = data[data.columns[-1]]
    data_shuffled = data[data.columns[:-1]]
    if to_one:
        data_shuffled = pd.DataFrame(preprocessing.scale(data_shuffled), columns=data.columns[:-1])
    # 划分训练集测试集
    # 更喜欢pandas的计算这里就任性点全变成dataframe的了
    data_train = data_shuffled.head(int(data_shuffled.shape[0] * train_size)).reset_index(drop=True)
    label_train = labels.head(int(data_shuffled.shape[0] * train_size)).reset_index(drop=True)
    # .reset_index(drop=True)可重置index
    data_test = data_shuffled.tail(int(data_shuffled.shape[0] * (1 - train_size))).reset_index(drop=True)
    label_test = labels.tail(int(data_shuffled.shape[0] * (1 - train_size))).reset_index(drop=True)
    return data_train, data_test, label_train, label_test


def eveluate(predict, result):
    result = result.tolist()
    correct = 0
    for index, i in enumerate(predict):
        correct += (i == result[index])
    print("准确率为", correct / len(predict) * 100, "%")


# love1.0
def love1(filename, coiled=False):
    # 计算p(a|b)
    def calculate_frq_ab(data, label, coiled):
        # 存储各个label对应的概率
        label_p = {}
        # unique就series能用
        for label_value in label.unique():
            # 存储这个label值下这个特征值组合的概率
            feature_p = {}
            # 筛选label后的数据
            data_filter = data.loc[label[label == label_value].index.values]
            # 每个feature一个个数过去
            for feature in data_filter.columns:
                feature_p[feature] = data_filter.value_counts(feature) / data_filter.shape[0]
            label_p[label_value] = feature_p
        return label_p
        # label = {
        #    label_value1: {feature1: df, feature2: df},
        #    label_value2: {feature1: df, feature2: df},
        # }


    # 计算p(a),p(b)
    def calculate_frq_a_b(data, label, coiled):
        p = {}
        p[label.name] = label.value_counts() / label.shape[0]
        for feature in data.columns:
            p[feature] = data.value_counts(feature) / data.shape[0]
        return p

    # 分类器,a为条件b为label
    def clf(data, label, p_ab, p_a_b):
        # label = label.unique()
        ret = []
        for sample in data.T.iteritems():
            r = []
            for label_i in label:
                p_b_i = p_a_b[label.name][label_i]
                p_a_i = 1
                p_ab_i = 1
                for index, feature_name in enumerate(data.columns):
                    try:
                        p_a_i *= p_a_b[feature_name][sample[1][index]]
                        p_ab_i *= p_ab[label_i][feature_name][sample[1][index]]
                    except KeyError as e:
                        # 出现概率为0的情况
                        p_a_i = 0
                        p_ab_i = 1
                        break
                r.append(p_b_i * p_a_i)
                # / p_ab_i
            r = label[r.index(max(r))]
            ret.append(r)
        return ret

    data_train, data_test, label_train, label_test = get_data(filename)
    p_ab = calculate_frq_ab(data_train, label_train, coiled)
    p_a_b = calculate_frq_a_b(data_train, label_train, coiled)
    print("训练准确率为")
    eveluate(clf(data_train, label_train, p_ab, p_a_b), label_train)
    print("测试准确率为")
    eveluate(clf(data_test, label_train, p_ab, p_a_b), label_test)


# 高斯朴素贝叶斯
def sk(files):
    while 1:
        for index, name in enumerate(files):
            print(index, ". ", name)
        print(len(files), ". 退出")
        try:
            choice = int(input())
            file = files[choice]
        except:
            return 0
        data_train, data_test, label_train, label_test = get_data(file)
        clf = GaussianNB()
        clf = clf.fit(data_train, label_train)
        predict = clf.predict(data_test)
        eveluate(predict, label_test)


if __name__ == '__main__':
    random.seed(1129)

    choice = 0
    while choice != 5:
        # # 因为有个label载columns里所以有data.columns-1个w偏置b还有一列
        # w = np.random.rand(data_shuffled.shape[1]).reshape(-1, 1)
        print("1. 恋爱1.0数据集\n2. 恋爱2.0数据集\n3. 西瓜数据集手写\n4. sklearn高斯朴素贝叶斯\n5. 退出")
        try:
            choice = int(input())
        except:
            break
        if choice == 1:
            print("恋爱1.0数据集手写求解中...")
            love1("lovedata_1.csv")
        elif choice == 2:
            print("恋爱2.0数据集手写求解中...")
            love1("lovedata_2.csv")
        elif choice == 3:
            print("西瓜数据集求解中...")
            love1("watermelon.csv")
        elif choice == 4:
            print('西瓜数据集sklearn yyds')
            sk(["lovedata_1.csv", "lovedata_2.csv", "watermelon.csv"])
        else:
            print("退出成功")
            choice = 6
            break

参考
吴恩达《机器学习》
sklearn官网

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

“机器学习之朴素贝叶斯分类器原理详解、公式推导(手推)、面试问题、简单实例(python实现,sklearn调包)” 的相关文章