master
/ 02.04 贝叶斯分析.ipynb

02.04 贝叶斯分析.ipynb @7439c2aview markup · raw · history · blame

Notebook

2.4 贝叶斯分析

贝叶斯分析是一种根据概率统计知识对数据进行分析的方法,属于统计学分类的范畴。

2.4.1 贝叶斯公式

  • 频率学派:从历史数据中计算某个事件的概率,认为只要采样足够多,则事件发生的频率就可以无限逼近真实概率。
  • 贝叶斯学派:认为某个事件发生的概率不仅与先前这个事件发生的概率相关(称为先验概率),也与后期计算该事件概率时所观测的“新近”信息有关(称为似然概率

贝叶斯概率计算公式表达: 后验概率 = 先验概率 × 似然概率

条件概率:

$P(A|B)$:表示事件 $B$ 发生的前提下,事件 $A$ 发生的概率

$$P(A|B)=\frac{P(A ∩ B)}{P(B)}$$

$P(B|A)$:表示事件 $A$ 发生的前提下,事件 $B$ 发生的概率

$$P(B|A)=\frac{P(A ∩ B)}{P(A)}$$

由于:

$$P(B|A){P(A)}=P(A|B){P(B)}={P(A ∩ B)}$$

可得贝叶斯公式:

$$P(A|B) = \frac{P(B|A)P(A)}{P(B)}$$

其中:

  • $P(A)$ 是事件 $A$ 发生的先验概率,与事件 $B$ 是否发生无关;
  • $P(B|A)$是事件 $A$ 发生前提下,事件 $B$ 发生的概率,也称为似然概率
  • $P(B)$ 是事件 $B$ 发生的先验概率,也称为标准化常量
  • $P(A|B)$是事件 $B$ 发生前提下,事件 $A$ 发生的概率,也是 $A$ 的后验概率。

2.4.2 贝叶斯推断

贝叶斯推断是一种基于贝叶斯公式进行分析的统计学方法。

小例子:根据邮件中的 “红包” 字样判别该邮件是不是垃圾邮件

In [ ]:
# 广告邮件的数量 
ad_number = 4000
# 正常邮件的数量
normal_number = 6000

# 所有广告邮件中,出现 “红包” 关键词的邮件的数量
ad_hongbao_number = 1000
# 所有正常邮件中,出现 “红包” 关键词的邮件的数量
normal_hongbao_number = 6

# 用户收到广告邮件的先验概率为
P_ad = ad_number / (ad_number + normal_number)
print("用户收到广告邮件的先验概率为 " + str(P_ad))

# 用户收到正常邮件的先验概率为
P_normal = normal_number / (ad_number + normal_number)
print("用户收到正常邮件的先验概率为 " + str(P_normal))

# 红包出现的概率
P_hongbao = (normal_hongbao_number + ad_hongbao_number) / (
            ad_number + normal_number)
print("邮件包含红包的先验概率为 " + str(P_hongbao))

# 广告邮件中出现 “红包” 关键词的条件概率
P_hongbao_ad = ad_hongbao_number / ad_number
print("广告邮件中出现 “红包” 关键词的条件概率为 " + str(P_hongbao_ad))

# 正确邮件中出现 “红包” 关键词的条件概率
P_hongbao_normal = normal_hongbao_number / normal_number
print("正常邮件中出现 “红包” 关键词的条件概率为 " + str(P_hongbao_normal))

# 根据贝叶斯定理可得
# 当邮件中出现 “红包” ,其为广告邮件的后验概率
P_ad_hongbao = P_ad * P_hongbao_ad / P_hongbao
print("当邮件中出现 “红包” ,其为广告邮件的后验概率为 " + str(P_ad_hongbao))

# 当邮件中出现 “红包” ,其为正常邮件的后验概率
P_normal_hongbao = P_normal * P_hongbao_normal / P_hongbao
print("当邮件中出现 “红包” ,其为正常邮件的后验概率为 " + str(P_normal_hongbao))

2.4.3 朴素贝叶斯分类器

一种常用的分类算法,其假设样本各个特征之间相互独立、互不影响

小例子:预测同学会不会在某店铺订餐。

目标:根据某同学的订单记录,如果向他推荐一家“价位低、口味偏甜、距离远”的店铺,判断他会下单吗?

数据:该同学的下单记录如下

店铺价位 店铺口味 店铺距离 是否下单
偏甜
清淡
偏辣
偏甜
偏甜
偏甜
清淡
偏辣

该同学在收到8次推荐后,下单4次和没有下单4次,则其“下单”,“不下单”的概率:
$$P(下单) = \frac{4}{8}=0.5$$
$$P(不下单) = \frac{4}{8}=0.5$$

该同学对 “价位低、口味偏甜、距离远” 这次推荐的 “下单” 或 “不下单” 的似然概率为(注意基本假设是店铺价位、口味、距离这些特质中间互相独立,互不影响):

$$ \begin{align} &P(价位=低,口味=偏甜,距离=远|下单)\\ =&P(价位=低|下单)×P(口味=偏甜|下单)×P(距离=远|下单)\\ =&\frac{3}{4}×\frac{3}{4}×\frac{1}{4}\\ ≈ & 0.141 & \\ & \\ & P(价位=低,口味=偏甜,距离=远|不下单)\\ =&P(价位=低|不下单)×P(口味=偏甜|不下单)×P(距离=远|不下单)\\ =&\frac{1}{4}×\frac{1}{4}×\frac{3}{4}\\ ≈ &0.047 \end{align} $$

根据贝叶斯公式,可以得到该同学在一家“价格低、口味偏甜、距离远”的店铺,

下单的后验概率为:

$$ \begin{align} &P(下单|价位=低,口味=偏甜,距离=远)\\ =&P(下单)×P(价位=低,口味=偏甜,距离=远|下单)\\ =&0.5×0.141\\ = &0.0705 \end{align} $$

不下单的后验概率为: $$ \begin{align} &P(不下单|价位=低,口味=偏甜,距离=远)\\ =&P(不下单)×P(价位=低,口味=偏甜,距离=远|不下单)\\ =&0.5×0.047\\ =&0.0235 \end{align} $$

由此可见,该同学这次会下单的概率大于不下单的概率。

上面的计算过程进行了一些简化,本来应该计算如下两个公式:

$$ \begin{align} &P(下单|价位=低,口味=偏甜,距离=远)\\ =&\frac{P(下单)×P(价位=低,口味=偏甜,距离=远|下单)}{P(价位=低,口味=偏甜,距离=远)}\\ \end{align} $$$$ \begin{align} &P(不下单|价位=低,口味=偏甜,距离=远)\\ =&\frac{P(不下单)×P(价位=低,口味=偏甜,距离=远|不下单)}{P(价位=低,口味=偏甜,距离=远)}\\ \end{align} $$

上述两个计算公式分母相同,对计算结果不影响,因此就从计算过程中略去了。

实践与体验

利用朴素贝叶斯分类器解决 MNIST 手写体数字识别问题

MNIST 是一个手写体数据集,它包含了各种各样的手写体数字图像及其对应的数字标签。其中每幅手写体图像的大小为 28×28 ,共有 784 个像素点,可记为一个 784 维的向量,每个 784 维向量对应着一个标签。

本次实验我们利用 tensorflow 库来来进行原始数据集的解析和读取,利用 sklearn 库来进行特征提取和分类。更多内容可参考tensorflow数据集部分,sklearn 的 bayes部分

1.在 Python 中导入相应库。

In [ ]:
import warnings
warnings.filterwarnings("ignore")
import numpy as np
from tensorflow.keras.datasets import mnist
from sklearn.naive_bayes import BernoulliNB
import matplotlib.pyplot as plt

2.读取 MNIST 训练集和测试集。

In [ ]:
print("读取数据中 ...")
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = train_images.reshape(train_images.shape[0], 784)
test_images = test_images.reshape(test_images.shape[0], 784)
print('读取完毕!')

我们使用下面的方法来查看其中几张图片。

In [ ]:
def plot_images(imgs):
    """绘制几个样本图片
    :param show: 是否显示绘图
    :return:
    """
    sample_num = min(9, len(imgs))
    img_figure = plt.figure(1)
    img_figure.set_figwidth(5)
    img_figure.set_figheight(5)
    for index in range(0, sample_num):
        ax = plt.subplot(3, 3, index + 1)
        ax.imshow(imgs[index].reshape(28, 28), cmap='gray')
        ax.grid(False)
    plt.margins(0, 0)
    plt.show()


plot_images(train_images)

3.根据 MNIST 训练集训练朴素贝叶斯分类器

In [ ]:
print("初始化并训练贝叶斯模型...")
classifier_BNB = BernoulliNB()
classifier_BNB.fit(train_images,train_labels)
print('训练完成!')

4.根据训练出的分类器对 MNIST 测试集中的图片进行识别,得到预测值。

In [ ]:
print("测试训练好的贝叶斯模型...")
test_predict_BNB = classifier_BNB.predict(test_images)
print("测试完成!")

5.将测试图片的预测值与实际值相比较,计算并输出分类器的正确率。

In [ ]:
accuracy = sum(test_predict_BNB==test_labels)/len(test_labels)
print('贝叶斯分类模型在测试集上的准确率为 :',accuracy)

6.对实验结果进行分析比较,列出 0-9 不同数字识别的准确率,比较其差异。

In [ ]:
# 记录每个类别的样本的个数,例如 {0:100} 即 数字为 0 的图片有 100 张 
class_num = {}
# 每个类别预测为 0-9 类别的个数,
predict_num = []
# 每个类别预测的准确率
class_accuracy = {}

for i in range(10):
    # 找到类别是 i 的下标
    class_is_i_index = np.where(test_labels == i)[0]
    # 统计类别是 i 的个数
    class_num[i] = len(class_is_i_index)

    # 统计类别 i 预测为 0-9 各个类别的个数
    predict_num.append(
        [sum(test_predict_BNB[class_is_i_index] == e) for e in range(10)])

    # 统计类别 i 预测的准确率
    class_accuracy[i] = round(predict_num[i][i] / class_num[i], 3) * 100

    print("数字 %s 的样本个数:%4s,预测正确的个数:%4s,准确率:%.4s%%" % (
    i, class_num[i], predict_num[i][i], class_accuracy[i]))
In [ ]:
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

sns.set(rc={'figure.figsize': (12, 8)})
np.random.seed(0)
uniform_data = predict_num
ax = sns.heatmap(uniform_data, cmap='YlGnBu', vmin=0, vmax=150)
ax.set_xlabel('真实值')
ax.set_ylabel('预测值')
plt.show()

通过热力图,我们看到 3 经常被错认为 5 和 8, 4 和 9 经常互相错认。

我们看看真实标签为 9,但是预测为 4 的错认的照片

In [ ]:
def get_imgs(images, true_labels, predict_labels, true_label,
             predict_label):
    """
    从全部图片中按真实标签和预测标签筛选出图片
    :param images: 一组图片
    :param true_labels: 每张图片的标签
    :param predict_labels: 模型预测的每张图片的标签
    :param true_label: 希望取得的图片的真实标签
    :param predict_label: 希望取得的图片的预测标签
    :return: 
    """
    # 所有类别为 true_label 的样本的 index 值
    true_label_index = set(np.where(true_labels == true_label)[0])
    # 所有预测类别为 predict_label 的样本的 index 值
    predict_label_index = set(np.where(predict_labels == predict_label)[0])
    # 取交集,即为真实类别为 true_label, 预测结果为 predict_label 的样本的 index 值
    res = list(true_label_index & predict_label_index)
    return images[res]
In [ ]:
imgs = get_imgs(test_images, test_labels, test_predict_BNB, 9, 4)
plot_images(imgs)