【ML学习笔记】朴素贝叶斯算法的demo(机器学习实战例子)

碍于这学期课程的紧迫,现在需要尽快从课本上掌握一些ML算法,我本不想经过danger zone,现在看来却只能尽快进入danger zone,数学理论上的缺陷只能后面找时间弥补了。

如果你在读这篇文章,希望你不要走像我一样的道路,此举实在是出于无奈,尽量不要去做一个心急的程序员,应当分清楚哪些资源是过了学生时期就不再容易获得的。

朴素贝叶斯算法简述

不同于前面说的k-近邻算法,贝叶斯分类器是一种概率分类器,而朴素贝叶斯则是建立在两个前提假设上的:

①特征之间相互独立
②每个特征同等重要

如果要对一些对象分类,如桶里有白球和黑球,随机拿出一个球猜想拿出的是什么球。如果完全没有这个球的其它特征信息,很难判断该猜是什么球,如果通过先前的知识,如先前拿出过100个球,其中60个都是黑色的,又不知道这个球的其它特征,为了减少错误率,肯定会猜这个球是黑球。这种基于标签取各个值的概率的判别方式,就是在用先验概率做判别:
这里写图片描述

而如果知道了一些特征,如白色的球往往更粗糙(粗糙的球是白色的概率比粗糙的球是黑色的概率大),那如果摸到的是粗糙的球,很显然就要判别为白色才会错误率最小了,这种获得了特征以后对标签的判别,就是在用后验概率做判别:
这里写图片描述

如果特征只有粗糙程度这一个,那么很好办,如果有很多特征,如大/小,有缺口/无缺口,重/轻…,这里举的特征都只有布尔型取值即0或者1,也就是在判决之前,能获得n个特征信息:
这里写图片描述

这时候这些特征的取值就可以组成一个向量了:
这里写图片描述

而这时要做判别的方式,也就是特征向量在这样的值下,对象属于哪一个类的概率最高,就判别为哪一类:
这里写图片描述

根据贝叶斯公式,对每个特征W而言,在该特征取值为w时属于类Ci的概率(后验概率)可以这样计算:
这里写图片描述

而对于多个特征信息的情况,在这个特征向量取这样的值的情况下,属于类Ci的概率(后验概率)也是一样的做法:
这里写图片描述

因为最后是要拿后验概率比较大小,找出最大的那个,而对于已经获得的一个特征序列而言,各个类在该特征下的后验概率与上式右边的分母——特征取该序列值的概率没有关系,所以只要去比较分子上的类条件概率先验概率
这里写图片描述

对于先验概率是比较容易求得的,而这里的类条件概率是已知类为Ci的条件下,特征向量取这个序列值的概率。

前面说了,朴素贝叶斯假设特征之间是相互独立的,因此特征的联合概率可以拆开来:
这里写图片描述

从而在朴素贝叶斯假设下,所要比较的类条件概率和类的先验概率乘积可以这样展开:
这里写图片描述

在现实的情况下,毕竟是用样本集中的频率去表征概率,上面的式子中的某个特征的类条件概率有可能算出来是0(因为样本集总是有限的),这样乘起来整个式子就是0,显然不应因为一个特征的值的出现而否决所有特征,所以在实际做的时候,会把每个特征的频数都初始化为1,这样即便后面再也没出现过,也不至于让这个频率变成0;同时显然要把每个类中各个特征出现的总频数初始化为特征的数目n,因为刚刚已经把每个特征的频数都初始化为1了。这是一件事。

另一件事是,即便这些数相乘起来不该得到0了,但是算法是在计算机上跑的,概率总是<1的值,太多很小的数相乘会造成下溢出。为了避免这件事,对概率取对数,因为对数和原来的数在相同的区域内同时增加或者减少,在相同的点取到极值,而我们要做的也仅仅是拿最后的概率去比较大小。对要比较大小的这块(类条件概率和先验概率的乘积)取对数得:
这里写图片描述

也就是说朴素贝叶斯算法要做的事情是:对每个类(标签的每种可能取值)而言,对每个特征在这个类的类条件概率取对数,然后全部加起来,再加上该类的先验概率的对数,对于所有类的这样的数,去比较大小,最大的那个所对应的类就成为朴素贝叶斯判别的那个类。

文本分类demo

问题描述

书上的例子是,训练集给出了一个文档集合和一个标签向量。文档集合不是矩阵,每一行的单词数目都不必一致。

文档集合中的每一行对应了一个文档,也就是说,这一行中有若干个单词,或许是网上的某个论坛下的一条评论中的一些单词。

标签向量还是对应着特征矩阵中的每一行,标签取值0表示那一行的言论是正当言论,标签取值1表示那一行的言论是不正当的。

而要做的事情是,对于给出的一个文档,里面有若干个词,要判别出它是正当言论还是不正当的。

①建立模块和加载数据集的函数

还是建立一个新的python模块,导入必要的包:

#-*-coding:utf-8-*-
from numpy import *
import operator
from matplotlib import pyplot as plt
   
   
  • 1
  • 2
  • 3
  • 4

因为只是demo,用6个样本作为样本集。直接返回文档集合和标签(实际上这不是一个矩阵,每行的单词个数都不必一致)。

#加载数据集(一些实验样本)
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是不正当言辞,0是正常言论
    return postingList,classVec #返回文档集合,和对应于每个文档的标签组成的向量
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这里写图片描述

②创建词表的函数

创建词表的意义是,把训练集中的所有词有序地排在一个列表中,这样就为后面的词向量打下了基础。因为可以在后面用词向量的每个位置上用1或者0来代表词表中这个位置的词有没有在那个文档中出现了。

#创建一个包含输入的所有文档中的词的不重复词表
def createVocabList(dataSet):
    vocabSet=set([]) #先建立一个空集合vocabSet
    #对于数据集中的每个记录(文档集合中的每个词条)
    for document in dataSet:
        #将其打散为词的集合,然后并(|操作符)入这个集合
        vocabSet=vocabSet | set(document)
    return list(vocabSet) #返回的即是每个词出现一次的list
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这里写图片描述

③建立词集模型的函数

建立词集模型,也就是去建立用0/1表示不出现/出现的词向量。设定这个函数是很有用的,因为在训练和使用分类器的时候都要把存了词汇的文档列表转化成和词表相关的词向量,才能去做概率计算。

#[A]词集模型
#判定词汇表中的哪些词出现在文档中(词汇表vocabList,输入文档inputSet)
#输出一个和词汇表等长的0/1向量,为1的位置表示词汇表中那个词在文档中出现了
def setOfWords2Vec(vocabList,inputSet):
    returnVec=[0]*len(vocabList) #先建立一个和词表等长的0向量
    #对于输入文档中的每个词
    for word in inputSet:
        #如果这个词在词汇表中
        if word in vocabList:
            #将0/1向量对应位置的值设置为1
            returnVec[vocabList.index(word)]=1
        else:
            print "词%s不在词表中!"%word #否则要提示出现了新词
    return returnVec #返回这个0/1向量
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

如利用前面的词表,把样本集中的一行行文档转化为一行行词向量:
这里写图片描述
这也就得到了特征矩阵。

④训练分类器的函数

训练分类器总是需要输入训练集的特征矩阵和标签向量,在朴素贝叶斯算法中,训练分类器的目的是得到每个类上每个词的类条件概率(不如把同一类的类条件概率放到一个向量里),即得到类条件概率p(w|c)向量,还要知道各个类的先验概率的值,因为这里是二分类问题,所以只需要知道一个值就行了,这里返回的是1号类的先验概率。

#朴素贝叶斯分类器的训练函数(特征矩阵,标签向量)
#得到的是针对0/1类的每个词的类条件概率p(w|c)向量,和1类先验概率
#而0类的先验概率p(c0)就是1-p(c1)
def TraPsBys(dataMat,labelVec):
    m=len(dataMat) #特征矩阵行数:样本集的样本数目
    n=len(dataMat[0]) #特征矩阵列数:特征数目
    #因为标签只有0/1故这个值是训练集中样本属于第1类的概率
    #即属于不正当言辞的先验概率p(c1)
    pClass1=sum(labelVec)/float(m)
    #现实改进①
    #当计算多个概率的乘积p(w0|c)p(w1|c)...p(wn|c)时
    #如果其中一个概率是0,最后的乘积也是0,为了避免这种影响
    #把所有词的出现次数初始化为1,分母初始化为2
    p0Num=ones(n) #存第0类(正常言辞)的各词出现频数向量
    p1Num=ones(n) #存第1类(不正言辞)的各词出现频数向量
    p0Denom=1.0*n #存词在第0类出现的总频数,初始化为n
    p1Denom=1.0*n #存词在第1类出现的总频数,初始化为n
    #对训练集中的每个记录行,i表示其行号
    for i in range(m):
        #如果是第1类
        if labelVec[i]==1:
            #把这行各个词出现情况加到1号类的向量上
            p1Num+=dataMat[i]
            #把这行词的总数目加到第1类频数上
            p1Denom+=sum(dataMat[i])
        #如果是第0类
        else:
            #把这行各个词出现情况加到0号类的向量上
            p0Num+=dataMat[i]
            #把这行词的总数目加到第0类频数上
            p0Denom+=sum(dataMat[i])
    #求特征的某值w出现于c类的类条件概率向量,即
    #P(w|c)=值w在c类中出现次数/各可能值在c类中出现总次数
    p0Vect=p0Num/p0Denom #第0类的各词,类条件概率密度=本词出现次数/总次数
    p1Vect=p1Num/p1Denom #第1类的各词,类条件概率密度=本词出现次数/总次数
    #现实改进②
    #太多很小的数相乘会下溢出,取对数来避免这种情况
    #对数和原来的数在相同的区域同时增减,而且在相同的点取极值
    #但要注意,原来数的相乘,就是取过对数数的相加
    p0Vect=log(p0Vect)
    p1Vect=log(p1Vect)
    #返回针对0/1类的每个词的类条件概率p(w|c)向量,和1类先验概率p(c1)
    return p0Vect,p1Vect,pClass1
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

如传入刚才的特征矩阵和标签向量,这里得到的类条件概率向量是取过对数的了。而先验概率还没有取对数,一方面是方便我们观察一下先验概率的取值;另一方面,要根据这个先验概率去计算另一类的先验概率,这样暂时不取对数还是方便一些。
这里写图片描述
属于1号类的先验概率:
这里写图片描述

⑤做分类用的函数

这个函数能够对输入的词向量,用训练好的参数(类条件概率向量和先验概率),给出预测的结果值。

具体的做法就是,对于这两个类(1号和0号类),分别把词向量表示的出现的各个特征取对数后的值全加起来,再加上这一类的先验概率取对数后的值。

对于这两个类都这样做,最后比较大小,哪个大就输出哪个类的标号即可。

#朴素贝叶斯分类函数(要分类的词向量,0/1类条件概率向量,1类先验概率)
def CsfPsBys(vec2Classify,p0Vec,p1Vec,pClass1):
    #因为0/1类条件概率向量在TraPsBys里取过对数了
    #所以类的条件概率在这里也要取一下对数
    p1=sum(vec2Classify*p1Vec)+log(pClass1)
    #同样求后验概率,第0类的先验概率p(c0)就是1-p(c1),因为是二分类问题
    p0=sum(vec2Classify*p0Vec)+log(1.0-pClass1)
    #用这两个求出来的描述后验概率的值做判别
    if p1>p0:
        return 1
    else:
        return 0
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

比如建立一个文档来测试一下:
这里写图片描述
预测为1,即预测为不正当言论。

⑥测试用的函数

一个封装好的便利函数,可以用来理解整个分类器的使用流程。

#测试这个demo,封装了所有操作的函数可称为'便利函数'
def TstPsBys():
    postingList,classVec=loadDataSet() #加载文档集合和标签向量
    vocabList=createVocabList(postingList) #用文档集创建不重复词表
    #以下要建立训练集,给出的文档集合需要转化成和词表相关的0/1向量集
    trainMat=[] #训练集初始化为空
    #对于文档集合中的每一行的文档postInDoc
    for postInDoc in postingList:
        #把这个文档用不重复词表vocabList转换成存在性的0/1向量
        #然后把这个表示词表上每个位置词出现情况的向量加入训练集
        #显然这个算法里丢失了文档中词汇的顺序信息!
        trainMat.append(setOfWords2Vec(vocabList,postInDoc))
    #用{有关词表的0/1式训练集,标签向量}做训练
    #得到p(w|c0)向量,p(w|c1)向量,p(c1)
    #即得到了类条件概率向量和先验概率,即贝叶斯公式的分子
    p0V,p1V,pC1=TraPsBys(trainMat,classVec)
    #测试①
    testEntry=['love','my','dalmation'] #要分类的词向量
    #转化为与词表等长的0/1式存在性向量
    thisDoc=array(setOfWords2Vec(vocabList,testEntry))
    #分类并输出结果
    print testEntry,"分类为:",CsfPsBys(thisDoc,p0V,p1V,pC1)
    #测试②
    testEntry=['stupid','garbage']
    thisDoc=array(setOfWords2Vec(vocabList,testEntry))
    print testEntry,"分类为:",CsfPsBys(thisDoc,p0V,p1V,pC1)
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

这里写图片描述

⑦建立词袋模型的函数

和建立词集模型的函数功能上是类似的。因为朴素贝叶斯分类器有两种实现方式,一种是基于伯努利模型实现,一种是基于多项式模型实现。前者不考虑词在文档中出现的次数,将没歌词的出现与否作为一个特征,称为词集模型;后者需要考虑词在文档中出现的次数,这也就包含了更多的信息,称为词袋模型

#[B]词袋模型
#判定词汇表中的哪些词出现在文档中(词汇表vocabList,输入文档inputSet)
#输出一个和词汇表等长的0/k向量,非0的位置表示词汇表中那个词在文档中出现了k次
def bagOfWords2Vec(vocabList,inputSet):
    returnVec=[0]*len(vocabList) #先建立一个和词表等长的0向量
    #对于输入文档中的每个词
    for word in inputSet:
        #如果这个词在词汇表中
        if word in vocabList:
            #将0/k向量对应位置的值加上1
            returnVec[vocabList.index(word)]+=1
        else:
            print "词%s不在词表中!"%word #否则要提示出现了新词
    return returnVec #返回这个0/k向量
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这里写图片描述

猜你喜欢

转载自blog.csdn.net/Mays_day/article/details/79187793