朴素贝叶斯算法原理其实比较简单,就是基于贝叶斯原理。在此先介绍一下贝叶斯原理。条件概率: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