飞道的博客

一个Python例子带你理解朴素贝叶斯算法原理

518人阅读  评论(0)

一个Python例子带你理解朴素贝叶斯算法原理

前言

前面我们在我们学习过KNN和决策树后,我们提出了一个问题,就是我们希望算法能够给我们一个分类概率,并不希望直接告诉我们分类情况。朴素贝叶斯将为我们解决这个问题。

源代码已上传GitHub,有需要的伙伴们自行去取,地址:朴素贝叶斯源代码

朴素贝叶斯原理解析

一、条件概率

概率我们都了解过吧,就是在某一件事情发生的可能性。

那条件概率呢,就是在概率的基础上我给它一个前置条件,举个例子:

现在有二个黑箱子,里面有一些形状大小相同的小球,分布情况如下图所示:

那么,我此时问,从箱子A中随机选一个小球,是红颜色的概率是多少?

很简单,答案是2/3.

也就是P(红|箱子A)=2/3 这里的箱子A 就是前置条件。

同理可得,P(红|箱子B)=1/4

如果我们将两个箱子的球合并在一起,此时不存在什么条件了,那么P(红) 就是一个普通的概率。

二、贝叶斯公式

贝叶斯公式是概率论中一个非常重要而且有用的公式,先看一下公式吧:

​ *P(A|B)=P(B|A)P(A)/P(B) (1)

如何去理解呢?咱们先看下面这个:

​ *P(A∩B) = P(A)P(B|A) (2)

P(A∩B) 表示事件A和事件B同时发生的概率,就等于事件A发生的概率乘以在事件A已发生的条件下,事件B发生的概率。

还是拿盒子小球作为例子,假设事件A为从箱子A中取球,事件B为取到红颜色的球。

那么,P(A∩B) 表示为从箱子A中取到红颜色球的概率

再看等号右边,P(A) 表示从箱子A中取球的概率,这里因为只有两个箱子,且形状大小无异,所以概率值为1/2;P(B|A) 表示从箱子A中取到红色球的概率,值为2/3.

这下大家能够明白上面式子(2)为什么相等了吧。

如果此时我们将A和B变量互换,此时有:

​ *P(B∩A) = P(B)P(A|B)

显然,P(B∩A)=P(A∩B) 均表示事件A和事件B同时发生的概率

所以有:

​ **P(B)P(A|B) = P(A)P(B|A) (3)

公式(3)就被称为贝叶斯公式,它揭示了条件概率中条件与结果的关系,简单的来说,这个公式让我们得以去根据条件去判断最终结果,也就是我们说的分类问题。

三、如何去应用贝叶斯公式?

到第二部分的时候,我会给大家使用源代码进行分析,这里主要是通过文字讲述一下如何应用。

我们要解决的问题是垃圾邮箱的分类问题,通过邮件内容,我们去判断这个邮箱是否是垃圾邮箱。

下图为正常邮箱内容:


这个为垃圾邮箱内容:

那么我们如何去分辨该邮件是否是垃圾邮件呢,听听这个思路

垃圾邮件中总是有一些词汇出现的频率比较高,如果我们针对邮件中的单词建立一个词向量(显示邮件中出现单词的数量),根据训练数据计算不同类型邮箱单词出现的概率,就能够利用这些数据,使用贝叶斯公式去预测邮件的归属问题。

上面不明白没关系,咱们将贝叶斯公式稍微改装一下:

img

图中Ci 表示邮件分类 i=0时,表示邮件是非垃圾邮件 i=1 表示邮件是垃圾邮件

d表示数据,也就是我们将要构建的每个邮件的词向量

词向量,就是用来记录单词出现次数的一个列表


是一个条件概率,表示在邮件类型为i的条件下,数据d的概率(这里理解为各个单词出现的概率)

同样也是一个条件概率,表示在数据d的情况下,邮件的属于i类的概率


表示邮件属于i的概率,就是一个普通的概率问题

好了,基本上我们就是通过这样一个公式来写我们代码,不过,写代码前,我们先说一下流程:

  1. 处理邮件数据,去除掉标点符号,将每个邮箱的单词放入列表中
  2. 统计邮件中所出现过的单词,存储到一个列表中,长度L就是我们所要创建词向量的大小
  3. 对每个邮件创建一个长度为L的列表(称之为词向量),初始值为1,遍历每个邮件的单词列表,统计单词出现次数.
  4. 分别对不同类型的邮件的词向量进行相加,再处理他们各自的单词总数,此时结果为
  5. 再将词向量的各个数相乘,最后再乘以数据集中邮件各个类别的概率

贝叶斯算法源代码解析

一、 加载处理数据

def textParse(text):
    text1 = re.split(r'\W+',text)
    text2 = [textfilter.lower() for textfilter in text1 if len(textfilter)>2]#返回字符长度大于2的字符
    # to do -这里可以添加自定义过滤字符串的规则
    return text2

def loadDataSet():
    fileurl1 = 'datasets/email/ham'
    fileurl2 = 'datasets/email/spam'#垃圾邮件
    postingList=[]
    classVec = []
    fulltext=[]
    for i in range(1,26):
        f1 = open(fileurl1+'/{}.txt'.format(i),errors='ignore')
        text1 = textParse(f1.read())
        postingList.append(text1)
        fulltext.extend(text1)
        classVec.append(1)
        f2 = open(fileurl2+'/{}.txt'.format(i),'r')
        text2 = textParse(f2.read())
        postingList.append(text2)
        fulltext.extend(text2)
        classVec.append(0)
    return postingList,classVec,fulltext

这里一共有两个函数,textParse()和loadDataSet()

textParse()方法用来去筛选掉邮件中的标点符号和长度小于2的单词

loadDataSet() 加载不同文件夹下的txt文件,里面存放着邮件的内容。文件ham表示文件下面的邮件为非垃圾邮件;文件spam表示文件下面的邮件为垃圾邮件。

垃圾邮件和非垃圾邮件各有25个,一共有50个邮件

二、创建词向量

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 is not in my vocabulary:",word)
    return returnVec

vocabList 就是步骤中咱们说到的所有邮件出现的单词列表

inputSet 输入数据,也就是经过咱们处理过后的邮件单词列表

这里主要就一个循环判断语句,用来统计inputSet 输入的数据中,单词出现的次数,以此来构建词向量

三、训练函数

def trainNB0(trainMatrix,trainCategory):
    #print("trainMatrix",trainMatrix)
    numTrainDocs = len(trainMatrix)#共有几个文本
    numWords = len(trainMatrix[0])#共有多少单词
    pAbusive = sum(trainCategory)/float(numTrainDocs)#文本中有多少是侮辱性文档
    p0Num = np.ones(numWords)#创建全为1的数组
    p1Num = np.ones(numWords)#
    p0Denom = 1.0*numWords#
    p1Denom = 1.0*numWords
    for i in range(numTrainDocs):
        print("trainCategory[i]",trainCategory[i])
        if trainCategory[i]==1:#文档的分类
            p1Num+=trainMatrix[i]#计算每个单词出现的次数
            p1Denom+=sum(trainMatrix[i])#统计文档类型属于1的所有文档单词个数
        else:
            p0Num+=trainMatrix[i]
            p0Denom+=sum(trainMatrix[i])
    p1Vect = np.log(p1Num/p1Denom)
    p0Vect = np.log(p0Num/p0Denom)
    return p0Vect,p1Vect,pAbusive

输入数据

trainMatrix :测试数据集的词向量列表

trainCategory:测试数据集的邮件分类情况,垃圾邮件为1 非垃圾邮件为0

处理过程

这里通过一个for循环,根据测试数据的邮件分类情况,对各个类别的词向量进行相加,并统计所有的单词数量。

然后将相加后的向量除以单词总数,计算出单词出现的频率

最后使用log函数进行转化

返回数据:

p0Vect:类型为0的经log函数转化后的词向量频率

p1Vect:类型为1的经log函数转化后的词向量频率

pAbusive:数据集中垃圾邮件的概率

四、分类函数

def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
    p0 = np.sum(vec2Classify*p0Vec)+np.log(pClass1)
    p1 = np.sum(vec2Classify*p1Vec)+np.log(1.0-pClass1)
    if p1>p0:
        return 1
    else:
        return 0

输入数据

vec2Classify 输入数据的词向量

p0Vec、p1Vec、pClass1 均为trainNB0()函数的返回值

处理过程

先拓展一个知识点:


这个是咱们前面提到的一个条件概率,d是词向量。

我们直到,在代码中,词向量是使用列表来表示,但是这里要得出一个数,怎么计算呢

概率论中有一个这样的公式:

当然前提假设,词向量中的各个值之间是相互独立的。

如果这里不明白的,可以去看一下概率论基础补一下

由于前面咱们使用了log函数,所以有

log(axbxcxd) = log(a)+log(b)+log©+log(d)

所以在代码中使用求和进行计算

最后对p0和p1进行概率比较,值较大的就是输入数据的所属类

五、测试函数

def testingNB():
    listOPosts,listClasses,fulltext = loadDataSet()
    # myVocabList = createVocabList(listOPosts)
    myVocabList = list(set(fulltext))
    #print("单词列表:",myVocabList)
    #随机取10个邮件作为测试使用
    #print("listOPosts.length:",len(listOPosts))
    trainsetnum = list(range(50))
    testset = []
    for i in range(10):
        index = int(random.uniform(0,len(trainsetnum)))
        testset.append(trainsetnum[index])
        del(trainsetnum[index])
    print("testset:",testset)
    #重新组装训练数据和测试数据
    traindata = []
    trainclass=[]
    for j in trainsetnum:
        traindata.append(setOfWords2Vec(myVocabList,listOPosts[j]))#这里直接转化为词向量
        trainclass.append(listClasses[j])
    testdata=[]
    testclass=[]
    for k in testset:
        testdata.append(setOfWords2Vec(myVocabList,listOPosts[k]))
        testclass.append(listClasses[k])
    p0V,p1V,pSpam = trainNB0(traindata,trainclass)
    errorcount=0
    for i in range(len(testdata)):
        wordVector = testdata[i]
        print("输出最终分类结果:",classifyNB(wordVector,p0V,p1V,pSpam))
        print("输出原本结果-:",testclass[i])
        if testclass[i]!=classifyNB(wordVector,p0V,p1V,pSpam):
            errorcount+=1
    print(" the error rate is:{}".format(errorcount))

这个函数只是进行测试模型的精准度,我测试了大概10次,一共出现错误没超过5个,也就是相当于正确率在95%以上,还是挺准的。

这里将训练数据为40个邮件,测试数据为10个邮件。

总结

这里咱们仅仅是贝叶斯公式的一个小小应用,我们实验的前提是这些单词之间没有什么联系,都是独立的个体。但是在实际中,有些单词总是会出现在某些单词后面或者前面,比如 am 总是出现在I后面,这些信息咱们没有充分利用到。

代码中,我们使用对数函数来解决下溢出问题(很多非常小的数相乘会失准),使用了将所有词出现的次数初始化为1,解决了一个概率为0总体乘积为0的情况。

总的来说,朴素贝叶斯公式还是一个比较优良的分类器,后面等我们学到更多分类算法后,再于其进行对比。


转载:https://blog.csdn.net/lzx159951/article/details/106211654
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场