李航的蓝皮书《统计学习方法》,可谓是机器学习的中文经典。其中所设计的一些算法,是机器学习的基础。这篇博文将要讲述蓝皮书中的第四章朴素贝叶斯法。
Part i 算法原理
朴素贝叶斯方法的整体思路是相对容易理解的:通过样本知识,可以求得先验概率,然后根据全概率公式,计算出所求事件的概率。朴素贝叶斯的算法框架如下:
=============================朴素贝叶斯=============================
输入:样本集X,标签y
输出:某个样本的分类
==================================================================
Step.1 计算先验概率和条件概率
Step.2 对于给定样本,计算该样本属于各类的概率
Step.3 确定样本属于哪一类
==================================================================
虽然整个算法非常简单,但是在实际操作中会存在一些细小的问题,如
1.在计算某个条件概率时如果出现了0的情况,则很有可能会影响到厚颜概率的计算。
2.对于离散变量可以很好的计算概率,可是对于连续变量就存在问题。
面对上述两个问题,问题一通常的解决方法是加入一个系数lambda,使概率不为0。故而对Step.1 中的公式进行如下的更改:
问题二的通常解决方法是假设其满足某个分布,通常为正太分布,则通过样本集来估计该分布的参数。在测试集中,根据实例的数值,来计算其在该分布下的概率。
Part ii Python 代码实现
定义一个NaiveBayes(object)
类,初始化参数λ=1
def __init__(self,lamda=1):
self.lamda = lamda
训练模型,训练模型的过程,其实就是计算各个概率的过程。对于离散变量直接其概率,对于连续变量,则计算他们的均值和方差。当然为了简化,额外设置了两个输入参数class_num
和ind
,其中class_num
表示类别的数目,而ind
表示离散变量的标号。
在这里以一个字典的形式记录下各个概率。
def fit(self,x,y,class_num,ind):
total_num,Len = x.shape
Feat_Len = np.max(x,axis=0)+1
prob = dict()
for i in range(class_num):
y_category_num = np.sum(y==i)
prob.update( {str(i+1):(y_category_num+self.lamda)/(total_num+class_num*self.lamda) } )
for j in range(Len):
temp_x = x[y==i,j]
if j in ind:
for k in range(Feat_Len[j]):
feat_category_num = np.sum(temp_x==k)
prob.update( {str(100*(i+1)+10*(j+1)+k+1): (feat_category_num + self.lamda) / (y_category_num + Feat_Len[j]*self.lamda) } )
if j not in ind:
mu = np.mean( temp_x)
prob.update( {str(100*(i+1)+10*(j+1)+1):mu} )
sigma = np.std( temp_x,ddof=1)
prob.update( {str(100*(i+1)+10*(j+1)+2):sigma} )
self.prob_matrix = prob
self.class_num = class_num
self.ind = ind
为了使模型能够预测,定义了一个predict(self,x)
的函数,直接输出预测数据的种类
def predict(self,x):
try:
Num,Len = x.shape
except:
Len = len(x)
Num = 1
x = x.reshape([1,-1])
p_pred = np.zeros([Num,self.class_num])
prob_matrix = self.prob_matrix
for n in range(Num):
for i in range(self.class_num):
pb = prob_matrix[str(i+1)]
print( prob_matrix[str(i+1)] )
for j in range(Len):
if j in self.ind:
pb *= prob_matrix[ str(100*(i+1)+10*(j+1)+x[n,j]+1) ]
print( prob_matrix[ str(100*(i+1)+10*(j+1)+x[n,j]+1) ] )
if j not in self.ind:
pb *= stats.norm.pdf(x[n,j], prob_matrix[str(100*(i+1)+10*(j+1)+1)], prob_matrix[str(100*(i+1)+10*(j+1)+2)])
print(stats.norm.pdf(x[n,j], prob_matrix[str(100*(i+1)+10*(j+1)+1)], prob_matrix[str(100*(i+1)+10*(j+1)+2)]) )
print('==================================')
p_pred[n,i] = pb
self.predict_prob_ = p_pred
return np.argmax(p_pred,axis=1)
Part iii 实验结果:
为了验证代码的准确性,采用了两个例子,一个是蓝皮书上的数据,另一个此网址的数据
令蓝皮书中的
S=0,M=1,L=2
,令网络数据集中的有房=1,无房=0,单身=0,已婚=1,离婚=2,拖欠贷款=1,不拖欠贷款=0
,则定义的两个数据集量化后如下表示:
def data_set(self,data):
if data == 1:
X = np.array([[1,1,1,1,1,2,2,2,2,2,3,3,3,3,3],[1,2,2,1,1,1,2,2,3,3,3,2,2,3,3]]).T -1
y = np.array([-1,-1,1,1,-1,-1,-1,1,1,1,1,1,1,1,-1])
y[y==-1]=0
ind= [0,1]
x_te = np.array([1,0])
else:
X = np.array([[1,0,0,1,0,0,1,0,0,0],[0,1,0,1,2,1,2,0,1,0],[125,100,70,120,95,60,220,85,75,90]]).T
y = np.array( [0,0,0,0,1,0,0,1,0,1] )
ind = [0,1]
x_te = np.array([0,1,120])
return X,y,ind,x_te
对这两组数据分别进行训练和预测:
print('蓝皮书数据集')
nb = NaiveBayes()
X,y,ind,x_te = nb.data_set(1)
nb.fit(X,y,2,ind)
x_te = np.array( [[1,0]] )
p1 = nb.predict(x_te)
print( '各类概率:',p1,'识别结果:', nb.predict_prob_)
print('网络数据集')
nb = NaiveBayes(lamda=0)
X,y,ind,x_te = nb.data_set(0)
nb.fit(X,y,2,ind)
p2 = nb.predict_prob(x_te)
print( '各类概率:',p2,'识别结果:',nb.predict_prob_)
最终的结果和书本保持一致。