<基础原理进阶>机器学习算法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什么的....总之是经过分割的一个好文档),并且给了一个类别向量,对应每一份文本的类别
当然实际操作中要自己动手获得这样的好数据(笑)
Step1: 建立字典
通过一个函数建立一个列表,不重复地收录所有文档里所有独立分割好的元素,叫做vocabList
注意返回类型转换成list,因为set函数是不支持index索引的。"|"是集合
4000
的并运算。这里print一下去看看vocabList章什么样子,实际上它长这样:
Step2: 文档转化成词向量
每一个文档都是由若干词组成的,这些词都含在vocabList里面。所以要把文本转化成向量形式:初始化每一个词向量长度等于vocabList,,所有元素都是零。如果vocabList里的词在这个文档中有,则对应位置标为1。比如Step0里面的三篇文档转化成词向量就是:
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) (注意代码是如何实现的!)
上面橙色部分标注的地方实现思路如下
假设从开始时前两次遍历到了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起到封装前面各个函数的功能,输入参数是外界传来的一个词向量(文档),输出是这个词向量(文档)的类别。
Step5: 测试
假定这里的二分类是以一个人是否抽烟为指标的健康与否(只要抽烟就不健康),前面的三个test文档很好地说明了这一点。
我们给一个测试样例:['a','girl','bad','smoke'],手工转换成词向量,期望输出是negative
测试结果是:
上面输出的是转换成log之后的p1,p0概率,1代表坏。因为都在[0,1]所以转换后都是负数。
p1>p0,所以选择1类,negative
反之选择['a',,'boy','sports'],期望输出是positive
测试结果是:
所以走完这一遍我们发现朴素贝叶斯实际上没有显式的训练过程,和KNN一样,只需要计算一次就好了(KNN是距离,贝叶斯是概率p(w|c0),p(w|c1))
全部代码如下:
本篇先到这里,下次再见~~
统计学里有两大学派:贝叶斯学派和频率学派。贝叶斯学派最大的特点是认为参数是随机的,并引入先验知识和逻辑推理去找到它;而频率学派认为参数是一个我们不知道的确定值,需要通过统计学过程求解。最著名的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 negativehhh失败了,原因也很明显:我们给的测试用例不够好,'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)
本篇先到这里,下次再见~~
相关文章推荐
- <基础原理进阶>机器学习算法python实现【4】--文本分析之支持向量机SVM【上】
- <基础原理进阶>机器学习算法python实现【5】--文本分析之支持向量机SVM(下)
- <基础原理进阶>机器学习算法python实现【1】--分类简谈&KNN算法
- <基础原理进阶>机器学习算法python实现【2】--ForwardPass&BackPropagation
- 基于朴素贝叶斯分类器的文本分类算法的实现过程分析
- 【机器学习算法-python实现】扫黄神器-朴素贝叶斯分类器的实现
- 【机器学习算法-python实现】扫黄神器-朴素贝叶斯分类器的实现
- 朴素贝叶斯分类器简单实现文本情感分析
- <深度学习系列>基于numpy和python的反向传播算法的实现与分析
- 【python数据挖掘课程】二十一.朴素贝叶斯分类器详解及中文文本舆情分析
- 基于朴素贝叶斯分类器的文本分类算法的实现过程分析
- 机器学习算法-朴素贝叶斯Python实现
- 【机器学习算法实现】主成分分析(PCA)——基于python+numpy
- [Java基础要义] HashMap的设计原理和实现分析
- 机器学习算法-K最近邻从原理到实现(Python)
- OC基础:内存(进阶):retain.copy.assign的实现原理 分类: ios学习 OC 2015-06-26 17:36 58人阅读 评论(0) 收藏
- <<Python基础教程>>学习笔记 | 第03章 | 字符串
- <<Python基础教程>>学习笔记 | 第13章 | 数据库支持
- Python 基于语句检测和语句频谱分析实现文本汇总算法 (document summary algorithm)
- 文本分类之情感分析 – 朴素贝叶斯分类器