0x00 数据准备
以下我我生成的随机数
前两列是向量,最后一列是标签
[[ -2.40190838 -9.46793749 0. ]
[ 0.48501661 -1.64782819 1. ]
[ -1.13857628 -1.52379494 1. ]
[ 0.03676704 -3.07497058 1. ]
[ 0.58966412 2.05349804 1. ]
[ 1.74603537 -2.5598727 1. ]
[ 0.9932642 -0.48002329 1. ]
[ -3.97025533 -10.74409641 0. ]
[ -1.21779287 -11.15836353 0. ]
[ -0.52577983 -11.34940749 0. ]
[ 0.52298726 -0.13703454 1. ]
[ -1.80888642 1.30322485 1. ]
[ -0.45292089 -6.04316334 0. ]
[ -2.65890181 -1.12446239 1. ]
[ 1.54891636 0.74589865 1. ]
[ -1.3087977 -7.71897353 0. ]
[ -0.47151448 -10.37571491 0. ]
[ -0.89524628 -10.96464394 0. ]
[ -2.86703029 -10.84498679 0. ]
[ -2.5972638 -9.71612662 0. ]]
在文末尾,会解释数据产生的来源。
0x01 笔算连续型朴素贝叶斯
连续型贝叶斯中每个向量的特征值是一个连续型数字,所以无法计算其概率,我们假设特征值服从高斯分布(正态分布,Gaussian),虽然我们无法计算具体的的概率,但是可以用概率密度替代。
回忆一下,计算正太分布需要哪些东西。
[1] 公式准备
[1.1] 计算数学期望(平均值mu)
按标签分离后
标签:0的数学期望为
[-1.67085098, -9.83834141]
标签:1的数学期望为
[ 0.03162864, -0.64453651]
[1.2] 计算标准差(sigma)
标签:0的标准差为
[1.1524603 , 1.62677798]
标签:1的标准差为
[1.37551258, 1.57723297]
[1.3] 返回一个概率密度(gaussian(x,mu,sigma))
就套用上面的公式,计算每个维度的概率密度
所以在这里就和离散的朴素贝叶斯不一样,离散型朴素贝叶斯在能计算出确切的条件概率,然后根据输入去查找,而连续型只能给一个公式,然后根据输入去带入。
[2] 计算先验算概率
统计 y(标签)=0和y(标签)=1的概率
[0.5,0.5]
[3] 预测,利用MAP估计进行评估
还是拿第一个数据来尝试
[ -2.40190838 -9.46793749 0. ]
第1个维度的值为-2.40190838
第2个维度的值为 -9.46793749
标签为 0
利用我们有的数据
先估算标签为0:
对于第1个维度的值为
x = -2.40190838
mu=-1.67085098 数学期望
sigma=1.1524603 标准差
exp(-(x-mu)**2/(2*sigma**2))/(sqrt_pi*sigma) = 0.28307756002538126
对于第2个维度的值为
x = -9.46793749
mu=-9.83834141 数学期望
sigma=1.62677798 标准差
exp(-(x-mu)**2/(2*sigma**2))/(sqrt_pi*sigma)=0.2389593899107375
P(标签为0的估算)=0.2389593899107375*0.28307756002538126*0.5
=0.03382202052054264
同理进行估计标签为1:
对于第1个维度的值为
x = -2.40190838
mu=0.03162864 数学期望
sigma=1.37551258 标准差
np.exp(-(x-mu)**2/(2*sigma**2))/(sqrt_pi*sigma)=0.060641622215243135
对于第2个维度的值为
x = -9.46793749
mu = -0.64453651
sigma = 1.57723297
np.exp(-(x-mu)**2/(2*sigma**2))/(sqrt_pi*sigma)= 4.048620642274784e-08
P(标签为1的估算)=0.060641622215243135*4.048620642274784e-08*0.5
=4.048620642274784e-08
所以最后的结果为标签为0,而且很明显差距很大。
可以说明可行。
0x02 python伪代码
其实这里的伪代码和离散型朴素贝叶斯相似。就不一一过于详细的解释。
[0] 数据导入
[0.1] 文本导入
现在数据导入不需要制作特征值字典,因为数据已经是数值化了,但是有个小问题,数据是文本,我们要转为对应的浮点型。
[0.2] x,y数据分离
但是数据分离还是要滴
x为
In [130]: x
Out[130]:
array([[ -2.40190838, -9.46793749],
[ 0.48501661, -1.64782819],
[ -1.13857628, -1.52379494],
[ 0.03676704, -3.07497058],
[ 0.58966412, 2.05349804],
[ 1.74603537, -2.5598727 ],
[ 0.9932642 , -0.48002329],
[ -3.97025533, -10.74409641],
[ -1.21779287, -11.15836353],
[ -0.52577983, -11.34940749],
[ 0.52298726, -0.13703454],
[ -1.80888642, 1.30322485],
[ -0.45292089, -6.04316334],
[ -2.65890181, -1.12446239],
[ 1.54891636, 0.74589865],
[ -1.3087977 , -7.71897353],
[ -0.47151448, -10.37571491],
[ -0.89524628, -10.96464394],
[ -2.86703029, -10.84498679],
[ -2.5972638 , -9.71612662]])
y为
In [132]: y
Out[132]:
array([0., 1., 1., 1., 1., 1., 1., 0., 0., 0., 1., 1., 0., 1., 1., 0., 0.,
0., 0., 0.])
[0.3] 制作字典
对于y(标签)我们还是要制作字典,对其转化。
label_dic
{"第0类":0,"第1类":1}
[0.4] 统计标签(y)的出现频次
为了后面计算先验概率
cat_counter
[10,10]
[0.5] 按标签分类数据
为了计算数学期望(mu)和标准差(sigma),带入公式更加方便
labelled_x
In [136]: labelled_x
Out[136]:
[array([[ -2.40190838, -3.97025533, -1.21779287, -0.52577983,
-0.45292089, -1.3087977 , -0.47151448, -0.89524628,
-2.86703029, -2.5972638 ],
[ -9.46793749, -10.74409641, -11.15836353, -11.34940749,
-6.04316334, -7.71897353, -10.37571491, -10.96464394,
-10.84498679, -9.71612662]]),
array([[ 0.48501661, -1.13857628, 0.03676704, 0.58966412, 1.74603537,
0.9932642 , 0.52298726, -1.80888642, -2.65890181, 1.54891636],
[-1.64782819, -1.52379494, -3.07497058, 2.05349804, -2.5598727 ,
-0.48002329, -0.13703454, 1.30322485, -1.12446239, 0.74589865]])]
第一部数据准备也就算完成了
[1] 计算公式
[1.1] 计算平均值
使用np.mean函数
mu = [np.mean(labelled_x[c][dim])for c in range(n_category)]
标签为(y=0) 标签为(y=1)
第一个维度[-1.670850984534065, 0.03162864419482627]
第二个维度[-9.838341405638564, -0.6445365102599536]
[1.2] 计算标准差
使用np.std函数
sigma = [np.std(labelled_x[c][dim]) for c in range(n_category)]
标签为(y=0) 标签为(y=1)
第一个维度[1.1524603038120147, 1.3755125846542104]
第二个维度[1.6267779834169904, 1.5772329705692298]
[1.3] 最后的公式为
gaussian_maximum_likelihood
np.exp(-(x - mu) ** 2 / (2 * sigma ** 2) / (sqrt_pi * sigma))
其中sqrt_pi = (2 *np. pi) ** 0.5
[2] 计算先验算概率
使用上面准备好的cat_counter来计算
p_category
[0.5,0.5]
[3] 预测,利用MAP估计进行评估
给我们一个数据,带入我们
-2.40190838 -9.46793749
我们要带入上面的公式
但是我们想像离散那样随拿随取 就像离散型朴素贝叶斯一样。
那么我们在data中储存函数吧。
data = [
gaussian_maximum_likelihood(self._labelled_x, n_category, dim) \
for dim in range(len(x))
]
到这里伪代码写的差不多了。发现连续型与离散型有好多的重复代码,毕竟大家都是贝叶斯家族的。就会有个小想法
我们扔出来一个类吧,让连续型和离散型都继承自那个类。
0x03python的实现
那个被扔出来的基类
先考虑一下那些是重复的
- __init__初始化
- 计算先验概率的函数
- 训练的框架
- 预测的算法
额,这部就是贝叶斯算法的框架吗?
可能你没懂不过没事
看代码
class NaiveBayes:
def __init__(self):
# 记录训练集的变量
self._x = None
# 记录训练集结果的变量
self._y = None
# 存储实际使用的条件概率的相关信息
self._data = None
# 决策函数
self._func = None
# 记录各个维度特征取值个数的数组
self._n_possibilities = None
# 记录按类别分开后的输入数据的数组
self._labelled_x = None
# 记录按类别相关信息的数组,定义有所不同
self._label_zip = None
# 第i类数据的个数
self._cat_counter = None
# 记录数据条件概率的原始极大似然法
self._con_counter = None
# 记录数值话类别时的转换关系
self._label_dic = None
# 数值化各个唯独特征时的转换关系
self._feat_dic = None
# 为了避免定义大量的property
def __getitem__(self, item):
if isinstance(item, str):
return getattr(self, '_' + item)
# 留下抽象方法让子类定义
def feed_data(self, x, y, sample_weight=None):
pass
def feed_sample_weight(self, sample_weight=None):
pass
# TODO:计算先验概率的函数,lb就是估计中的平滑项
# lb为1表示拉普拉斯平滑
def get_prior_probability(self, lb=1):
return [(_c_num + lb) / (len(self._y) + lb * len(self._cat_counter)) \
for _c_num in self._cat_counter]
# TODO:进行训练
def fit(self, x=None, y=None, sample_weight=None, lb=1):
if x is not None and y is not None:
self.feed_data(x, y, sample_weight)
self._func = self._fit(lb)
# 重写了
def _fit(self, lb):
pass
@staticmethod
def _transfer_x(x):
pass
# TODO:预测单一样本
def predict_one(self, x, get_raw_result=False):
if isinstance(x, np.ndarray):
x = x.tolist()
else:
x = x[:]
# 相关数值化方法进行数值话
x = self._transfer_x(x)
m_arg = 0
m_probability = 0
for i in range(len(self._cat_counter)):
p = self._func(x, i)
logging.debug("p"+str(i))
logging.debug(p)
if p > m_probability:
m_arg = i
m_probability = p
if not get_raw_result:
return self._label_dic[m_arg]
return m_probability
# TODO:多样本预测
def predict(self, x, get_raw_result=False):
return np.array([self.predict_one(xx, get_raw_result) for xx in x])
# TODO:对预测进行评估
def evaluate(self, x, y):
y_pred = self.predict(x)
print("正确率:{:12.6}%".format(100 * np.sum(y_pred == y) / len(y)))
引用大佬的github
接下来就是填空题了
from ml.bys.b_NaiveBayes.Basic import *
class DataUtil:
# [0] 数据导入
def get_dataset(path, train_num=None, tar_idx=None, shuffle=True):
x = []
# [0.1] 文本导入
with open(path, "r", encoding="utf-8") as f:
for sample in f:
x.append(sample.strip().split(","))
if shuffle:
np.random.shuffle(x)
tar_idx = -1 if tar_idx is None else tar_idx
# [0.2] x, y数据分离
y = np.array([xx.pop(tar_idx) for xx in x])
x = np.array(x)
if train_num is None:
return x, y
return (x[:train_num], y[:train_num]), (x[train_num:], y[train_num:])
sqrt_pi = (2 * np.pi) ** 0.5
# [1] 计算公式
class NBFunctions:
@staticmethod
def gaussian_maximum_likelihood(labelled_x, n_category, dim):
# [1.1] 计算平均值
mu = [np.mean(labelled_x[c][dim]) for c in range(n_category)]
# [1.2] 计算标准差
sigma = [np.std(labelled_x[c][dim]) for c in range(n_category)]
# [1.3] 最后的公式为
def func(_c):
def sub(xx):
return NBFunctions.gaussian(xx, mu[_c], sigma[_c])
return sub
return [func(_c=c) for c in range(n_category)]
@staticmethod
# [1.3] 最后的公式为
def gaussian(x, mu, sigma):
return np.exp(-(x - mu) ** 2 / (2 * sigma ** 2) / (sqrt_pi * sigma))
class GaussianNB(NaiveBayes):
def feed_data(self, x, y, sample_weight=None):
# []注意文本转浮点
x = np.array([list(map(lambda c: float(c), sample)) for sample in x])
# [0.3] 制作字典
labels = list(set(y))
label_dic = {label: i for i, label in enumerate(labels)}
y = np.array([label_dic[yy] for yy in y])
# [0.4] 统计标签(y)的出现频次
cat_counter = np.bincount(y)
labels = [y == value for value in range(len(cat_counter))]
# [0.5] 按标签分类数据
labelled_x = [x[label].T for label in labels]
self._x = x.T
self._y = y
self._labelled_x = labelled_x
self._label_zip = labels
self._cat_counter = cat_counter
self._label_dic = {i: _l for _l, i in label_dic.items()}
self.feed_sample_weight(sample_weight)
def feed_sample_weight(self, sample_weight=None):
if sample_weight is not None:
local_weight = sample_weight * len(sample_weight)
for i, label in enumerate(self._label_zip):
self._labelled_x[i] *= local_weight[label]
def _fit(self, lb):
n_category = len(self._cat_counter)
# [2] 计算先验算概率
p_category = self.get_prior_probability(lb)
data = [
NBFunctions.gaussian_maximum_likelihood(self._labelled_x, n_category, dim) \
for dim in range(len(self._x))
]
# data为一个函数,不信可以打印一下
self._data = data
def func(input_x, tar_category):
rs = 1
for d, xx in enumerate(input_x):
rs *= data[d][tar_category](xx)
return rs * p_category[tar_category]
return func
@staticmethod
def _transfer_x(x):
return x
0x04 数据是怎么来的
对了,还是要解释一下数字是怎么来的,其实这里是为了把连续型朴素贝叶斯的内部原理讲清楚才是这么复杂的.
如果可以调用库就不一样了
在sklearn.datasets中有一个 make_blobs函数可以生成随机的聚类数据
import numpy as np
if __name__ == '__main__':
# 我们随机生成一些数据吧
from sklearn.datasets import make_blobs
import time
"""
聚类数据生成器
Parameters
----------
n_samples : int, optional (default=100)
The total number of points equally divided among clusters.
待生成的样本的总数
n_features : int, optional (default=2)
The number of features for each sample.
是每个样本的特征数。
centers : int or array of shape [n_centers, n_features], optional
(default=3)
The number of centers to generate, or the fixed center locations.
表示类别数
cluster_std : float or sequence of floats, optional (default=1.0)
The standard deviation of the clusters.
表示每个类别的方差
center_box : pair of floats (min, max), optional (default=(-10.0, 10.0))
The bounding box for each cluster center when centers are
generated at random.
shuffle : boolean, optional (default=True)
Shuffle the samples.
random_state : int, RandomState instance or None, optional (default=None)
If int, random_state is the seed used by the random number generator;
If RandomState instance, random_state is the random number generator;
If None, the random number generator is the RandomState instance used
by `np.random`.
"""
# 100个数据,2个向量,2个标签,随机数种子2(为了能够生成一样的数据),方差1.5
x, y = make_blobs(20, 2, centers=2, random_state=2, cluster_std=1.5)
from sklearn.naive_bayes import GaussianNB
model = GaussianNB()
model.fit(x, y)
# 这一步偷偷把数据拿出来
print(np.concatenate([x, np.array([y]).T], axis=1))
y_new = model.predict(x)
print("正确率:{:12.6}%".format(100 * np.sum(y_new == y) / len(y)))
当然啦,这两种方法的正确率为100%.