基于Python的朴素贝叶斯算法实现

来源:互联网 发布:域名后缀为 .qq.com 编辑:程序博客网 时间:2024/05/01 12:21

本文主要介绍了贝叶斯算法的基本原理以及基于Python的朴素贝叶斯分类算法的实现。

  • 贝叶斯定理
    我们先从理论方面来讲讲著名的贝叶斯定理

    • 条件概率
      条件概率(又称后验概率)就是事件A在另外一个事件B已经发生条件下的发生概率。条件概率表示为P(A|B),读作“在B条件下A的概率”。
      比如,在同一个样本空间 Ω中的事件或者子集A与B,如果随机从Ω中选出的一个元素属于B,那么这个随机选择的元素还属于A的概率就定义为在B的前提下A的条件概率,所以:P(A|B)=|AB||B|,接着分子、分母都除以Ω得到:
      P(A|B)P(AB)P(B)
    • 联合概率
      联合概率表示两个事件共同发生的概率,如:A与B的联合概率表示为P(AB)或者P(A,B)
    • 边缘概率(先验概率)
      边缘概率是某个事件发生的概率。在联合概率中,把最终结果中那些不需要的事件通过合并成它们的全概率,而消去它们(对离散随机变量用求和得全概率,对连续随机变量用积分得全概率),这称为边缘化(marginalization),比如A的边缘概率表示为P(A),B的边缘概率表示为P(B)
    • 贝叶斯公式
      首先,在事件B发生之前,我们对事件A的发生有一个基本的概率判断,称为A的先验概率,用P(A)表示;同理,事件B的先验概率可用P(B)表示。
      在事件B发生之后,我们对事件A的发生概率重新评估,称为A的后验概率,用P(A|B)表示;同理,事件B的后验概率为P(B|A)
      根据条件概率的定义,在事件B发生的条件下事件A发生的概率是:
      P(A|B)=P(AB)P(B)

      同理,事件A发生的条件下时间B发生的概率是:
      P(B|A)=P(AB)P(A)

      整理与合并上述两条等式,便可以得到:
      P(A|B)P(B)=P(AB)=P(B|A)P(A)

      P(B)非零,我们便可以得到贝叶斯定理的表达式:
      P(A|B)=P(A)P(B|A)P(B)

    应用:如P(A)=0.5表示的是酒后驾驶的概率为0.5P(B)=0.1表示的是交警抽查的概率(但要注意的是,条件A和条件B是相互独立的),那么P(B|A)=P(A)×P(B)=0.05则表示酒后驾驶被交警抽查到的概率为0.05。那么问题来了,已知一老司机被交警抽查到了,那么我们就可以用贝叶斯定理求出他酒后驾驶的概率(P(A|B))是多少了。

  • 朴素贝叶斯分类
    目前最为广泛的两种分类模型是决策树模型(Decision Tree Model)和朴素贝叶斯模型(Naive Bayesian Model,NBM)。而朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法。朴素贝叶斯分类器基于一个“朴素”的假定:给定目标值时属性之间相互条件独立。基于这个假定和贝叶斯定理,我们就可以在P(A|B)P(B|A)间进行转换。
    就文本分类而言,它认为每个词袋中的两两词之间的关系是相互独立的,因此得到了朴素贝叶斯的定义:

    1. x={a1,a2,,am}为一个待分类项,而每个ax的一个特征属性。
    2. 有类别集合 C={y1,y2,,yn}
    3. 计算P(y1|x),P(y2|x),,P(yn|x)
    4. 如果P(yk|x)=max{P(y1|x),P(y2|x),,P(yn|x)},则xyk

    那么问题来了,关键的第3步如何进行计算?
    (1) 找到一个已知分类的待分类项集合(也就是训练集);
    (2) 统计得到在各个类别下各个特征属性的条件概率估计,即:

    P(a1|y1),P(x2|y1),,P(am|y1);P(a1|y2),P(x2|y2),,P(am|y2);P(a1|yn),P(x2|yn),,P(am|yn);

    (3)如果各个特征属性是条件独立的(或者假设它们之间是相互独立的),则根据贝叶斯定理有如下推导:
    P(yi|x)=P(x|yi)P(yi)P(x)

    因为分母对于所有类别为常数,只要将分子最大化皆可。又因为诶各特征属性是条件独立的,所以有:
    P(x|yi)P(yi)=P(a1|y1)P(a2|y2)P(am|yi)P(yi)=P(yi)j=1mP(aj|yi)

    根据上述分析,朴素贝叶斯分类的流程可以表示如下:

    1. 训练数据产生训练样本集:TF-IDF;
    2. 对每个类别计算P(yi)
    3. 对每个特征属性计算所有划分的条件概率;
    4. 对每个类别计算P(x|yi)P(yi)
    5. P(x|yi)P(yi)的最大项作为x的所属类别。
      所谓的TF-IDF就是词频逆文档频率,如果某个词或短语在一篇文章中出现的频率高,并且在其他文章中很少出现,则认为此词或短语具有很好的类别区分能力,适合用来分类。
      • 词频(Term Frequency, TF)指的是某一个给定的词语在该文档中出现的频率。这个数字是对词数(Term Count)的归一化,以防止它偏向长的文件。对于在某一特定文件里的词语来说,它的重要性可表示为:
        TFij=ni,jknk,j

        其中,ni,j是该词在文件中出现的次数,knk,j是文件中所有字词的出现次数之和。
      • 逆向文件频率(Inverse Document Frequency, IDF)是一个词语普遍重要性的度量。某一特定词语的IDF,可以由总文件数目除以包含该词语的文件的数目,再将得到的商取对数得到:
        IDFi=logDj:tidj

        其中,D表示语料库中的文件总数;j包含词语的文件数目(如果该词语不在语料库中,就会导致分母为零,因此一般情况下使用1+dD:td作为分母)。
      • TF-IDF权重策略就是计算TF与IDF的乘积,某一特定文件内的高词语频率,以及该词语在整个文件集合中的低文件频率(TFIDFij=TFijI×IDFij),可以产生高权重的TF-IDF。因此,TF-IDF倾向于过滤掉常见的词语,保持重要的词语。
  • 代码实现

    • 创建简单的英文语料作为数据集
      def loadDataSet():    postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],\        ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],\        ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him','my'],\        ['stop', 'posting', 'stupid', 'worthless', 'garbage']\        ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],\        ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]    classVec = [0,1,0,1,0,1]    #1 is abusive, 0 not     return postingList,classVec

    postingList是训练集文本,classVec是每个文本对应的分类。

    • 编写一个贝叶斯算法类,创建默认的构造方法

      class NBayes(object):  def __init__(self):      self.vocabulary = [] # 词典      self.idf=0           # 词典的idf权值向量      self.tf=0            # 训练集的权值矩阵      self.tdm=0           # P(x|yi)      self.Pcates = {}     # P(yi)--是个类别字典      self.labels=[]       # 对应每个文本的分类,是个外部导入的列表          self.doclength = 0   # 训练集文本      self.vocablen = 0    # 词典词长      self.testset = 0     # 测试集 
    • 导入训练集,生成算法参数和数据结构

      def train_set(self,trainset,classVec):    self.cate_prob(classVec)   # 计算每个分类在数据集中的概率:P(yi)     self.doclength = len(trainset)    tempset = set()    [tempset.add(word) for doc in trainset for word in doc ] # 生成词典    self.vocabulary = list(tempset)    self.vocablen = len(self.vocabulary)    self.calc_wordfreq(trainset)  # 计算词频数据集    self.build_tdm()            # 按分类累计向量空间的每维值:P(x|yi)
    • 计算在数据集中每个分类的高铝P(yi)的cate_prob函数

      def cate_prob(self,classVec):    self.labels = classVec    labeltemps = set(self.labels) # 获取全部分类    for labeltemp in labeltemps:        # 统计列表中重复的分类:        self.labels.count(labeltemp)        self.Pcates[labeltemp] = float(self.labels.count(labeltemp))/float(len(self.labels)) 
    • 生成普通的词频向量的calc_wordfreq函数

      # 生成普通的词频向量def calc_wordfreq(self,trainset):    self.idf = np.zeros([1,self.vocablen]) # 1*词典数    self.tf = np.zeros([self.doclength,self.vocablen]) # 训练集文件数*词典数    for indx in xrange(self.doclength):    # 遍历所有的文本        for word in trainset[indx]:          # 遍历文本中的每个词              self.tf[indx,self.vocabulary.index(word)] +=1            # 找到文本的词在字典中的位置+1        for signleword in set(trainset[indx]):            self.idf[0,self.vocabulary.index(signleword)] +=1 
    • 按分类累计计算向量空间的每维值P(x|yi)的build_tdm函数

      #按分类累计向量空间的每维值:P(x|yi)def build_tdm(self):     self.tdm = np.zeros([len(self.Pcates),self.vocablen]) #类别行*词典列     sumlist = np.zeros([len(self.Pcates),1])  # 统计每个分类的总值     for indx in xrange(self.doclength):         # 将同一类别的词向量空间值加总         self.tdm[self.labels[indx]] += self.tf[indx]           # 统计每个分类的总值--是个标量         sumlist[self.labels[indx]]= np.sum(self.tdm[self.labels[indx]])    self.tdm = self.tdm/sumlist  # 生成P(x|yi)
    • 将测试集映射到当前词典的map2vocab函数

      def map2vocab(self,testdata):    self.testset = np.zeros([1,self.vocablen])    for word in testdata:        self.testset[0,self.vocabulary.index(word)] +=1
    • 预测分类结果,输出预测的分类类别的predict函数

      def predict(self,testset):    if np.shape(testset)[1] != self.vocablen: # 如果测试集长度与词典不相等,退出程序        print "输入错误"        exit(0)    predvalue = 0    # 初始化类别概率    predclass = ""    # 初始化类别名称    for tdm_vect,keyclass in zip(self.tdm,self.Pcates):    # P(x|yi) P(yi)        # 变量tdm,计算最大分类值        temp = np.sum(testset*tdm_vect*self.Pcates[keyclass])        if temp > predvalue:            predvalue = temp            predclass = keyclass    return predclass
    • 使用TF-IDF策略改进算法

      # 以生成 tf-idf方式生成向量空间的calc_tfidf函数def calc_tfidf(self,trainset):     self.idf = np.zeros([1,self.vocablen])     self.tf = np.zeros([self.doclength,self.vocablen])     for indx in xrange(self.doclength):         for word in trainset[indx]:             self.tf[indx,self.vocabulary.index(word)] +=1   # 消除不同句长导致的偏差     self.tf[indx] = self.tf[indx]/float(len(trainset[indx]))     for signleword in set(trainset[indx]):         self.idf[0,self.vocabulary.index(signleword)] +=1     self.idf = np.log(float(self.doclength)/self.idf)     self.tf = np.multiply(self.tf,self.idf) # 矩阵与向量的点乘 tf x idf
    • 分类结果

      # -*- coding: utf-8 -*-import sysimport osfrom numpy import *import numpy as npfrom Nbayes_lib import *dataSet,listClasses = loadDataSet()  # 导入外部数据集# dataset: 句子的词向量# listClass是句子所属的类别 [0,1,0,1,0,1]nb = NBayes()                        # 实例化nb.train_set(dataSet,listClasses)    # 训练数据集nb.map2vocab(dataSet[0])           # 随机选择一个测试句print nb.predict(nb.testset)           # 输出分类结果

    分类结果为:

    1
1 0