机器学习:多种朴素贝叶斯及混合贝叶斯的实现
贝叶斯定理由英国数学家贝叶斯( Thomas Bayes 1702-1761年) 发展,用来描述两个条件概率之间的关系,比如 P(A|B) 和 P(B|A)。贝叶斯数学模型,在假设特征之间互不相关联,互相独立的情况下,利用特征的联合分布与先验概率得到最终的预测类别。这里翻译成文字的语言就是,事件A在事件B发生的前提下发生的条件概率=事件A事件B共同发生的概率/事件B发生的概率。根据同时根据以上公式
目录
贝叶斯介绍:
贝叶斯定理由英国数学家贝叶斯( Thomas Bayes 1702-1761年) 发展,用来描述两个条件概率之间的关系,比如 P(A|B) 和 P(B|A)。贝叶斯数学模型,在假设特征之间互不相关联,互相独立的情况下,利用特征的联合分布与先验概率得到最终的预测类别。
贝叶斯数学原理:
这里翻译成文字的语言就是,事件A在事件B发生的前提下发生的条件概率=事件A事件B共同发生的概率/事件B发生的概率。根据同时根据以上公式,可以将P(AB)转换一下。
那么将上式公式带入贝叶斯的原理公式中,这便是贝叶斯的关键公式。
如何让它从数学公式变成一个数学模型呢,所以我们需要把A和B写的更贴近数据一些。
那么目标就变得明显了,要得到x1,x2,x3...属于k类的概率,我们需要知道,属于k类时x1,x2,x3..的概率。
先验概率:
在上式中被称为先验概率,根据领域知识或以往的经验,确定未观测数据的先验概率分布。是从样本总体估计样本为某一类的概率,可以简单理解为,某类占总样本的个数。所以它的计算公式其中一种可以表示为:
这也是我们下面模型,使用到的公式。
联合概率密度函数:
表示在给定类别y=k的条件下,特征向量
的联合概率密度函数。如果直接计算以上联合概率密度,将会使算法异常冗杂。所以,贝叶斯假设特征之间互相独立,根据概率论的基础知识,当两个特征互相独立的时候,可以产生以下运算:
所以可以将联合概率密度分割开来。
表示特征向量x的边缘概率密度函数,也就是所有类别下的特征向量x的联合概率密度函数。在实际应用中,由于p(x)对于所有类别都是相同的,因此可以省略掉,只需要计算分子部分即可。
伯努利贝叶斯:
伯努利贝叶斯是一种基于贝叶斯定理的分类算法,它适用于处理二元特征的分类问题。该算法假设每个特征都是独立的,并且每个特征的取值只能是0或1,表示特征的存在与否。
伯努利概率质量函数:
伯努利分布,简单来看就是二项分布,因此,多适用于二项分布中。伯努利分布是一种离散概率分布,用于描述只有两个可能结果的随机试验。在伯努利分布中,每次试验的结果只能是成功或失败,成功的概率记为p,失败的概率记为1-p。因其为离散的分布,所以使用概率质量函数来描述它的取值。其公式为:
训练阶段:
我们在构建伯努利贝叶斯的时候需要知道每个特征的条件概率,在这里我将每一列特征当做一个整体,并且假设每组特征都服从伯努利分布。那么如果数据集中有3个特征x1,x2,x3,先以类别作为划分,划出为某一类的所有数据,假设为0,划分之后得到所有最后取值都为0的数据集,然后分别对其取和,得到三个数,将每一组取得的和除以
,也就是单个样本除总样本,得到每个样本的条件概率,完成对0之后再对1的类重复进行以上操作。对于二分类,两次之后,将会得到两组p,分别对应为0的p和为1的p。训练阶段也就此完成。
预测阶段:
利用我们从训练阶段求到的p带入概率质量函数中,分别计算为0的概率或为1的概率,取较大的概率值当做预测值。由于我们提前知道了,为0为1的p值,那么将其写入公式中,也就是描述在y=0或1的前提下,x的取值,求得贝叶斯的联合概率密度。于是可以将上述公式转换为下面的公式:
那么将所有特征的相乘便求得了,联合概率密度函数,在乘以先验概率即可得到最终属于0和属于1的概率。再进行对比,取大的值来当做预测结果即可。
代码展示
import numpy as np
class BernoulliNB:
def fit(self, X, y):
n_samples, n_features = X.shape#获取样本数,特征数
self.classes = np.unique(y)
n_classes = len(self.classes)
self.priors = np.bincount(y)/n_samples#先验概率
self.p=np.zeros((n_classes,n_features))#建立伯努利概率p的容器
for i in self.classes:
sum_all_row=np.sum(X[y==i],axis=0)#按照类别,每个特征求和
sum_all=np.sum(X[y==i])#按照类别,总样本求和
#得到伯努利概率p,为确保概率不为0使用了拉普拉斯平滑
self.p[i]=(sum_all_row+1)/(sum_all+2)
def predict(self,x):
x=np.array(x)
predict=[]
for i in range(len(self.priors)):
x1=x.copy()
for j in range(len(x)):
x1[j,:]=(self.p[i,:]**x1[j,:])*((1-self.p[i,:])**(1-x1[j,:]))
#伯努利概率质量计算公式
predict.append(np.prod(x1,axis=1)*self.priors[i])#贝叶斯公式
predict=np.array(predict)
return np.argmax(predict,axis=0)
导入数据集进行测试。数据集样式,可以看到这组数据并不符合伯努利分布,看一下模型在不同的数据分布上的效果:
相同种子数,没有进行数据处理,与sklearn进行比较。

差距并不明显,手写模型成功。

拓展:
在预测阶段,若是特征很多,直接相乘可能会超过numpy的数字上限,让数据产生无穷小,-inf。这个时候可以使用最大似然估计方法,预测概率取对数,将相乘改为相加。并且对于公式来说,我们只需要对比为0和为1的概率大小,所以可以公式变为:
改成这样需要注意,log里面的数不能小于等于0,若是概率为0可能是前面步骤的某一点没有进行平滑造成。
高斯贝叶斯:
高斯贝叶斯是基于高斯分布产生的分类器,它假设所有特征服从高斯分布,根据概率论的大数定理,我们知道当数据大到一定程度的时候,数据的均值将会趋近于数据的期望值。那么当均值等于期望值会发生什么呢,原数据将会成为一个高斯分布。所以高斯贝叶斯也适用于数据量较大的情况下。
高斯概率分布:
高斯概率分布,也称为正态分布或钟形曲线,是一种连续概率分布,常用于描述自然界中许多现象的分布情况。高斯分布的概率密度函数(PDF)可以表示为:
其中,μ是分布的均值(期望值),σ是分布的标准差。
训练阶段:
与伯努利训练相同,高斯的训练,也需要取得不同类别下的标准差和均值。原理为,将每一列当做高斯分布,那么训练集与测试集独立同分布,可以假设测试集的均值和训练集均值相同,方差同理。那么对于训练集只需要求得每一个类下,每一列均值和标准差。
预测阶段:
高斯的预测需要用到训练集中得到的均值和标准差,即将每个测试集带入高斯概率密度函数中,求得一个全为高斯概率的矩阵,对其每行进行累乘即得到。预测和训练较为简单。
代码展示:
class GaussianNB1:
def fit(self,x,y):
x=np.array(x)
y=np.array(y)
num_samples,num_featers=x.shape
self.num_class=np.unique(y)
self.posteriors_mean=np.zeros((len(self.num_class),num_featers))
self.posteriors_std=np.zeros((len(self.num_class),num_featers))
self.py=np.bincount(y)/num_samples
for i in self.num_class:
px1=x[np.where(y==i)[0]].T
self.posteriors_mean[i,:]=np.mean(px1,axis=1)#获取属于每个类下的均值
self.posteriors_std[i,:]=np.var(px1,axis=1)#获取属于每个类下的标准差
def predict(self,x1):
results=[]
for i in self.num_class:
x2=x1.copy()
x2=(x2-self.posteriors_mean[i,:])**2
e=np.exp((-1)*(x2)/(2*self.posteriors_std[i,:]*self.posteriors_std[i,:]))
p=(1/(np.sqrt(2*np.pi*self.posteriors_std[i,:]*self.posteriors_std[i,:])))*e
#公式带入
result=(np.prod(p,axis=1)*self.py[i])#概率
results.append(result)
results=np.array(results)
return1=[]
return1.append(self.num_class[np.argmax(results,axis=0)])
return1=np.array(return1)
return return1[0]
导入数据集,与伯努利贝叶斯数据集相同,对比与官方的区别:
from sklearn.naive_bayes import GaussianNB
x_train,x_test,y_train,y_test=train_test_split(data.iloc[:,:-1],data.iloc[:,-1],random_state=421)#相同种子数
# 创建朴素贝叶斯分类器
gnb1=GaussianNB1()#手写
gnb = GaussianNB()#sklearn官方
stand=StandardScaler()
x_train=stand.fit_transform(x_train)
x_test=stand.fit_transform(x_test)
gnb.fit(x_train,y_train)
print('sklearn贝叶斯得分',gnb.score(x_test,y_test))
gnb1.fit(x_train,y_train)
y_pre=gnb1.predict(x_test)
print('手搓高斯贝叶斯得分为',accuracy_score(y_test,y_pre))
print('手搓高斯贝叶斯精确率为',precision_score(y_test,y_pre))
print('手搓高斯贝叶斯召回率为',recall_score(y_test,y_pre))
可以看出手写高斯贝叶斯得分略高于sklearn的得分,并且召回率达到了恐怖的1,手写模型成功。

混合贝叶斯模型:
在熟悉以上两种算法之后,会发现,不论如何接近分布,都是假设其为某一种分布。那假如提前知道了数据分布,那么我们可以使用更精确地概率分布来假设,也就是说,每一列的x特征可能都有其单独的分布,那么下面的每一个P都为一种概率分布求到的概率。
于是我简单的尝试了将伯努利和高斯混合起来,跑天猫复购的数据。简单瞄一眼密密麻麻的数据:

总共有50w条数据,40多个特征。经过观察,里面有几列只有0和1的,我将其假设为伯努利分布,剩下其他的假设为高斯分布。使用的jyputer断断续续的写的,整理了其中关键的代码进行展示。
import pandas as pd #导入数据,删除无用的列
data=pd.read_csv('data/features.csv')
y=data['label'].copy()
data=data.drop(['label','origin','user_id', 'merchant_id'],axis=1)
import numpy as np
from sklearn.decomposition import PCA
import matplotlib.pyplot as plot
# 创建PCA对象,指定降维后的维度为2
pca = PCA(n_components=15)
#高斯组数据
gocs_data=data.drop(['age_0.0', 'age_1.0', 'age_2.0', 'age_3.0', 'age_4.0',
'age_5.0', 'age_6.0', 'age_7.0', 'age_8.0', 'gender_0.0', 'gender_1.0',
'gender_2.0'],axis=1)
#伯努利组数据
bonuli_data=np.array(data[['age_0.0', 'age_1.0', 'age_2.0', 'age_3.0', 'age_4.0',
'age_5.0', 'age_6.0', 'age_7.0', 'age_8.0', 'gender_0.0', 'gender_1.0',
'gender_2.0']].copy())
#使用mpmath自定义精度
import mpmath as mp
from sklearn.model_selection import train_test_split
mp.dps=256
#伯努利概率分布
sample=len(bonuli_data)
py=np.bincount(y)/len(y)
def high_precision_func(x):
return mp.mpf(x)
# 使用vectorize函数将函数向量化
vectorized_func = np.vectorize(high_precision_func)
#伯努利组训练
for i in range(len(np.unique(y))):
x_c=x_train_b[y_train_b==i]
sum_row=np.sum(x_c,axis=0)
sum_all=np.sum(x_c)
p[i]=(sum_row+1)/(sum_all+2)
p=vectorized_func(p)
#高斯组训练
gocs_mean=[]
gocs_std=[]
for i in np.unique(y):
px=x_train[np.where(y_train_g==i)[0]]
print('1 ',px.shape)
gocs_mean.append(np.mean(px,axis=0))
gocs_std.append(np.var(px,axis=0))
gocs_mean=np.array(gocs_mean)
gocs_std=np.array(gocs_std)
#伯努利预测
y_b_p=[]
for i in p:
y_b_p.append(i**x_test_b*(1-i)**(1-x_test_b))
y_b_p
#高斯预测
y_g_p=[]
for i in range(len(np.unique(y))):
mi=(((-1)*(x_test-gocs_mean[i])**2+1)/((2*gocs_std[i]**2)+2))
y_g_p.append(np.power(np.e,mi)*(1/(np.sqrt(2*np.pi)*gocs_std[i]+2)))
y_g_p
#联合预测
kinds=np.bincount(y)
final_p=[]
for i in range(len(kinds)):
final_p.append(py[i]*np.prod(y_b_p[i],axis=1)*np.prod(y_g_p[i],axis=1))
#设置为1的阈值
xm=0
xm=final_p[1]
xn=final_p[0]
print(np.mean(final_p[1]))
print(np.mean(final_p[0]))
xm_1=np.mean(final_p[1])
xm_1+=0.65*xm_1
xm[xm>=xm_1]=1
xm[(xm<xm_1)]=0
y_pre=[]
for i in xm:
y_pre.append(int(i))
结果,特别说明:这组数据集对分类器非常不友好,它的正类出现概率仅为0.03其他全为负类,所以对于该模型,应该更注重它的召回率和精确率:

官方sklearn的高斯贝叶斯:

由于我手动修改了阈值,但阈值是我估摸着进行设置的,所以准确率较低,召回率相较于sklearn要稍微高,这里使用的sklearn的高斯贝叶斯没有进行降维操作,对这个结果也应该有影响,但结果和sklearn的模型相差不大。模型手写成功。
如有不足,还请各位大佬指出。博主会继续虚心学习
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)