朴素贝叶斯的基本思想是: 基于特征条件独立假设学习输入和输出的联合概率分布,然后,基于此模型,对给定的输入$x$,利用贝叶斯订立求出后验概率最大的输出$y$,根据《统计学习方法》书中的规划,这里将简述朴素贝叶斯的学习和分类、朴素贝叶斯的参数估计方法。
输入:训练数据集 $$T=\{ \left( x_1,y_1 \right),\left( x_2,y_2 \right),\left( x_i,y_i \right),...,\left( x_N,y_N \right) \}$$
朴素贝叶斯的目的是: 通过训练数据集$T$学习到联合概率分布$P(X,Y)$,具体来讲,这个联合概率分布的求解根据联合概率密度求解的公式$P(x|y)=\frac{P(x,y)}{P_Y(y)}$又分为两个部分:
$$P(Y=c_k),k=1,2,...,K$$先验概率分布:
$$P(X=x|Y=c_k)=P(X^\left(1\right)=x^\left(1\right),...,X^\left(n\right)=x^\left(n\right)|Y=c_k),k=1,2,...,K$$条件概率分布:
其中,条件独立性假设
$$ P\left( X=x|Y=c_k \right)=P( X^\left(1\right)=x^\left(1\right),...,X^\left(n\right)=x^\left(n\right) )
=\prod_{j=1}^n P \left(X^\left(j \right)=x^\left( j \right)| Y=c_k\right)$$
关于生成模型和判别模型:
根据上面的式子,这里由于要求解的是$P(x|y)=\frac{P(x,y)}{P_Y(y)}$,实质上是一种生成数据的机制,所以属于生成模型,可能有的同学会听说过生成对抗网络(GAN)的概念,没错,里面的生成也是生成模型,在GAN中,生成模型也是用来生成数据的,但是可能它生成的数据更加复杂,不再单单是一个高斯分布了,很有可能是一张图什么的,这个是后话了,这里只要知道朴素贝叶斯是生成模型、它学习的是一种生成数据的机制,就可以了。但是话又说回来了,你都知道如何生成数据了,当然就可以知道如何对数据进行区分了,不是么哈哈~
好了,这里简言之,直接描述朴素贝叶斯的算法过程
输入:
- 训练数据$T=\{ \left( x_1,y_1 \right),\left( x_2,y_2 \right),\left( x_i,y_i \right),...,\left( x_N,y_N \right) \}$,其中,$x_i = \left(x_i^\left(1\right),x_i^\left(2\right),...,x_i^\left(n\right) \right)^T$;
输出:
- 实例$x$的分类结果
算法过程:
$$P\left( Y = c_k \right) = \frac{\sum_{i=1}^N I(y_i=c_i)}{N},k=1,2,...,K$$
- 计算先验概率以及条件概率
$$P\left( X^\left(j\right)=a_{jl} | Y=c_k \right) = \frac{\sum_{i=1}^N I(x^\left(j\right)=a_{jl},y_i=c_k)}{\sum_{i=1}^N I(y_i=c_i)}$$
$$j=1,2,...,n; l=1,2,...,S_j,k=1,2,...,K$$
2. 对于给定的实例$x$,计算
$$P(Y=c_k)\prod_{j=1}^n P(X^\left(j\right) = x^\left(j\right)|Y=c_k),k=1,2,...,K $$
3. 确定实例$x$的类
$$y=\arg\underset{c_k}{\max} P(Y=c_k) P(X^\left(j\right) = x^\left(j\right)|Y=c_k)$$
# 给定数据集,其中SML分别替换为1,2,3
import numpy as np
x = np.asarray([[1,1],[1,2],[1,2],[1,1],[1,1],
[2,1],[2,2],[2,2],[2,3],[2,3],
[3,3],[3,2],[3,2],[3,3],[3,3]])
y = np.asarray([-1,-1,1,1,-1,
-1,-1,1,1,1,
1,1,1,1,-1])
计算$P(Y=1)$和$P(Y=-1)$
AllNum = np.size(y)
# P(Y=1)
PosY = np.sum(y==1)/AllNum
#P(Y=-1)
NegY = np.sum(y==-1)/AllNum
print('P(Y=1)=',PosY,', P(Y=-1)=',NegY)
P(Y=1)= 0.6 , P(Y=-1)= 0.4
计算$P\left( X^\left(j\right)=a_{jl} | Y=c_k \right) = \frac{\sum_{i=1}^N I(x^\left(j\right)=a_{jl},y_i=c_k)}{\sum_{i=1}^N I(y_i=c_i)}$
# y 取值范围
y_ = np.unique(y)
# x 的维度
feanum = np.size(x,1)
min_result_dictionary = {}
for k in range(len(y_)):# 对于每个Y类别
P_Y = np.sum(y==y_[k])/np.size(y) # 计算P(Y=k)的值
print('P(Y =',y_[k],') =',P_Y)
k_x = x[y==y_[k],:]# 保留在P(Y=k)条件下所有的x样本
for j in range(feanum): # 对于每个特征
fea_val_range = np.unique(x[:,j])# 该维度下属性值的取值范围
for l in range(len(fea_val_range)): # 对于特征下的第l个取值
P_X_Y = np.sum(k_x[:,j] == fea_val_range[l])/np.sum(y==y_[k])
print('P(X特征维度=',j,'特征值=',fea_val_range[l],'| Y=',y_[k],')=',P_X_Y)
split = ','
key = split.join([str(y_[k]),str(j),str(fea_val_range[l])])
min_result_dictionary[key] = P_X_Y
# min_result_dictionary
P(Y = -1 ) = 0.4 P(X特征维度= 0 特征值= 1 | Y= -1 )= 0.5 P(X特征维度= 0 特征值= 2 | Y= -1 )= 0.3333333333333333 P(X特征维度= 0 特征值= 3 | Y= -1 )= 0.16666666666666666 P(X特征维度= 1 特征值= 1 | Y= -1 )= 0.5 P(X特征维度= 1 特征值= 2 | Y= -1 )= 0.3333333333333333 P(X特征维度= 1 特征值= 3 | Y= -1 )= 0.16666666666666666 P(Y = 1 ) = 0.6 P(X特征维度= 0 特征值= 1 | Y= 1 )= 0.2222222222222222 P(X特征维度= 0 特征值= 2 | Y= 1 )= 0.3333333333333333 P(X特征维度= 0 特征值= 3 | Y= 1 )= 0.4444444444444444 P(X特征维度= 1 特征值= 1 | Y= 1 )= 0.1111111111111111 P(X特征维度= 1 特征值= 2 | Y= 1 )= 0.4444444444444444 P(X特征维度= 1 特征值= 3 | Y= 1 )= 0.4444444444444444
当给定$x=(2,S)^T$,也即$x=(2,1)^T$,计算$P(Y=c_k)\prod_{j=1}^n P(X^\left(j\right) = x^\left(j\right)|Y=c_k)$:
testx = np.array([2,1])
labelvalue =[]
for k in range(len(y_)):
P_Y = np.sum(y==y_[k])/np.size(y) # 计算P(Y=k)的值
j_p = 1# 记录每个类别下的概率值
for j in range(feanum):
split = ','
key = split.join([str(y_[k]),str(j),str(testx[j])])
j_p = j_p * min_result_dictionary[key]
labelvalue.append(P_Y*j_p)
返回值最大时候得到的类别标签$y=\arg\underset{c_k}{\max} P(Y=c_k) P(X^\left(j\right) = x^\left(j\right)|Y=c_k)$
y_[labelvalue.index(max(labelvalue))]
-1
综上可以得知,实际上朴素贝叶斯是在给定每个类别标签的情况下,查看这个数据“合理性”有多高,“合理性”越高,说明越有可能是这个类别,而这个“合理性”的度量,就是使用了概率来衡量,最后动过一个$\arg \max$操作,得到了对应的类别标签。
用心的读者也许会发现,如果用以上的方法(实际是“极大似然估计”)可能会得到概率值为0的情况,然后会影响后续一系列操作(如求最大值等)。根据《统计学习方法》一书,我们在这里加一个步骤,将原来的式子做小的修改:
$$P\left( X^\left(j\right)=a_{jl} | Y=c_k \right) = \frac{\sum_{i=1}^N I(x^\left(j\right)=a_{jl},y_i=c_k)}{\sum_{i=1}^N I(y_i=c_i)} \rightarrow P\left( X^\left(j\right)=a_{jl} | Y=c_k \right) = \frac{\sum_{i=1}^N I(x^\left(j\right)=a_{jl},y_i=c_k)+\lambda}{\sum_{i=1}^N I(y_i=c_i)+S_j\lambda}$$
其中,$\lambda \geq 0$,当$\lambda = 0$的时候等同于原始的极大似然估计,而通常,这里取$\lambda = 1$,称为拉普拉斯平滑,这时,对于$l=1,2,...,S_j$,$k=1,2,...,K$,有
$$P_\lambda(X^\left(j\right)=a_{jl}|Y=c_k)>0$$
$$\sum_{l=1}^{S_j}P_\lambda(X^\left(j\right)=a_{jl}|Y=c_k) = 1$$
这两个式子说明拉普拉斯平滑之后的式子确实是一个概率分布(即有每个概率大于0,总概率和等于1),此时,得到鲜艳概率贝叶斯估计为: $$P_\lambda(Y=c_k)=\frac{\sum_{i=1}^N I(y_i=c_k)+\lambda}{N+K\lambda}$$
此时我们修改上面的代码,运行可以得到如下结果
# y 取值范围
y_ = np.unique(y)
# x 的维度
feanum = np.size(x,1)
lmda = 1
min_result_dictionary = {}
for k in range(len(y_)):# 对于每个Y类别
P_Y = (np.sum(y==y_[k])+lmda)/(np.size(y)+lmda*len(y_)) # 计算P(Y=k)的值
print('P(Y =',y_[k],') =',P_Y)
k_x = x[y==y_[k],:]# 保留在P(Y=k)条件下所有的x样本
for j in range(feanum): # 对于每个特征
fea_val_range = np.unique(x[:,j])# 该维度下属性值的取值范围
for l in range(len(fea_val_range)): # 对于特征下的第l个取值
P_X_Y = (np.sum(k_x[:,j] == fea_val_range[l])+lmda)/(np.sum(y==y_[k])+lmda*len(fea_val_range))
print('P(X特征维度=',j,'特征值=',fea_val_range[l],'| Y=',y_[k],')=',P_X_Y)
split = ','
key = split.join([str(y_[k]),str(j),str(fea_val_range[l])])
min_result_dictionary[key] = P_X_Y
# min_result_dictionary
P(Y = -1 ) = 0.4117647058823529 P(X特征维度= 0 特征值= 1 | Y= -1 )= 0.4444444444444444 P(X特征维度= 0 特征值= 2 | Y= -1 )= 0.3333333333333333 P(X特征维度= 0 特征值= 3 | Y= -1 )= 0.2222222222222222 P(X特征维度= 1 特征值= 1 | Y= -1 )= 0.4444444444444444 P(X特征维度= 1 特征值= 2 | Y= -1 )= 0.3333333333333333 P(X特征维度= 1 特征值= 3 | Y= -1 )= 0.2222222222222222 P(Y = 1 ) = 0.5882352941176471 P(X特征维度= 0 特征值= 1 | Y= 1 )= 0.25 P(X特征维度= 0 特征值= 2 | Y= 1 )= 0.3333333333333333 P(X特征维度= 0 特征值= 3 | Y= 1 )= 0.4166666666666667 P(X特征维度= 1 特征值= 1 | Y= 1 )= 0.16666666666666666 P(X特征维度= 1 特征值= 2 | Y= 1 )= 0.4166666666666667 P(X特征维度= 1 特征值= 3 | Y= 1 )= 0.4166666666666667
testx = np.array([2,1])
labelvalue =[]
for k in range(len(y_)):
P_Y = np.sum(y==y_[k])/np.size(y) # 计算P(Y=k)的值
j_p = 1# 记录每个类别下的概率值
for j in range(feanum):
split = ','
key = split.join([str(y_[k]),str(j),str(testx[j])])
j_p = j_p * min_result_dictionary[key]
labelvalue.append(P_Y*j_p)
print(labelvalue)
[0.05925925925925926, 0.03333333333333333]
返回值最大时候得到的类别标签
y_[labelvalue.index(max(labelvalue))]
-1
注:上面由于浮点精度问题,这里的结果与《统计学习方法》书中的结果存在一点差异,但是原理就是这样了!
# 4.2定义自己的朴素贝叶斯算法
import numpy as np
class myNaiveBayes():
def train(self,x,y,lmda=None):
"""
x: 训练集
y: 类别标签向量
lmda: 拉普拉斯平滑参数值,默认为1
"""
self.x = x
self.y = y
self.lmda = lmda
model = []
# y 取值范围
y_ = np.unique(y)
# x 的维度
feanum = np.size(x,1)
min_result_dictionary = {}
P_Y = np.empty(len(y_))
for k in range(len(y_)):# 对于每个Y类别
P_Y[k] = np.sum(y==y_[k])/np.size(y) # 计算P(Y=k)的值
k_x = x[y==y_[k],:]# 保留在P(Y=k)条件下所有的x样本
for j in range(feanum): # 对于每个特征
fea_val_range = np.unique(x[:,j])# 该维度下属性值的取值范围
for l in range(len(fea_val_range)): # 对于特征下的第l个取值
P_X_Y = (np.sum(k_x[:,j] == fea_val_range[l])+lmda)/(np.sum(y==y_[k])+lmda*len(fea_val_range))
split = ','
key = split.join([str(y_[k]),str(j),str(fea_val_range[l])])
min_result_dictionary[key] = P_X_Y
model.append(feanum) # 特征维度数目 0
model.append(y_) # 类别取值范围 1
model.append(min_result_dictionary) # dictionary保存的概率值 2
model.append(np.size(y)) # 样本个数 3
model.append(P_Y)
return model
def predict(self,x,model):
"""
x 应该与train中的x具有相同的维度数
y 为预测类别标签
"""
y = []
self.model = model
self.x = x
if self.x.ndim==1:
self.x = self.x.reshape(1,len(x))
for i in range(len(self.x)):
testx = np.asarray(self.x[i,:])
labelvalue =[]
for k in range(len(self.model[1])):
P_Y = model[4][k]
j_p = 1# 记录每个类别下的概率值
for j in range(model[0]):
split = ','
key = split.join([str(model[1][k]),str(j),str(testx[j])])
j_p = j_p * model[2][key]
labelvalue.append(P_Y*j_p)
y.append(y_[labelvalue.index(max(labelvalue))])
return y
def accuracy(self,y,realy):
self.y = y
self.realy = realy
return 1-sum(np.sign(np.abs(y-realy)))/len(y)
定义课本的15个实例
x = np.asarray([[1,1],[1,2],[1,2],[1,1],[1,1],
[2,1],[2,2],[2,2],[2,3],[2,3],
[3,3],[3,2],[3,2],[3,3],[3,3]])
y = np.asarray([-1,-1,1,1,-1,
-1,-1,1,1,1,
1,1,1,1,-1])
验证课本的15个实例
mynb = myNaiveBayes()
model = mynb.train(x,y,lmda=1)
newy = mynb.predict(x,model)
print('正确率:',mynb.accuracy(y,newy))
正确率: 0.7333333333333334
课本测试用例
temp = np.asarray([2,1])
newy = mynb.predict(temp,model)
print(newy)
[-1]