查看原文
其他

自我代码提升之朴素贝叶斯

2017-08-17 JQstyle 数据取经团


作者:数据取经团 - 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,可以接受。相比于某些复杂的模型,朴素贝叶斯的性能还有些差距。

系列相关:

1.自我代码提升之逻辑回归

2.自我代码提升之K近邻算法



本文代码及APP获取方式:下图扫码关注公众号,在公众号中回复 朴素贝叶斯实现 即可~


联系作者:当你对文章有任何疑问和建议可以在公众号直接发消息,我们看到都会回复哒,一起交流数据~








您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存