从简单的概率分类器开始,给出一些假设(条件独立性)学习朴素贝叶斯分类器。“朴素“意味最原始、最简单的假设。
优点:在数据较少的情况下仍然有效,可以处理多类别问题。
缺点:对输入数据的准备方式(数据的关联性)比较敏感。
适用数据类型:标称型数据
注:标称型:一般在有限的数据中取,而且只存在‘是’和‘否’两种不同的结果(一般用于分类) 数值型:可以在无限的数据中取,而且数值比较具体化,例如4.02,6.23这种值(一般用于回归分析)
朴素贝叶斯是贝叶斯决策理论的一部分,决策理论的核心思想是选择具有最高概率的决策。
朴素贝叶斯的一般过程
收集数据:可以使用任何方法。本章使用RSS源。
准备数据:需要数值型或者布尔型数据。
分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果更好。
训练算法:计算不同的独立特征的条件概率。
测试算法:计算错误率。
使用算法:一个常见的朴素贝叶斯应用是文档分类。可以在任意的分类场景中使用朴素贝叶斯分类器,不一定非要是文本。
注:朴素贝叶斯分类器通常有两种实现方式: 一种基于贝努利模型实现, 一种基于多项式模型实现。这里采用前一种,该实现方式中并不考虑词在文档中出现的次数,只考虑出不出现,因此在这个意义上相当于假设词是等权重的
制作数据集和标签,这里为了简化减少了数据量,可以添加
#定义数据集
def loadDataSet():
postingList=[['quit', 'buying', 'worthless', 'dog', 'food', 'stupid'],
['my', 'dog', 'has', 'flea', 'problems', 'help','please']]
classVec=[1,0]
return postingList,classVec
数据集文档中包含重复词条,利用set集合返回一个不重复的词表,首先创建一个空集合,遍历文档集合进行与运算添加到新集合里
#利用set集合创建不重复的列表
def createVocabList(dataSet):
vocabSet= set([])
for document in dataSet:
vocabSet=vocabSet|set(document)
return list(vocabSet)
该函数输入参数为词汇表及某个文档,输出的是文档向量,向量每一元素为1或哦,分别表示词汇表中的单词在输入文档中是否。首先一个和词汇表等长的0向量,遍历新文档单词,若词汇表含有该单词,对应索引处设值为1
#给inputdata,转换为文档类别向量
def set0fWord2Vec(vocabList,inputSet):
returnVec=[0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)]=1
else: print("the word:%d is not in my Vocabularay!"%word)
return returnVec
这里我新建一个主函数main()来运行演示结果
import bayes_copy
list0Posts,listClasses=bayes_copy.loadDataSet()
myVocabList=bayes_copy.createVocabList(list0Posts)
print(myVocabList)
a=bayes_copy.set0fWord2Vec(myVocabList,list0Posts[0])
print(a)
输出结果为
['help', 'food', 'problems', 'quit', 'my', 'stupid', 'dog', 'buying', 'please', 'has', 'flea', 'worthless']
[0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1]
由于词表没有排序,myVocabList为乱序,所以最终输出结果也为乱序
这里是对贝叶斯算法的分析
该函数的伪代码如下:
计算每个类别中的文档数目
对每篇训练文档:
对每个类别:
如果词条出现文档中一增加该词条的计数值
增加所有词条的计数值
对每个类别:
对每个词条:
将该词条的数目除以总词条数目得到条件概率
返回每个类别的条件概率
def trainNB0(trainMatrix, trainCategory):
"""
Function: 朴素贝叶斯分类器训练函数
Args: trainMatrix:文档矩阵
trainCategory:类别标签向量
Returns: p0Vect:非侮辱性词汇概率向量
p1Vect:侮辱性词汇概率向量
pAbusive:侮辱性文档概率
"""
#获得训练集中文档个数
numTrainDocs = len(trainMatrix)
#获得训练集中单词个数
numWords = len(trainMatrix[0])
#计算文档属于侮辱性文档的概率
pAbusive = sum(trainCategory)/float(numTrainDocs)
#初始化概率的分子变量,分母变量
#p0Num = zeros(numWords); p1Num = zeros(numWords)
#p0Denom = 0.0; p1Denom = 0.0
#为了避免概率为0的出现影响最终结果,将所有词初始化为1,分母初始化为2
#初始化概率的分子变量,分母变量
p0Num = ones(numWords); p1Num = ones(numWords)
p0Denom = 2.0; p1Denom = 2.0
#遍历训练集trainMatrix中所有文档
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
#对每个元素做除法求概率,为了避免下溢出的影响,对计算结果取自然对数
p1Vect = log(p1Num/p1Denom)
p0Vect = log(p0Num/p0Denom)
#返回两个类别概率向量和一个概率
return p0Vect, p1Vect, pAbusive
最终得到的类别概率向量为p(wi|c1)和p(wi|c0),w中的元素比较多利用Numpy数组快速计算。p1Num += trainMatrix[i]相当于trainMatrix[i]代表词汇表在某一文档中出现时将0改为1的向量
trainMat=[]
for postinDoc in list0Posts:
trainMat.append(bayes_copy.set0fWord2Vec(myVocabList,postinDoc))
这段很巧妙,搞了半天才看懂,trainMat的输出结果为[[0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1], [1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0]]这与p0V和p1V结果不一样,前者是全部词汇量myVocabList相对于list0Posts中每个postinDoc的变化,而后者是类别确定情况下啊,myVocabList中各元素出现该类别的概率。
前言:利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属千某个类别的概率,即计算p(w0|1)p (w1 |1)p(w2 |1)。如果其中某个概率值为0, 那么最后的乘积也为0。为降低这种影响,可以将 所有词的出现数初始化为1, 并将分母初始化为2。
在文本编辑器中打开bayes.py文件,并将trainNB0()的第4行和第5行修改为:
pONum = ones(numWords); plNum = ones(numWords)
pODenom = 2.0; plDenom = 2.0
另一个问题是当遇到多个概率值很小相乘时出现下溢出或者由于浮点数舍入导致的错误,可以利用对数进行解决。
plVect=log(plNum/plDenom)
pOVect = log(pONum/pODenom)
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
#向量元素相乘后求和再加到类别的对数概率上,等价于概率相乘
p1 = sum(vec2Classify * p1Vec) + log(pClass1)
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
上面的函数即为分类测试代码,vec2Classify为要分类的向量
import bayes_copy
from numpy import *
from bayes_copy import set0fWord2Vec,classifyNB
#创建词汇表
list0Posts,listClasses=bayes_copy.loadDataSet()
myVocabList=bayes_copy.createVocabList(list0Posts)
#训练部分
trainMat=[]
for postinDoc in list0Posts:
trainMat.append(bayes_copy.set0fWord2Vec(myVocabList,postinDoc))
p0V,p1V,pAb=bayes_copy.trainNB0(trainMat,listClasses)
#测试部分
testEntry=['love','my','dalmation']
thisDoc=array(set0fWord2Vec(myVocabList,testEntry))
print(testEntry,'classifid as:',classifyNB(thisDoc,p0V,p1V,pAb))
上面为测试代码,输出结果如下:
the word:love is not in my Vocabularay!
the word:dalmation is not in my Vocabularay!
['love', 'my', 'dalmation'] classifid as: 0
前言:
set-of-words model(词集模型)一个词在文档中只出现过一次
bag-of-words model(词袋模型)一个词在文档中出现过多次
将returnVec[vocabList.index(word)]=1
改为returnVec[vocabList.index(word)]+=1
(1) 收集数据:提供文本文件。
(2) 准备数据:将文本文件解析成词条向量。
(3) 分析数据:检查词条确保解析的正确性。
(4) 训练算法:使用我们之前建立的trainNB0( )函数。
(5) 测试算法:使用classifyNB(),并且构建一个新的测试函数来计算文档集的错误
(6) 使用算法:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上
通过string.split()进行切分文档为词向量
def textParse(bigString):
import re
#使用re去除除单词数字意外单位任何字符
listofTokens=re.split(r'\W*',bigString)
#将单词全部改为小写,且去除空格
return [tok.lower() for tok in listofTokens if len(tok)>2]
这里主要是增加了对数据的处理,以及对训练集与测试集的构建
def spamTest():
"""
Function: 贝叶斯垃圾邮件分类器
Args: 无
Returns: float(errorCount)/len(testSet):错误率
vocabList:词汇表
fullText:文档中全部单词
"""
#初始化数据列表
docList = []; classList = []; fullText = []
#导入文本文件
for i in range(1, 26):
#切分文本
wordList = textParse(open('D:\Program Files\JetBrains\PycharmProjects\deeplearning\ML-master\ML-master\Ch04\email/spam/%d.txt' % i).read())
#切分后的文本以原始列表形式加入文档列表
docList.append(wordList)
#切分后的文本直接合并到词汇列表
fullText.extend(wordList)
#标签列表更新
classList.append(1)
#切分文本
#print('i = :', i)
wordList = textParse(open('D:\Program Files\JetBrains\PycharmProjects\deeplearning\ML-master\ML-master\Ch04\email/ham/%d.txt' % i).read())
#切分后的文本以原始列表形式加入文档列表
docList.append(wordList)
#切分后的文本直接合并到词汇列表
fullText.extend(wordList)
#标签列表更新
classList.append(0)
#创建一个包含所有文档中出现的不重复词的列表
vocabList = createVocabList(docList)
#初始化训练集和测试集列表
trainingSet = list(range(50)); testSet = []
#随机构建测试集,随机选取十个样本作为测试样本,并从训练样本中剔除
for i in range(10):
#随机得到Index
randIndex = int(random.uniform(0, len(trainingSet)))
#将该样本加入测试集中
testSet.append(trainingSet[randIndex])
#同时将该样本从训练集中剔除
del(trainingSet[randIndex])
#初始化训练集数据列表和标签列表
trainMat = []; trainClasses = []
#遍历训练集
for docIndex in trainingSet:
#词表转换到向量,并加入到训练数据列表中
trainMat.append(bag0fWords2VecMN(vocabList, docList[docIndex]))
#相应的标签也加入训练标签列表中
trainClasses.append(classList[docIndex])
#朴素贝叶斯分类器训练函数
p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses))
#初始化错误计数
errorCount = 0
#遍历测试集进行测试
for docIndex in testSet:
#词表转换到向量
wordVector = bag0fWords2VecMN(vocabList, docList[docIndex])
#判断分类结果与原标签是否一致
if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
#如果不一致则错误计数加1
errorCount += 1
#并且输出出错的文档
print("classification error",docList[docIndex])
#打印输出信息
print('the erroe rate is: ', float(errorCount)/len(testSet))
#返回词汇表和全部单词列表
return docList,vocabList, fullText
总结:根据邮件进行正则化处理得到数据集,对数据集进行拆分得到训练集和测试集,然后进行遍历训练得到类别概率,最后进行分类,计算错误率,由于是随机选取电子邮件测试,所以每次错误率会不一样。
在这个最后的例子当中,我们将分别从美国的两个城市中选取一些人,通过分析这些人发布的征婚广告信息,来比较这两个城市的人们在广告用词上是否不同。如果结论确实是不同,那么他们各自常用的词是哪些?从人们的用词当中,我们能否对不同城市的人所关心的内容有所了解?
(1) 收集数据:从RSS源收集内容,这里需要对RSS源构建一个接口。
(2) 准备数据:将文本文件解析成词条向量。
(3) 分析数据:检查词条确保解析的正确性。
(4) 训练算法:使用我们之前建立的trainNB0( )函数。
(5) 测试算法:观察错误率,确保分类器可用。可以修改切分程序,以降低错误率,提高分类结果。
(6) 使用算法:构建一个完整的程序,封装所有内容。给定两个RSS源,该程序会显示最常用的公共词。
import feedparser
ny=feedparser.parse ('http://newyork.craigslist.org/stp/index.rss')
sf=feedparser.parse ('http://sfbay.craigslist.org/stp/index.rss ')
RSS源要在函数外导入,这样做的原因是RSS源会随时间而改变。如果想通过改变代码来比较程序执行的差异,就应该使用相同的输
入。重新加载RSS源就会得到新的数据,但很难确定是代码原因还是输入原因导致输出结果的改变。
def calcMostFreq(vocabList, fullText):
"""
Function: 计算出现频率
Args: vocabList:词汇表
fullText:全部词汇
Returns: sortedFreq[:30]:出现频率最高的30个词
"""
import operator
freqDict = {}
for token in vocabList:
freqDict[token] = fullText.count(token)
sortedFreq = sorted(freqDict.items(), key=operator.itemgetter(1), reverse=True)
return sortedFreq[:30]
def localWords(feed1, feed0):
"""
Function: RSS源分类器
Args: feed1:RSS源
feed0:RSS源
Returns: vocabList:词汇表
p0V:类别概率向量
p1V:类别概率向量
"""
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(1)
#切分文本
wordList = textParse(feed0['entries'][i]['summary'])
#切分后的文本以原始列表形式加入文档列表
docList.append(wordList)
#切分后的文本直接合并到词汇列表
fullText.extend(wordList)
#标签列表更新
classList.append(0)
#获得词汇表
vocabList = createVocabList(docList)
#获得30个频率最高的词汇
top30Words = calcMostFreq(vocabList, fullText)
#去掉出现次数最高的那些词
for pairW in top30Words:
if pairW[0] in vocabList: vocabList.remove(pairW[0])
trainingSet = list(range(2*minLen)); testSet = []
#随机构建测试集,随机选取二十个样本作为测试样本,并从训练样本中剔除
for i in range(20):
#随机得到Index
randIndex = int(random.uniform(0, len(trainingSet)))
#将该样本加入测试集中
testSet.append(trainingSet[randIndex])
#同时将该样本从训练集中剔除
del(trainingSet[randIndex])
#初始化训练集数据列表和标签列表
trainMat = []; trainClasses = []
#遍历训练集
for docIndex in trainingSet:
#词表转换到向量,并加入到训练数据列表中
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
#相应的标签也加入训练标签列表中
trainClasses.append(classList[docIndex])
#朴素贝叶斯分类器训练函数
p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses))
#初始化错误计数
errorCount = 0
#遍历测试集进行测试
for docIndex in testSet:
#词表转换到向量
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
#判断分类结果与原标签是否一致
if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
#如果不一致则错误计数加1
errorCount += 1
#打印输出信息
print('the erroe rate is: ', float(errorCount)/len(testSet))
#返回词汇表和两个类别概率向量
return vocabList, p0V, p1V
难点解析:freqDict.items()将freqDict字典分解为元组列表,operator.itemgetter(1)按照第二个元素的次序对元组进行排序,reverse=True是逆序,即按照从大到小的顺序排列。第一个函数遍历词汇表中的每个词并统计它在文本中出现的次数,然后根据出现次数从高到低对词典进行排序。
def getTopWords(ny, sf):
"""
Function: 最具表征性的词汇显示函数
Args: ny:RSS源
sf:RSS源
Returns: 打印信息
"""
import operator
#RSS源分类器返回概率
vocabList, p0V, p1V=localWords(ny, sf)
#初始化列表
topNY = []; topSF = []
#设定阈值,返回大于阈值的所有词,如果输出信息太多就提高阈值
for i in range(len(p0V)):
if p0V[i] > -4.5 : topSF.append((vocabList[i], p0V[i]))
if p1V[i] > -4.5 : topNY.append((vocabList[i], p1V[i]))
#排序
sortedSF = sorted(topSF, key=lambda pair: pair[1], reverse=True)
print("SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**")
#打印
for item in sortedSF:
print(item[0])
#排序
sortedNY = sorted(topNY, key=lambda pair: pair[1], reverse=True)
print("NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**")
#打印
for item in sortedNY:
print(item[0])
函数getTopWords()使用两个RSS源作为输入,然后训练并测试朴素贝叶 斯分类器,返回使用的概率值。然后创建两个列表用于元组的存储。与之前返回排名最高的X个 单词不同,这里可以返回大于某个阀值的所有词。这些元组会按照它们的条件概率进行排序。