Level3 — PART 4 机器学习算法 — 朴素贝叶斯

目录

贝叶斯定理

[朴素贝叶斯模型(Naive Bayes Model)](#朴素贝叶斯模型(Naive Bayes Model))

估计

离散估计

极大似然估计

案例

朴素贝叶斯扩展

高斯贝叶斯分类器

原理

应用

源码分析

伯努利贝叶斯分类器

原理

源码分析

多项朴素贝叶斯分类器

半朴素贝叶斯分类器

模拟题

[CDA LEVEL III 模拟题(一)](#CDA LEVEL III 模拟题(一))

[CDA LEVEL III 模拟题(二)](#CDA LEVEL III 模拟题(二))


贝叶斯定理

贝叶斯定理由英国数学家贝叶斯( Thomas Bayes 1702-1761**)** 发展,用来描述两个条件概率之间的关系,比如 是两个随机事件。

按照乘法法则,可以立刻导出:

其中表示随机事件和随机事件同时发生的概率,表示在事件发生的情况下,事件发生的概率,类似地,表示在事件发生的情况下,事件发生的概率。

如上公式也可变形为:


朴素贝叶斯模型(Naive Bayes Model)

假设有个数据,每一个数据由个特征构成,如下:

特征1 特征2 ... 特征n 类别
1 ...
2 ...
. . . . . . . . . . . . . . .
m ...

表示第个数据,为第i个数据的第j个特征的值,表示数据的类别值,它是中某个类别值。

现在的任务是:给定一个由个特征构成的新数据,如何基于贝叶斯定理来对数据进行分类呢?

对于每个类别,在已知数据的情况下,计算属于类别的概率,即条件概率。这样会得到个条件概率的值。其中条件概率最大值对应的类别就作为数据所属的分类。这是合理的,因为概率表示数据属于类别的可能性,值越大,说明数据属于类别的可能性也就越大。

那么如上的分类问题就转换为:在已知数据和上面表格中m个数据以及它们的分类的情况下,求如下最大条件概率对应的类别的问题:

回顾上面的贝叶斯定理:

,贝叶斯公式转化为:

(1)

使用上面已知的数据计算出公式右边的三个概率就可以计算出。 如何计算这些概率呢?对于条件概率,它表示当类别为时,样本值为的概率,通常情况下,该概率为0。为了可以计算出该概率,我们需要做独立性假设,即假设每个特征之间是相互独立的,也即

这样

式中表示类别为的情况下,第个特征值为的概率。因此条件概率可以如下计算:

上面的问题转化为如下计算问题:

以上就是朴素贝叶斯模型的原理。之所以称其"朴素","Naive",是因为我们对特征做了独立性假设:特征之间相互独立。

事实上,读者已经看到。对于始终是固定的,因此不影响条件概率大小的比较,在具体计算时,无需计算该值。

上面(1)式还可以转换为如下联合概率的形式:

后文提到的sklearn库的naive_bayes模块就是采用该式实现的。


估计

有了上面的贝叶斯模型,如何对进行估计,从而计算出。这里介绍了两种估计方法:离散估计和极大似然估计。

离散估计

假设数据的每个特征取值范围都是有限的几个值,也就是说特征是类别数据 ,那么可以如下估计

(1)估计

表示的是类别发生的概率,可以使用表格中类别的频率与数据总个数的比值来近似估计。假设表中类别为的数据个数为,则可以如下估计出

(2)估计

表示的是数据发生的概率,由于数据的每个特征之间是相互独立的,所以

,

其中表示数据的第个特征取值为的概率,它可以使用的频率与数据总个数的比值来代替,假设第个特征值为的数据个数为,则

可以如下估计:

(3)估计

表示当类别为的情况下,数据的第个特征值为的概率,假设在类别为的数据中,第个特征值为的数据个数为,则可以如下估计:

(4)估计

该值可以如下估计:

注:离散估计适用于数据特征为类别型的数据。


极大似然估计

所谓极大似然估计(Maximum Likelihood Estimation,简称MLE),不严谨地说,就是已经知道了随机变量服从的概率密度函数形式为,但参数是未知的。然后通过实验或者其他方式获得了关于随机变量个数据,比如。如何使用这些数据估计出参数的值呢?

首先需要回答的是为什么可以使用这些数据估计参数?这点应该不难理解,因为的取值不同,会影响获得的数据,也就是说在一定程度上决定了会产生这个数据,所以当然可以使用这些数据估计参数。那么,什么样的才会获得像这样形式的数据呢?从概率的角度来说,也就是为何值时,会使获得像这样的数据可能性更大呢?这就是极大似然估计的思想。使出现的可能性最大的,就是的一个极大似然估计。

下面举个例子来理解下,比如最常见的"抛硬币"。对于一枚不均匀的硬币,假设抛掷后只会出现正面和反面,出现正面记作1,反面记作0,正面出现的概率为是未知的,它就是上面我们提到的。进一步假设每次抛硬币相互独立。虽然不知道概率的具体值,但是我们知道抛硬币出现的结果是一个随机变量,假设为,它服从如下的二项分布

通过抛掷10次硬币,获得了如下10个数据:

1, 0, 0, 1, 1, 1, 1, 0, 1, 1

下面使用这10个数据估计出概率。用表示这组数据,表示在概率为的情况下,出现数据组的概率。根据极大似然估计思想,需要求解的就是使最大的值。但是首先需要给出的具体表达式,因为每次抛掷硬币是相互独立的,所以可以如下表示:

这就是所谓的极大似然函数

因为的最大值和对数的最大值是等价的,所以上式两边取对数,得到:

两边对求导,得到

令导数为0,得到:

.

也就是说当概率时,出现上面10个数据的可能性最大。这与实际得到的10个数据是相符的,从上面数据就可以看到,10个数据中,有7次是正面朝上。其实概率的极大似然估计就是获得的10个数据的平均值。概率有从侧面验证了硬币确实是不均匀的。

注:极大似然估计适用于已知特征数据服从某个形式的概率密度函数。


案例

表中是飞机延误相关的训练数据,总计14条数据。

Season Weather A_Control Airline Delay
Summer Sunny no CZ no
Summer Sunny no CA no
Autumn Sunny no CZ yes
WinterSpring RainyOrSnowy no CZ yes
WinterSpring Cloudy yes CZ yes
WinterSpring Cloudy yes CA no
Autumn Cloudy yes CA yes
Summer RainyOrSnowy no CZ no
Summer Cloudy yes CZ yes
WinterSpring RainyOrSnowy yes CZ yes
Summer RainyOrSnowy yes CA yes
Autumn RainyOrSnowy no CA yes
Autumn Sunny yes CZ yes
WinterSpring RainyOrSnowy no CA no

它包含4个特征列:Season、Weather、A_Control、Airline,1个类别列(或标签列)Delay。其中

Season有三种不同类型的取值:Summer、Autumn和WinterSpring

Weather有三种不同类型的取值:Sunny、Cloudy和RainyOrSnowy

A_Control有两种不同类型的取值:no和yes,

Airline有两种不同类型的取值:CA和CZ。

基于上述训练数据,预测新样本

x=<Season=Summer,Weather=RainyOrSnowy,A_Control=no,Airline=CA>

是否会延误,也就是预测Delay的值是no还是yes?

从样本数据,可以看到这些特征数据是类别型数据,可以使用离散估计来计算。回顾上面的贝叶斯模型

对于当前案列,

=<Season=Summer,Weather=RainyOrSnowy,A_Control=no,Airline=CA>

(1)估算

也就是估算

P(Season=Summer,Weather=RainyOrSnowy,A_Control=no,Airline=CA)

=P(Season=Summer) P(Weather=RainyOrSnowy) P(A_Control=no) P(Airline=CA)

=

(2)估算

也就是估算

(3)估算

Delay取值yes的情况:

P(Season=Summer|Delay=yes)=

P(Weather=RainyOrSnowy|Delay=yes)=

P(A_Control=no|Delay=yes)=

P(Airline=CA|Delay=yes)=

于是得到

Delay取值no的情况:

P(Season=Summer|Delay=yes)=

P(Weather=RainyOrSnowy|Delay=yes)=

P(A_Control=no|Delay=yes)=

P(Airline=CA|Delay=yes)=

于是得到

(4)计算

其中

=<Season=Summer,Weather=RainyOrSnowy,A_Control=no,Airline=CA>

Delay取值yes:

Delay取值no:

有读者可能会怀疑计算有误,概率怎么会大于1。原因就在于我们做了独立性假设,而该案例中特征之间并不都是相互独立的,例如Season和Weather存在关联性,通常夏天的天气可能会是Sunny,而春季和冬季的天气可能会是RainyOrSnowy。

从计算结果来看,新样本

x=<Season=Summer,Weather=RainyOrSnowy,A_Control=no,Airline=CA>

的Delay预测值为no,也就是不会延误。

手工计算总是不现实的,下面使用Python代码方式来实现。

训练数据集如下,其存储在csv格式的delay.csv文件中:

Season	Weather	A_Control	Airline	Delay
Summer	Sunny	no	CZ	no
Summer	Sunny	no	CA	no
Autumn	Sunny	no	CZ	yes
WinterSpring	RainyOrSnowy	no	CZ	yes
WinterSpring	Cloudy	yes	CZ	yes
WinterSpring	Cloudy	yes	CA	no
Autumn	Cloudy	yes	CA	yes
Summer	RainyOrSnowy	no	CZ	no
Summer	Cloudy	yes	CZ	yes
WinterSpring	RainyOrSnowy	yes	CZ	yes
Summer	RainyOrSnowy	yes	CA	yes
Autumn	RainyOrSnowy	no	CA	yes
Autumn	Sunny	yes	CZ	yes
WinterSpring	RainyOrSnowy	no	CA	no
Summer	RainyOrSnowy	no	CA

最后一行数据是需要预测的数据。

sklearn库的naive_bayes模块提供了CategoricalNB朴素贝叶斯分类器来处理特征为类别型数据的分类问题,代码如下:

python 复制代码
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File    :   Bayes.py    
@Contact :   raogx.vip@hotmail.com
@License :   (C)Copyright 2017-2018, Liugroup-NLPR-CASIA

@Modify Time      @Author    @Version    @Desciption
------------      -------    --------    -----------
2024/8/24 12:19   gxrao      1.0         None
'''

import pandas as pd
from sklearn.naive_bayes import CategoricalNB
from sklearn.preprocessing import LabelEncoder

if __name__ == '__main__':
    # 读取数据.
    df = pd.read_csv(filepath_or_buffer='G:\pycharm_workspace\machine_learning\cda\data\delay.csv',
                     sep="\t",
                     header=0)
    print('原始数据df=', '\n', df)

    print("==========================================================")
    # 对数据进行编码,字符串特征转换成数字型特征
    label_encoder = LabelEncoder()
    label_df = pd.DataFrame()
    for col in df.columns:
        label_df[col] = label_encoder.fit_transform(df[col])
    print('转换后的数据label_df=', '\n', label_df)

    print("==========================================================")
    # 定义离散型的朴素贝叶斯分类器
    bayes_classifier = CategoricalNB()

    # 数据划分为训练集和测试集.
    train_df = label_df[:-1]
    test_df = label_df[-1:]

    X_train = train_df.drop('Delay', axis=1)
    print('训练数据特征列X_train=', '\n', X_train)
    y_train = train_df['Delay']
    print('训练数据标签列y_train=', '\n', y_train)

    # 使用训练数据拟合分类器
    bayes_classifier.fit(X_train, y_train)

    print("==========================================================")
    # 预测测试数据.
    X_test = test_df.drop('Delay', axis=1)
    predictions = bayes_classifier.predict(X_test)

    # 将预测结果数值转换成原始字符串.
    prediction_ray = label_encoder.inverse_transform(predictions)
    print('预测结果predictions', '\n', prediction_ray)

(1)pd.read_csv()从csv格式的文件中读取样本数据

(2)LabelEncoder()用于将类别字符串转换成数字,因为Python无法基于字符串直接计算,需要将其转换成数字。可以理解成对类别字符串进行编码。

(3)label_encoder.inverse_transform()将预测的数字型结果再转回编码前的字符串形式。

程序运行结果如下:

java 复制代码
原始数据df= 
           Season       Weather A_Control Airline Delay
0         Summer         Sunny        no      CZ    no
1         Summer         Sunny        no      CA    no
2         Autumn         Sunny        no      CZ   yes
3   WinterSpring  RainyOrSnowy        no      CZ   yes
4   WinterSpring        Cloudy       yes      CZ   yes
5   WinterSpring        Cloudy       yes      CA    no
6         Autumn        Cloudy       yes      CA   yes
7         Summer  RainyOrSnowy        no      CZ    no
8         Summer        Cloudy       yes      CZ   yes
9   WinterSpring  RainyOrSnowy       yes      CZ   yes
10        Summer  RainyOrSnowy       yes      CA   yes
11        Autumn  RainyOrSnowy        no      CA   yes
12        Autumn         Sunny       yes      CZ   yes
13  WinterSpring  RainyOrSnowy        no      CA    no
14        Summer  RainyOrSnowy        no      CA   NaN
==========================================================
转换后的数据label_df= 
     Season  Weather  A_Control  Airline  Delay
0        1        2          0        1      0
1        1        2          0        0      0
2        0        2          0        1      1
3        2        1          0        1      1
4        2        0          1        1      1
5        2        0          1        0      0
6        0        0          1        0      1
7        1        1          0        1      0
8        1        0          1        1      1
9        2        1          1        1      1
10       1        1          1        0      1
11       0        1          0        0      1
12       0        2          1        1      1
13       2        1          0        0      0
14       1        1          0        0      2
==========================================================
训练数据特征列X_train= 
     Season  Weather  A_Control  Airline
0        1        2          0        1
1        1        2          0        0
2        0        2          0        1
3        2        1          0        1
4        2        0          1        1
5        2        0          1        0
6        0        0          1        0
7        1        1          0        1
8        1        0          1        1
9        2        1          1        1
10       1        1          1        0
11       0        1          0        0
12       0        2          1        1
13       2        1          0        0
训练数据标签列y_train= 
 0     0
1     0
2     1
3     1
4     1
5     0
6     1
7     0
8     1
9     1
10    1
11    1
12    1
13    0
Name: Delay, dtype: int32
==========================================================
预测结果predictions 
 ['no']

Process finished with exit code 0

结果最终预测Delay为no,与手工计算结果一致。


朴素贝叶斯扩展

naive_bayes模块中,朴素贝叶斯分类器根据特征服从的分布不同有不同的分类器实现,常见的有

(1)高斯贝叶斯分类器(GaussianNB),

(2)伯努利贝叶斯分类器(BernoulliNB),

(3)多项贝叶斯分类器(MultinomialNB),

(4)类别贝叶斯分类器(CategoricalNB)。

如图:

类之间的继承关系如图:

下面逐个介绍它们的原理以及Python源码实现。


高斯贝叶斯分类器

高斯贝叶斯分类器就是假设特征服从的分布是高斯分布。

原理

现在我们回到贝叶斯公式的估计上来,假设已经知道了在分类为的情况下,第个特征服从如下的正态分布:

这里就是我们前面极大似然估计里面说的未知参数,只不过这里是两个未知参数,但是道理是一样的。

事实上,对于每个分类,都有一个与之对应。从表中找到类别为的所有数据,然后将它们的第列特征的值提取出来组成一个集合,记为。事实上就是在分类为,第个特征服从的正态分布的情况下产生的数据。使达到最大的就是的极大似然估计。

下面构造极大似然函数具体推导,因为各数据之间相互独立,所以

两边取对数,得到

两边对求偏导数,得到:

令偏导数为0,得到的极大似然估计:

从结果来看,分别是集合中所有数据的平均值和方差。

以上估计出了,同理使用极大似然估计很容易估计出,而可以使用离散估计。


应用

下面使用Python中实现的高斯贝叶斯分类器来训练数据,并对数据进行分类,具体代码如下:

python 复制代码
from sklearn import datasets
from sklearn.naive_bayes import GaussianNB

iris = datasets.load_iris()
gnb = GaussianNB()
y_pred = gnb.fit(iris.data, iris.target).predict(iris.data)
print("Number of mislabeled points out of a total %d points : %d"
      % (iris.data.shape[0],(iris.target != y_pred).sum()))

代码主要分为以下几个步骤:

(1)加载scikit-learn中自带了标准数据集iris,对应代码如下:

python 复制代码
iris = datasets.load_iris()

scikit-learn中自带了一些标准数据集(datasets), 例如在分类中有irisdigits,在回归中有boston house prices dataset。我们这里使用的就是iris数据集,关于iris数据集,Wikipedia中介绍如下:

但是截至目前,iris数据集中已经变成150个样本,每个样本有4个特征,如下:

python 复制代码
from sklearn import datasets

iris = datasets.load_iris()
print(iris.data.shape)

程序输出结果:

(150, 4)

我们可以查看iris数据集的data和target。如下:

python 复制代码
print('前5条data:',iris.data[0:5])
print('target:',iris.target)
python 复制代码
前5条data: [[5.1 3.5 1.4 0.2]
 [4.9 3.  1.4 0.2]
 [4.7 3.2 1.3 0.2]
 [4.6 3.1 1.5 0.2]
 [5.  3.6 1.4 0.2]]
target: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]

(2)初始化高斯贝叶斯分类器GaussianNB,这里使用的是默认初始化,对应代码如下:

python 复制代码
gnb = GaussianNB()

(3)使用标准数据集iris训练出一个高斯贝叶斯分类器,其实这一步使用极大似然估计完成了正态分布中的未知参数估计,对应代码如下:

python 复制代码
gnb.fit(iris.data, iris.target)

(4) 使用训练出来的高斯贝叶斯分类器对数据进行预测,这里预测的是标准数据集iris,对应代码如下:

python 复制代码
y_pred = gnb.fit(iris.data, iris.target).predict(iris.data)

程序运行结果:

python 复制代码
Number of mislabeled points out of a total 150 points : 6

结果表明:使用iris数据集训练得到的高斯贝叶斯分类器,只对6个样本分类错误,准确度可以接受。


源码分析

首先来看三个贝叶斯分类器的基类BaseNB,具体代码如下:

python 复制代码
class BaseNB(six.with_metaclass(ABCMeta, BaseEstimator, ClassifierMixin)):
    """贝叶斯估计(naive Bayes estimators)的抽象类"""

    @abstractmethod
    def _joint_log_likelihood(self, X):

    def predict(self, X):
        jll = self._joint_log_likelihood(X)
        return self.classes_[np.argmax(jll, axis=1)]

    def predict_log_proba(self, X):
        jll = self._joint_log_likelihood(X)
        # normalize by P(x) = P(f_1, ..., f_n)
        log_prob_x = logsumexp(jll, axis=1)
        return jll - np.atleast_2d(log_prob_x).T

    def predict_proba(self, X):
        return np.exp(self.predict_log_proba(X))

该类包含了一个抽象方法和三个已经实现的方法:

(1)_joint_log_likelihood(self, X)

该方法是是一个抽象方法,具体实现由子类完成。它用于计算联合概率的对数,也就是。返回如下阶联合概率对数矩阵:

为啥要取对数?是因为概率一般很小,几乎接近于0 ,如果不取对数,会存在精度丢失,而取对数后,在不考虑正负的情况下,该值很大。

(2)predict(self, X)

该方法通过调用_joint_log_likelihood(self, X)获得联合概率对数矩阵,并选择矩阵每行中最大值对应的下标,该下标也是类的下标。为什么呢?回顾贝叶斯模型:

该式两边取对数,得到

这说明值越大,就越大,也即

(3)predict_log_proba(self, X)

该方法就是标准化联合概率对数矩阵。为啥要标准化?是因为对于每个i来说,的大小不均衡,也就是所谓的量纲不同,有的偏大,有的偏小。对于第行:

然后第行的每个元素减去,也就是

这样就得到一个标准化后的联合概率对数矩阵。

注意:标准化不是归一化,它不一定满足标准化后每行的和是1。

(4)predict_proba(self, X)

该方法是通过消除标准化后的联合概率对数矩阵中的对数,计算真正的概率。

np.exp(self.predict_log_proba(X))

也就是

不就是样本属于类别的概率吗。

下面来看它的一个实现类GaussianNB的源码,使用GaussianNB分类器分类,主要分为5个阶段:

(1)数据加载

查看上面的应用。

(2)分类器初始化

当数据加载之后,接下来是初始化GaussianNB分类器,实际上调用的是__init__()方法,源码如下:

python 复制代码
    def __init__(self, priors=None):
        self.priors = priors

该方法中初始化了priors,其实是的先验概率,可以由程序员事先给定,如果选择默认实现,就使用离散估计中的

(3)训练或拟合

当GaussianNB分类器初始化结束后,调用了fit()方法,具体代码如下:

python 复制代码
    def fit(self, X, y, sample_weight=None):
        X, y = check_X_y(X, y)
        return self._partial_fit(X, y, np.unique(y), _refit=True,
                                 sample_weight=sample_weight)

该方法很简单,首先检查数据X和分类y是否有效,然后调用_partial_fit()训练数据,注意参数_refit=True,表示如果之前已经拟合过,全部抛弃,重新开始拟合。事实上,_partial_fit()方法才是GaussianNB分类器真正核心,具体代码如下:

python 复制代码
    def _partial_fit(self, X, y, classes=None, _refit=False,
                     sample_weight=None):
        #检查数据有效性
        X, y = check_X_y(X, y)
        
        if sample_weight is not None:
            sample_weight = check_array(sample_weight, ensure_2d=False)
            check_consistent_length(y, sample_weight)

        epsilon = 1e-9 * np.var(X, axis=0).max()
        #如果重新拟合,将类别设置为None
        if _refit:
            self.classes_ = None
        #检查是否第一次拟合
        if _check_partial_fit_first_call(self, classes):
            #初始化特征数,类别数,u_k和sigma_k
            n_features = X.shape[1]
            n_classes = len(self.classes_)
            self.theta_ = np.zeros((n_classes, n_features))
            self.sigma_ = np.zeros((n_classes, n_features))

            self.class_count_ = np.zeros(n_classes, dtype=np.float64)

            # Initialise the class prior
            n_classes = len(self.classes_)
            # 当P(c_k)不为空时,对其进行有效性检验
            if self.priors is not None:
                priors = np.asarray(self.priors)
                # Check that the provide prior match the number of classes
                if len(priors) != n_classes:
                    raise ValueError('Number of priors must match number of'
                                     ' classes.')
                # Check that the sum is 1
                if priors.sum() != 1.0:
                    raise ValueError('The sum of the priors should be 1.')
                # Check that the prior are non-negative
                if (priors < 0).any():
                    raise ValueError('Priors must be non-negative.')
                self.class_prior_ = priors
            #第一次拟合时,如果P(C_K)为None,全部初始化为0
            else:
                # Initialize the priors to zeros for each class
                self.class_prior_ = np.zeros(len(self.classes_),
                                             dtype=np.float64)
        else:
            if X.shape[1] != self.theta_.shape[1]:
                msg = "Number of features %d does not match previous data %d."
                raise ValueError(msg % (X.shape[1], self.theta_.shape[1]))
            # Put epsilon back in each time
            self.sigma_[:, :] -= epsilon

        classes = self.classes_

        unique_y = np.unique(y)
        unique_y_in_classes = np.in1d(unique_y, classes)

        if not np.all(unique_y_in_classes):
            raise ValueError("The target label(s) %s in y do not exist in the "
                             "initial classes %s" %
                             (unique_y[~unique_y_in_classes], classes))
        #对于每个类c_k,估计u_k和sigma_k
        for y_i in unique_y:
            i = classes.searchsorted(y_i)
            X_i = X[y == y_i, :]

            if sample_weight is not None:
                sw_i = sample_weight[y == y_i]
                N_i = sw_i.sum()
            else:
                sw_i = None
                N_i = X_i.shape[0]

            new_theta, new_sigma = self._update_mean_variance(
                self.class_count_[i], self.theta_[i, :], self.sigma_[i, :],
                X_i, sw_i)

            self.theta_[i, :] = new_theta
            self.sigma_[i, :] = new_sigma
            self.class_count_[i] += N_i

        self.sigma_[:, :] += epsilon

        # p(c_k)的计算,它可通过实例化GaussianNB分类器时参数传入
        # 如果p(c_k)为None,使用频率代替
        if self.priors is None:
            # Empirical prior, with sample_weight taken into account
            self.class_prior_ = self.class_count_ / self.class_count_.sum()

        return self

已通过注释方式对代码的核心部分进行了解释。对照高斯贝叶斯分类器的极大似然估计推导,不难理解。

(4)预测

当拟合(训练)好高斯贝叶斯分类器后,就可以调用predict()方法使用它对数据进行预测分类了,实际上,GaussianNB类并未对predict()方法进行重写,而是直接调用基类BaseNB中已经实现的predict()方法,接着predict()调用GaussianNB实现的_joint_log_likelihood()方法,具体代码如下:

python 复制代码
    def _joint_log_likelihood(self, X):
        check_is_fitted(self, "classes_")

        X = check_array(X)
        joint_log_likelihood = []
        for i in range(np.size(self.classes_)):
            jointi = np.log(self.class_prior_[i])
            n_ij = - 0.5 * np.sum(np.log(2. * np.pi * self.sigma_[i, :]))
            n_ij -= 0.5 * np.sum(((X - self.theta_[i, :]) ** 2) /
                                 (self.sigma_[i, :]), 1)
            joint_log_likelihood.append(jointi + n_ij)

        joint_log_likelihood = np.array(joint_log_likelihood).T
        return joint_log_likelihood

该方法具体完成矩阵计算。联合概率计算公式:

源码中的jointi就是,n_ij就是,它根据如下高斯分布计算得到

当predict()方法拿到矩阵后就可以完成预测分类了。

(5)评估

查看上面应用。


伯努利贝叶斯分类器

原理

首先看一个经典分类问题:文本分类。我们将一切可能的词组成一个字典。当然实际中"一切可能"的词都放入字典中是不可能的,但是我们先这么假设。我们如下来构造文本的一个特征向量:

首先按照字典中词的个数构造一个维向量,通常字典是比较大的,所以对应的也非常大。当字典的第个词在文本中出现过,维向量的第个分量就取值为1,否则取值为0。这样就得到一个取值为0或1的维向量,它就是文本对应的特征向量。

我们的任务是使用文本的特征向量对文本进行分类。每一个文本特征向量就是一个数据,只是每个数据的特征只能取值为0或1。这就是伯努利贝叶斯分类器(Bernoulli Naive Bayes)的模型。它是一种实现服从多元伯努利分布数据的朴素贝叶斯训练和分类算法。下面使用贝叶斯公式,对伯努利贝叶斯分类器模型进行推导。推导过程与上面的高斯朴素分类器一样,只不过这里的数据服从的不再是正态分布,而是伯努利分布。对于元随机向量,分量是一个随机变量,在分类为的情况下,服从如下伯努利分布:

注意到,所以

就是需要估计的参数,不失一般性,我们估计在分类为下的参数,简记为。简化上式得到

后面代码实现也是采用的这种形式。

从表中找到类为的所有数据,然后将它们的第列特征数据提取出来组成一个集合,记为,并设中等于的数有个。同样使用极大似然估计,构造似然函数

两边取对数,得到

两边对求导数,得到:

令导数为0,得到的极大似然估计:

从结果来看,是类别为的数据中第个分量为1的数据的占比。

从结果来看,的极大似然估计似乎很完美,但是如果获取的数据中的第个分量全部为0,则使用这些数据估计出的概率。这是不符合实际情况的,虽然获得的数据中没有出现第个分量为1的数据,但不代表第个分量为1的数据不会在以后预测中出现。为了规避这个问题,引入一个平滑系数,如下:

其中


源码分析

从类继承关系图来看,伯努利贝叶斯分类器BernoulliNB,并没有直接继承BaseNB,而是继承BaseDiscreteNB。该类是离散贝叶斯分类器的具体抽象,包含BernoulliNB和MultinomialNB两个子类。其中MultinomialNB就是后面要详细介绍的多项式贝叶斯分类器。依然按照数据加载、分类器初始化、训练(拟合)、预测和评估五个阶段来分析BernoulliNB的源码:

(1)数据加载

略。

(2)分类器初始化

python 复制代码
    def __init__(self, alpha=1.0, binarize=.0, fit_prior=True,
                 class_prior=None):
        self.alpha = alpha
        self.binarize = binarize
        self.fit_prior = fit_prior
        self.class_prior = class_prior

self.alpha:初始化平滑系数;

self.binarize:二值化的阈值,BernoulliNB分类器处理的只有0和1值的情况,如果输入是连续值,可以使用二值化阈值将连续值转换成0和1的值;

self.fit_prior:是否学习类的先验概率,默认为true,这样会在训练数据不断增加的同时来优化类的先验概率。

self.class_prior:类的先验概率,可以事先指定类的先验概率。如果未指定,使用类的频率代替。

(3)训练(拟合)

BernoulliNB中并没有fit()方法,而是调用的基类BaseDiscreteNB中的fit()方法,源码如下:

python 复制代码
    def fit(self, X, y, sample_weight=None):
        #数据检查
        X, y = check_X_y(X, y, 'csr')
        #获取数据的特征数
        _, n_features = X.shape

        #标签处理,获取矩阵Y,Y的行表示数据,列表示类别,取值只有0和1
        labelbin = LabelBinarizer()
        Y = labelbin.fit_transform(y)
        self.classes_ = labelbin.classes_
        if Y.shape[1] == 1:
            Y = np.concatenate((1 - Y, Y), axis=1)

        Y = Y.astype(np.float64)
        if sample_weight is not None:
            sample_weight = np.atleast_2d(sample_weight)
            Y *= check_array(sample_weight).T

        class_prior = self.class_prior

        n_effective_classes = Y.shape[1]
        #初始化类别和特征频率
        self.class_count_ = np.zeros(n_effective_classes, dtype=np.float64)
        self.feature_count_ = np.zeros((n_effective_classes, n_features),
                                       dtype=np.float64)
        #调用实现类的_count/_update_feature_log_prob/_update_class_log_prior方法
        self._count(X, Y)
        alpha = self._check_alpha()
        self._update_feature_log_prob(alpha)
        self._update_class_log_prior(class_prior=class_prior)
        return self

该方法比较容易理解,已通过注释方式将核心部分进行了解析。fit()方法核心是调用了子类BernoulliNB的三个方法:_count()、_update_feature_log_prob()和_update_class_log_prior(),具体来看:

python 复制代码
    def _count(self, X, Y):
        """Count and smooth feature occurrences."""
        if self.binarize is not None:
            X = binarize(X, threshold=self.binarize)
        self.feature_count_ += safe_sparse_dot(Y.T, X)
        self.class_count_ += Y.sum(axis=0)

_count()方法计算特征和类的频率。

python 复制代码
   def _update_feature_log_prob(self, alpha):
        """Apply smoothing to raw counts and recompute log probabilities"""
        smoothed_fc = self.feature_count_ + alpha
        smoothed_cc = self.class_count_ + alpha * 2

        self.feature_log_prob_ = (np.log(smoothed_fc) -
                                  np.log(smoothed_cc.reshape(-1, 1)))

_update_feature_log_prob()方法计算加入平滑系数后的

python 复制代码
    def _joint_log_likelihood(self, X):
        """Calculate the posterior log probability of the samples X"""
        check_is_fitted(self, "classes_")

        X = check_array(X, accept_sparse='csr')

        if self.binarize is not None:
            X = binarize(X, threshold=self.binarize)

        n_classes, n_features = self.feature_log_prob_.shape
        n_samples, n_features_X = X.shape

        if n_features_X != n_features:
            raise ValueError("Expected input with %d features, got %d instead"
                             % (n_features, n_features_X))

        neg_prob = np.log(1 - np.exp(self.feature_log_prob_))
        # Compute  neg_prob · (1 - X).T  as  ∑neg_prob - X · neg_prob
        jll = safe_sparse_dot(X, (self.feature_log_prob_ - neg_prob).T)
        jll += self.class_log_prior_ + neg_prob.sum(axis=1)

        return jll

_joint_log_likelihood方法计算联合概率对数矩阵

(4)预测

略。

(5)评估

略。

整个计算流程如图:


多项朴素贝叶斯分类器

多项朴素贝叶斯分类器(Multinomial Naive Bayes)是一种实现服从多项分布数据的分类算法。多项分布其实是伯努利分布的直接推广。在多项分布中,出现的结果不再是两个(0或1),而是)个。所以,伯努利分布是多项分布中的情况。

假设在分类为的情况下,第个特征服从多项分布,多项分布出现的可能结果为取值0或1,

从表中找到类为的所有数据,然后将它们的第列特征数据提取出来组成一个集合,记为,并设中值为的数有个。同样使用极大似然估计,构造似然函数

两边取对数,得到

两边对求导数,得到:

令导数为0,得到的极大似然估计:

这个结果和离散估计是一致的。

与伯努利分布类似,引入一个平滑系数,如下:

源码与伯努利朴素贝叶斯分类器类似,不再累述。

我们回到文本分类的问题上来,在伯努利朴素分类器中,提取了文本特征向量,每个向量取值为0或1。但是在文本分类和主题分析时,这种方法不是很好。它只度量了字典中的词是否在文本中出现,对于出现的频率并没有度量。现在换一种方法提取文本特征。

构建一个维向量,向量的第个分量表示字典中第个位置的词在文本中出现的频率。

此时,每个分量的取值是非负整数。这个就是文本的多项分布模型。文字对主题文本的影响分为两部分:

(1)某个文字在文本中出现的比例越高,它与文本主题也就越相关;

(2)一个文字出现的文本数占总文本数的比例越小,它与文本主题也就越相关;

第一条就是熟悉的TF,第二条是IDF,假设文本的特征向量为,则


半朴素贝叶斯分类器

在朴素贝叶斯分类器中,我们严格假设特征之间完全独立的。在实际情况中,几乎是不可能的。这就需要考虑特征之间的依赖关系。例如常见的半朴素贝叶斯分类器有SPODE、TAN、AODE。

TAN分类器是由Friedman 等人提出的一种树状贝叶斯网络, 是朴素贝叶斯分类器的一种改进模型, TAN 分类器的分类性能明显优于朴素贝叶斯分类器,其基本思路是放松朴素贝叶斯分类器中的独立性假设条件, 借鉴贝叶斯网络中表示依赖关系的方法, 扩展朴素贝叶斯的结构, 使其能容纳属性间存在的依赖关系, 但对其表示依赖关系的能力加以限制。

半朴素贝叶斯分类器的原理,这里就不再详述。


模拟题

CDA LEVEL III 模拟题(一)

CDA LEVEL III 模拟题(二)

相关推荐
果冻人工智能37 分钟前
2025 年将颠覆商业的 8 大 AI 应用场景
人工智能·ai员工
代码不行的搬运工38 分钟前
神经网络12-Time-Series Transformer (TST)模型
人工智能·神经网络·transformer
石小石Orz40 分钟前
Three.js + AI:AI 算法生成 3D 萤火虫飞舞效果~
javascript·人工智能·算法
罗小罗同学1 小时前
医工交叉入门书籍分享:Transformer模型在机器学习领域的应用|个人观点·24-11-22
深度学习·机器学习·transformer
孤独且没人爱的纸鹤1 小时前
【深度学习】:从人工神经网络的基础原理到循环神经网络的先进技术,跨越智能算法的关键发展阶段及其未来趋势,探索技术进步与应用挑战
人工智能·python·深度学习·机器学习·ai
阿_旭1 小时前
TensorFlow构建CNN卷积神经网络模型的基本步骤:数据处理、模型构建、模型训练
人工智能·深度学习·cnn·tensorflow
羊小猪~~1 小时前
tensorflow案例7--数据增强与测试集, 训练集, 验证集的构建
人工智能·python·深度学习·机器学习·cnn·tensorflow·neo4j
极客代码1 小时前
【Python TensorFlow】进阶指南(续篇三)
开发语言·人工智能·python·深度学习·tensorflow
zhangfeng11331 小时前
pytorch 的交叉熵函数,多分类,二分类
人工智能·pytorch·分类
Seeklike1 小时前
11.22 深度学习-pytorch自动微分
人工智能·pytorch·深度学习