本文承接上文关于朴素贝叶斯模型的介绍,讲一下在python中的实现。这里要注意一点,我在看《机器学习实战》中发现,书中论述的模型有一些错误,混淆了多项式模型和伯努利模型,这里对其进行一些更正。
一、对基本概念的进一步解释
(1)为什么要使用拉普拉斯校准?要知道拉普拉斯平滑针对的是特征属性离散分布的情况下求先验条件概率。朴素贝叶斯分类是计算多个特征值的概率得到整个类别的概率,若某个特征值为0,则会使得整个概率的乘积变为0,这明显不符合实际情况,不能因为出现一个曾经没有出现过的特性就否定了整个事物的存在性。所以我们假设整个训练数据库很大,以至于对每一个特征的数量加1并不会影响整体的准确性。方法就是求条件概率的时候分子上加1,分母上加K(K等于特征的取值范围,比如在本文中,一个单词只有出现和不出现两种情况此时K=2,对于温度比如有高中低三种情况,这时K为3(我给命名为类伯努利吧,因为这个和多项式分布也不一样))
开始比较迷惑,现在解释一下这个流程。我们在有一个训练数据后,可能这个训练数据分为很多类,比如体育、音乐、科技等等,提到孙悦这个词,我们在体育中出现了,在音乐中出现了,但是对于科技我们不能一定认为孙悦出现的概率就是0了,所以做法是给这个词一个很小的概率也就是我们的拉普拉斯校准。
(2)对于连续的情况求先验概率,一般是在假设特征分布为高斯分布(正态分布)的情况下,求解此时的均值和方差,得到条件先验概率的.
(3)为什么要取自然对数进行运算?之所以取对数不只是是因为方便运算,而且由于我们再进行多个先验概率乘积的时候,由于乘积会非常小计算机可能承载不了这么多位的数据而造成下溢。解决方法就是取对数将连乘变为连加。
二、朴素贝叶斯的两种模型
在利用朴素贝叶斯进行文本分类的时候,假设我们已经把单词切开,这时有两种贝叶斯模型可以应用:一种是多项式模型,一种是伯努利模型。
(1)伯努利模型
伯努利模型是词集型方法,在一个文本中一个词只要出现了就计为1,表示出现,但是并不考虑这个词出现的次数。也就是在统计中根据词的出现与否决定分类类别。这实际上不太符合实际的应用,因为可能一句话中很多无关词会影响分类结果。
具体流程为:我们首先假设训练数据为一些文本集合,其中d=(t1,t2,…,tk),d为一个文本,t为文档中出现的单词(这里的单词是不重复的,可以用Python中的集合set来实现),c为文本的类别,伯努利模型的先验概率和先验的条件概率为:
P(c) = 类c下文件总数/整个训练样本的文件总数
P(tk|c) = (类c下包含某单词tk的文件数+1)/(类c下文件总数+2)
这里注意条件概率的分母,为c类的文件总数
在《机器学习实战》的程序清单4-2中计算p1Denom时就是这部分出现了错误,他开始初始化过程明明用的是伯努利模型,但是这里的分母却用了多项式模型的分母,把两者混淆了。其实只要改为p1Denom +=1就可以了。当然可能在计算的时候用前一种方法可能并不会出现太大的误差,但是和标准表达式却是不一样的。+1是为了进行拉普拉斯校准。
(2)多项式模型
多项式模型考虑了词频的作用,设某文档d=(t1,t2,…,tk),tk是该文档中出现过的单词,允许重复,则
P(c)= 类c下单词总数/整个训练样本的单词总数
P(tk|c)=(类c下单词tk在各个文档中出现过的次数之和+1)/(类c下单词总数+|V|)
V是训练样本的单词表(即抽取单词,单词出现多次,只算一个),|V|则表示训练样本包含多少种单词(也就是非重复的单词种类)。 P(tk|c)可以看作是单词tk在证明d属于类c上提供了多大的证据,而P(c)则可以认为是类别c在整体上占多大比例(有多大可能性)。
例子参见朴素贝叶斯分类学习算法。
(3)扩展
其实在Laplace校准的时候,可以通过调整Laplace factor使分布更接近实际情况。现在一般的介绍过程都是用一个比较小的文本,在这个时候可能特征数量比较小,当特征数量非常多的时候,可能处理过程中的冗余信息也很多,怎么样优化这个过程是一个问题,是不是可以设定阈值,忽略一些无关词,这个后续考虑。
三、Python实现
首先实现最简单的伯努利方法, 我代码写的不是很好稍微有点乱,如果有错误请指正。我用的数据为:
sunny,hot,high,FALSE,0 sunny,hot,high,TRUE,0 overcast,hot,high,FALSE,1 rainy,mild,high,FALSE,1 rainy,cool,normal,FALSE,1 rainy,cool,normal,TRUE,0 overcast,cool,normal,TRUE,1 sunny,mild,high,FALSE,0 sunny,cool,normal,FALSE,1 rainy,mild,normal,FALSE,1 sunny,mild,normal,TRUE,1 overcast,mild,high,TRUE,1 overcast,hot,normal,FALSE,1
from numpy import *
class NaiveBayesClassifier(object):
def __init__(self):
self.dataMat = list()
self.labelMat = list()# training data and label
self.pLabel1 = 0
self.p0Vec = list()
self.p1Vec = list()# prior probability
self.vocablist = list()#vocabulary
def loadDataSet(self,filename):
fr = open(filename)
for line in fr.readlines():
lineArr = line.strip().split(',')
self.dataMat.append(lineArr[0: -1])
self.labelMat.append(lineArr[-1])
print('success load file!!!')
def __creatVocab(self):# use set to remove Duplicate and build a vocabulary
vocabSet = set()
trainMat = []
for i in range(len(self.dataMat)):
vocabSet = set(self.dataMat[i])| vocabSet
self.vocablist = list(vocabSet)
print("Vocablist = ", self.vocablist)
returnVec = [0] * len(self.vocablist)
labelNum = len(self.dataMat)
for j in range(0,labelNum):
for word in self.dataMat[j]:
if word in self.vocablist:
returnVec[self.vocablist.index(word)] = 1
trainMat.append(returnVec)
return trainMat
def train(self):#1,Bernoulli
trainMat = self.__creatVocab()
dataNum = len(trainMat)# number of labels
featureNum = len(trainMat[0])
intlabelMat = [int(i) for i in self.labelMat]
self.pLabel1 = sum(intlabelMat)/float(dataNum)
p0Num = ones(featureNum)
p1Num = ones(featureNum)
for i in xrange(dataNum):
if self.labelMat[i] == 1:
p1Num += trainMat[i]
else:
p0Num += trainMat[i]
self.p0Vec = log(p0Num / (featureNum + 2.0))
self.p1Vec = log(p1Num / (featureNum + 2.0))
def classify(self,testData):
vec = [0]*len(self.vocablist)
for word in testData:
if word in self.vocablist:
vec[self.vocablist.index(word)] = 1
p1 = sum(vec * self.p1Vec) + log(self.pLabel1)
p0 = sum(vec * self.p0Vec) + log(1.0 - self.pLabel1)
return p1 > p0
def main():
Bay = NaiveBayesClassifier()
Bay.loadDataSet('weather.txt')
Bay.train()
m = Bay.classify(['rainy','mild','high','TRUE'])# right category is 0
print m
if __name__ == '__main__':
main()
把上面的代码进一步改进成多项式的结构,主要是变化在__creatVocab()和train()函数上:
from numpy import *
class NaiveBayesClassifier2(object):
def __init__(self):
self.dataMat = list()
self.labelMat = list()# training data and label
self.pLabel1 = 0
self.p0Vec = list()
self.p1Vec = list()# prior probability
self.vocablist = list()#vocabulary
def loadDataSet(self,filename):
fr = open(filename)
for line in fr.readlines():
lineArr = line.strip().split(',')
self.dataMat.append(lineArr[0: -1])
self.labelMat.append(lineArr[-1])
print('success load file!!!')
print(self.dataMat)
def __creatVocab(self):# use set to remove Duplicate and build a vocabulary
vocabSet = set()
trainmat = []
labelNum = len(self.dataMat)
for i in range(labelNum):
vocabSet = set(self.dataMat[i])| vocabSet
self.vocablist = list(vocabSet)
print("Vocablist = ", self.vocablist)
returnVec = [0] * len(self.vocablist)
wordnum = 0
for j in range(labelNum):
returnVec = [0] * len(self.vocablist)
for word in self.dataMat[j]:
if word in self.vocablist:
returnVec[self.vocablist.index(word)] += 1
wordnum += 1
trainmat.append(returnVec)
return trainmat, wordnum
def train(self):
trainMat, wordnum = self.__creatVocab()
print('trainMat =', trainMat)
labelNum = len(trainMat) # number of labels
featureNum = len(trainMat[0])
intlabelMat = [int(i) for i in self.labelMat]
print intlabelMat
p0Num = zeros(featureNum)
p1Num = zeros(featureNum)
for i in xrange(labelNum):
if intlabelMat[i] == 1:
p1Num += trainMat[i]
else:
p0Num += trainMat[i]
self.pLabel1 = sum(p1Num) / float(wordnum)
self.p0Vec = log((p0Num+ 1.0) / (featureNum + sum(p0Num)))
self.p1Vec = log((p1Num+ 1.0) / (featureNum + sum(p1Num)))
def classify(self, testData):
vec = [0] * len(self.vocablist)
for word in testData:
if word in self.vocablist:
vec[self.vocablist.index(word)] += 1
p1 = sum(vec * self.p1Vec) + log(self.pLabel1)
p0 = sum(vec * self.p0Vec) + log(1.0 - self.pLabel1)
return p1 > p0
def main():
Bay = NaiveBayesClassifier2()
Bay.loadDataSet('weather.txt')
Bay.train()
m = Bay.classify(['rainy','mild','high','TRUE'])
print m
if __name__ == '__main__':
main()
这里的代码主要参考了机器学习实战,改正了它代码的一部分错误。其实贝叶斯在实际应用中还有很多优化方法,会后续介绍