目录
1、朴素贝叶斯
2、朴素贝叶斯算法实例1--文档分类
3、朴素贝叶斯算法实例2--过滤垃圾邮件
朴素贝叶斯算法是有监督的学习算法,解决的是分类问题,如客户是否流失、是否值得投资、信用等级评定等多分类问题。该算法的优点在于简单易懂、学习效率高、在某些领域的分类问题中能够与决策树、神经网络相媲美。但由于该算法以自变量之间的独立(条件特征独立)性和连续变量的正态性假设为前提,就会导致算法精度在某种程度上受影响。
朴素贝叶斯是贝叶斯决策理论的一部分,所以讲述朴素贝叶斯之前有必要快速了解一下贝叶斯决策理论。
假设现在我们有一个数据集,它由两类数据组成,数据分布如图4-1所示。
假设有位读者找到了描述图中两类数据的统计参数。我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中用圆点表示的类别)的概率,用p2(x, y)表示数据点(x,y)属于类别2(图中用三角形表示的类别)的概率,那么对于一个新数据点(x,y),可以用下面的规则来判断它的类别:
①如果p1 (x,y) > p2(x,y),那么类别为1。
②如果p2(x,y) > p1(x,y),那么类别为2。
也就是说,我们会选择高概率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策。回到图4-1,如果该图中的整个数据使用6个浮点数"来表示,并且计算类别概率的Python代码只有两行,那么你会更倾向于使用下面哪种方法来对该数据点进行分类?
①使用第1章的kNN,进行1000次距离计算;
②使用第2章的决策树,分别沿x轴、y轴划分数据;
③计算数据点属于每个类别的概率,并进行比较。
使用决策树不会非常成功,而和简单的概率计算相比,kNN的计算量太大。因此,对于上述问题,最佳选择是使用刚才提到的概率比较方法。
提到贝叶斯决策理论要求计算两个概率p1(x, y)和p2(x,y)。如果p1(x,y) > p2(x, y),那么属于类别1;如果p2(x,y) > p1(x,y),那么属于类别2。
但这两个准则并不是贝叶斯决策理论的所有内容。使用p1( )和p2( )只是为了尽可能简化描述,而真正需要计算和比较的是和。这些符号所代表的具体意义是:给定某个由x、y表示的数据点,那么该数据点来自类别的概率是多少?数据点来自类别的概率又是多少?注意这些概率与刚才给出的概率p(x,y|c)并不一样,不过可以使用贝叶斯准则来交换概率中条件与结果。具体地,应用贝叶斯准则得到:
使用这些定义,可以定义贝叶斯分类准则为:
①如果,那么属于类别
②如果,那么属于类别
使用贝叶斯准则,可以通过已知的三个概率值来计算未知的概率值。后面就会给出利用贝叶斯准则来计算概率并对数据进行分类的代码。现在介绍了一些概率理论,你也了解了基于这些理论构建分类器的方法,接下来就要将它们付诸实践。
机器学习的一个重要应用就是文档的自动分类。在文档分类中,整个文档(如一封电子邮件)是实例,而电子邮件中的某些元素则构成特征。虽然电子邮件是一种会不断增加的文本,但我们同样也可以对新闻报道、用户留言、政府公文等其他任意类型的文本进行分类。我们可以观察文档中出现的词,并把每个词的出现或者不出现作为一个特征,这样得到的特征数目就会跟词汇表中的词目一样多。朴素贝叶斯是上节介绍的贝叶斯分类器的一个扩展,是用于文档分类的常用算法。
便用每个词作为特征并观察它们是否出现,这样得到的特征数目会有多少呢?针对的是哪一种人类语言呢?当然不止一种语言。据估计,仅在英语中,单词的总数就有500 000之多。为了能进行英文阅读,估计需要掌握数千单词。
假设词汇表中有1000个单词。要得到好的概率分布,就需要足够的数据样本,假定样本数为N。前面讲到的约会网站示例中有1000个实例,手写识别示例中每个数字有200个样本,而决策树示例中有24个样本。其中,24个样本有点少,200个样本好一些,而1000个样本就非常好了。约会网站例子中有三个特征。由统计学知,如果每个特征需要N个样本,那么对于10个特征将需要个样本,对于包含1000个特征的词汇表将需要个样本。可以看到,所需要的样本数会随着特征数目增大而迅速增长。
如果特征之间相互独立,那么样本数就可以从减少到1000×N。所谓独立指的是统计意义上的独立,即一个特征或者单词出现的可能性与它和其他单词相邻没有关系。举个例子讲,假设单词bacon出现在unhealthy后面与出现在delicious后面的概率相同。当然,我们知道这种假设并不正确,bacon常常出现在delicious附近,而很少出现在unhealthy附近,这个假设正是朴素贝叶斯分类器中朴素( naive)一词的含义。朴素贝叶斯分类器中的另一个假设是,每个特征相同重要。
以在线社区的留言板为例。为了不影响社区的发展,我们要屏蔽侮辱性的言论,所以要构建一个快速过滤器,如果某条留言使用了负面或者侮辱性的语言,那么就将该留言标识为内容不当。过滤这类内容是一个很常见的需求。对此问题建立两个类别:侮辱类和非侮辱类,使用1和0分别表示。
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 是侮辱性文字 0 是正常言论
return postingList,classVec
#创建集合的并集
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document)
return list(vocabSet)
#根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
def setOfWordVex(vocabList,inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else:
print("%s is not in vocalList",word)
return returnVec
我们来测试一下代码:
DataSet,classVec = loadDataSet()
vocabSet = createVocabList(DataSet)
ans = setOfWordVex(vocabSet,DataSet[0])
得到的输出为:
2、朴素贝叶斯分类训练函数
前面介绍了如何将一组单词转换为一组数字,接下来看看如何使用这些数字计算概率。
#朴素贝叶斯分类器训练函数
def trainNBO(trainMatrix,trainCategroy):
numTrainDocs = len(trainMatrix) #计算训练的文档数目
numWords = len(trainMatrix[0]) #计算每篇文档的词条数
pAbusive = sum(trainCategroy)/float(numTrainDocs) #文档属于侮辱类的概率
#初始化概率
p0num = np.ones(numWords) #拉普拉斯平滑
p1num = np.ones(numWords)
p0Denom = 2.0
p1Denom = 2.0
for i in range(numTrainDocs):
if trainCategroy[i] == 1: #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
p1num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else: #统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
p0num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = log(p1num/p1Denom) #取对数,取值不同,但是不影响最终结果。
p0Vect = log(p0num/p0Denom) #可以防止下溢出,这是由于太多太小的数相乘造成的。
return p0Vect,p1Vect,pAbusive #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率
利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算。如果其中一个概率值为0,那么最后的乘积也为0。为降低这种影响,可以将所有词的出现数初始化为1,并将分母初始化为2。
另一个遇到的问题是下溢出,这是由于太多很小的数相乘造成的。当计算乘积时,由于大部分因子都非常小,所以程序会下溢出或者得到不正确的答案。(读者可以用Python尝试相乘许多很小的数,最后四舍五入后会得到0。)一种解决办法是对乘积取自然对数。在代数中有1n(a*b) = ln (a)+1n(b),于是通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。
3、朴素贝叶斯分类函数
使用NumPy的数组来计算两个向量相乘的结果。这里的相乘是指对应元素相乘,即先将两个向量中的第1个元素相乘,然后将第2个元素相乘,以此类推。接下来将词汇表中所有词的对应值相加,然后将该值加到类别的对数概率上。最后,比较类别的概率返回大概率对应的类别标签。这一切不是很难,对吧?
def classifyNB(vecClassify,p0Vec,p1Vec,pClass1):
p1 = sum(vecClassify*p1Vec)+log(pClass1)
p0 = sum(vecClassify*p0Vec)+log(1-pClass1)
if p1>p0:
return 1
else :
return 0
def testingNB():
DataSet,classVec = loadDataSet()
vocabSet = createVocabList(DataSet)
trainMat = []
for postinDoc in DataSet:
trainMat.append(setOfWordVex(vocabSet,postinDoc))
p0V,p1V,pAb = trainNBO(trainMat,classVec)
testEntry = ['love','my','dalmation']
thisDoc = array(setOfWordVex(vocabSet,testEntry))
print(classifyNB(thisDoc,p0V,p1V,pAb))
我们测试一下代码,得到:
4、词袋模型
目前为止,我们将每个词的出现与否作为一个特征,这可以被描述为词集模型。如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为词袋模型。在词袋中,每个单词可以出现多次,而在词集中,每个词只能出现一次。为适应词袋模型,需要对函数setofWordsvec()稍加修改,修改后的函数称为bagofwordsvec ()。
下面的函数给出了基于词袋模型的朴素贝叶斯代码。它与函数setofwordsvec()几乎完全相同,唯一不同的是每当遇到一个单词时,它会增加词向量中的对应值,而不只是将对应的数值设为1。
#词带模型
def bagOfWordVecMN(vocabList,inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1 #与词集模型不同之处
return returnVec
现在分类器已经构建好了,下面我们将利用该分类器来过滤垃圾邮件。
5、完整代码
见下个实例吧。哈。
在前面那个简单的例子中,我们引人了字符串列表。使用朴素贝叶斯解决一些现实生活中的问题时,需要先从文本内容得到字符串列表,然后生成词向量。下面这个例子中,.我们将了解朴素贝叶斯的一个最著名的应用:电子邮件垃圾过滤。首先看一下如何使用通用框架来解决该问题。
1、准备数据,切分文本
前一节介绍了如何创建词向量,并基于这些词向量进行朴素贝叶斯分类的过程。前一节中的词向量是预先给定的,下面介绍如何从文本文档中构建自己的词列表。
数据:数据在这! 提取码:kfpg
#切分文本
def textParse(bigString):
listOfTokens = re.split(r'\W+', bigString)
return [tok.lower() for tok in listOfTokens if len(tok)>2]
def spamTest():
docList = []
classList = []
fullText = []
for i in range(1,26):#遍历25个txt文件
wordList = textParse(open('email/spam/%d.txt' % i, 'r').read()) #读取每个垃圾邮件,并字符串转换成字符串列表
docList.append(wordList)
fullText.extend(wordList)
classList.append(1) #标记垃圾邮件,1表示垃圾文件
wordList = textParse(open('email/ham/%d.txt' % i).read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)#标记非垃圾邮件
# print(docList)
vocabList = createVocabList(docList) #创建词汇表,不重复
trainingSet = list(range(50)) #训练集
print(trainingSet)
testSet = [] # 测试集
for i in range (10): #随机构建训练集、测试集
randIndex = int(random.uniform(0,len(trainingSet)))
testSet.append(trainingSet[randIndex])
del(trainingSet[randIndex])
trainMat = []
trainClasses = []
for docIndex in trainingSet:
trainMat.append(setOfWordVex(vocabList,docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V,p1V,pSpam = trainNBO(array(trainMat),array(trainClasses))
errorCount = 0
for docIndex in testSet:
wordVector = setOfWordVex(vocabList,docList[docIndex])
if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
errorCount += 1
print('the error is %f',errorCount/float(len(testSet)))
测试一下得到:
函数spamTest ()会输出在10封随机选择的电子邮件上的分类错误率。既然这些电子邮件是随机选择的,所以每次的输出结果可能有些差别。如果发现错误的话,函数会输出错分文档的词表,这样就可以了解到底是哪篇文档发生了错误。如果想要更好地估计错误率,那么就应该将上述过程重复多次,比如说10次,然后求平均值。我这么做了一下,获得的平均错误率为6%。
这里一直出现的错误是将垃圾邮件误判为正常邮件。相比之下,将垃圾邮件误判为正常邮件要比将正常邮件归到垃圾邮件好。为避免错误,有多种方式可以用来修正分类器,这些将在第7章中进行讨论。
2、完整代码
from numpy import *
import numpy as np
import random
import re
#数据集
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 是侮辱性文字 0 是正常言论
return postingList,classVec
#创建集合的并集
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document)
return list(vocabSet)
#根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
def setOfWordVex(vocabList,inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else:
print("%s is not in vocalList",word)
return returnVec
#词带模型
def bagOfWordVecMN(vocabList,inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1 #与词集模型不同之处
return returnVec
#朴素贝叶斯分类器训练函数
def trainNBO(trainMatrix,trainCategroy):
numTrainDocs = len(trainMatrix) #计算训练的文档数目
numWords = len(trainMatrix[0]) #计算每篇文档的词条数
pAbusive = sum(trainCategroy)/float(numTrainDocs) #文档属于侮辱类的概率
#初始化概率
p0num = np.ones(numWords) #拉普拉斯平滑
p1num = np.ones(numWords)
p0Denom = 2.0
p1Denom = 2.0
for i in range(numTrainDocs):
if trainCategroy[i] == 1: #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
p1num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else: #统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
p0num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = log(p1num/p1Denom) #取对数,取值不同,但是不影响最终结果。
p0Vect = log(p0num/p0Denom) #可以防止下溢出,这是由于太多太小的数相乘造成的。
return p0Vect,p1Vect,pAbusive #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率
def classifyNB(vecClassify,p0Vec,p1Vec,pClass1):
p1 = sum(vecClassify*p1Vec)+log(pClass1)
p0 = sum(vecClassify*p0Vec)+log(1-pClass1)
if p1>p0:
return 1
else :
return 0
def testingNB():
DataSet,classVec = loadDataSet()
vocabSet = createVocabList(DataSet)
trainMat = []
for postinDoc in DataSet:
trainMat.append(setOfWordVex(vocabSet,postinDoc))
p0V,p1V,pAb = trainNBO(trainMat,classVec)
testEntry = ['love','my','dalmation']
thisDoc = array(setOfWordVex(vocabSet,testEntry))
print(classifyNB(thisDoc,p0V,p1V,pAb))
#切分文本
def textParse(bigString):
listOfTokens = re.split(r'\W+', bigString)
return [tok.lower() for tok in listOfTokens if len(tok)>2]
#
def spamTest():
docList = []
classList = []
fullText = []
for i in range(1,26):#遍历25个txt文件
wordList = textParse(open('email/spam/%d.txt' % i, 'r').read()) #读取每个垃圾邮件,并字符串转换成字符串列表
docList.append(wordList)
fullText.extend(wordList)
classList.append(1) #标记垃圾邮件,1表示垃圾文件
wordList = textParse(open('email/ham/%d.txt' % i).read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)#标记非垃圾邮件
# print(docList)
vocabList = createVocabList(docList) #创建词汇表,不重复
trainingSet = list(range(50)) #训练集
print(trainingSet)
testSet = [] # 测试集
for i in range (10): #随机构建训练集、测试集
randIndex = int(random.uniform(0,len(trainingSet)))
testSet.append(trainingSet[randIndex])
del(trainingSet[randIndex])
trainMat = []
trainClasses = []
for docIndex in trainingSet:
trainMat.append(setOfWordVex(vocabList,docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V,p1V,pSpam = trainNBO(array(trainMat),array(trainClasses))
errorCount = 0
for docIndex in testSet:
wordVector = setOfWordVex(vocabList,docList[docIndex])
if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
errorCount += 1
print('the error is %f',errorCount/float(len(testSet)))
# DataSet,classVec = loadDataSet()
# vocabSet = createVocabList(DataSet)
# ans = setOfWordVex(vocabSet,DataSet[0])
# print(ans)
# trainMat = []
# for postinDoc in DataSet:
# trainMat.append(setOfWordVex(vocabSet,postinDoc))
#
# p0V,p1V,pAb = trainNBO(trainMat,classVec)
# testingNB()
spamTest()