需要分类器做出分类决策,可以使分类器给出各个类别的概率估计值,然后选择概率最高的作为其的类别。在这里使用到了概率论中的贝叶斯公式:P(A|B)=P(A)*P(B|A)/P(B),其中P(A|B)是后验概率,P(A)是先验概率,P(B|A)/P(B)为调整因子(在已知结果的情况下对先验概率大小做出相应调整得到后验概率)
使用朴素贝叶斯进行文档分类
可以观察文档中出现的单词,并把每个词的出现或者不出现作为一个特征,这样的得到的特征数目就是单词表的词目数。
那么由统计学得知,如果每个特征需要N个样本,则一千个特征的词汇表需要N^1000个样本,但是如果假设特征之间是相互独立的,那么样本数就可以减少到1000*N(特征相互独立即特征之间完全没有关联和规律,互不影响,那么对其来说样本N^1000或是1000*N的意义是一样的),这也就是“朴素”的含义。当然这种假设并不正确,因为绝大多数特征之间并不可能是毫无关联毫无规律的,尽管如此,但朴素贝叶斯的实际效果却很好。
朴素贝叶斯的分类器通常有两种实现方式:一种是基于伯努力模型实现,一种是基于多项式模型实现。在文档分类中我们选择第一种模型,不考虑词在文档中出现多少次,只考虑出不出现(记录为0或1),因此在这个意义上我们假设的是所有词都是等权重的。
首先准备数据:需要我们把文本转换为相应的词条向量才能进行后面的运算。
import numpy as np
from functools import reduce
def loadDataSet():
datalist=[['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']]
label = [0,1,0,1,0,1] #各个词条的类别标签向量,1代表属于侮辱性词汇,0代表不是
return datalist,label
def creatVocablist(dataset): #创建一个词汇表,记录所有出现过的单词
vocabuset=set([])
for i in dataset:
vocabuset=vocabuset|set(i) #set(i)将该行中重复单词去除,并运算符将其并在词汇列表中
return list(vocabuset)
def setwords_tovec(vocabulist,inputset): #根据词汇表,将inputset向量化,word存在则1,否则为零
vec=[0]*len(vocabulist) #初始化一个词汇表长度的列表
for word in inputset:
if word in vocabulist:
vec[vocabulist.index(word)]=1
else:print("%s is not in my vocabulary!"% word)
return vec
datalist,label=loadDataSet()
vocabulist=creatVocablist(datalist) #得到词汇表
trainmat=[]
for line in datalist:
trainmat.append(setwords_tovec(vocabulist,line)) #得到向量化后的训练集
print("总词汇表为:\n" ,vocabulist)
print("向量化的训练集为:\n" ,trainmat)
下一步要开始训练算法,从词向量来计算概率。
我们的目标是已知一个词条的各个特征(有哪些单词出现了),然后给这个词条分类(是否属于侮辱性词条),X表示这是一个向量,它由多个数值组成,A代表属于侮辱类,B代表属于非侮辱类,那么根据贝叶斯公式:
文档属于侮辱类概率:P(A|W)=P(A)*P(W|A)/P(W)
文档不属于侮辱类概率:P(B|W)=P(B)*P(W|B)/P(W)
只需要比较两者大小即可,概率大的就是该文档的类别。其中P(W)可以用全概率公式求解,但此处并不用,因为比较大小P(W)可以同时约去,减少计算量。
那么P(A)就是所说的先验概率:P(A)=训练集文档中侮辱类数目÷训练集文档总数目
由特征独立分布可得:P(W|A)=P(w1|A)*P(w2|A)*......*P(wn|A),P(wn|A)则很好求,就是分类为A的词条中该单词出现的次数÷A分类所有单词出现次数的总数。
求出P(A)、P(W|A)就可以得到P(A|W),同理得到P(B|W),比较两者大小,选择概率大的作为它的分类结果。
#朴素贝叶斯分类器训练函数,求解需要的参数P(W|A),P(W|B),P(A),1-P(A)
def trainNB(trainmat,trainlabel):
numdocs=len(trainmat)
numwords=len(trainmat[0])
Pabusive=sum(trainlabel)/float(numdocs) #文档属于侮辱类的概率,即先验概率P(A)
p0num=np.ones(numwords);p1num=np.ones(numwords) #初始化为一,加一平滑,解决零概率问题,避免出现某一特征概率为零从而导致P(X|A)为零
p0denom=2.0;p1denom=2.0 #分母初始化为二,原理相同
for i in range(numdocs):
if(trainlabel[i]==1):
p1num+=trainmat[i] #计算属于侮辱类的概率
p1denom+=sum(trainmat[i])
else:
p0num+=trainmat[i]
p0denom+=sum(trainmat[i])
p1vec=np.log(p1num/p1denom)
p0vec=np.log(p0num/p0denom) #对特征概率P(Xn|A)做对数处理,避免下溢出(因为数值太小不断相乘越来越小导致最后四舍五入为零),采用对数处理不会造成任何损失。
return p0vec,p1vec,Pabusive
#根据得到的参数 p0vec,p1vec,Pabusive来计算最后的P1,P0并比较大小
def NBclassify(vec,p0vec,p1vec,Pabusive):
#reduce函数会对参数序列按照指定函数方法进行运算,使用lambda匿名函数
#p1=reduce(lambda x,y:x*y,vec*p0vec)*Pabusive 根据公式本该如此计算,但是在取了对数之后可以利用对数的性质将乘法运算化为求和(log(ab)=log(a)+log(b))
#p0=reduce(lambda x,y:x*y,vec*p1vec)*(1-Pabusive)
#改进后为:
p1=sum(vec*p1vec)+np.log(Pabusive) #并且取对数将连乘运算通过对数转换为求和,还避免了零概率问题!因为只要有一个为零则相乘结果就会变为零!
p0=sum(vec*p0vec)+np.log(1-Pabusive)
print('p0:',p0)
print('p1:',p1)
if p1 > p0: #比较两者的概率选取最大的哪一个
return 1
else:
return 0
最后输入测试集,通过得到的分类器对其进行测试。
testEntry = ['love', 'stupid', 'dalmation'] #测试词条样本
thisDoc = np.array(setwords_tovec(vocabulist, testEntry)) #将测试样本向量化
p0vec,p1vec,Pabusive=trainNB(trainmat,label) #放入训练器求解参数
print(p0vec,p1vec)
if NBclassify(thisDoc,p0vec,p1vec,Pabusive):
print(testEntry,'属于侮辱类') #执行分类并打印分类结果
else:
print(testEntry,'属于非侮辱类')
结果显示为:
参考学习自《机器学习实战》