自我代码提升之朴素贝叶斯
作者:数据取经团 - JQstyle
(本文阅读时长:10分钟)
朴素贝叶斯的简介
在机器学习的分类算法的各个门类中,贝叶斯分类方法是一个比较重要的组成部分。而隶属于贝叶斯分类方法的算法,均是以贝叶斯定理为基础的。
对于一个分类问题,目标是Y{y1,y2…yN},已知条件X{x1,x2…xN},需要通过用X来推断出Y。在贝叶斯定理的思想下,预测目标y是否属于某一类ck,我们需要计算出相应的概率P(y=ck|x),进而判断Y的类别。通过贝叶斯公式,可以通过下式求得:
通过给定的训练数据集合,我们可以直接计算出P(y=ci)和单个变量x的条件(后验)概率P(xi|y=ci) ,需要知道P(x|y=c),还得计算出后验概率P(x|y=ci),我们还需要一个假设:
即条件独立性假设,这便是朴素贝叶斯算法的核心(即为什么“朴素”)。条件独立性假设使得各个特征属性之间彼此相互独立,因此只要将各个特征在给定类别ci的后验概率相乘就可以估算出当前各个特征取值的后验概率P(y=ci|x)。这是一个很强的假设(现实中往往很难成立)。
下面给出二分类朴素贝叶斯的训练和预测流程:
对于输入的X和Y
1.计算出先验概率:
2.计算出每一个特征各个取值的后验(条件)概率:
3.对于给定的新的数据,按照条件独立性假设和贝叶斯公式求得y所属各个类别的概率,按照概率大小决定分类结果:
改进:拉普拉斯平滑
由于朴素贝叶斯建模过程中,对于每一条特征在y的条件下后验概率的计算时,若训练集的某个特征取值在当前类别中未出现,则其后验概率为0,这样将会导致在预测中该特征在取到当前值的时候概率为0(无论其他特征如何),可能会严重影响模型的预测。因此在通常情况下,我们会在单特征各取值的后验概率公式的分子适当加一个数值,来避免概率值为0的情况:
当l为1时,则称之为拉普拉斯平滑法。
朴素贝叶斯的特点
作为比较简单的分类器之一,朴素贝叶斯有着自己的特点。其优势在于:首先,朴素贝叶斯源于古典数学的理论基础,有着稳定的分类效率;然后,朴素贝叶斯算法对小规模的数据表现很好,且可以处理多分类任务,适合增量式训练,即使数据量超出内存时,我们可以一批批的去增量训练;其次,朴素贝叶斯模型对缺失数据不太敏感,算法也比较简单,常用于文本分类(如垃圾邮件区分)。
同时,朴素贝叶斯模型的缺陷也很明显:第一,理论上,朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为朴素贝叶斯模型假设属性之间相互独立,但这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果较差;第二,建模过程中需要知道先验概率,而先验概率很多时候取决于假设,假设的模型可以有很多种,因此在某些时候会由于假设的先验模型的原因导致预测效果不佳;第三,由于我们是通过先验和数据来决定后验的概率从而决定分类,所以分类决策存在一定的错误率。
代码的实现过程
导入包和数据
接下来,我们同样采用Python去实现简单的朴素贝叶斯分类器。首先加载相应的模块,并且导入数据。numpy用于建模计算,pandas仅用于导入数据。本文所用的数据依旧是马疝病的分类数据集
import numpy as npimport pandas as pd #用于加载数据集
horse = pd.read_table(u'.../horseColicTraining.txt',
sep='\t',names=['x' + str(i) for i in range(21)]+['y'])
horse_t = pd.read_table(u'.../horseColicTest.txt',
sep='\t',names=['x' + str(i) for i in range(21)]+['y'])
连续型变量离散化
通常,朴素贝叶斯只能直接处理离散型(类别)变量(也可以假定变量的分布来进行概率推测,本文不涉及这一方面内容),所以对于那些连续(数值)变量需要首先离散化处理,建立一个分箱器,对数据集中连续型变量进行分箱。
#分箱器
def bin_get(x,your_bins):
new_x = x.copy()
for i in range(your_bins.shape[0]):
if i==0:
new_x[x<=your_bins[i]]=0
if i>0:
new_x[(x>your_bins[i-1])&(x<=your_bins[i])]=i
if i==your_bins.shape[0]-1:
new_x[x>your_bins[i]]=(i+1)
return new_x
在分箱过程中,以0.2,0.4,0.6,0.8四个分位数为截点,分为5箱。若数据集unique值类别较少,则视为离散变量,不做处理。并且将分箱以后的DataFrame转换为numpy矩阵形式。
#分箱
horse_bin = horse.copy()
horse_t_bin = horse_t.copy()
for i in range(horse.shape[1]-1):
if len(set(horse_bin.ix[:,i])) > 7:#只对拥有7个unique值以上的变量进行分箱工作
horse_bin.iloc[:,i] = bin_get(horse.ix[:,i],
np.array(horse.ix[:,i].quantile([0.2,0.4,0.6,0.8])))
horse_t_bin.iloc[:,i] = bin_get(horse_t.ix[:,i],
np.array(horse.ix[:,i].quantile([0.2,0.4,0.6,0.8])))#转化为numpy矩阵便于建模
horse_bin = np.array(horse_bin)
horse_t_bin = np.array(horse_t_bin)
朴素贝叶斯模型定义
首先定义模型类,先写入先验概率计算函数、条件(后验)概率计算函数等,并考虑拉普拉斯平滑的问题:
class Naive_Bayes_JQ:
def pri_prob_for1(self,x,lapras=0): #对先验概率的计算函数
prob_dir1 = {}
for i in set(x):
prob_dir1[i] = float(sum(x == i)+lapras)/(len(x)+lapras*len(set(x))) return(prob_dir1) def pri_prob_for2(self,x,y,lapras=0): #先验概率的计算函数(X且Y)
prob_dir2 = {}
for i in set(x):
for j in set(y):
prob_dir2[(i,j)] = float(sum(y[x==i]==j)+lapras)/(len(x)+lapras*len(set(x)))
return(prob_dir2)
def con_prob(self,x,y,lapras=0): #条件概率的计算函数(Y到X)
prob_dir3 = {}
pxy = self.pri_prob_for2(x,y,lapras)
py = self.pri_prob_for1(y,lapras)
for i in set(x):
for j in set(y):
if lapras !=0:
n=0
for k in range(len(y)):
n += ((y[k]==j) and (x[k]==i))
prob_dir3[(i,j)] = float(n+lapras)/(sum(y==j)+lapras*len(set(x)))
else:
prob_dir3[(i,j)] = pxy[(i,j)]/py[j]
return(prob_dir3)
然后定义模型的训练过程,主要是对y的各取值的先验概率和X的各个特征的后验概率进行计算,该函数包含在模型类中:
def Naive_Bayes_fit(self,x,y,lapras=0): #模型训练,得到所有Y的先验概率和Y到X各类的条件概率
self.pri_prob_y = self.pri_prob_for1(y,lapras=lapras)
self.con_prob_xy = {}
for i in range(x.shape[1]):
self.con_prob_xy[i] = self.con_prob(x[:,i],y,lapras=lapras)
此后,给出预测函数,预测函数可以给出类别,也可以给出每个类别的预测概率,该函数包含在模型类中:
def predict_line(self,x,prob=False): #单行预测函数
tar_pro = {}
for i in range(len(self.pri_prob_y)):
tar_pro[self.pri_prob_y.keys()[i]] = self.pri_prob_y[i]
for j in range(x.shape[0]):
tar_pro[self.pri_prob_y.keys()[i]] = tar_pro[self.pri_prob_y.keys()[i]]*self.con_prob_xy[j][(x[j],self.pri_prob_y.keys()[i])]
if prob:
prob_sum = sum(np.array(tar_pro.values()))
for k in tar_pro.keys():
tar_pro[k] = tar_pro[k]/prob_sum
return(tar_pro)
type_pre = np.array(tar_pro.keys())[np.array(tar_pro.values())==np.array(max(tar_pro.values()))][0]
return(type_pre)
def Naive_Bayes_predict(self,x,prob=False):#预测函数
y_pre = []
for i in range(x.shape[0]):
y_pre.append (self.predict_line(x[i,:],prob))
return(np.array(y_pre))
最后,我们定义评价函数,以准确度Accuracy为准,该函数包含在模型类中:
def Accuracy_score(self,x,y):
y_pre = self.Naive_Bayes_predict(x)
Accuracy_value = sum(y_pre==y)/float(y.shape[0])
return Accuracy_value
对马疝病数据集的尝试
实例化以后,我们将训练数据集输入模型,设置参数引入拉普帕斯平滑,并且对训练集进行预测(输出概率):
#测试数据示例
model2 = Naive_Bayes_JQ() #实例化模型
model2.Naive_Bayes_fit(horse_bin[:,:21],horse_bin[:,21],lapras=1) #模型训练(采用拉普拉斯平滑)
model2.Naive_Bayes_predict(horse_t_bin[:,:21],prob=True) #输出概率
得到的预测概率如下(部分):
最后,我们评估模型对测试集的预测准确度,发现准确度超过了0.73,可以接受。相比于某些复杂的模型,朴素贝叶斯的性能还有些差距。
系列相关:
本文代码及APP获取方式:下图扫码关注公众号,在公众号中回复 朴素贝叶斯实现 即可~
联系作者:当你对文章有任何疑问和建议可以在公众号直接发消息,我们看到都会回复哒,一起交流数据~