贝叶斯决策理论的核心思想:选择最高概率的决策。朴素贝叶斯是贝叶斯决策理论的一部分。
下面不加证明地直接给出贝叶斯定理:
朴素贝叶斯分类的正式定义如下:
因为分母对于所有类别为常数,因为我们只要将分子最大化皆可。又因为各特征属性是条件独立的,所以有:
现在实际的来研究一个文本分类的问题,下面是朴素贝叶斯分析问题的一般过程:
1.收集数据:可以使用任何方法。
2.准备数据:需要数值型或者布尔型的数据。
3.分析数据:有大量的特征时,绘制特征作用不大,此时使用直方图效果会更好。
4.训练算法:计算不同的独立特征的条件概率。
5.测试算法:计算错误率。
6.使用算法:一个常见的朴素贝叶斯应用是文档分类,可以在任意的分类场景中使用朴素贝叶斯分类器,不一定非要是文本。
为了简单起见,先研究一个社区的评论条目,构建一个快速过滤器,来分辨每条评论属于侮辱性评论还是非侮辱性评论,分别用1和0来代表,接下来的代码演示如何将词条数据转换成布尔型或数值型的数据:
词表到向量的转换函数:
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 is abusive, 0 not
return postingList,classVec
上面这个load数据的函数,功能很简单就是创建两个数组,并返回它们,一个是词条列表即评论,共6个评论,一个是每个词条所对应的类别列表。这里所说的词条也就是一个句子,比如例子中的一个评论,后续还可能是一篇文章,一封邮件等,而词条列表则是由这些词条组成的数组。后面会这么用这个函数:
def createVocabList(dataSet):
vocabSet = set([]) #create empty set
for document in dataSet:
vocabSet = vocabSet | set(document) #union of the two sets
return list(vocabSet)
这个函数旨在创建包含上面词条列表所有词汇,但不会重复的数组。先创建一个空的set数据类型,然后遍历每个词条,用 ‘|’求并集,返回不重复的单词。看一下函数的效果:
返回的不重复的词汇列表,可能是没按照顺序的,不过这个无关紧要。
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
上面这个函数的作用就是将词汇数组,输出成程序可以更好识别的向量。向量的每一个元素为1或0,表示词汇表中的单词在输入文档(词条)中是否出现。实现原理是遍历文档每个词,再判断每个词是否出现在词汇表中,进而对输出向量进行赋值操作。看一下效果:
将朴素贝叶斯的定理应用到这个社区评论过滤的案例中,前面已经提到用x,y表示的贝叶斯定理,现在用w 代表一个向量,该向量由多个数值组成,其实际意义就是社区每条评论的数值化结果即[0,1,0,0,1,1,1….]这种形式,用字母Ci代表评论的类别,在这个案例中只有两个类别:C1=1,侮辱性评论,C2=非侮辱性评论。那我们这个案例的研究目的就是分辨新输入的评论属于哪一类别,也就是给定评论内容(词条数组向量)的条件下,属于哪种类别的概率,用符号表示就是P(Ci | w)。有贝叶斯定理可以得到:
利用这个公式,每当输入一个文档,我们会计算这个文档属于每一个类别的概率,选择最大概率的那个类别作为评判结果,体现了贝叶斯决策理论的核心思想:选择最高概率的决策。
主要分析一下右边的式子:
分子:P(w | Ci)代表着在已知类别的情况下,对每个单词求其出现的概率,这个就跟前一篇介绍生成学习算法中大象和小狗的例子差不多了,在这里w回事由很多值组成的向量,即可写成P(w0,w1,w2,w3,…,wn|Ci),按照前面朴素贝叶斯的假设:w中每个特征条件独立,所以可以写成:P(w0 | Ci)P(w1 | Ci)P(w2 | Ci)P(w3 | Ci)…..P(wn | Ci) ,按照这样来计算上述概率。P(Ci)为ci类别的个数占总个数的概率,比如这个案例中p(C1)=p(c2)=3/6=0.5。
分母:是表示某个词向量的概率,在实际情况中,词向量是给定的也就是输入数据,所以分母通常为常数,所以可忽略不计,不影响整体概率结果。
下面这个函数是求P(w |ci)的过程:
def trainNB0(trainMatrix,trainCategory):
numTrainDocs = len(trainMatrix) #trainMatrix的长度,也就是几篇文档
numWords = len(trainMatrix[0]) #trainMatrix中每篇文档的长度,即有多少个单词
pAbusive = sum(trainCategory)/float(numTrainDocs) #trainCategory中类别1的个数除以文档总数,也就是类别1的比例
p0Num = ones(numWords); p1Num = ones(numWords) #创建全是0的数组,后面为了防止出现全0导致概率无效,换成了全1数组,change to ones()
p0Denom = 2.0; p1Denom = 2.0 #分母基数初始化为2 ,防止出现分母为0,或者比分子小情况,change to 2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1: #判断每篇文档的类别属于
p1Num += trainMatrix[i] #同一类别,每个词汇列表中单词出现次数的累计
p1Denom += sum(trainMatrix[i]) #在同一类别所有文档中,词汇列表出现的单词个数总和,包括重复
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = log(p1Num/p1Denom) #这里原本是不加log运算,得出的结果就是概率,不过后面会有乘法计算,为了避免最后结果变成0,采用log运算 #change to log()
p0Vect = log(p0Num/p0Denom) #change to log()
return p0Vect,p1Vect,pAbusive #分别返回类别0中,各个单词的概率;类别1中各个单词的概率;类别为1的概率
上面这些函数的过程,下面用手写草图详细介绍下其中数据的处理流程,便于理解:
上述函数的运行结果就是得到在两个类别下的每个单词的概率:
下面是加上log函数的结果:
p0v: [-2.56494936 -2.56494936 -2.56494936 -3.25809654 -2.56494936 -2.56494936
-2.56494936 -3.25809654 -2.56494936 -3.25809654 -3.25809654 -2.56494936
-2.56494936 -3.25809654 -2.56494936 -2.56494936 -2.56494936 -3.25809654
-2.15948425 -3.25809654 -3.25809654 -3.25809654 -2.56494936 -1.87180218
-3.25809654 -3.25809654 -2.56494936 -2.56494936 -2.56494936 -2.56494936
-2.56494936 -2.56494936]
p1v: [-1.94591015 -3.04452244 -3.04452244 -2.35137526 -3.04452244 -3.04452244
-3.04452244 -1.94591015 -3.04452244 -2.35137526 -2.35137526 -3.04452244
-3.04452244 -1.65822808 -3.04452244 -3.04452244 -3.04452244 -2.35137526
-2.35137526 -2.35137526 -2.35137526 -2.35137526 -3.04452244 -3.04452244
-2.35137526 -2.35137526 -3.04452244 -2.35137526 -2.35137526 -3.04452244
-3.04452244 -3.04452244]
经过数据训练之后我们得到两个类别下每个单词会出现的概率,以上数据仅为参考,我发现函数 createVocabList 每次出来的结果都不一样,是随机的,我用python是3.6的,所以这里就不一一对比数据的准确性,我再debug的时候对比是没错的。
关键部分理解完了,下面分析一下如何使用这个训练结果,我贴上全部的代码:
import numpy as np
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 is abusive, 0 not
return postingList, classVec
def createVocabList ( dataSet):
vocabSet = set([]) #create empty 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
def trainNB0(trainMatrix,trainCategory):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pAbusive = sum(trainCategory)/float(numTrainDocs)
p0Num = np.ones(numWords); p1Num = np.ones(numWords)
p0Denom = 2.0; p1Denom = 2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = np.log(p1Num/p1Denom)
p0Vect = np.log(p0Num/p0Denom)
return p0Vect, p1Vect, pAbusive
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + np.log(pClass1) # element-wise mult
p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
if __name__ == '__main__':
listOPosts, listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
print(myVocabList)
trainMat = []
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB0(np.array(trainMat), np.array(listClasses))
# print("p0v:",p0V)
# print("p1v:",p1V)
testEntry = ['love', 'my', 'dalmation']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print (testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
testEntry = ['stupid', 'garbage']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
主要业务逻辑看main函数,
p0V, p1V, pAb = trainNB0(np.array(trainMat), np.array(listClasses))
这一步过后就得到了每个单词的概率向量了。接下来开始分类,对于新的数据(语句),这边先得对照词汇表转化成向量。
testEntry = ['love', 'my', 'dalmation']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
向量化的结果(这个也要看词汇表排序的,仅做参考):
[0 0 1 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
下面进入分类:
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + np.log(pClass1) # element-wise mult
p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
输入的四个参数,第一个是新数据的向量化结果,第二第三分别是各类别下每个单词概率的向量,第四个为其中某一类别的概率。
函数主要实现了贝叶斯公式中 右边分子的部分,也就是 在给定类别下这个词向量的概率和这个类别的概率的相乘。
这里的sum(vec2Classify * p1Vec) 操作,实际上就是:
[0 0 1 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] * [-2.56494936 -2.56494936 -2.56494936 -3.25809654 -2.56494936 -2.56494936
-2.56494936 -3.25809654 -2.56494936 -3.25809654 -3.25809654 -2.56494936
-2.56494936 -3.25809654 -2.56494936 -2.56494936 -2.56494936 -3.25809654
-2.15948425 -3.25809654 -3.25809654 -3.25809654 -2.56494936 -1.87180218
-3.25809654 -3.25809654 -2.56494936 -2.56494936 -2.56494936 -2.56494936
-2.56494936 -2.56494936]
一一对应相乘最后相加,可以看到这个过程,0所对应的项相乘都变成0了,只有在词汇表中出现过的单词为1 余对应概率相乘才会有有效结果,最后相加是求这整个词向量的概率结果,这里想加并没有和前面的“w中每个特征条件独立,所以可以写成:P(w0 | Ci)P(w1 | Ci)P(w2 | Ci)P(w3 | Ci)…..P(wn | Ci) ,按照这样来计算上述概率。”矛盾,这边是log函数相加,log(P(w0 | Ci)P(w1 | Ci)P(w2 | Ci)P(w3 | Ci)…..P(wn | Ci))= log((P(w0 | Ci))+log(P(w1 | Ci))+……+log(P(wn | Ci))。最后加上类别概率的log结果,其实就是公式中的P(w|Ci)*P(Ci)的相乘结果的log,可以拆成两个log相加。
分类函数中,把把每个类别的概率都算出来,然后进行对比,谁比较大就属于那个类别。这就是简单的朴素贝叶斯分类算法,选择概率最大的那个。