GaussianNB 的实现

(本文会用到的所有代码都在这里

本文主要介绍连续型朴素贝叶斯——GaussianNB 的实现。在有了实现离散型朴素贝叶斯的经验后,实现连续型朴素贝叶斯模型其实只是个触类旁通的活了

不过在介绍实现之前,我们还是需要先要知道连续型朴素贝叶斯的算法是怎样的。处理连续型变量有一个最直观的方法:使用小区间切割、直接使其离散化。由于这种方法较难控制小区间的大小、而且对训练集质量的要求比较高,所以我们选用第二种方法:假设该变量服从正态分布(或称高斯分布,Gaussian Distribution)、再利用极大似然估计来计算该变量的“条件概率”。具体而言、GaussianNB 通过如下公式计算“条件概率”

这里有两个参数:,它们可以用极大似然估计法定出:

其中,是类别的样本数。需要注意的是,这里的“条件概率”其实是“条件概率密度”,真正的条件概率其实是 0(因为连续型变量单点概率为 0)。这样做的合理性涉及到了比较深的概率论知识,此处不表(其实我想表也表不出来)

所以在实现 GaussianNB 之前、我们需要先实现一个能够计算正态分布密度和进行正态分布极大似然估计的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import numpy as np
from math import pi, exp
# 记录常量以避免重复运算
sqrt_pi = (2 * pi) ** 0.5
class NBFunctions:
# 定义正态分布的密度函数
@staticmethod
def gaussian(x, mu, sigma):
return np.exp(
-(x - mu) ** 2 / (2 * sigma)) / (sqrt_pi * sigma ** 0.5)
# 定义进行极大似然估计的函数
# 它能返回一个存储着计算条件概率密度的函数的列表
@staticmethod
def gaussian_maximum_likelihood(labelled_x, n_category, dim):
mu = [np.sum(
labelled_x[c][dim]) /
len(labelled_x[c][dim]) for c in range(n_category)]
sigma = [np.sum(
(labelled_x[c][dim]-mu[c])**2) /
len(labelled_x[c][dim]) for c in range(n_category)]
# 利用极大似然估计得到的和、定义生成计算条件概率密度的函数的函数 func
def func(_c):
def sub(xx):
return NBFunctions.gaussian(xx, mu[_c], sigma[_c])
return sub
# 利用 func 返回目标列表
return [func(_c=c) for c in range(n_category)]

对于 GaussianNB 本身,由于算法中只有条件概率相关的定义变了、所以只需要将相关的函数重新定义即可。此外,由于输入数据肯定是数值数据、所以数据预处理会简单不少(至少不用因为要对输入进行特殊的数值化处理而记录其转换字典了)。考虑到上一章说明 MultinomialNB 的实现时已经基本把我们框架的思想都说明清楚了,在接下来的 GaussianNB 的代码实现中、我们会适当地减少注释以提高阅读流畅度(其实主要还是为了偷懒)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from b_NaiveBayes.Original.Basic import *
class GaussianNB(NaiveBayes):
def feed_data(self, x, y, sample_weight=None):
# 简单地调用 Python 自带的 float 方法将输入数据数值化
x = np.array([list(map(
lambda c: float(c), sample)) for sample in x])
# 数值化类别向量
labels = list(set(y))
label_dic = {label: i for i, label in enumerate(labels)}
y = np.array([label_dic[yy] for yy in y])
cat_counter = np.bincount(y)
labels = [y == value for value in range(len(cat_counter))]
labelled_x = [x[label].T for label in labels]
# 更新模型的各个属性
self._x, self._y = x.T, y
self._labelled_x, self._label_zip = labelled_x, labels
self._cat_counter, self.label_dic = (
cat_counter, {i: _l for _l, i in label_dic.items()}
self.feed_sample_weight(sample_weight)

可以看到,数据预处理这一步确实要轻松很多。接下来只需要再定义训练用的代码就行,它们和 MultinomialNB 中的实现也大同小异:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 定义处理样本权重的函数
def feed_sample_weight(self, sample_weight=None):
if sample_weight is not None:
local_weights = sample_weight * len(sample_weight)
for i, label in enumerate(self._label_zip):
self._labelled_x[i] *= local_weights[label]
def _fit(self, lb):
n_category = len(self._cat_counter)
p_category = self.get_prior_probability(lb)
# 利用极大似然估计获得计算条件概率的函数、使用数组变量 data 进行存储
data = [
NBFunctions.gaussian_maximum_likelihood(
self._labelled_x, n_category, dim)
for dim in range(len(self._x))]
self._data = data
def func(input_x, tar_category):
# 将输入转换成二维数组(矩阵)
input_x = np.atleast_2d(input_x).T
rs = np.ones(input_x.shape[1])
for d, xx in enumerate(input_x):
rs *= data[d][tar_category](xx)
return rs * p_category[tar_category]
# 由于数据本身就是数值的,所以数据转换函数只需直接返回输入值即可
@staticmethod
def _transfer_x(x):
return x

至此,连续型朴素贝叶斯模型就搭建完毕了

连续型朴素贝叶斯同样能够进行和离散型朴素贝叶斯类似的可视化,不过由于我们接下来就要实现适用范围最广的朴素贝叶斯模型:混合型朴素贝叶斯了,所以我们这里不打算进行 GaussianNB 合理的评估、而打算把它归结到对混合型朴素贝叶斯的评估中

观众老爷们能赏个脸么 ( σ'ω')σ