机器学习实战:朴素贝叶斯

机器学习实战

 

第4章     基于概率的分类方法:朴素贝叶斯


  • 基于贝叶斯的决策方法

优点: 在数据较少的情况下仍然有效,可以处理多类别问题。

缺点: 对于输入数据的准备方式较为敏感。 

使用数据类型:标称型数据

 


  •  算法流程

计算每个类别的数量

       对每篇训练文档:

              判断所属类别

              在所属类别中:

                     增加出现了的词条的计数

                     增加该类别总词条计数

对每个类别:

       对每个词条:

               用该词条除以该类别总词条数得到条件概率

返回条件概率

      

 


  • 准备数据

from numpy import *

#创建训练数据集
#在列表postingList中,每一个子列表表示一个文档
#classVec是由与文档对应的标签组成的向量
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'],
                 ['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

#创建一个所有文档中出现的所有词构成的去重列表
def createVocabList(dataSet):
    vocabSet = set([])  #create empty set
    for document in dataSet:
        vocabSet = vocabSet | set(document) #union of the two sets
    return list(vocabSet)

#用一个向量来表示一个文档
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else: print "the word: %s is not in my Vocabulary!" % word
    return returnVec

#词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec

 


 

  • 训练分类器

对于待分类的文档来说,已知其转化成向量后的形式。

我们现在要计算的,实际上是该文档向量和各类别标签的联合概率

而无论标签有多少种取值,文档向量的表示是统一的,即 p(w) 是一个向量的通用属性。

因此,实际上分类的结果,既可以看作是条件概率p(c1|w)和p(c0|w)之间的比较结果,也可以看作是联合概率p(c1,w)和p(c0,w)的比较结果。

def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    #标签向量中1的个数,除以文档的总数,得到训练集中【类别为1的概率】
    pAbusive = sum(trainCategory)/float(numTrainDocs)
    #p0Num和P1Num是两个向量,用来保存【所有同类文档的词分布的累加】
    p0Num = ones(numWords); p1Num = ones(numWords)       
    #P0Denom和p1Denom用来保存【所有同类文档的词总数量】
    p0Denom = 2.0; p1Denom = 2.0    
    #文档总数和标签总数一一对应                    
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            #假设类别1的文档共有5个
            #分别是[1,0,1,1,1],[1,0,0,0,1],[1,1,0,0,1],[1,0,0,0,1],[0,1,0,1,1]
            #循环结束后,p1Num的值最终为[4,2,1,2,5]
            #这个结果表示:类别1的训练文档中,各个单词出现的次数分别为4,2,1,2,5次
            p1Num += trainMatrix[i]
            #同理,使用上面的例子,循环结束后,这里得到的应该是初始化的2,与4+2+1+2+5=14的和
            #最终等于16,这个结果的意义是:类别1的文档中,总共出现了16个单词
            p1Denom += sum(trainMatrix[i])
        else:
            #在当标签等于类别0时的计算过程,与标签等于1时同理
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    #p1Vect和p0Vect计算出来的,实际上是两个【保存条件概率的向量】
    #仍然使用上面的例子,p1Num==[4,2,1,2,5],p1Denom==16
    #为了方便表示,这里不使用改进后的取对数的概率值
    #通过计算,得到p1Vect==[0.25,0.125,0.0625,0.125,0.3125]
    #这个结果的意义是:【在类别1中,各个单词出现的概率分别为0.25,0.125,0.0625,0.125,0.3125】
    #用符号表示,就是p(w|c1),等价于p(w1,w2,w3,w4,w5|c1)
    #根据朴素贝叶斯假设,所有单词相互独立
    #因此p(w1,w2,w3,w4,w5|c1)可以等价为p(w1|c1)p(w2|c1)p(w3|c1)p(w4|c1)p(w5|c1)
    p1Vect = log(p1Num/p1Denom)          
    p0Vect = log(p0Num/p0Denom)
    #返回两个保存着条件概率的向量,和一个保存类别1文档概率的值          
    return p0Vect,p1Vect,pAbusive

  • 使用分类器

这个分类的逻辑是这样的:

首先要明确原始的计算公式是:p(c_{i}|w)=\frac{p(w|c_{i})p(c_{i})}{p(w)}

而p(w)这一项,是由待分类的文档中每一个词,在单词表中出现的概率组成的向量

在代码中并不需要计算这一步,因为它并不涉及到某个标签,单纯指的是各个词出现的概率,而不是在类别1或类别0中出现的概率。

因此对于每个p(ci|w)来说,要计算它们所需要使用的p(w)实际是相同的,所以省略掉这一项,完全不会改变相对之间的大小关系,同时如上所述,也可以理解为等式两边同乘p(w),我们比较的对象就转移到了联合概率p(ci,w)上。

综上所述,我们在代码中的实现,实际上是计算p(ci,w)的过程。

即是计算 :p(c_{i},w)=p(w|c_{i})p(c_{i})

而w是一个向量,实际上也可以表示成:p(c_{i},w)=p(w_{0},w_{1},w_{2}...w_{n}|c_{i})p(c_{i})

根据朴素贝叶斯的假设,特征之间相互独立,则还可以进一步拆分成为:p(c_{i},w)=p(w_{0}|c_{i})p(w_{1}|c_{i})...p(w_{n}|c_{i})p(c_{i})

这个形式是存在缺陷的,一方面某个概率为零会导致乘积为零,这显然是不符合实际的。另一个方面是多个小概率相乘,会存在下溢出的问题。因此将上面公式的两边同时取对数。

p_{i} 来表示 \log p(c _{i},w)

而右边则变成了\sum_{j=0}^{n}p(w_{j}|c_{i})+p(c_{i})

就得到了下面的代码。

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    #在这里,vec2Classify指示着每个单词出现与否
    #p1Vec和p0Vec指示着在训练文档集中,每个类别下,每个单词的出现频率
    #两者对应元素相乘,得到的结果表示,测试文档中每个单词,在单词表中出现的频率
    #p1和p0的计算,是基于概率的乘法公式
    #p(c1|w)=p(w|c1)p(c1)/p(w)
    #这个p(w)和上面函数计算的p(w|c1)和p(w|c0)是不同的
    #它并没有依赖于标签是0还是1,而是表示,测试文档中,每个词在单词表中所出现的频率
    #因此对于p(c1|w)和p(c0|w),同乘一个相等的p(w),对彼此间的大小关系没有影响
    #下面的两行代码,实际上计算的是p(c1,w)和p(c0,w)
    #在不使用改进的对数条件概率时
    #p1=vec2Classify*p1Vec*pClass1
    #p0=vec2Classify*p0Vec*pClass0
    #而pClass1和pClass0是从训练文档集中得到的信息
    #由此,就得到了,测试文档w属于类别1或类别0的概率
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)    
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else: 
        return 0

 


  • 测试代码

在测试代码中可以看到,setOfWords2Vec函数分别处理了训练文档中的每一个子文档,并且计算出了三个要使用的先验概率,通过对测试文档的加工,实际上等价于得到了第四个已知的先验概率。基于此,通过条件概率公式就获得了我们想要的结果。

def testingNB():
    listOPosts,listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts)
    trainMat=[]
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses))
    testEntry = ['love', 'my', 'dalmation']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb)
    testEntry = ['stupid', 'garbage']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb)

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值