贝叶斯分类器-对文本的分类

前言

翻看贝叶斯的案例,我们就会发现90%以上的案例都是文本分类,如果想以后转做文本数据数据挖掘,那么贝叶斯应该是必须点亮的技能灯。“真的是任重道远啊,希望三天时间能skip到下一个算法。”

正文

我们将文本数据的标签用c表示,c包含多个变量,c_i,特征用w表示,w_i,也就是下面说的词条,同时假设,特征之间是相互独立的。

另外在实际做的过程中,也会发现,相比于之前做的贝叶斯分类器,特征是二维变量,而在文本分类器中,实际上特征只是一维变量,这在计算类条件概率上会简单一些。当然,复杂的贝叶斯分类器应该也会涉及到多特征维度的情况。当特征太多的时候,也就到了贝叶斯的极限了,除非我们依然可以保证相互独立性,负责贝叶斯的预测误差就会出现。#仅为个人理解,希望指正,以后有新的认识,我会回来修正这个理解。

下面是伪代码:

#计算每个类别中的文档数目  #也就是$P(C_i)$,各类标签的文档数目
#对每篇训练文档:
#    对每个类别
#        如果词条出现在文档中:增加该词条计数值
#        增加该词条计数值 
#    对每个类别:
#        对每个词条:
#            将该词条的数目除以总词条数目得到条件概率   #求$P(w|c_i)$

核心思想:利用文本构建词库向量

有关词向量的概念解释,参考:https://blog.csdn.net/michael_liuyu09/article/details/78029062

其实这里的只是简单的向量化,方便我们统计词频,但是深入到NLP的研究中,词库向量就会被利用计算相关性,这在NLP中应该比较重要,现在没有涉及,以后希望有机会

import numpy as np
#数据导入模块
def loadDataSet():
    postingList=[['my','dog','has','flea','problems','help','please'],
                 ['maybe','not','take','him','to','dog','park','stupid'],
                 ['my','dalmation','id','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):  #dataSet是指loadDataSet的反馈文本
    vocabSet = set([])  #python中的set是一个无序,去重的集合
    for document in dataSet:
        vocabSet = vocabSet | set(document)  #set(document)对每一个句子进行去重唯一,然后与vocabSet进行合并,扩充vocabSet
    return(list(vocabSet))
#构建词库向量
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0]*len(vocabList)  #构建0向量,[0,1]分布
    for word in inputSet: #对样本数据的进行遍历,出现词汇表单词的,则在对应值输出1
        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) #计算$P_c_i$
    p0Num=np.zeros(numwords)
    p1Num=np.zeros(numwords)
    p0Denom = 0.0; p1Denom = 0.0
    for i in range(numTrainDocs): #遍历每一篇文本
        if trainCategory[i]==1: #条件概率分类1的情况
            p1Num += trainMatrix[i] #累计每个词汇出现的次数
            p1Denom += sum(trainMatrix[i]) #累计分类1中的所有词汇的出现次数
        else:  #条件概率分类0的情况
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect=p1Num/p1Denom   #计算每个词汇在分类1中出现的概率 P(w_i|c_1)
    p0Vect=p0Num/p0Denom   #计算每个词汇在分类1中出现的概率 P(w_i|c_0)
    return(p0Vect, p1Vect, pAbusive)     
#main函数
if __name__ == "__main__":
    listOPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts)
    trainMat = []
    for postinDoc in listOPosts:  #对文本内容逐行遍历,进行向量化
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0V,p1V,pAb = trainNB0(trainMat,listClasses) #
    print(p0V, p1V, pAb)
output1

其实现在,我们正常情况下就可以计算了

套用我们的公式,P(c_i|w) = \frac{P(w|c_i)P(c_i)}{P(w)}

如果单纯考虑后验概率最大化,我们只需要计算分子部分,上面的P(w|c_i)我们可以通过p0V,p1V连乘得到。然后分开比较大小就可以帮助我们做出判断。

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): #vec2Classify是我们将目标文本向量化的产物
    p1 = sum(vec2Classify * p1Vec) * pClass1
    p0 = sum(vec2Classify * p0Vec) * (1 - pClass1)
    if (p1 > p0):
        return(1)
    if (p1 < p0):
        return(0)
testEntry = ['love', 'my', 'dalmation']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as', classifyNB(thisDoc, p0V, p1V, pAb))
testEntry = ['stupid']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as', classifyNB(thisDoc, p0V, p1V, pAb))
output2

但是现实情况并非如此完美,如果我们已有的样本中某分类下并没有该特征词,会导致p(w_i|c_i)=0,这会进而导致我们在计算似然函数时结果为0的情况,因此为了避免这个问题,我们引入“拉普拉斯修正”。在条件概率式子中分子分母分别加入一个正数,\lambda>0。当\lambda=0的时候,就是我们平时说的极大似然函数,当\lambda=1的时候,我们称为拉普拉斯平滑。

公式为:p(w|c_i) = \frac{\sum_{i=1}^N(I(x_j|c_i))+\lambda}{\sum_{i=1}^N(I(c_i))+S_j\lambda}p(ci)=\frac{\sum_{i=1}^NI(c_i)+\lambda}{N+K\lambda} ,其中,S_j为每一种X的种类数,K为属性个数

在这里我们用拉普拉斯平滑,令\lambda=1,因为我们样本标签分为2类,一类是好的语言,一类是有侮辱性的语言,那么,在我们令K=2,同时我们的样本每个单词同属一类特征,那么,S=1

在代码里,我们初始化p0Num=1, p1Num=1, p0Denom=2.0, p1Denom=2.0

在这里我们会遇到一个新的问题,那就是数据溢出,在计算中,当数字非常小的时候,而我们还在做连乘的时候,会出现数字下溢出的问题,为了避免这个问题实际中,我们用转换函数,换种方式计算,避免数字过小的问题。我们引入ln(a*b)=ln(a)+ln(b)

我们先比较f(x)ln(f(x))的区别

import matplotlib.pyplot as plt
x=np.linspace(0.01,0.9*np.pi,30)
f=np.sin(x)  #我们假设原函数f(x)为sin函数
g=np.log(f)  #我们假设实际函数为log(f(x))
plt.plot(x,f)
plt.plot(x,g)
plt.legend(['f(x)','log(f(x))'])
plt.show()
output3

在上图中,我们发现虽然两个函数不完全相同,但是,两个函数的极值点很接近,这对于我们贝叶斯在使用极大似然定理里,影响不大,因此,我们引入ln函数替换连乘问题。

接下来,我们对前面的部分函数进行优化

#统计频数,计算后验概率
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix) #计算我们的文本容量,文件数
    numwords = len(trainMatrix[0]) #计算样本库词汇数
    pAbusive = sum(trainCategory)/float(numTrainDocs) #计算$P_c_i$
    p0Num=np.ones(numwords)
    p1Num=np.ones(numwords)
    p0Denom = 2.0; p1Denom = 2.0
    for i in range(numTrainDocs): #遍历每一篇文本
        if trainCategory[i]==1: #条件概率分类1的情况
            p1Num += trainMatrix[i] #累计每个词汇出现的次数
            p1Denom += sum(trainMatrix[i]) #累计分类1中的所有词汇的出现次数
        else:  #条件概率分类0的情况
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect=np.log(p1Num/p1Denom)   #计算每个词汇在分类1中出现的概率 P(w_i|c_1)
    p0Vect=np.log(p0Num/p0Denom)   #计算每个词汇在分类1中出现的概率 P(w_i|c_0)
    return(p0Vect, p1Vect, pAbusive)
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): #vec2Classify是我们将目标文本向量化的产物
    p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + np.log(1 - pClass1)
    if (p1 > p0):
        return(1)
    if (p1 < p0):
        return(0)
if __name__ == "__main__":
    listOPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts)
    trainMat = []
    for postinDoc in listOPosts:  #对文本内容逐行遍历,进行向量化
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0V,p1V,pAb = trainNB0(trainMat,listClasses) #
    print(p0V, p1V, pAb)
output4

在这里,我们可以发现,文本向量矩阵的值已经变了,但是不影响我们的结果。

testEntry = ['love', 'my', 'dalmation']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as', classifyNB(thisDoc, p0V, p1V, pAb))
testEntry = ['stupid','dalmation']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as', classifyNB(thisDoc, p0V, p1V, pAb))
output5

到这里,贝叶斯方法的实践应该是比较深入了,算法的使用也好,还是具体一些细节问题的思考也好,接下来就是抽时间看下邮件分类有没有时间做

你可能感兴趣的:(贝叶斯分类器-对文本的分类)