4.1基于贝叶斯决策理论的分类方法
优点:在数据较少的情况下任然有效,可以处理多类别问题。
缺点:对于输入数据的准备方式较为敏感 。
适用数据类型:标称型数据。
贝叶斯决策理论的核心思想:选择具有最高概率的决策。
4.2条件概率
贝叶斯准备告诉我们如何交换条件概率中的条件与结果,即如果已知$p(x\mid c)$,要求$p(c\mid x)$,那么可以使用下面的计算方法:
$$ p(c\mid x)=\frac{p(x\mid c)p(c)}{p(x)} $$
4.3使用条件概率来分类
贝叶斯分类准则:
如果$p(c_{1}\mid x,y)>p(c_{2}\mid x,y)$,那么属于类别$c_{1}$。
如果$p(c_{1}\mid x,y)<p(c_{2}\mid x,y)$,那么属于类别$c_{2}$。
4.4使用朴素贝叶斯进行文档分类
朴素贝叶斯的一般过程:
1、收集数据:可以使用任何方法。本章使用RSS源。
2、准备数据:需要数值型或者布尔型数据。
3、分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果更好。
4、训练算法:计算不同的独立特征的条件概率。
5、测试算法:计算错误率。
6、使用算法:一个常见的朴素贝叶斯应用是文档分类。可以在任何的分类场景中使用朴素贝叶斯分类器,不一定非要是文本。
4.5使用Python进行文本分类
4.5.1准备数据:从文本中构建词向量
我们将把文本看成单词向量或者词条向量,也就是说将句子转换为向量。考虑出现在所有文档中的所有单词,再决定将哪些词纳入词汇表或者说所要的词汇集合,然后必须要将每一篇文档转换为词汇表上的向量。
创建一个叫bayes.py的新文件,添加代码:
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] # 1 is abusive, 0 not
return postingList, classVec
def createVocabList(dataSet):
vocabSet = set([]) # create empty set
for document in dataSet:
vocabSet = vocabSet | set(document) # union of the two sets
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
测试代码:
import bayes
listOPosts,listClasses = bayes.loadDataSet()
myVocabList = bayes.createVocabList(listOPosts)
print(myVocabList)
vec = bayes.setOfWords2Vec(myVocabList,listOPosts[0])#返回的是词表list的第i个小list里面有没有前面的这些词
print(vec)
结果:
4.5.2训练算法:从词向量计算概率
使用公式 $p(c\mid w)=\frac{p(x\mid c)p(c)}{p(w)}$对每个类计算该值,然后比较这两个概率值的大小($w$表示一个向量)。
首先可以通过类别 $i$ (侮辱性留言或非侮辱性留言)中文档数除以总的文档数来计算概率$p(c_{i})$。接下来计算$p(w\mid c_{i})$,这里就要用到朴素贝叶斯假设。这里假设所有词都相互独立,该假设也称作条件独立性假设。
在bayes.py中添加代码:
def trainNB0(trainMatrix,trainCategory):
numTrainDocs=len(trainMatrix) #训练文档数目
numWords=len(trainMatrix[0]) #每篇文档词条数
pAbusive=sum(trainCategory)/float(numTrainDocs) #属于侮辱类文档的概率
p0Num=np.zeros(numWords) #词条出现向量初始化为0
p1Num=np.zeros(numWords)
p0Denom=0.0 #分母
p1Denom=0.0
for i in range(numTrainDocs):
if trainCategory[i]==1:
p1Num+=trainMatrix[i] #向量相加
p1Denom+=sum(trainMatrix[i]) #统计侮辱类条件概率p(w1|c1),p(w2|c1)……
else:
p0Num+=trainMatrix[i] #统计非侮辱类条件概率p(w1|c0),p(w2|c0)……
p0Denom+=sum(trainMatrix[i])
p1Vect=p1Num/p1Denom
p0Vect=p0Num/p0Denom
return p0Vect,p1Vect,pAbusive #返回属于非侮辱类条件概率数组,属于侮辱类条件概率数组,文档属于侮辱类概率
测试代码:
listOposts,listClasses = bayes.loadDataSet()
myVocabList = bayes.createVocabList(listOposts)#创建不重复的词表
trainMat = []
for postinDoc in listOposts:#按小list计数
trainMat.append(bayes.setOfWords2Vec(myVocabList,postinDoc))#在trainmat中不断加入训练数据,加入的是词表list里面每个小list在总词表中的有无。
#append加入也不是直接加入,而是把每次加入的对象单独作为一个新的list元素
p0V,p1V,pAb = bayes.trainNB0(trainMat,listClasses)
print(pAb)
print(p0V)
print(p1V)
结果:
4.5.3测试算法:根据现实情况修改分类器
在利用贝叶斯分类器对文档进行分类时,会遇到两个问题:
1、需要计算多个概率的乘积,即计算$p(w_{1}\mid c_{1})p(w_{2}\mid c_{1})……$,如果其中一个概率为0,则结果为0,为了降低这种影响,我们需要对概率值进行“平滑”处理,即分子加1,分母增加Ni表示第i个属性可能的取值数,这种方法称为拉普拉斯平滑,在本例中每个词可能取值数为2,即所有分母加2,分子加1。
2、许多小数相乘会造成下溢,为了解决这个问题通常采取乘积取对数。
在这里我们对上面的分类器进行修改:
p0Num=np.ones(numWords)
p1Num=np.ones(numWords)
p0Denom=2.0
p1Denom=2.0
p1Vect=log(p1Num/p1Denom)
p0Vect=log(p0Num/p0Denom)
得到的结果为:
现在已经准备好构建完整的分类器了。
在bayes.py中添加代码:
'''
构建贝叶斯分类函数
'''
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
p1=sum(vec2Classify*p1Vec)+np.log(pClass1) #对应元素相乘log(A*B)=log(A)+log(B)所以这里+log(pClass1)
p0=sum(vec2Classify*p0Vec)+np.log(1.0-pClass1) #这里计算的p(w|Ci)*p(Ci)=p(w0,w1,……|Ci)*p(Ci)=p(w0|Ci)p(w1|Ci)……p(Ci)
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(np.array(trainMat),np.array(listClasses)) #训练分类器,注意列表到array格式的转化
testEntry=['love','my','dalmation']
thisDoc=np.array(setOfWords2Vec(myVocabList,testEntry))
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'属于侮辱类')
else:
print(testEntry,'属于非侮辱类')
testEntry=['stupid','garbage']
thisDoc=np.array(setOfWords2Vec(myVocabList,testEntry))
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'属于侮辱类')
else:
print(testEntry,'属于非侮辱类')
测试代码:
bayes.testingNB()
结果:
4.5.4准备数据:文档词袋模型
我们将每个词出现与否作为一个特征,这种方式为词集模型。如果一个词在文档中不止出现一次,则为词袋模型,为了适应词袋模型,我们需要对函数setOfWords2Vec()进行修改。
在bayes.py中添加代码:
#基于词袋模型
def bagOfWords2VecMN(vocabList,inputSet):
returnVec=[0]*len(vocabList)
for word in inputSet:
returnVec[vocabList.index(word)]+=1 #词汇没出现一次就加1
return returnVec
4.6示例:使用朴素贝叶斯过滤垃圾邮件
4.6.1准备数据:切分文本
对于一个文本字符串,可以使用Python的string.split()方法将其切分。
代码:
mySent='This book is the best book on Python or M.L. I have ever laid eyes upon.'
print(mySent.split())
结果:
可以看到,切分的结果不错,但是标点符号也被当成了词的一部分。可以使用正则表示式来切分句子,其中分隔符是除单词、数字外的任意字符串。
代码:
import re
mySent='This book is the best book on Python or M.L. I have ever laid eyes upon.'
regEx=re.compile('\\W+') #除单词数字外的任意字符串
print(regEx.split(mySent))
结果:
现在得到了一系列词组成的词表,但是里面的空字符串需要去除掉。可以计算每个字符串的长度,只返回长度大于零的字符串。并且可以采用python内嵌的字符串全部转换大小写的方法(.lower() or .upper())
代码:
import re
mySent='This book is the best book on Python or M.L. I have ever laid eyes upon.'
listOfTokens = re.split(r'\W+',mySent)
print([tok.lower() for tok in listOfTokens if len(tok) > 0])
结果:
4.6.2测试算法:使用朴素贝叶斯进行交叉验证
在bayes.py中添加代码:
import re
def textParse(bigString): #input is big string, #output is word list
listOfTokens = re.split(r'\W+', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 0]
import random
def spamTest():
docList=[]
classList=[]
fullText=[]
for i in range(1,26): #遍历25个文件
wordList=textParse(open('email/spam/%d.txt'%i,'r').read())
docList.append(wordList)
fullText.append(wordList)
classList.append(1) #垃圾邮件标记为1
wordList=textParse(open('email/ham/%d.txt'%i,'r').read())
docList.append(wordList)
fullText.append(wordList)
classList.append(0) #非垃圾邮件标记为0
vocabList=createVocabList(docList) #创建不重复词汇表
trainingSet=list(range(50)) #共50个文件,索引值0-50,列表格式
testSet=[]
for i in range(10):
randIndex=int(random.uniform(0,len(trainingSet))) #随机选择10个作为测试集
testSet.append(trainingSet[randIndex]) #添加到测试集列表
del(trainingSet[randIndex]) #删除该索引值
trainMat=[]
trainClasses=[]
for docIndex in trainingSet:
trainMat.append(setOfWords2Vec(vocabList,docList[docIndex])) #将选中的索引值对应样本添加到训练集
trainClasses.append(classList[docIndex])
p0V,p1V,pSpam=trainNB0(np.array(trainMat),np.array(trainClasses))
errorCount=0
for docIndex in testSet:
wordVector=setOfWords2Vec(vocabList,docList[docIndex])
if classifyNB(np.array(wordVector),p0V,p1V,pSpam)!=classList[docIndex]:
errorCount+=1 #统计分类错误的样本数量
print('分类错误的测试集:',docList[docIndex])
print('错误率:%.2f%%'%(float(errorCount)/len(testSet)*100))
测试代码:
import bayes
#测试
bayes.spamTest()