您的位置:首页 > 编程语言 > Python开发

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

2017-10-01 18:34 831 查看
下半年要接触两个和文本分析有关的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索引的。"|"是集合
4000
的并运算。这里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.18910570433
negative


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

p1>p0,所以选择1类,negative
反之选择['a',,'boy','sports'],期望输出是positive
测试结果是:

-4.96981329958
-5.55004837471
negative
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 positve

import numpy as np
import math
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
def 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 Vect

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

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'
#testInputVect = input()
testInputVect = [1,1,0,1,0,0,0,0,0]
NaiveBayes(testInputVect)


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

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐