《机器学习实战》第四章:朴素贝叶斯(1)基本概念和文本分类
来源:互联网 发布:校园网络突然连接不上 编辑:程序博客网 时间:2024/06/09 18:04
最近一段时间压力有点大——毕业和工作的问题。每天早出晚归。于是写博客都变成了一种休闲放松的方式。哎。
不要怂,就是干。Keep calm & carry on.
咱们得加快点节奏了。
-------------------------------------------------------------------------------------
今天要讲的是朴素贝叶斯(Naive Beyes)。
这个naive就是 Too young, too simple. Sometimes naive 里面的 naive. 等下还会具体讲。
背景是这样的,我们之前在KNN和决策树里面,都是要求分类器给出“该数据属于哪一类”的明确答案。这样的逻辑有点儿类似于“非黑即白”。现在我们希望分类器给出一个最优的类别猜测,这个猜测就是基于概率估计值所做出的。
-------------------------------------------------------------------------------------
朴素贝叶斯(Naive Beyes)
(1)是一种基于概率论的分类方法。
(2)核心思想:利用贝叶斯定理,计算数据属于各个类别的概率,选择概率最高的类别作为其分类。
(3)优点:在数据较少的情况下仍然有效。
缺点:对于输入数据的准备方式较为敏感。
适用数据类型:离散型。
-------------------------------------------------------------------------------------
使用条件概率进行分类
条件概率不讲了,给条公式:
由此可得到上面给出的贝叶斯定理。
现在拿一个简单的“分类”问题来举例:
假设我们有一个数据集。每条数据都x和y两个属性和1个类别(标签)。标签的种类有2种。我们的目标是把一条新的数据分到2种标签之一里面。
我们用p1(x,y)表示数据(x,y)属于类别1的概率,p2(x,y)表示数据(x,y)属于类别2的概率。那么,用条件概率公式来表示这两个概率,就分别是:P(c1|x,y)和P(c2|x,y),具体意义就是:由x和y这两个属性值确定的数据,其属于类别1、类别2的概率。
由贝叶斯定理,可得到:
解释一下等号右边三项的意思。
p(x,y|ci) 指的是:基于一个样本数据集,在ci这个类中,数据(x,y)出现的概率。
p(ci)指的是:基于这个样本数据集,属于c1这个类别的数据,占所有数据的数量。
p(x,y)指的是:在所有样本数据中,数据(x,y)出现的概率。
-------------------------------------------------------------------------------------
使用朴素贝叶斯进行文档分类
好。我们现在看一个具体一点的例子。我们现在要把一篇文档进行分类,比如一封邮件是不是垃圾邮件,一条帖子是否是侮辱类的评论。
我们可以观察文档中出现的词,并且把每个词的 出现/不出现,或者 出现次数 作为一个特征。这样,特征的数量就会是样本数据集中所有出现过的单词的数量。(我们把所有这些单词组成的集合称为“词汇表”)
这时候问题来了:如果要得到好的概率分布(也就说我们想得到包含所有情况的样本数据),那么就得把每个单词的出现/不出现都组合起来(如果用出现次数作为特征,那会更加够呛)。如果词汇表有1000个单词,那么就得有2的1000次方条样本。要命了。
可是!如果特征之间是相互独立的,所需样本的数量就会大大减少。
所谓“独立”就是指一个单词(或特征)的出现与否,与其他单词(特征)出现与否没有半毛钱关系。
这显然是一个“天真”的假设,而正是因为这个假设,朴素贝叶斯被称为是“朴素”的。是谓naive。
记得《高级数据库》的老师是这样说的,“这个假设虽然看起来很不靠谱,但在实际运用的时候,往往能达到比较好的效果。”
这里还要补充一下的是,朴素贝叶斯分类器通常有两种实现方式:
(1)基于伯努利分布:不考虑单词在文档中出现的次数。只要出现就是1,不出现就是0.
(2)基于多项式模型:考虑此在文档中出现的次数。出现几次就是几,不出现就是0.
-------------------------------------------------------------------------------------
用Python进行文本分类
这一节要上代码了。好激动!
例子就是文本分类。
我们拿到一条一条的文本之后,肯定需要把文本拆开,获得一个一个单词。显然,处理英文文本就比中文文本方便得多,毕竟人家单词之间都是用空格隔开的,咱们的词语都是粘在一起的,老半天才出现一个标点符号。中文文本需要采用一些算法来进行分词,现有的软件包括【jieba分词】等等(我只是联想到了最近在做一个相关的项目,所以多扯了几句)。
我们的例子是英文的。我们的目标是构建一个快速过滤器,如果某条帖子使用了侮辱性的语言,我们就把它识别出来,然后和谐掉它。于是我们的数据集里面,每条数据的标签就是“侮辱类”和“非侮辱类”中的一种。
这里我还是想再多扯几句。有人也许会想,如果判断一条帖子是否是侮辱性的,那么只要看它里面包不包含侮辱性的词语不就行了吗?我想说,这样一来的话,分类器的工作模式就是“不分青红皂白,一棒子打死”。而我们采用的贝叶斯分类器,是基于概率比较的,他的工作模式就是:你出现了这个侮辱性的词(或特征),我不能马上给把你拉去枪毙,而只是把你的“嫌疑”提升了,我变得更加怀疑你了。等到看完你所有单词(特征),我才会做出最终的判决。这就减少了错判的可能性。
这是我自己的理解。
我们首先做的是把每条样本文本作转换为词向量。
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,classVecdef createVocabList(dataSet): vocabSet = set([]) for document in dataSet: vocabSet = vocabSet | set(document) #union of the two sets return list(vocabSet)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: %s is not in my Vocabulary!" % word return returnVec
loadDataSet:样本数据集和标签
createVocabList:创建词汇表,| 的作用是两个集合取并集。其他集合操作看这里
setOfWords2Vec:在inputSet中检查词汇表vocabList中的单词,有的话改词特征值标为1,否则值标为0.
测试一下,这里选的是第二条帖子['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid']:
listOPosts, listClasses = loadDataSet() myVocabList = createVocabList(listOPosts) #print myVocabList print setOfWords2Vec(myVocabList, listOPosts[1])
现在,我们要从词向量计算概率。套用贝叶斯定理:
其中,w是词向量(也就是特征向量)。如果将w展开为一个个独立的特征,p(w|ci)可以写成p(w0, w1, w2, ..., wn | ci)
我们这里运用朴素贝叶斯假设,于是 p(w0, w1, w2, ..., wn | ci) = p(w0|ci) p(w1|ci) p(w2|ci) ... p(wn|ci)
以下就是朴素贝叶斯分类器的训练函数:
def trainNB0(trainMatrix, trainCategory): #trainMatrix是训练文档列表,trainCategory是训练文档标签列表 numTrainDocs = len(trainMatrix) #训练文档的数量 numWords = len(trainMatrix[0]) #每篇训练文档中单词的数量(词汇表单词数量) pAbusive = sum(trainCategory)/float(numTrainDocs) #概率:标签为abusive的文档占总文档数的比例 p0Num = zeros(numWords); p1Num = zeros(numWords) p0Denom = 0.0; p1Denom = 0.0 for i in range(numTrainDocs): if trainCategory[i] == 1: p1Num += trainMatrix[i] #到目前为止,标签为abusive的文档中,词汇表的单词出现过多少次 p1Denom += sum(trainMatrix[i]) #到目前为止,标签为abusive的文档中出现过单词的总量 else: p0Num += trainMatrix[i] #到目前为止,标签为normal的文档中,词汇表的单词出现过多少次 p0Denom += sum(trainMatrix[i]) #到目前为止,标签为normal的文档中出现过单词的总量 p1Vect = p1Num/p1Denom #标签为abusive的文档中,词汇表的各个单词的出现概率 p0Vect = p0Num/p0Denom #标签为normal的文档中,词汇表的各个单词的出现概率 return p0Vect, p1Vect, pAbusive第4行:trainCategory里面,abusive(侮辱类)是1,否则是0,所以sum(trainCategory)计算的是标签为abusive的文档数。pAbusive 是标签为abusive的文档占总文档数的比例。那么1-pAbusive 就是标签为非abusive的文档占总文档数的比例。当然,如果分类数大于2的话,就必须要做额外的计算了。
第5行:p0Num 和 p1Num 都初始化为长度为numWords 的零向量。用来统计到目前为止,标签为abusive/非abusive的文档中,词汇表的单词各出现过多少次。由于这个例子里面是2个分类,所以只有2项这个。有n个分类就有n项pnNum。
第6行:p0Denom 和p1Denom用来统计到目前为止,标签为abusive/非abusive的文档中出现过单词的总量。这里基于的是伯努利分布,出现了就是1,没出现就是0。同样,有n个分类就有n项pnDenom。
第14、15行:这里用一个向量除以一个数,就是把向量中的每一项除以了这个数。p1Vect和p0Vect分别是标签为abusive/非abusive的文档中,词汇表的各个单词的出现概率。
测试一下:
listOPosts, listClasses = loadDataSet() myVocabList = createVocabList(listOPosts) trainMat = [] for post in listOPosts: trainMat.append(setOfWords2Vec(myVocabList, post)) p0V, p1V, pAb = trainNB0(trainMat, listClasses) print 'pAb=',pAb, '\n', 'p0V=',p0V, '\n', 'p1V=',p1V
现在有一个问题:在使用贝叶斯分类器的时候,我们需要计算多个概率的乘积,例如p(w0|ci) p(w1|ci) p(w2|ci) ... p(wn|ci)
如果其中有某项的值是0,那么乘积也变成了0,计算就无意义了。
解决办法是把训练函数的第5行,p0Num 和 p1Num由0向量改为1向量;p0Denom 和 p1Denom 也由0.0的初始值改为2.0的初始值。
另外一个问题是向下溢出(underflow):如果多个很小的数相乘,可能四舍五入最后会得到0。解决办法当然是用log把乘法变成加法,把训练函数的第14、15行,改为:
p1Vect = log(p1Num/p1Denom)
p0Vect = log(p0Num/p0Denom)
这样一来,训练函数就变成了:
def trainNB0(trainMatrix, trainCategory): #trainMatrix是训练文档列表,trainCategory是训练文档标签列表 numTrainDocs = len(trainMatrix) #训练文档的数量 numWords = len(trainMatrix[0]) #每篇训练文档中单词的数量 pAbusive = sum(trainCategory)/float(numTrainDocs) #概率:标签为abusive的文档占总文档数的比例 p0Num = ones(numWords); p1Num = ones(numWords) p0Denom = 2.0; p1Denom = 2.0 for i in range(numTrainDocs): if trainCategory[i] == 1: p1Num += trainMatrix[i] #到目前为止,标签为abusive的文档中,词汇表的单词出现过多少次 p1Denom += sum(trainMatrix[i]) #到目前为止,标签为abusive的文档中出现过单词的总量 else: p0Num += trainMatrix[i] #到目前为止,标签为normal的文档中,词汇表的单词出现过多少次 p0Denom += sum(trainMatrix[i]) #到目前为止,标签为normal的文档中出现过单词的总量 p1Vect = log(p1Num/p1Denom) p0Vect = log(p0Num/p0Denom) return p0Vect, p1Vect, pAbusive
分类器就此构建好了,接下来可以拿新数据来分类了。分类函数:
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): p1 = sum(vec2Classify * p1Vec) + log(pClass1) #element-wise mult p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1) if p1 > p0: return 1 else: return 0
vec2Classify:待分类的词向量
p0Vec:样本数据集中,标签为abusive的文档中,词汇表的各个单词的出现概率。
p0Vec:样本数据集中,标签为非abusive的文档中,词汇表的各个单词的出现概率。
pClass1:样本数据集中,abusive文档出现的概率。那么1-pClass1就是非abusive文档出现的概率。
第2行:计算 p1,即p(vec2Classify | c1)。vec2Classify * p1Vec是向量相乘:得出vec2Classify中单词的概率的向量,之所以概率间用sum求和,是因为之前已经通过log把乘法变成了加法。之后加上log(pClass1)也是这原因。至于式子里为什么没有出现贝叶斯定理中的分母,是因为计算p1和p2的时候会除以一个相同的概率 p(vec2Classify),(用了log之后除法变成减法)对比较大小无影响,所以省略了。
第3行:计算 p2,即p(vec2Classify | c2)。
分别用两个向量测试一下:
listOPosts,listClasses = loadDataSet() myVocabList = createVocabList(listOPosts) trainMat=[] for postinDoc in listOPosts: trainMat.append(setOfWords2Vec(myVocabList, postinDoc)) p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses)) testEntry = ['love', 'my', 'dalmation'] thisDoc = array(setOfWords2Vec(myVocabList, testEntry)) print testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb) testEntry = ['stupid', 'garbage'] thisDoc = array(setOfWords2Vec(myVocabList, testEntry)) print testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb)
最后的最后,我们再做一点点改进。
我们之前按照伯努利分布实现了文本分类的贝叶斯分类器,每个词出现与否作为一个特征,也可称为“词集模型”(set-of-words model)。现在我们将每个词出现的次数作为一个特征,称为“词带模型”(bag-of-words model)。
要改动代码的话很简单,改一下setOfWords2Vec函数即可,改为bagOfWords2Vec:
def bagOfWords2VecMN(vocabList, inputSet): returnVec = [0]*len(vocabList) for word in inputSet: if word in vocabList: returnVec[vocabList.index(word)] += 1 #改动这里即可 return returnVec
呼。内容有点多。下一篇博客会讲2个更大一些的实例。回见~
- 《机器学习实战》第四章:朴素贝叶斯(1)基本概念和文本分类
- 机器学习实战-第四章(朴素贝叶斯)
- 机器学习实战python版第四章基于概率论的分类方法 朴素贝叶斯
- 机器学习实战第四章——朴素贝叶斯分类(源码解析)
- [完]机器学习实战 第四章 基于概率论的分类方法:朴素贝叶斯(Naive Bayesian Classification)
- 【机器学习 基本概念】朴素贝叶斯分类
- 《机器学习实战》读书笔记 第四章 朴素贝叶斯(part 1)
- 机器学习实战——第四章:朴素贝叶斯
- 《机器学习实战》第四章 4.1-4.5 朴素贝叶斯
- 机器学习实战+第四章_朴素贝叶斯
- 机器学习实战之第四章 朴素贝叶斯
- 《机器学习实战》朴素贝叶斯(Naive Bayes)分类
- 机器学习实战:朴素贝叶斯分类(一)
- 机器学习实战:朴素贝叶斯分类(二)
- 《机器学习》第四章朴素贝叶斯分类器问题总结(python2.7->3.5)
- 《机器学习实战》读书笔记 第四章 朴素贝叶斯(part 2)
- 《机器学习实战》第四章:朴素贝叶斯(2)两个实例
- 机器学习实战-使用朴素贝叶斯分类器来做垃圾邮件分类
- 51nod:1019 逆序数(树状数组|归并排序|vector)
- OkHttp Wiki翻译(一)使用OkHttp
- POJ 1661Help Jimmy(逆向DP Or 记忆化搜索 Or 最短路径)
- Nginx slab的实现 --- 第五篇“基于页的内存释放”
- 冒泡排序的优化
- 《机器学习实战》第四章:朴素贝叶斯(1)基本概念和文本分类
- 重装系统-装机教程
- webview头部自定义view要跟webview一起滚动
- Linux Nginx 环境中同时运行多个PHP版本
- 基于Tiny4412的DHT11温湿度传感器的Linux设备驱动的简单实现
- 极光推送技术原理:移动无线网络长连接
- 编写远程监控机器的内存的插件
- 【深入Java虚拟机】之六:Java语法糖
- 使用 Zipkin 和 Brave 实现分布式系统追踪(基础篇