朴素贝叶斯算法原理其实比较简单,就是基于贝叶斯原理。在此先介绍一下贝叶斯原理。条件概率:P(A|B)就是在B发生的条件下A发生的概率。而P(AB) = P(A|B) *P(B) = P(B|A) *P(A),由此可以推出P(B|A) = [P(A|B) *P(B)]/P(A),这个公式就给出了P(A|B)和P(B|A)之间相互转换的公式。也就是先验概率与后验概率之间的相互转换,这也是贝叶斯学派与频率学派之间最大的区别,频率学派只关注事件本身,不涉及先验概率。下面就进入朴素贝叶斯算法的实现过程,会具体将贝叶斯公式应用到我们的例子中。
首先,生成文档列表,该文档列表由六段组成,并在classVec中将这六段文档分类,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]
return postingList,classVec
创建词列表,定义一个空集,遍历整个文档列表,通过并集的方式将所有词添加到该集合里,最后将集合转换为列表并返回。
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document)
return list(vocabSet)
将每一段文字转换为词向量,首先定义一个全0向量,向量维数与词的总数相同,然后遍历输入文档,将输入文档中的词对应词库中所在位置置1,返回一个0,1向量,1的位置对应着该文档中出现的词。
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
在这里,我们重写贝叶斯准则,P(ci|w)=P(w|ci)P(ci)P(w), 其中ci 是类别,w代表着每个词出现的概率,将w看成独立特征的话可以写成P(w0,w1,...,wn|ci) ,这里假设每个词之间相互独立,故可以写成P(w0|ci)P(w1|ci),...,P(wn|ci)
def trainNB0(trainMatrix,trainCategory):
numTrainDocs = len(trainMatrix) #计算文档的数目
numWords = len(trainMatrix[0]) #计算每篇文档的长度
pAbusive = sum(trainCategory)/float(numTrainDocs)#计算侮辱性文档的概率,因为侮辱性分类标签是1,所以可以直接累加
p0Num = ones(numWords); p1Num = ones(numWords) #将向量置0,p0Num和p1Num是词向量,下文中将其累加代表着每个词出现的次数。
p0Denom = 0.0; p1Denom = 0.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 = p1Num/p1Denom #计算条件概率
p0Vect = p0Num/p0Denom #计算条件概率
return p0Vect,p1Vect,pAbusive
上述代码中还存在一些问题需要修改一下,在使用朴素贝叶斯进行分类时,要计算多个概率的乘积以计算文档所属的类别,即计算p(w0|1)p(w1|1)….p(wn|1),其中若有一个为零则结果为零,为消除这种影响,将初始值置0.5。即:
p0Num = ones(numWords); p1Num = ones(numWords)
p0Denom = 2.0; p1Denom = 2.0
另一个问题就是下溢出,当计算p(w0|1)p(w1|1)….p(wn|1)时,由于每个概率都是很小的数,在计算多个乘积最终结果可能会下溢出。取自然对数是个不错的选择,而且不会对结果造成任何影响。
p1Vect = log(p1Num/p1Denom)
p0Vect = log(p0Num/p0Denom)
朴素贝叶斯分类函数。vec2Classify*p1Vec得到了所有分类为1的词的对数概率,在对其求和根据对数的运算法则就得到了概率乘积的对数。再加上类别概率的对数,就得到了p(w0|1)p(w1|1)….p(wn|1)p(1) = p(w0,w1,…,wn,1)的对数值。分别比较p0和p1的大小就能判断出这些词是属于哪一类。
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
p1 = sum(vec2Classify*p1Vec) + log(pClass1)
p0 = sum(vec2Classify*p0Vec) + log(1 - pClass1)
if p1 > p0:
return 1
else:
return 0
定义一个测试函数,分别测试[‘love’,’my’,’dalmation’]和[‘stupid’,’garbage’]两组词。
def testingNB():
dataSet,classVec = loadDataSet()
voset = createVocabList(dataSet)
trainmat = []
for i in dataSet:
trainmat.append(setOfWords2Vec(voset,i))
p0,p1,pAb = trainNB0(trainmat,classVec)
testEntry = ['love','my','dalmation']
thisDoc = array(setOfWords2Vec(voset,testEntry))
print testEntry,'classified as:',classifyNB(thisDoc,p0,p1,pAb)
testEntry = ['stupid','garbage']
thisDoc = array(setOfWords2Vec(voset,testEntry))
print testEntry,'classified as:',classifyNB(thisDoc,p0,p1,pAb)
测试结果如下所示:
['love', 'my', 'dalmation'] classified as: 0
['stupid', 'garbage'] classified as: 1
上述代码中setOfWords2Vec函数采用的是词集模型,即每个单词只出现一次,出现记1,未出现记0,但是实际中每个单词未必只出现一次,故要将词集模型修改为词袋模型(bagOfWords2Vec),其实和词集模型并没有太大不同,只是在计数时将置1改为加1。
def bagOfWords2Vec(vocabList, inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec