<基础原理进阶>机器学习算法python实现【3】--文本分析之朴素贝叶斯分类器

来源:互联网 发布:mac 照片很大 找不到 编辑:程序博客网 时间:2024/06/05 06:50

下半年要接触两个和文本分析有关的project,于是先从简单的东西开始做起~

统计学里有两大学派:贝叶斯学派和频率学派。贝叶斯学派最大的特点是认为参数是随机的,并引入先验知识和逻辑推理去找到它;而频率学派认为参数是一个我们不知道的确定值,需要通过统计学过程求解。最著名的bayes公式就是:

P(C|w)= [P(w|C)*P(C)] / P(w)

本质上是一个条件概率公式,引入了prior knowledge P(S),所以求解左边的就等价于求解右边的式子。朴素贝叶斯即基于这样的一个公式。

基本思想如下:

我们要解决一个离散空间的分类问题,假设点是(x,y),二分类C0,C1。

利用上面的公式分别求得该点隶属于0,1类别的概率P0,P1

P0>P1, 属于C0

P0<P1, 属于C1

所以接下来要着重看如何求得它分别属于0,1两类的概率。我们的主题是“文本分析”,所以接下来会沿着这个思路走。


Step0: 拿到文本数据

假设我们拿到的一大堆训练文本都是独立词条的组合,如下面的代码段所示(当然可以混入词语,句子,url什么的....总之是经过分割的一个好文档),并且给了一个类别向量,对应每一份文本的类别

当然实际操作中要自己动手获得这样的好数据(笑)

def loadDataSet():    postingList = [['a','good','boy','nice','handsome'],                   ['a','bad','girl','smoke','sports'],                   ['a','handsome','boy','smoke','sports']                  ]    classVec = [0,1,1]    return postingList, classVec


Step1: 建立字典

通过一个函数建立一个列表,不重复地收录所有文档里所有独立分割好的元素,叫做vocabList

def createVocabList(dataSet):    vocabList = set([])    for document in dataSet:        vocabList = vocabList | set(document)    print  list(vocabList)    return list(vocabList)

注意返回类型转换成list,因为set函数是不支持index索引的。"|"是集合的并运算。这里print一下去看看vocabList章什么样子,实际上它长这样:

['a', 'boy', 'good', 'sports', 'bad', 'smoke', 'girl', 'handsome', 'nice']


Step2: 文档转化成词向量

每一个文档都是由若干词组成的,这些词都含在vocabList里面。所以要把文本转化成向量形式:初始化每一个词向量长度等于vocabList,,所有元素都是零。如果vocabList里的词在这个文档中有,则对应位置标为1。比如Step0里面的三篇文档转化成词向量就是:

[1, 1, 1, 0, 0, 0, 0, 1, 1] [1, 0, 0, 1, 1, 1, 1, 0, 0] [1, 1, 0, 1, 0, 1, 0, 1, 0]
对应着vocabList去还原,果然没错吧!

def word2vec(vocabList,inputSet):    Vect = [0]*len(vocabList)    for word in inputSet:        if word in vocabList:            Vect[vocabList.index(word)] = 1        else:            print '%s is not in vocabList!' %word    return Vect


Step3: 概率计算(重点)

bayes重头戏是处理概率的事情。回到我们的bayes公式,w代表着词向量,C代表类别(c0, c1)。根据MAP估计我们只需要关注分子部分,即P(w|C)*P(C)[MAP:maximum a posterior]

P(C)只需要将该类文档数量除以文档总数。

假定词语间是条件独立的(naive bayes assumption),那么可以将条件概率展开:

P(w|C)= P(w1*w2*...*wk|C)

= P(w1|C)P(w2|C,w1)P(w3|C,w1,w2)......

= P(w1|C)P(w2|C).....P(wk|C)

P(wk|C)的含义就是,在属于该类时这个词出现的概率。比如我们有两个词向量分别属于0,1类:

[1,0,0,1], [0,1,1,1]

C=C0时,对应的P(w|C)为:[0.5,0,0,0.5]    = [p(w1|c0),p(w2|c0),p(w3|c0),p(w4|c0)]

C=C1时,   对应的P(w|C)为:[0,0.3,0.3,0.3] = [p(w1|c1),p(w2|c1),p(w3|c1),p(w4|c1)]

至此我们推出了一切必要的数学公式,只需要代码实现即可。思路如下:

遍历已转化成词向量的文档:

对每个文档(词向量):

属于C0类:

该词对应的数量++1(注意代码是如何实现的!)【1】

C0类词数量++词向量里实际出现的词数量(有多少个1就有多少实际出现的单词)【2】

属于C1类:同理

之后用【1】/【2】就得到p(w|c0), p(w|c1) (注意代码是如何实现的!)

def trainNB(trainMatrix, trainCategory):    trainDoc_Num = len(trainMatrix)    words_Num = len(trainMatrix[0])    p_1 = sum(trainCategory)/float(trainDoc_Num)  #P(C1)    p_0_Num = np.ones(words_Num); p_1_Num = np.ones(words_Num)    p_0_Denom = 2.0; p_1_Denom = 2.0    for i in range(trainDoc_Num):        if trainCategory[i] == 1:            p_1_Num   += trainMatrix[i]            p_1_Denom += sum(trainMatrix[i])        else:            p_0_Num   += trainMatrix[i]            p_0_Denom += sum(trainMatrix[i])    p_0_Vect = np.log((p_0_Num)/p_0_Denom)    p_1_Vect = np.log((p_1_Num)/p_1_Denom)    return p_1, p_1_Vect, p_0_Vect

上面橙色部分标注的地方实现思路如下

假设从开始时前两次遍历到了c0类的两个词向量[1,0,0,1],[0,1,1,1]:

Initial: p_0_Num=[0,0,0,0], denom=0

1st: p_0_Num=[0,0,0,0]+[1,0,0,1]=[1,0,0,1], denom=2

2nd: p_0_Num =[1,0,0,1] +[0,1,1,1] =[1,1,1,2], denom=2+3=5

p_0_Vect=[1,1,1,2]/denom=[0.2,0.2,0.2,0.4],即为p(w|c0)列表。

注意这里是理想情况,在代码里初始化时都是1元素,分母denom初始化成2:这是laplace平滑,防止出现0/0的分式。因为可能有某一个p(wi|c)=0造成连乘为0,所以索性让他们都先出现一次。

注意p_0_Vect,p_1_Vect在初始化时就是一个numpy数组,之后才能做整体的除法。list类型是做不了的。

注意最后取了对数,虽然数值不同但和原数代表相同的含义,增减性也相同,这么做是防止出现一大堆小数连乘导致最后被四舍五入为零。


Step4: 最后处理

用classifyNB函数实现我们最开始说的根据概率大小决定类别的功能。注意这里将分子整体取对数把连乘化作累加,而p_0_Vect, p_1_Vect本来就是取过对数的,所以把p(c)取对数就OK。NaiveBayes起到封装前面各个函数的功能,输入参数是外界传来的一个词向量(文档),输出是这个词向量(文档)的类别。

def classifyNB(inputVect,p_1_Vect, p_0_Vect, p_1):    P1 = sum(inputVect*p_1_Vect) + math.log(p_1)    P0 = sum(inputVect*p_0_Vect) + math.log(1-p_1)    print P1,'\n', P0    if P1 > P0:        return 1    else:        return 0

def NaiveBayes(InputVect):    dataSet, classVec = loadDataSet()    vocabList = createVocabList(dataSet)    trainMatrix = []    for i_th_doc in dataSet:        trainMatrix.append(word2vec(vocabList,i_th_doc))    print trainMatrix    p_1, p_1V, p_0V = trainNB(trainMatrix, classVec)    testResult = classifyNB(InputVect, p_1V, p_0V, p_1)    if testResult == 1:        print 'negative'    else:        print 'positive'

Step5: 测试

假定这里的二分类是以一个人是否抽烟为指标的健康与否(只要抽烟就不健康),前面的三个test文档很好地说明了这一点。

我们给一个测试样例:['a','girl','bad','smoke'],手工转换成词向量,期望输出是negative

testInputVect = [1,0,0,0,1,1,1,0,0]NaiveBayes(testInputVect)

测试结果是:

-6.7615727688 -8.18910570433negative

上面输出的是转换成log之后的p1,p0概率,1代表坏。因为都在[0,1]所以转换后都是负数。

p1>p0,所以选择1类,negative

反之选择['a',,'boy','sports'],期望输出是positive

测试结果是:

-4.96981329958 -5.55004837471negative
hhh失败了,原因也很明显:我们给的测试用例不够好,'a','boy','sports'在negative样本里也出现过,所以它区分得不够好。但可喜的是两类的概率要比之前接近了,说明我们的方法还是对的,撤销了'smoke'这个在两个negative样本中都出现的词以后,被判定为negative的概率大大降低(数值上为提高,因为是负数)。期待之后拿真实样例做检测时可以正确~


所以走完这一遍我们发现朴素贝叶斯实际上没有显式的训练过程,和KNN一样,只需要计算一次就好了(KNN是距离,贝叶斯是概率p(w|c0),p(w|c1))

全部代码如下:

#NaiveBayes by SkyOrca, UCAS# coding=utf-8#p_1 is for negative, p_0 is for positveimport numpy as npimport mathdef loadDataSet():    postingList = [['a','good','boy','nice','handsome'],                   ['a','bad','girl','smoke','sports'],                   ['a','handsome','boy','smoke','sports']                  ]    classVec = [0,1,1]    return postingList, classVecdef createVocabList(dataSet):    vocabList = set([])    for document in dataSet:        vocabList = vocabList | set(document)#   print  list(vocabList)    return list(vocabList)def word2vec(vocabList,inputSet):    Vect = [0]*len(vocabList)    for word in inputSet:        if word in vocabList:            Vect[vocabList.index(word)] = 1        else:            print '%s is not in vocabList!' %word    return Vectdef trainNB(trainMatrix, trainCategory):    trainDoc_Num = len(trainMatrix)    words_Num = len(trainMatrix[0])    p_1 = sum(trainCategory)/float(trainDoc_Num)  #P(C1)    p_0_Num = np.ones(words_Num); p_1_Num = np.ones(words_Num)    p_0_Denom = 2.0; p_1_Denom = 2.0    for i in range(trainDoc_Num):        if trainCategory[i] == 1:            p_1_Num   += trainMatrix[i]            p_1_Denom += sum(trainMatrix[i])        else:            p_0_Num   += trainMatrix[i]            p_0_Denom += sum(trainMatrix[i])    p_0_Vect = np.log((p_0_Num)/p_0_Denom)    p_1_Vect = np.log((p_1_Num)/p_1_Denom)    return p_1, p_1_Vect, p_0_Vectdef classifyNB(inputVect,p_1_Vect, p_0_Vect, p_1):    P1 = sum(inputVect*p_1_Vect) + math.log(p_1)    P0 = sum(inputVect*p_0_Vect) + math.log(1-p_1)#   print P1,'\n', P0    if P1 > P0:        return 1    else:        return 0def NaiveBayes(InputVect):    dataSet, classVec = loadDataSet()    vocabList = createVocabList(dataSet)    trainMatrix = []    for i_th_doc in dataSet:        trainMatrix.append(word2vec(vocabList,i_th_doc))    print trainMatrix    p_1, p_1V, p_0V = trainNB(trainMatrix, classVec)    testResult = classifyNB(InputVect, p_1V, p_0V, p_1)    if testResult == 1:        print 'negative'    else:        print 'positive'#testInputVect = input()testInputVect = [1,1,0,1,0,0,0,0,0]NaiveBayes(testInputVect)

本篇先到这里,下次再见~~

 







阅读全文
0 0