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

作者:数据取经团-JQstyle

原文地址:http://mp.weixin.qq.com/s/OkteiebuHbSuN9HIDSDsQA

朴素贝叶斯的简介

  在机器学习的分类算法的各个门类中,贝叶斯分类方法是一个比较重要的组成部分。而隶属于贝叶斯分类方法的算法,均是以贝叶斯定理为基础的。

  对于一个分类问题,目标是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 np
import 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近邻算法

分享到:更多 ()