数据挖掘学习笔记:朴素贝叶斯 | Python复现

发布于:2024-10-09 ⋅ 阅读:(8) ⋅ 点赞:(0)

数据挖掘学习笔记:朴素贝叶斯

机器学习系列(四):朴素贝叶斯(华强买瓜版) - yyxy的文章 - 知乎

十分钟,让你再也忘不掉贝叶斯分类 - VoidHaruhi的文章 - 知乎

《机器学习》(西瓜书)公式详解

【吃瓜教程】《机器学习公式详解》(南瓜书)与西瓜书公式推导

到底要如何理解条件概率? - 石溪的回答 - 知乎

如何在 Python 中从零开始实现朴素贝叶斯

贝叶斯决策论

​  假设当前有一个 N N N分类任务,即 Y = { c 1 , c 2 , ⋯   , c N } \mathcal{Y}=\{c_1,c_2,\cdots,c_N\} Y={c1,c2,,cN},将 λ i j \lambda_{ij} λij定义为将一个真实标记 c j c_j cj的样本误分类为 c i c_i ci所产生的损失。如果其目标为最小化分类错误率,则损失 λ i j \lambda_{ij} λij可写为:
λ i j = { 0 , i = j   ; 1 , otherwise , (1) \lambda_{ij} = \begin{cases} 0, & i=j\ ; \\ 1, & \text{otherwise\ ,} \tag{1} \end{cases} λij={01i=j ;otherwise ,(1)
此时,对于单个样本 x \boldsymbol{x} x而言,定义其期望损失为如下条件风险的形式:
R ( c i ∣ x ) = ∑ j = 1 N λ i j P ( c j ∣ x ) , (2) R(c_i|\boldsymbol{x})=\sum_{j=1}^N{\lambda_{ij}P(c_j|\boldsymbol{x})},\tag{2} R(cix)=j=1NλijP(cjx),(2)
上式中, P ( c j ∣ x ) P(c_j|\boldsymbol{x}) P(cjx)为后验概率。

  那么,贝叶斯决策论的任务就是去寻找一个判定准则 h : X ↦ Y h:\mathcal{X}\mapsto\mathcal{Y} h:XY,以最小化全部样本构成的总体风险:
R ( h ) = E x [ R ( h ( x ) ) ∣ x ] . (3) R(h)=\mathbb{E}_x[R(h(\boldsymbol{x}))|\boldsymbol{x}].\tag{3} R(h)=Ex[R(h(x))x].(3)
  在定义完上述概念之后,我们就可以引入贝叶斯判定准则,即最小化总体风险 R ( h ) R(h) R(h)。因此,只需在每个样本上选择那个能使条件风险 R ( c ∣ x ) R(c|\boldsymbol{x}) R(cx)最小的类别标记:
h ∗ ( x ) = arg ⁡ min ⁡ c ∈ Y   R ( c ∣ x ) , (4) h^*(\boldsymbol{x})=\underset{c\in \mathcal{Y}} {\arg\min}\ R(c|\boldsymbol{x}),\tag{4} h(x)=cYargmin R(cx),(4)
此时, h ∗ h^* h被称为贝叶斯最优分类器。

  对公式(2)展开得:
R ( c i ∣ x ) = 1 ∗ P ( c 1 ∣ x ) + ⋯ + 1 ∗ P ( c i − 1 ∣ x ) + 0 ∗ P ( c i ∣ x ) + 1 ∗ P ( c i + 1 ∣ x ) + ⋯ + 1 ∗ P ( c N ∣ x ) , (5) R(c_i|\boldsymbol{x})=1*P(c_1|\boldsymbol{x})+\cdots+1*P(c_{i-1}|\boldsymbol{x})+0*P(c_i|\boldsymbol{x})+1*P(c_{i+1}|\boldsymbol{x})+\cdots+1*P(c_N|\boldsymbol{x}),\tag{5} R(cix)=1P(c1x)++1P(ci1x)+0P(cix)+1P(ci+1x)++1P(cNx),(5)
对于一个 N N N分类任务而言,所有类别预测的概率总和一定为1,即:
∑ j = 1 N P ( c j ∣ x ) = 1. (6) \sum_{j=1}^N{P(c_j|\boldsymbol{x})}=1.\tag{6} j=1NP(cjx)=1.(6)
  此时,条件风险可化简为:
R ( c i ∣ x ) = 1 − P ( c i ∣ x ) . (7) R(c_i|\boldsymbol{x})=1-P(c_i|\boldsymbol{x}).\tag{7} R(cix)=1P(cix).(7)
  于是,最小化分类错误率的贝叶斯最优分类器可写为:
h ∗ ( x ) = arg ⁡ max ⁡ c ∈ Y   P ( c ∣ x ) . (8) h^*(\boldsymbol{x})=\underset{c\in \mathcal{Y}}{\arg\max}\ P(c|\boldsymbol{x}).\tag{8} h(x)=cYargmax P(cx).(8)
  对每个样本 x \boldsymbol{x} x,选择能使后验概率 P ( c ∣ x ) P(c|\boldsymbol{x}) P(cx)最大的类别标记。

生成式模型与判别式模型

  如SVM这样的机器学习模型,其本质是在特征空间内寻找一个超平面把类别样本划分开,是一个从几何角度思考的模型,并没有涉及概率的计算。所谓判别式模型,就是直接对后验概率进行建模,求出每个类别的概率进行分类。下面要将的朴素贝叶斯则属于生成式模型,其先对联合概率先进行建模,再推导出后验概率,即:
P ( c ∣ x ) = P ( x , c ) P ( x ) . (9) P(c|\boldsymbol{x})=\frac{P(\boldsymbol{x},c)}{P(\boldsymbol{x})}.\tag{9} P(cx)=P(x)P(x,c).(9)
  公式(9)是很简单的条件概率一般定义,可以从古典概型进行推导。

  假定一个试验有 N N N个等可能的结果,事件 A A A B B B分别包含 M 1 M_1 M1个和 M 2 M_2 M2个结果,这其中有 M 12 M_{12} M12个结果是公共的,这就是同时发生事件 A A A 和事件 B B B,即 A ∩ B A\cap B AB事件所包含的试验结果数。

img

图源《到底要如何理解条件概率?石溪的回答》:https://www.zhihu.com/question/322520602/answer/2364488211
  那么已知在事件$B$发生的前提条件下,事件$A$发生的概率为:

P ( A ∣ B ) = M 12 M 2 . (10) P(A|B)=\frac{M_{12}}{M_2}.\tag{10} P(AB)=M2M12.(10)
  对于上式,可以进行展开:
P ( A ∣ B ) = M 12 N M 2 N = P ( A B ) P ( B ) . (11) P(A|B)=\frac{\frac{M_{12}}{N}}{\frac{M_2}{N}}=\frac{P(AB)}{P(B)}.\tag{11} P(AB)=NM2NM12=P(B)P(AB).(11)
  故可以得到条件概率的一般定义:
P ( A ∣ B ) = P ( A B ) P ( B ) . (12) P(A|B)=\frac{P(AB)}{P(B)}.\tag{12} P(AB)=P(B)P(AB).(12)

  回到公式(9),用贝叶斯定理可以恒等变形为:
P ( c ∣ x ) = P ( c ) P ( x ∣ c ) P ( x ) = P ( c ) P ( x 1 , ⋯   , x d ∣ c ) P ( x ) (13) P(c|\boldsymbol{x})=\frac{P(c)P(\boldsymbol{x}|c)}{P(\boldsymbol{x})}=\frac{P(c)P(x_1,\cdots,x_d|c)}{P(\boldsymbol{x})}\tag{13} P(cx)=P(x)P(c)P(xc)=P(x)P(c)P(x1,,xdc)(13)
其中, P ( c ) P(c) P(c)是先验概率, P ( x ∣ c ) P(\boldsymbol{x}|c) P(xc)是样本 x \boldsymbol{x} x相对于类别标记 c c c是类条件概率,也叫似然, P ( x ) P(\boldsymbol{x}) P(x)是归一化用的证据因子。

  在公式(13)中最难建模的是类条件概率,假如样本 x = ( x 1 , x 2 , ⋯   , c d ) \boldsymbol{x}=(x_1,x_2,\cdots,c_d) x=(x1,x2,,cd) d d d个特征,每个特征又有多个取值,那么样本相对于类别 c c c的组合数不胜数,甚至在数据集中都没有这种组合。那如何把这个类条件概率计算出来呢?朴素贝叶斯给了这么一种方法。

朴素贝叶斯

  朴素贝叶斯假设对于已知类别,各个属性相互独立,即满足属性条件独立性假设。那么后验概率可以写成:
P ( c ∣ x ) = P ( c ) P ( x ) ∏ i = 1 d P ( x i ∣ c ) , (14) P(c|\boldsymbol{x})=\frac{P(c)}{P(\boldsymbol{x})}\prod_{i=1}^d{P({x_i}|c)},\tag{14} P(cx)=P(x)P(c)i=1dP(xic),(14)
其中, d d d为属性数目, x i x_i xi表示 x \boldsymbol{x} x在第 i i i个属性上的取值。基于贝叶斯判定准则可得:
h ∗ ( x ) = arg ⁡ max ⁡ c ∈ Y   P ( c ) P ( x ) ∏ i = 1 d P ( x i ∣ c ) . (15) h^*(x)=\underset{c\in \mathcal{Y}}{\arg\max}\ \frac{P(c)}{P(\boldsymbol{x})}\prod_{i=1}^dP(x_i|c).\tag{15} h(x)=cYargmax P(x)P(c)i=1dP(xic).(15)
  综上所述,样本所属的哪个类别的后验概率最大,就选择哪个类别作为模型 h ∗ ( x ) h^*(x) h(x)的预测结果。那么,现在的问题就是,如何计算类条件概率和先验概率,才能使得模型预测得最准,即条件风险最小。

  对于先验概率的计算很简单,利用大数定理,在训练集样本足够多的情况下用频率近似概率:
P ( c ) = ∣ D c ∣ ∣ D ∣ , (16) P(c)=\frac{|D_c|}{|D|},\tag{16} P(c)=DDc,(16)
其中, ∣ D c ∣ |D_c| Dc表示训练集 D D D中类别标记为 c c c的样本集合, ∣ D c ∣ |D_c| Dc表示集合 D c D_c Dc的样本总数。

  对于类条件概率的计算要分成两种情况讨论:离散属性和连续属性。对于离散属性的类条件概率计算思路和公式(10)一样,因此第 i i i个属性为离散属性的类条件概率为:
P ( x i ∣ c ) = ∣ D c , x i ∣ ∣ D c ∣ , (17) P(x_i|c)=\frac{|D_{c,x_i}|}{|D_c|},\tag{17} P(xic)=DcDc,xi,(17)
其中, ∣ D c , x i ∣ |D_{c,x_i}| Dc,xi表示 ∣ D c ∣ |D_c| Dc在第 i i i个属性上取值为 x i x_i xi的样本组成的集合。

  然而,如果某个属性值在训练集中没有与某个类别同时出现过,直接使用公式(17)计算,则会让连乘式子结果为0。为避免出现这种情况,在估计概率值时应该加以平滑,按《西瓜书》的示例,使用拉普拉斯修正:
P ^ ( c ) = ∣ D c ∣ + 1 ∣ D ∣ + N P ^ ( x i ∣ c ) = ∣ D c , x i ∣ + 1 ∣ D c ∣ + N i \begin{align} \hat P(c) &= \frac{|D_c|+1}{|D|+N}\tag{18} \\ \hat P(x_i|c) &= \frac{|D_{c,x_i}|+1}{|D_c|+N_i}\tag{19} \end{align} P^(c)P^(xic)=D+NDc+1=Dc+NiDc,xi+1(18)(19)
其中, N N N表示类别数量, N i N_i Ni表示第 i i i个属性出现的取值数量。

  对于连续属性,假设满足正态分布,那么其概率密度函数为:
p ( x i ∣ c ) = 1 2 π σ c , i exp ⁡ { − ( x i − μ c , i ) 2 2 σ c , i 2 } , (20) 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}\},\tag{20} p(xic)=2π σc,i1exp{2σc,i2(xiμc,i)2},(20)
其中, μ c , i \mu_{c,i} μc,i σ c , i 2 \sigma_{c,i}^2 σc,i2分别表示第 c c c类样本在第 i i i个属性上取值的均值和方差。

  接下来,我们使用极大似然估计去估计其均值和方差。公式(20)为一元正态分布的概率密度函数,下面对于多元正态分布的概率密度函数 p ( x ∣ c ) ∼ N ( μ c , σ c 2 ) p(\boldsymbol{x}|c)\sim \mathcal{N}(\boldsymbol{\mu}_c,\boldsymbol{\sigma}_c^2) p(xc)N(μc,σc2)而言,其等价形式为:
p ( x ∣ μ c , σ c 2 ) = 1 ( 2 π ) d ∣ Σ c ∣ exp ⁡ { − 1 2 ( x − μ c ) T Σ c − 1 ( x − μ c ) } , (21) p(\boldsymbol{x}|\boldsymbol{\mu}_c,\boldsymbol{\sigma}_c^2)=\frac{1}{\sqrt{(2\pi)^d|\boldsymbol{\Sigma}_c|}}\exp\{-\frac{1}{2}(\boldsymbol{x}-\boldsymbol{\mu}_c)^T\boldsymbol{\Sigma}_c^{-1}(\boldsymbol{x}-\boldsymbol{\mu}_c)\},\tag{21} p(xμc,σc2)=(2π)dΣc 1exp{21(xμc)TΣc1(xμc)},(21)
其中, d d d表示 x \boldsymbol{x} x的维数, Σ c = σ c 2 \boldsymbol{\Sigma}_c=\boldsymbol{\sigma}_c^2 Σc=σc2为正定协方差矩阵。

  对数极大似然估计的参数求解形式为:
θ c ^ = arg ⁡ max ⁡ θ c   L L ( θ c ) = arg ⁡ min ⁡ θ c   − ∑ x ∈ D c log ⁡ P ( x ∣ θ c ) (22) \begin{align} \hat{\boldsymbol{\theta}_c} &= \underset{\boldsymbol{\theta}_c}{\arg\max}\ LL(\boldsymbol{\theta}_c) \\ &= \underset{\boldsymbol{\theta}_c}{\arg\min}\ -\sum_{\boldsymbol{x}\in D_c}\log P(\boldsymbol{x}|\boldsymbol{\boldsymbol{\theta}_c}) \end{align} \tag{22} θc^=θcargmax LL(θc)=θcargmin xDclogP(xθc)(22)
  通过极大似然估计的均值和方差如下:
μ ^ c = 1 ∣ D c ∣ ∑ x ∈ D c x , (23) \hat {\boldsymbol{\mu}}_c=\frac{1}{|D_c|}\sum_{\boldsymbol{x}\in D_c}\boldsymbol{x},\tag{23} μ^c=Dc1xDcx,(23)

σ ^ c 2 = 1 ∣ D c ∣ ∑ x ∈ D c ( x − μ ^ c ) ( x − μ ^ c ) T . (24) \hat{\boldsymbol{\sigma}}_c^2=\frac{1}{|D_c|}\sum_{\boldsymbol{x}\in D_c}(\boldsymbol{x}-\hat {\boldsymbol{\mu}}_c)(\boldsymbol{x}-\hat {\boldsymbol{\mu}}_c)^T.\tag{24} σ^c2=Dc1xDc(xμ^c)(xμ^c)T.(24)

  详细的推导过程见南瓜书第63~64页。

  假如说,将朴素贝叶斯应用于你的数据集上发现效果并不理想,那么可以考虑更换概率分布,这或许可以看作是贝叶斯分类的超参数。当然,也有可能数据集上的各个属性不独立。

实现代码

  代码中使用的数据集来自和鲸社区:皮马印第安人糖尿病数据库

  复现代码和划分的数据集见Git仓库:朴素贝叶斯

import math
import pandas as pd

# 皮马印第安人糖尿病数据
train_data = pd.read_csv(r'train_data.csv').values
test_data = pd.read_csv(r'test_data.csv').values

# 划分连续和离散型变量
continuous_columns = [1, 2, 3, 4, 5, 6]  # Glucose, BloodPressure, SkinThickness, Insulin, BMI, DiabetesPedigreeFunction
discrete_columns = [0, 7]  # Pregnancies, Age

# 将数据按类别分离
separated_data = {0: [], 1: []}  # 创建一个空字典用来存储两个类别下的样本
for row in train_data:
    class_label = row[-1]  # 获取类别列
    separated_data[class_label].append(row)  # 把相同类别下的样本放到一起

# 分别计算两个类别下每个连续变量的均值和标准差、和离散变量的频率
class_summaries = {}  # 创建一个空字典用来存储两个类别下连续变量和离散变量的统计信息
for class_label, rows in separated_data.items():
    summaries = {}

    # 连续型变量的均值和标准差
    continuous_summaries = []
    for col_idx in continuous_columns:
        column = [row[col_idx] for row in rows]  # 把连续变量的一整列提取出来
        mean_val = sum(column) / len(column)  # 计算均值 μ_c
        variance = sum([(x - mean_val) ** 2 for x in column]) / len(column)  # 计算方差 σ_c^2
        stdev_val = math.sqrt(variance)  # 计算标准差 σ_c
        continuous_summaries.append((mean_val, stdev_val))

    # 离散型变量的频率计算
    discrete_summaries = []
    for col_idx in discrete_columns:
        column = [row[col_idx] for row in rows]  # 把离散变量的一整列提取出来
        value_counts = {val: column.count(val) for val in set(column)}  # 统计离散变量中每个取值出现的个数,加1是拉普拉斯修正
        total_count = len(rows)
        discrete_summaries.append((value_counts, total_count))

    # 存储两个类别下连续型和离散型的特征统计信息
    summaries['continuous'] = continuous_summaries
    summaries['discrete'] = discrete_summaries
    class_summaries[class_label] = summaries

# 拉普拉斯修正后的先验概率
class_prior = {}
total_samples = len(train_data)  # 样本长度 |D|
num_classes = len(class_summaries)  # 类别数 N

for class_label in separated_data:  # 取出两个类别的样本
    class_count = len(separated_data[class_label])  # 两个类别各自的样本数量 |D_c|
    class_prior[class_label] = (class_count + 1) / (total_samples + num_classes)

# 分类
predictions = []
for row in test_data:  # 对测试集的每一个样本进行分类
    probabilities = {}
    for class_label, summaries in class_summaries.items():  # 两个类别下连续变量和离散变量的统计信息

        # 将第一个类条件概率初始化为先验概率
        probabilities[class_label] = class_prior[class_label]

        # 连续变量的类条件概率
        for i, (mean_val, stdev_val) in enumerate(summaries['continuous']):
            x = row[continuous_columns[i]]  # 测试集每个样本中每个连续变量的取值
            exponent = math.exp(-((x - mean_val) ** 2 / (2 * stdev_val ** 2)))
            continous_probability = (1 / (math.sqrt(2 * math.pi) * stdev_val)) * exponent
            probabilities[class_label] *= continous_probability

        # 离散变量的类条件概率
        for i, (value_counts, total_count) in enumerate(summaries['discrete']):
            x = row[discrete_columns[i]]
            num_values = len(value_counts)  # 变量取值的数量 N_i
            discrete_probability = (value_counts.get(x, 0) + 1) / (total_count + num_values)  # 拉普拉斯修正
            probabilities[class_label] *= discrete_probability

    # 计算归一化因子 P(x)
    total_probability = sum(probabilities.values())
    for class_label in probabilities:
        probabilities[class_label] /= total_probability
    best_class = max(probabilities, key=probabilities.get)  # 看看哪个类别的后验概率大
    predictions.append(best_class)

# 计算准确率
correct_predictions = sum(1 for i in range(len(test_data)) if test_data[i][-1] == predictions[i])
accuracy = (correct_predictions / len(test_data)) * 100.0
print(f'code_accuracy: {accuracy:.4f}%')

半朴素贝叶斯

  朴素贝叶斯是基于各属性都相互独立的假设进行的,但现实生活中并没有如此理想。半朴素贝叶斯则适当考虑一部分属性间的相互依赖信息,从而既不需进行完全联合概率计算,又不至于彻底忽略了比较强的属性依赖关系。

  回顾公式(14),朴素贝叶斯的类条件概率考虑的是只与一个属性 c c c有关,而半朴素贝叶斯不仅考虑属性 c c c,还考虑其他的一个属性,即独依赖估计(ODE):
P ( c ∣ x ) ∝ P ( c ) ∏ i = 1 d P ( x i ∣ c , p a i ) , (25) P(c|\boldsymbol{x})\propto P(c)\prod_{i=1}^d{P({x_i}|c,pa_i)},\tag{25} P(cx)P(c)i=1dP(xic,pai),(25)
其中, p a i pa_i pai表示属性 x i x_i xi所依赖的属性,称为 x i x_i xi的父属性。

  对于如何确定父属性,最直接的做法是假设所有属性都依赖于同一个属性,即超父独依赖估计(SPODE):
P ( c ∣ x ) = P ( x , c ) P ( x ) = P ( c , x i ) P ( x 1 , ⋯   , x i − 1 , x i + 1 , ⋯   , x d ∣ c , x i ) P ( x ) ∝ P ( c , x i ) ∏ j = 1 d P ( x j ∣ c , x i ) , \begin{align} P(c|\boldsymbol{x})=\frac{P(\boldsymbol{x},c)}{P(\boldsymbol{x})}&=\frac{P(c,x_i)P(x_1,\cdots,x_{i-1},x_{i+1},\cdots,x_d|c,x_i)}{P(\boldsymbol{x})}\\ &\propto P(c,x_i)\prod_{j=1}^d{P(x_j|c,x_i)},\tag{26} \end{align} P(cx)=P(x)P(x,c)=P(x)P(c,xi)P(x1,,xi1,xi+1,,xdc,xi)P(c,xi)j=1dP(xjc,xi),(26)
属性都依赖于同一个属性,即超父独依赖估计(SPODE):
P ( c ∣ x ) = P ( x , c ) P ( x ) = P ( c , x i ) P ( x 1 , ⋯   , x i − 1 , x i + 1 , ⋯   , x d ∣ c , x i ) P ( x ) ∝ P ( c , x i ) ∏ j = 1 d P ( x j ∣ c , x i ) , \begin{align} P(c|\boldsymbol{x})=\frac{P(\boldsymbol{x},c)}{P(\boldsymbol{x})}&=\frac{P(c,x_i)P(x_1,\cdots,x_{i-1},x_{i+1},\cdots,x_d|c,x_i)}{P(\boldsymbol{x})}\\ &\propto P(c,x_i)\prod_{j=1}^d{P(x_j|c,x_i)},\tag{26} \end{align} P(cx)=P(x)P(x,c)=P(x)P(c,xi)P(x1,,xi1,xi+1,,xdc,xi)P(c,xi)j=1dP(xjc,xi),(26)
其中, x i x_i xi为超父属性。