朴素贝叶斯模型,说白了就是在事件独立的情况下,计算某一特征属于哪个类别的概率大,那么就把这一特征归于这一类。
公式:p(c|wi) = p(wi|c)*p(c) / p(wi)
如果:p(c1|wi)>p(c2|wi),则wi属于类c1
p(c1|wi)<p(c2|wi),则wi属于类c2
# coding=utf-8
from numpy import *
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 #原始训练文档,各文档所属分类
'''创建词集
dataSet:原始文档
返回值:词集
['cute', 'love', 'help', 'garbage', 'quit', 'I', 'problems', 'is', 'park', 'stop', 'flea', 'dalmation', 'licks', 'food', 'not', 'him', 'buying', 'posting', 'has', 'worthless', 'ate', 'to', 'maybe', 'please', 'dog', 'how', 'stupid', 'so', 'take', 'mr', 'steak', 'my']
'''
def createVocabList(dataSet):
vocabSet = set([]) #set最后的结果就是这种形式的,若加入的每个元素都是list类型,则将会把多个list合并为一个list;若加入的元素是普通字符串,则将会合并字符串中的每一个字符
for document in dataSet:
# print set(document)
vocabSet = vocabSet | set(document) #求并集
# print vocabSet
#print list(vocabSet)
return list(vocabSet) #将set转化为list
'''
为每一个文档创建词集01表,即,每个文档都对应一个词集01向量,若词集中的某一个单词在这个文档中出现,则这个文档向量中的这个位置为1
vocabList:词集
inputSet:输入文档
returnVec:输入文档对应的词集01表
如:['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'] ->
[0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1]
'''
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 doesn't exist in vocabList" % word
return returnVec
'''
训练函数
trainMatrix:文档矩阵
[[0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0],
[1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0]]
trainCategory:是否为侮辱性文档 向量(侮辱性矩阵,即标记为1,则表示为侮辱性文档。sum即求向量累加和,将为1的全部加起来,即为侮辱性文档数目)
p0Vect:每篇文档的词汇非侮辱性权重(p(wi|c),即在这篇文档是非侮辱性文档的前提下,某个词汇出现的概率)
p1Vect:每篇文档词汇的侮辱性权重
pAbusive:为侮辱性文档的概率
[-3.04452244 -3.04452244 -3.04452244 -2.35137526 -2.35137526 -3.04452244
-3.04452244 -3.04452244 -2.35137526 -2.35137526 -3.04452244 -3.04452244
-3.04452244 -2.35137526 -2.35137526 -2.35137526 -2.35137526 -2.35137526
-3.04452244 -1.94591015 -3.04452244 -2.35137526 -2.35137526 -3.04452244
-1.94591015 -3.04452244 -1.65822808 -3.04452244 -2.35137526 -3.04452244
-3.04452244 -3.04452244]
[-3.04452244 -3.04452244 -3.04452244 -2.35137526 -2.35137526 -3.04452244
-3.04452244 -3.04452244 -2.35137526 -2.35137526 -3.04452244 -3.04452244
-3.04452244 -2.35137526 -2.35137526 -2.35137526 -2.35137526 -2.35137526
-3.04452244 -1.94591015 -3.04452244 -2.35137526 -2.35137526 -3.04452244
-1.94591015 -3.04452244 -1.65822808 -3.04452244 -2.35137526 -3.04452244
-3.04452244 -3.04452244]
0.5
'''
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix)#文档总数
numWords = len(trainMatrix[0])#词集中词汇的数目
pAbusive = sum(trainCategory) / float(numTrainDocs)
p0Num = ones(numWords)#向量,长度为整个词集的长度,表示属于侮辱性词汇文档中,词集中每个词汇出现的次数。本来为zeros,使用拉普拉斯平滑,改为ones
p1Num = ones(numWords)
p0Denom = 2.0#训练词集中,所有词汇的总数目。本来为0.0,使用拉普拉斯平滑,改为2
p1Denom = 2.0
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 = log(p1Num / p1Denom)#p1Vect = p1Num / p1Denom,为避免下溢,取对数
p0Vect = log(p0Num / p0Denom)
return p0Vect, p1Vect, pAbusive
'''
分类函数
vec2Classify:要分类的文档
p0Vec:非侮辱性文档词集的威胁权重(p(wi|c0)组成的向量)
p1Vec:侮辱性文档词集的威胁权重
pClass1:侮辱性文档出现的概率
'''
def classifyNB(vec2Classify, p0Vect, p1Vect, pClass1):
p1 = sum(vec2Classify * p1Vect) + log(pClass1)#相乘指两个向量对应相乘,结果即为p(w1|c)+p(w2|c)+…+p(wn|c)(这里都取了对数),待分类向量若为某一特征为0,则这一项就刚好不存在了;若为1,乘以概率还为概率本身
p0 = sum(vec2Classify * p0Vect) + log(1.0 - pClass1)
print sum(vec2Classify * p1Vect), sum(vec2Classify * p0Vect)
print '2:',log(pClass1), log(1.0 - pClass1)
print p1, p0
if p1 > p0:
return 1
else:
return 0
'''
解析文本
bigString:一封邮件,即一个原始文档
返回文档分词后的列表
'''
def testParse(bigString):
import re
listOfTokens = re.split(r'\W*', bigString) #大写W是非单词字符,即以非单词字符为分隔符
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
'''
利用建立好的分类模型对广告分类器进行训练和测试
'''
def spamTest():
docList = []#文档列表,包含多个列表的列表
classList = []#类别列表
# fullText = []#词集列表
for i in range(1, 26):
wordList = testParse(open('H:/email/spam/%d.txt' % i).read())
docList.append(wordList)
#fullText.extend(wordList)
classList.append(1)
wordList = testParse(open('H:/email/ham/%d.txt' % i).read())
docList.append(wordList)
# fullText.extend(wordList)
classList.append(0)
vocabList = createVocabList(docList)
trainingSet = range(50)#训练文档编号
testSet = []#测试文档编号
for i in range(10):
randIndex = int(random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[randIndex])
del(trainingSet[randIndex])
trainMatrix = []
trainClasses = []
for docIndex in trainingSet:
returnVec = setOfWords2Vec(vocabList, docList[docIndex])
trainMatrix.append(returnVec)
trainClasses.append(classList[docIndex])
p0Vect, p1Vect, pSpam = trainNB0(array(trainMatrix), array(trainClasses)) # 训练
#错误率检测,使用之前随机选择的测试文档进行测试
errorCount = 0
for docIndex in testSet:
wordVec = setOfWords2Vec(vocabList, docList[docIndex])
if classifyNB(array(wordVec), p0Vect, p1Vect, pSpam) != classList[docIndex]:
errorCount += 1
print docList[docIndex]
print 'the error rate is : ' , float(errorCount) / len(testSet)
'''
利用建立好的分类模型对侮辱性语言分类器进行训练和测试
'''
def abusiveTest():
postingList, classVec = loadDataSet()#导入原始数据
vocabList = createVocabList(postingList)#创建词集向量
trainMatrix = []#创建文档词集01矩阵
for document in postingList:
returnVec = setOfWords2Vec(vocabList, document)
trainMatrix.append(returnVec)
p0Vect, p1Vect, pAbusive = trainNB0(trainMatrix, classVec)#训练
#测试
testEntry = ['love', 'my', 'haha']
thisDoc = setOfWords2Vec(vocabList, testEntry)
print thisDoc
print testEntry, 'is:' , classifyNB(thisDoc, p0Vect, p1Vect, pAbusive)
testEntry = ['stupid', 'garbage']
thisDoc = setOfWords2Vec(vocabList, testEntry)
print thisDoc
print testEntry, 'is:', classifyNB(thisDoc, p0Vect, p1Vect, pAbusive)
def main():
abusiveTest()
#spamTest()
if __name__ == '__main__':
main()
以下内容来自:
http://www.cnblogs.com/zy230530/p/6847243.html
一,引言
前两章的KNN分类算法和决策树分类算法最终都是预测出实例的确定的分类结果,但是,有时候分类器会产生错误结果;本章要学的朴素贝叶斯分类算法则是给出一个最优的猜测结果,同时给出猜测的概率估计值。
1 准备知识:条件概率公式
相信学过概率论的同学对于概率论绝对不会陌生,如果一时觉得生疏,可以查阅相关资料,在这里主要是想贴出条件概率的计算公式:
P(A|B)=P(A,B)/P(B)=P(B|A)*P(A)/P(B)
2 如何使用条件概率进行分类
假设这里要被分类的类别有两类,类c1和类c2,那么我们需要计算概率p(c1|x,y)和p(c2|x,y)的大小并进行比较:
如果:p(c1|x,y)>p(c2|x,y),则(x,y)属于类c1
p(c1|x,y)<p(c2|x,y),则(x,y)属于类c2
我们知道p(x,y|c)的条件概率所表示的含义为:已知类别c1条件下,取到点(x,y)的概率;那么p(c1|x,y)所要表达的含义呢?显然,我们同样可以按照条件概率的方法来对概率含义进行描述,即在给定点(x,y)的条件下,求该点属于类c1的概率值。那么这样的概率该如何计算呢?显然,我们可以利用贝叶斯准则来进行变换计算:
p(ci|x,y)=p(x,y|ci)*p(ci)/p(x,y)
利用上面的公式,我们可以计算出在给定实例点的情况下,分类计算其属于各个类别的概率,然后比较概率值,选择具有最大概率的那么类作为点(x,y)的预测分类结果。
以上我们知道了通过贝叶斯准则来计算属于各个分类的概率值,那么具体而言,就是计算贝叶斯公式中的三个概率,只要得到了这三个概率值,显然我们就能通过贝叶斯算法预测分类的结果了。因此,到了这里,我们就知道了朴树贝叶斯算法的核心所在了。
3 朴素贝叶斯中朴素含义
"朴素"含义:本章算法全称叫朴素贝叶斯算法,显然除了贝叶斯准备,朴素一词同样重要。这就是我们要说的条件独立性假设的概念。条件独立性假设是指特征之间的相互独立性假设,所谓独立,是指的是统计意义上的独立,即一个特征或者单词出现的可能性与它和其他单词相邻没有关系。举个例子来说,假设单词bacon出现在unhealthy后面是与delisious后面的概率相同。当然,我们知道其实并不正确,但这正是朴素一词的含义。同时,朴素贝叶斯另外一个含义是,这些特征同等重要。虽然这些假设都有一定的问题,但是朴素贝叶斯的实际效果却很好。
二,朴素贝叶斯完成文档分类
朴素贝叶斯的一个非常重要的应用就是文档分类。在文档分类中,整个文档(比如一封电子邮件)是实例,那么邮件中的单词就可以定义为特征。说到这里,我们有两种定义文档特征的方法。一种是词集模型,另外一种是词袋模型。顾名思义,词集模型就是对于一篇文档中出现的每个词,我们不考虑其出现的次数,而只考虑其在文档中是否出现,并将此作为特征;假设我们已经得到了所有文档中出现的词汇列表,那么根据每个词是否出现,就可以将文档转为一个与词汇列表等长的向量。而词袋模型,就是在词集模型的基础上,还要考虑单词在文档中出现的次数,从而考虑文档中某些单词出现多次所包含的信息。
好了,讲了关于文档分类的特征描述之后,我们就可以开始编代码,实现具体的文本分类了
1 拆分文本,准备数据
要从文本中获取特征,显然我们需要先拆分文本,这里的文本指的是来自文本的词条,每个词条是字符的任意组合。词条可以为单词,当然也可以是URL,IP地址或者其他任意字符串。将文本按照词条进行拆分,根据词条是否在词汇列表中出现,将文档组成成词条向量,向量的每个值为1或者0,其中1表示出现,0表示未出现。
接下来,以在线社区的留言为例。对于每一条留言进行预测分类,类别两种,侮辱性和非侮辱性,预测完成后,根据预测结果考虑屏蔽侮辱性言论,从而不影响社区发展。
词表到向量的转换函数
#---------------------------从文本中构建词条向量-------------------------
#1 要从文本中获取特征,需要先拆分文本,这里特征是指来自文本的词条,每个词
#条是字符的任意组合。词条可以理解为单词,当然也可以是非单词词条,比如URL
#IP地址或者其他任意字符串
# 将文本拆分成词条向量后,将每一个文本片段表示为一个词条向量,值为1表示出现
#在文档中,值为0表示词条未出现
#导入numpy
from numpy import *
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'],
['my','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):
#新建一个存放词条的集合
vocabSet=set([])
#遍历文档集合中的每一篇文档
for document in dataSet:
#将文档列表转为集合的形式,保证每个词条的唯一性
#然后与vocabSet取并集,向vocabSet中添加没有出现
#的新的词条
vocabSet=vocabSet|set(document)
#再将集合转化为列表,便于接下来的处理
return list(vocabSet)
#根据词条列表中的词条是否在文档中出现(出现1,未出现0),将文档转化为词条向量
def setOfWords2Vec(vocabSet,inputSet):
#新建一个长度为vocabSet的列表,并且各维度元素初始化为0
returnVec=[0]*len(vocabSet)
#遍历文档中的每一个词条
for word in inputSet:
#如果词条在词条列表中出现
if word in vocabSet:
#通过列表获取当前word的索引(下标)
#将词条向量中的对应下标的项由0改为1
returnVec[vocabSet.index(word)]=1
else: print('the word: %s is not in my vocabulary! '%'word')
#返回inputet转化后的词条向量
return returnVec
需要说明的是,上面函数creatVocabList得到的是所有文档中出现的词汇列表,列表中没有重复的单词,每个词是唯一的。
2 由词向量计算朴素贝叶斯用到的概率值
这里,如果我们将之前的点(x,y)换成词条向量w(各维度的值由特征是否出现的0或1组成),在这里词条向量的维度和词汇表长度相同。
p(ci|w)=p(w|ci)*p(ci)/p(w)
我们将使用该公式计算文档词条向量属于各个类的概率,然后比较概率的大小,从而预测出分类结果。
具体地,首先,可以通过统计各个类别的文档数目除以总得文档数目,计算出相应的p(ci);然后,基于条件独立性假设,将w展开为一个个的独立特征,那么就可以将上述公式写为p(w|ci)=p(w0|ci)*p(w1|ci)*...p(wN|ci),这样就很容易计算,从而极大地简化了计算过程。
函数的伪代码为:
计算每个类别文档的数目
计算每个类别占总文档数目的比例
对每一篇文档:
对每一个类别:
如果词条出现在文档中->增加该词条的计数值#统计每个类别中出现的词条的次数
增加所有词条的计数值#统计每个类别的文档中出现的词条总数
对每个类别:
将各个词条出现的次数除以类别中出现的总词条数目得到条件概率
返回每个类别各个词条的条件概率和每个类别所占的比例
代码如下:
#训练算法,从词向量计算概率p(w0|ci)...及p(ci)
#@trainMatrix:由每篇文档的词条向量组成的文档矩阵
#@trainCategory:每篇文档的类标签组成的向量
def trainNB0(trainMatrix,trainCategory):
#获取文档矩阵中文档的数目
numTrainDocs=len(trainMatrix)
#获取词条向量的长度
numWords=len(trainMatrix[0])
#所有文档中属于类1所占的比例p(c=1)
pAbusive=sum(trainCategory)/float(numTrainDocs)
#创建一个长度为词条向量等长的列表
p0Num=zeros(numWords);p1Num=zeros(numWords)
p0Denom=0.0;p1Denom=0.0
#遍历每一篇文档的词条向量
for i in range(numTrainDocs):
#如果该词条向量对应的标签为1
if trainCategory[i]==1:
#统计所有类别为1的词条向量中各个词条出现的次数
p1Num+=trainMatrix[i]
#统计类别为1的词条向量中出现的所有词条的总数
#即统计类1所有文档中出现单词的数目
p1Denom+=sum(trainMatrix[i])
else:
#统计所有类别为0的词条向量中各个词条出现的次数
p0Num+=trainMatrix[i]
#统计类别为0的词条向量中出现的所有词条的总数
#即统计类0所有文档中出现单词的数目
p0Denom+=sum(trainMatrix[i])
#利用NumPy数组计算p(wi|c1)
p1Vect=p1Num/p1Denom #为避免下溢出问题,后面会改为log()
#利用NumPy数组计算p(wi|c0)
p0Vect=p0Num/p0Denom #为避免下溢出问题,后面会改为log()
return p0Vect,p1Vect,pAbusive
3 针对算法的部分改进
1)计算概率时,需要计算多个概率的乘积以获得文档属于某个类别的概率,即计算p(w0|ci)*p(w1|ci)*...p(wN|ci),然后当其中任意一项的值为0,那么最后的乘积也为0.为降低这种影响,采用拉普拉斯平滑,在分子上添加a(一般为1),分母上添加ka(k表示类别总数),即在这里将所有词的出现数初始化为1,并将分母初始化为2*1=2
#p0Num=ones(numWords);p1Num=ones(numWords)
#p0Denom=2.0;p1Denom=2.0
2)解决下溢出问题
正如上面所述,由于有太多很小的数相乘。计算概率时,由于大部分因子都非常小,最后相乘的结果四舍五入为0,造成下溢出或者得不到准确的结果,所以,我们可以对成绩取自然对数,即求解对数似然概率。这样,可以避免下溢出或者浮点数舍入导致的错误。同时采用自然对数处理不会有任何损失。
#p0Vect=log(p0Num/p0Denom);p1Vect=log(p1Num/p1Denom)
下面是朴素贝叶斯分类函数的代码:
#朴素贝叶斯分类函数
#@vec2Classify:待测试分类的词条向量
#@p0Vec:类别0所有文档中各个词条出现的频数p(wi|c0)
#@p0Vec:类别1所有文档中各个词条出现的频数p(wi|c1)
#@pClass1:类别为1的文档占文档总数比例
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
#根据朴素贝叶斯分类函数分别计算待分类文档属于类1和类0的概率
p1=sum(vec2Classify*p1Vec)+log(pClass1)
p0=sum(vec2Classify*p0Vec)+log(1.0-pClass1)
if p1>p0:
return 1
else:
return 0
#分类测试整体函数
def testingNB():
#由数据集获取文档矩阵和类标签向量
listOPosts,listClasses=loadDataSet()
#统计所有文档中出现的词条,存入词条列表
myVocabList=createVocabList(listOPosts)
#创建新的列表
trainMat=[]
for postinDoc in listOPosts:
#将每篇文档利用words2Vec函数转为词条向量,存入文档矩阵中
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))\
#将文档矩阵和类标签向量转为NumPy的数组形式,方便接下来的概率计算
#调用训练函数,得到相应概率值
p0V,p1V,pAb=trainNB0(array(trainMat),array(listClasses))
#测试文档
testEntry=['love','my','dalmation']
#将测试文档转为词条向量,并转为NumPy数组的形式
thisDoc=array(setOfWords2Vec(myVocabList,testEntry))
#利用贝叶斯分类函数对测试文档进行分类并打印
print(testEntry,'classified as:',classifyNB(thisDoc,p0V,p1V,pAb))
#第二个测试文档
testEntry1=['stupid','garbage']
#同样转为词条向量,并转为NumPy数组的形式
thisDoc1=array(setOfWords2Vec(myVocabList,testEntry1))
print(testEntry1,'classified as:',classifyNB(thisDoc1,p0V,p1V,pAb))
这里需要补充一点,上面也提到了关于如何选取文档特征的方法,上面用到的是词集模型,即对于一篇文档,将文档中是否出现某一词条作为特征,即特征只能为0不出现或者1出现;然后,一篇文档中词条的出现次数也可能具有重要的信息,于是我们可以采用词袋模型,在词袋向量中每个词可以出现多次,这样,在将文档转为向量时,每当遇到一个单词时,它会增加词向量中的对应值
具体将文档转为词袋向量的代码为:
def bagOfWords2VecMN(vocabList,inputSet):
#词袋向量
returnVec=[0]*len(vocabList)
for word in inputSet:
if word in vocabList:
#某词每出现一次,次数加1
returnVec[vocabList.index(word)]+=1
return returnVec
程序运行结果:
三,实例:朴素贝叶斯的另一个应用--过滤垃圾邮件
1 切分数据
对于一个文本字符串,可以使用python的split()方法对文本进行切割,比如字符串'hello, Mr.lee.',分割结果为['hell0,','Mr.lee.'] 这样,标点符合也会被当成词的一部分,因为此种切割方法是基于词与词之间的空格作为分隔符的
此时,我们可以使用正则表达式来切分句子,其中分割符是除单词和数字之外的其他任意字符串,即
import re
re.compile('\\W*')
这样就得到了一系列词组成的词表,但是里面的空字符串还是需要去掉,此时我们可以通过字符的长度,去掉长度等于0的字符。并且,由于我们是统计某一词是否出现,不考虑其大小写,所有还可以将所有词转为小写字符,即lower(),相应的,转为大写字符为upper()
此外,需要注意的是,由于是URL,因而可能会出现en和py这样的单词。当对URL进行切分时,会得到很多的词,因此在实现时也会过滤掉长度小于3的词。当然,也可以根据自己的实际需要来增加相应的文本解析函数。
2 具体代码如下:
#贝叶斯算法实例:过滤垃圾邮件
#处理数据长字符串
#1 对长字符串进行分割,分隔符为除单词和数字之外的任意符号串
#2 将分割后的字符串中所有的大些字母变成小写lower(),并且只
#保留单词长度大于3的单词
def testParse(bigString):
import re
listOfTokens=re.split(r'\W*',bigString)
return [tok.lower() for tok in listOPosts if len(tok)>2]
def spamTest():
#新建三个列表
docList=[];classList=[];fullTest=[]
#i 由1到26
for i in range(1,26):
#打开并读取指定目录下的本文中的长字符串,并进行处理返回
wordList=testParse(open('email/spam/%d.txt' %i).read())
#将得到的字符串列表添加到docList
docList.append(wordList)
#将字符串列表中的元素添加到fullTest
fullTest.extend(wordList)
#类列表添加标签1
classList.append(1)
#打开并取得另外一个类别为0的文件,然后进行处理
wordList=testParse(open('email/ham/&d.txt' %i).read())
docList.append(wordList)
fullTest.extend(wordList)
classList.append(0)
#将所有邮件中出现的字符串构建成字符串列表
vocabList=createVocabList(docList)
#构建一个大小为50的整数列表和一个空列表
trainingSet=range(50);testSet=[]
#随机选取1~50中的10个数,作为索引,构建测试集
for i in range(10):
#随机选取1~50中的一个整型数
randIndex=int(random.uniform(0,len(trainingSet)))
#将选出的数的列表索引值添加到testSet列表中
testSet.append(trainingSet[randIndex])
#从整数列表中删除选出的数,防止下次再次选出
#同时将剩下的作为训练集
del(trainingSet[randIndex])
#新建两个列表
trainMat=[];trainClasses=[]
#遍历训练集中的吗每个字符串列表
for docIndex in trainingSet:
#将字符串列表转为词条向量,然后添加到训练矩阵中
trainMat.append(setOfWords2Vec(vocabList,fullTest[docIndex]))
#将该邮件的类标签存入训练类标签列表中
trainClasses.append(classList[docIndex])
#计算贝叶斯函数需要的概率值并返回
p0V,p1V,pSpam=trainNB0(array(trainMat),array(trainClasses))
errorCount=0
#遍历测试集中的字符串列表
for docIndex in testSet:
#同样将测试集中的字符串列表转为词条向量
wordVector=setOfWords2Vec(vocabList,docList[docIndex])
#对测试集中字符串向量进行预测分类,分类结果不等于实际结果
if classifyNB(array(wordVector),p0V,p1V,pSpam)!=classList[docIndex]:
errorCount+=1
print('the error rate is:',float(errorCount)/len(testSet))
代码中,采用随机选择的方法从数据集中选择训练集,剩余的作为测试集。这种方法的好处是,可以进行多次随机选择,得到不同的训练集和测试集,从而得到多次不同的错误率,我们可以通过多次的迭代,求取平均错误率,这样就能得到更准确的错误率。这种方法称为留存交叉验证
四,实例:朴素贝叶斯从个人广告中获取区域倾向
在本例中,我们通过从不同的城市的RSS源中获得的同类型广告信息,比较两个城市人们在广告用词上是否不同。如果不同,那么各自的常用词是哪些?而从人们的用词当中,我们能否对不同城市的人所关心的内容有所了解?如果能得到这些信息,分析过后,相信对于广告商而言具有不小的帮助。
1 利用RSS源得到文本数据,Universal Feed Parser是Python中最常见的RSS程序库。通过如下语句可以获得相应的文本数据
import feedparser
ny=feedparser.parse('http://newyork.craigslist.org/stp/index.rss')
ny['entries']
len(ny['entries'])
2 获取并统计相关数据:
#实例:使用朴素贝叶斯分类器从个人广告中获取区域倾向
#RSS源分类器及高频词去除函数
def calMostFreq(vocabList,fullTest):
#导入操作符
import operator
#创建新的字典
freqDict={}
#遍历词条列表中的每一个词
for token in vocabList:
#将单词/单词出现的次数作为键值对存入字典
freqDict[token]=fullTest.count(token)
#按照键值value(词条出现的次数)对字典进行排序,由大到小
sortedFreq=sorted(freqDict.items(),keys=operator.itemgetter(1),reverse=true)
#返回出现次数最多的前30个单词
return sortedFreq[:30]
def localWords(feed1,feed0):
import feedparser
#新建三个列表
docList=[];classList=[];fullTest=[]
#获取条目较少的RSS源的条目数
minLen=min(len(feed1['entries']),len(feed0['entries']))
#遍历每一个条目
for i in range(minLen):
#解析和处理获取的相应数据
wordList=textParse(feed1['entries'][i]['summary'])
#添加词条列表到docList
docList.append(wordList)
#添加词条元素到fullTest
fullTest.extend(wordList)
#类标签列表添加类1
classList.append(1)
#同上
wordList=testParse(feed0['entries'][i]['summary'])
docList.append(wordList)
fullTest.extend(wordList)
#此时添加类标签0
classList.append(0)
#构建出现的所有词条列表
vocabList=createVocabList(docList)
#找到出现的单词中频率最高的30个单词
top30Words=calMostFreq(vocabList,fullTest)
#遍历每一个高频词,并将其在词条列表中移除
#这里移除高频词后错误率下降,如果继续移除结构上的辅助词
#错误率很可能会继续下降
for pairW in top30Words:
if pairW[0] in vocabList:
vocabList.remove(pairW[0])
#下面内容与函数spaTest完全相同
trainingSet=range(2*minLen);testSet=[]
for i in range(20):
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]:
errorCount+=1
print('the error rate is:',float(errorCount)/len(testSet))
return vocabList,p0V,p1V
需要说明的是,这里用到了将出现次数最多的30个单词删除的方法,结果发现去掉了这些最常出现的高频词后,错误率大幅度上升,这表明了文本中的小部分高频单词占据了文本中绝大部分的用词。产生这种情况的原因是因为语言中大部分是冗余和结果辅助性内容。所以,我们不仅可以尝试移除高频词的方法,还可以进一步从某个预定词表(停用词表)中移除结构上的辅助词,通过移除辅助性词,分类错误率会所有下降
此外,为了得到错误率的精确估计,应进行多次上述实验,从而得到错误率平均值。
3 对得到的数据进行分析:
得到各个用词的概率之后,我们可以设定相应的阈值,保留大于相应阈值的用词及其出现的概率,然后按照概率的大小进行排序;最后返回两个地域最具表征性的词汇,观察和比较他们的异同。
代码如下:
#最具表征性的词汇显示函数
def getTopWords(ny,sf):
import operator
#利用RSS源分类器获取所有出现的词条列表,以及每个分类中每个单词出现的概率
vocabList,p0V,p1V=localWords(ny,sf)
#创建两个元组列表
topNY=[];topSF=[]
#遍历每个类中各个单词的概率值
for i in range(len(p0V)):
#往相应元组列表中添加概率值大于阈值的单词及其概率值组成的二元列表
if(p0V[i]>-6.0):topSF.append((vocabList[i],p0V[i]))
if(p1V[i]>-6.0):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**')
#遍历列表中的每一个二元条目列表
for item in sortedSF:
#打印每个二元列表中的单词字符串元素
print(item[0])
#解析同上
sortedNY=sorted(topNY,key=lambda pair:pair[1],reverse=true)
print('SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**')
for item in sortedNY:
print(item[0])
尽管朴素贝叶斯的条件独立性假设存在一定的问题,但是朴素贝叶斯算法仍然能取得比较理想的分类预测结果。
此外,朴素贝叶斯在数据较少的情况下仍然适用,虽然例子中为两类类别的分析,但是朴素贝叶斯可以处理多分类的情况;朴素贝叶斯的一个不足的地方是,对输入的数据有一定的要求,需要花费一定的时间进行数据的处理和解析。朴素贝叶斯中用来计算的数据为标称型数据,我们需要将字符串特征转化为相应的离散值,用于后续的统计和计算。