上一篇文章【机器学习】朴素贝叶斯-条件概率
已经提过了利用朴素贝叶斯进行文档分类的步骤,下面我们来看每个步骤的目的,搞清楚我们要处理的数据满足什么条件,是什么格式,我们所写的每一个函数的入参是什么,处理完成后的出参,即处理结果是什么样的,又是如何将计算的条件概率应用于贝叶斯公式,得到文档所属的类型。
一篇文档属于侮辱性文档还是非侮辱性文档,是根据文档内容判断的,文档由单词构成,判断一篇文档是什么类型也就是判断文档中这些单词出现的情况下,文档属于什么类型,每个单词都看做一个独立特征的话(事实上可能并不是独立的,这里就是用到了朴素贝叶斯假设,假设所有词都是互相独立的,即条件独立性假设,这样一来接下来的事都好办了),这篇文档就有多个特征,特征数就是单词的数量。我们用下面的公式计算条件概率P(w|ci),进而求条件概率P(ci|w)。
P(w|ci)=P(w0,w1,w2..wn|ci)=P(w0|ci)P(w1|ci)P(w2|ci)…P(wn|ci)
首先,分词:文档-->词
我们使用Python的string.split()方法进行分词,并依赖正则表达式处理特殊要求,如空格以及大小写转换。这里的样本数据集为6个已知类别的文档,每个文档均已被分好词。
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','worthlsess','dog','food','stupid']
]
classVec=[0,1,0,1,0,1] #0代表正常言论,1代表侮辱性文字
return postingList,classVec
然后,构建词向量:词-->词向量
我们有一些样本数据,把样本数据中包含的文档作为全集,可以构建一个词全集,这个词全集是不重复的词列表,我们把样本都转换成词列表长度的向量,用1和0分别表示本文档中存在/不存在词列表对应位置的单词。以后要对某个新的文档进行分类,我们把新文档中的词和词列表比对,也构建同样的向量,该向量就是词向量。
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document)
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 = zeros(numWords);p1Num = zeros(numWords)
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|ci)P(w1|ci)P(w2|ci),他们的值大多都非常小,所以程序会下溢出或者结果被四舍五入后得到0,这都不是我们想要的。一个比较好的办法是对乘积取自然对数。
P(c1|w) = P(w|c1)*P(c1)/P(w)
P(c0|w) = P(w|c0)*P(c0)/P(w)
要比较P(c1|w)和P(c0|w)的大小,因为分母P(w)相同,所以只需比较 P(w|c1)*P(c1)和P(w|c0)*P(c0)的大小。分别取对数后,P(w|c1)*P(c1)变成了lnP(w|c1)+ lnP(c1),同理,P(w|c0)*P(c0)变成了lnP(w|c0)+ lnP(c0)。还记得前面的P(w|ci)=P(w0|ci)P(w1|ci)P(w2|ci)…P(wn|ci)吗?
代入后,lnP(w|c1)+ lnP(c1)变成了ln (P(w0|c1)P(w1|c1)P(w2|c1)…P(wn|c1))+ lnP(c1),继续利用对数的性质,得到了ln P(w0|c1) + ln P(w1|c1) +…+P(wn|c1))+ lnP(c1)。
干脆,我们把上面的概率计算结果直接取对数,改为:
p1Vect=log(p1Num/p1Denom)
p0Vect=log(p0Num/p0Denom)
这样,某文档属于各个类别的概率就可以由如下函数求得:
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
p1 = sum(vec2Classify*p1Vec) + log(pClass1)
p0 = sum(vec2Classify*p0Vec) + log(pClass1)
if p1>p0:
return 1
else:
return 0
第五,测试
写了一个测试函数,给文档进行分类。
def testingNB():
listOPosts,listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
trainMat = []
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V,p1V,pAb = trainNB0(trainMat,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)
运行结果:
所以,['love','my','dalmation']这篇文档属于非侮辱性文档,['stupid','garbage']这篇文档属于侮辱性文档。分类显然是正确的。