一、朴素贝叶斯分类器原理
这一部分的内容来自《机器学习-周志华》。
(1)朴素贝叶斯表达式的推导
1.假设有分类器h(x)->c,将样本x归类为类c,那么最优的贝叶斯分类器为

2.上式可以转化为
3.由于P(x)对于所有类都相同,因此求h*(x)=arg maxP(c|x)就是求如下式子
(2)求解朴素贝叶斯分类器
(3)例子
(4)拉普拉斯修正
二、使用朴素贝叶斯分类器对文本进行分类
本例子是手工制作六个短文本及其对应标签(标签1-侮辱性,标签0-非侮辱性),然后使用该6条数据训练朴素贝叶斯分类进,然后进行分类测试:测试某条文本信息是否具有侮辱性。
(1)将6个短文本数据转化为词向量
'''
def loadDataSet():创建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', 'worthless', 'dog', 'food', 'stupid']]
classVec = [,,,,,] #0代表非侮辱类,1代表侮辱类
return postingList,classVec
'''
def createVocabList(dataSet):该函数的作用是创建词汇表vocabSet。
dataSet的格式是函数loadDataSet()返回的格式,可以将
dataSet出现的所有单词聚集到一个set中(每种单词只会出现一次)
'''
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):inputSet只是某个短文本,该函数将inputSet按照词汇表vocabList的格式转化为词向量。
例如vocabList=['cute','love','help'],当短文本出现单词'cute',但没出息单词'love'、'help',则
该行短文本对应的词向量为[1,0,0]
'''
def setOfWords2Vec(vocabList, inputSet):
returnVec = []*len(vocabList)
for word in inputSet:
if word in vocabList: #当该word在词汇表vocabList中,则对应位置标记为1,注意如果当前短文本inputSet某个单词
# 出现了多次,那么最后该单词在词向量对应位置还是标记为1
returnVec[vocabList.index(word)] =
else: print ("the word: %s is not in my Vocabulary!" % word)
return returnVec
展示结果:
(2)使用数据集对应的词向量集训练贝叶斯分类器
训练贝叶斯分类器,实质就是分别统计在标签为1(侮辱性)的短文本中各种单词出现的频率、在标签为0(非侮辱性)的短文本中各种单词出现的概率。
所用函数:
'''
def trainNB(trainMatrix, trainCategory): 其中trainMatrix是词向量矩阵, trainCategory是每个词向量对应的标签。
返回p0Vect(向量p0Vect表示在非侮辱性文档中,各种单词出现的频率),
p1Vect(向量p1Vect表示在侮辱性文档中,各种单词出现的频率),
pAbusive(侮辱性的短文本在总体短文本数量中的比例)
'''
def trainNB(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) #获取词向量矩阵的行数,就是获取短文本的个数(每个短文本对应一个词向量)
numWords = len(trainMatrix[]) #词向量的长度就是单词表的长度,这也是获取单词表的单词个数
pAbusive = sum(trainCategory) / float(numTrainDocs) #sum(trainCategory)用来统计标签中1(表示侮辱性)的个数,这句
# 代码用来计算侮辱性的短文本在总体短文本数量中的比例
p0Num = zeros(numWords); #创建长度等于词向量长度的0向量p0Num
p1Num = zeros(numWords) #创建长度等于词向量长度的0向量p1Num
p0Denom = ;
p1Denom =
for i in range(numTrainDocs):
if trainCategory[i] == :
p1Num += trainMatrix[i] #统计侮辱性的文档中,各个单词出现的次数。最终返回的向量格式与词汇表格式一样,每个
#单元对应一种单词,每个单元对应的数值就是该种单词出现的次数
p1Denom += sum(trainMatrix[i]) #统计侮辱性的文档中的单词总数
else:
p0Num += trainMatrix[i] #统计非侮辱性的文档中,各个单词出现的次数。
p0Denom += sum(trainMatrix[i]) #统计非侮辱性的文档中的单词总数
p1Vect = p1Num / p1Denom #向量p1Vect表示在侮辱性文档中,各种单词出现的频率
p0Vect = p0Num / p0Denom #向量p0Vect表示在非侮辱性文档中,各种单词出现的频率
return p0Vect, p1Vect, pAbusive
结果:
(3)使用贝叶斯分类器进行分类
这里测试的测试短文本[‘love’, ‘my’, ‘dalmation’]、[‘stupid’, ‘garbage’],要求也贝斯分类器能够将第一个文本分类为非侮辱性(标签为0),将第二个文本分类为侮辱性(标签为1)。
所用函数:
'''
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):vec2Classify是待分类短文本对应的词向量。p0Vec表示在非侮辱
性文档中,各种单词出现的频率。p1Vec表示在侮辱性文档中,各种单词出现的频率。
该函数可能判断vec2Classify是属于侮辱性文本还是非侮辱性文本
'''
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
p1=prod(vec2Classify*p1Vec)*pClass1 #vec2Classify*p1Vec表示在p1Vec中,将没有在待分类词向量vec2Classify中出现的单
#词的频率置0。prod(vec2Classify*p1Vec)表示将向量(vec2Classify*p1Vec)的各个元素相乘。
#这句代码是求vec2Classify属于侮辱性短文本的概率
p0=prod(vec2Classify*p0Vec)*(-pClass1) #这句代码是求vec2Classify属于非侮辱性短文本的概率
if p1>p0:
return
else:
return
'''
testingNB():使用函数loadDataSet()的返回值作为训练集,分别测试短文本['love', 'my', 'dalmation']、['stupid', 'garbage']
是属于侮辱性文本还是非侮辱性文本
'''
def testingNB():
listOPosts,listClasses = loadDataSet() #获取短文本训练集
myVocabList = createVocabList(listOPosts) #获取训练集对应的单词表
trainMat=[]
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc)) #将短文本训练集转化对应的词向量集
p0V,p1V,pAb = trainNB(array(trainMat),array(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))
结果:
结果不尽人意,贝斯分类器能够将第一、二个文本都分类为非侮辱性(标签为0)【具体原因,参见《周志华-机器学习》中朴素贝叶斯分类器需要拉普拉斯修正的原因】,这就需要作拉普拉斯修正,如何修正如下文。
(4)对贝叶斯分类器进行修改,使其更适用于实际情况
1.对函数trainNB()进行拉普拉斯修正(具体原理见“一、朴素贝叶斯分类器原理-(4)拉普拉斯修正”)
p0Num=ones(numWords); p1Num=ones(numWords)
p0Denom=2.0; p1Denom=2.0
2.将函数trainNB()中的词频率向量 p1Vect、p0Vect中各个元素取对数
p1Vect = log(p1Num/p1Denom)
p0Vect = log(p0Num/p0Denom)
词向量中各元素对应的是每种词出现的频率,在贝叶斯求概率时,需要将这些词频率相乘,但是当这些频率很小时,程序会下溢出(下溢出:指当要表示的数据的绝对值小于计算机所能表示的最小绝对值时,程序会四舍五入得到0)。为了解决下溢出,可以将频率取对数解决。例如两个频率为a,b,则取对数后,乘法需要变成加法:ln(a*b) = ln(a)+ln(b),将函数classifyNB(vec2Classify,p0Vec,p1Vec,pClass1),修改为classifyNB0(vec2Classify, p0Vec, p1Vec, pClass1)。最后分类的准则是比较向量p1Vect与p0Vect的中各元素相乘后的大小关系,取对数后,虽然向量p1Vect与p0Vect的中各元素的值发生了改变,但是不影响最终的判断结果。
以下是取对数前后的函数图象,它们有相同的单调区间,且在相同的x值上取得极值。
3.修改后对应的函数
'''
def trainNB0(trainMatrix, trainCategory): 其中trainMatrix是词向量矩阵, trainCategory是每个词向量对应的标签。
返回p0Vect(向量p0Vect表示在非侮辱性文档中,各种单词出现的频率的对数),
p1Vect(向量p1Vect表示在侮辱性文档中,各种单词出现的频率的对数),
pAbusive(侮辱性的短文本在总体短文本数量中的比例)
'''
def trainNB0(trainMatrix,trainCategory):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[])
pAbusive = sum(trainCategory)/float(numTrainDocs)
p0Num = ones(numWords); p1Num = ones(numWords) #进行拉普拉斯修正,change to ones()
p0Denom = ; p1Denom = #进行拉普拉斯修正,change to 2.0
for i in range(numTrainDocs):
if trainCategory[i] == :
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = log(p1Num/p1Denom) #防止相乘的因子过小,造成相乘结果为0,因此取对数
p0Vect = log(p0Num/p0Denom) #防止相乘的因子过小,造成相乘结果为0,因此取对数
return p0Vect,p1Vect,pAbusive
'''
classifyNB0(vec2Classify,p0Vec,p1Vec,pClass1):vec2Classify是待分类短文本对应的词向量。这里的p0Vec,p1Vec是从函数
trainNB0(trainMatrix,trainCategory)获得,因此p0Vec表示在非侮辱性文档中,各种单词出现的频率的对数。p1Vec表示在侮辱性文
档中,各种单词出现的频率的对数。该函数可能判断vec2Classify是属于侮辱性文本还是非侮辱性文本
'''
def classifyNB0(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1)#vec2Classify*p1Vec表示在p1Vec中,将没有在待分类词向量vec2Classify中出现的单
#词的频率的对数置0。由于p1Vec是单词出现频率的对数,因此sum()将这些对数相加,相
#当于将频率相乘。该函数原理与函数classifyNB(vec2Classify,p0Vec,p1Vec,pClass1)相同
p0 = sum(vec2Classify * p0Vec) + log( - pClass1) #与上一句代码原理相同,不过这句代码是计算属于非侮辱性短文本的概率
if p1 > p0:
return
else:
return
'''
testingNB0():使用函数loadDataSet()的返回值作为训练集,分别测试短文本['love', 'my', 'dalmation']、['stupid', 'garbage']
是属于侮辱性文本还是非侮辱性文本
'''
def testingNB0():
listOPosts,listClasses = loadDataSet() #获取短文本训练集
myVocabList = createVocabList(listOPosts) #获取训练集对应的单词表
trainMat=[]
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc)) #将短文本训练集转化对应的词向量集
p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses))
testEntry = ['love', 'my', 'dalmation']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print (testEntry,'classified as: ',classifyNB0(thisDoc,p0V,p1V,pAb))
testEntry = ['stupid', 'garbage']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print (testEntry,'classified as: ',classifyNB0(thisDoc,p0V,p1V,pAb))
4.运行结果
(5)将词集模型(set-of-words model)修改为词袋模型(bag-of-words model)
'''
def bagOfWords2VecMN(vocabList, inputSet):该函数是基于词袋模型的,与函数setOfWords2Vec(vocabList, inputSet)唯一
不同的是,每当遇到一个单词则增加词向量的对应值,而不只是将对应的值置为1
'''
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = []*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] +=
return returnVec
三、使用朴素贝叶斯过滤垃圾邮件
本例子的数据集为50封邮件(25封垃圾邮件、25封非垃圾邮件),随机抽取10封作为测试集,剩下的40封作为训练集来训练贝叶斯分类器。
1. 分词函数函数textParse(bigString)
'''
def textParse(bigString): 输入长字符串bigString,返回一个单词列表(每个单词的长度>2)
'''
def textParse(bigString):
import re
listOfTokens = re.split(r'\W*', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > ]
2.对垃圾邮件使用交叉验证法测试分类结果
交叉验证法:这个函数随机从50封邮件中(25封垃圾邮件、25封非垃圾邮件)中随机抽取10封作为测试集,剩下的40封作为训练集。
'''
spamTest():从文件夹email下获取25个垃圾邮件和25个非垃圾邮件的txt文件,随机地从这50个邮件中抽取10个作为测试集,剩下的40个
作为训练集,然后使用基于词袋模型的词向量进行贝叶斯分类。
'''
def spamTest():
docList=[]; classList = []; fullText =[]
for i in range(,):
wordList = textParse(open('email/spam/%d.txt' % i).read())
docList.append(wordList)
fullText.extend(wordList)
classList.append()
wordList = textParse(open('email/ham/%d.txt' % i).read())
docList.append(wordList)
fullText.extend(wordList)
classList.append()
vocabList = createVocabList(docList)#create vocabulary
trainingSet = list(range()); #trainingSet表示docList中参与训练的短文本词向量的下标
testSet=[]
for i in range(): #从训练集trainingSet中随机抽取10个下标添加到测试集,并将这10个下标从训练集中删除
randIndex = int(random.uniform(,len(trainingSet)))
testSet.append(trainingSet[randIndex])
del(trainingSet[randIndex])
trainMat=[]; trainClasses = []
for docIndex in trainingSet:#将docList[]中参与训练的短文本转化为基于词袋模型的词向量
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex]) #将参与训练的短文本对应的标签添加到数组trainClasses中
p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
errorCount =
for docIndex in testSet: #对10个用于测试短文本进行贝叶斯分类
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
if classifyNB0(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
errorCount +=
print ("classification error",docList[docIndex])
print ('the error rate is: ',float(errorCount)/len(testSet))
#return vocabList,fullText
四、使用朴素贝叶斯分类器分析征婚广告信息
征婚广告信息分别从来与两个城市(newyork、sfbay)的RSS源获得,本例子共有两个任务:使用贝叶斯分类器测试某条数据是来自newyork还是sfbay的、分别找出newyork和sfbay最具有代表性的频繁用词。
(1)获取RSS源数据 —- feedparser包的parse函数示例
从浏览器中输入”https://newyork.craigslist.org/stp/index.rss“,页面如下面白色图片。黑色图片中的ny[‘entries’][0][‘summary’]表示获取0号item的description(下图红框部分)
(2)测试某条数据是来自newyork还是sfbay的
首先使用函数calcMostFreq(vocabList,fullText)去除高频词,因为高频词是两个不同地区的共同点,难以作为某条信息来源的区分依据。然后使用函数localWords(feed1,feed0)进行区分。
'''
calcMostFreq(vocabList,fullText):vocabList是词汇表,fullText是所有的文本。该函数返回出现频率最高的前30个单词。
'''
def calcMostFreq(vocabList,fullText):
import operator
freqDict = {}
for token in vocabList:
freqDict[token]=fullText.count(token)
sortedFreq = sorted(freqDict.items(), key=operator.itemgetter(), reverse=True)
return sortedFreq[:]
'''
def localWords(feed1,feed0):原理和函数spamTest()一样,不同的是从两个不同的RSS源feed1,feed0获取文本信息。
该函数将总体数据集文本fullText中,统计出前30个出现频率最高的单词,从词汇表vocabList中删除,之后的贝叶斯分类中这30个单词
不给予考虑(因为,出现频率高,说明该单词的出现与否,不太能表明该文本是来自feed1还是feed0)。
该函数从数据集当中随机抽取20个作为测试集,剩下的数据作为训练集。
'''
def localWords(feed1,feed0):
import feedparser
docList=[]; classList = []; fullText =[]
minLen = min(len(feed1['entries']),len(feed0['entries']))
for i in range(minLen):
wordList = textParse(feed1['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append() #NY is class 1
wordList = textParse(feed0['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append()
vocabList = createVocabList(docList)#create vocabulary
top30Words = calcMostFreq(vocabList,fullText) #remove top 30 words
for pairW in top30Words:
if pairW[] in vocabList:
vocabList.remove(pairW[])
trainingSet = list(range(*minLen)); testSet=[] #create test set
for i in range():
randIndex = int(random.uniform(,len(trainingSet)))
testSet.append(trainingSet[randIndex])
del(trainingSet[randIndex])
trainMat=[]; trainClasses = []
for docIndex in trainingSet:#train the classifier (get probs) trainNB0
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
errorCount =
for docIndex in testSet: #classify the remaining items
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
if classifyNB0(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
errorCount +=
print ('the error rate is: ',float(errorCount)/len(testSet))
return vocabList,p0V,p1V
(3)找出newyork、sfbay两个城市最具有表征性的词语
经过测试,p0V向量代表的城市使用阈值-4.7(由于之前将词频率取对数了,因此词频率的对数为负数)最能代表sfbay,p1V向量代表的城市使用阈值-4.6最能代表newyork。
1.所用函数
'''
def getTopWords(ny,sf):通过函数localWords(feed1,feed0)获取去除出现频率最高的前30个单词所对应的两个
词频率向量p0V(对应的词来源于sf),p1V(对应的词来源于ny)。该函数将向量p0V,p1V中出现的对数频率>-6.0的单词输出,这
些单词分别最能代表城市ny和城市sf
'''
def getTopWords(ny,sf):
import operator
vocabList,p0V,p1V=localWords(ny,sf)
topNY=[]; topSF=[]
for i in range(len(p0V)):
if p0V[i] > - : topSF.append((vocabList[i],p0V[i]))
if p1V[i] > - : topNY.append((vocabList[i],p1V[i]))
sortedSF = sorted(topSF, key=lambda pair: pair[], reverse=True)
print ("最能代表sfbay的单词为:")
listSF=[]
for item in sortedSF:
listSF.append(item[])
print (listSF)
sortedNY = sorted(topNY, key=lambda pair: pair[], reverse=True)
print ("最能代表newyork的单词为:")
listNY=[]
for item in sortedNY:
listNY.append(item[])
print(listNY)
2.结果