贝叶斯分类算法是统计学的一种分类方法,它是一类利用概率统计知识进行分类的算法。在许多场合,朴素贝叶斯(Naïve Bayes,NB)分类算法可以与决策树和神经网络分类算法相媲美,该算法能运用到大型数据库中,而且方法简单、分类准确率高、速度快。
那么,什么是贝叶斯算法呢?这里我将会进行详细的解释。
贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类。而朴素贝叶斯分类是贝叶斯分类中最简单,也是常见的一种分类方法。
那么在掌握朴素贝叶斯算法之前,我们必须了解条件概率和全概率。
假设现在有一个装了7块石头的罐子,其中3块是灰色的,4块是黑色的。如果从罐子中随机取出一块石头,那么是灰色石头的可能性是多少?由于取石头有7种可能,其中3种为灰色,所以取出灰色石头的概率为3/7。那么取到黑色石头的概率又是多少呢?很显然,是4/7。我们使用P(gray)来表示取到灰色石头的概率,其概率值可以通过灰色石头数目除以总的石头数目来得到。
如果这7块石头如图所示放在两个桶中,那么上述概率应该如何计算?
要计算P(gray)或者P(black),事先得知道石头所在桶的信息会不会改变结果?你有可能已经想到计算从B桶中取到灰色石头的概率的办法,这就是所谓的条件概率(conditional probability)。假定计算的是从B桶取到灰色石头的概率,这个概率可以记作P(gray|bucketB),我们称之为“在已知石头出自B桶的条件下,取出灰色石头的概率”。不难得到,P(gray|bucketA)值为2/4,P(gray|bucketB) 的值为1/3。 条件概率的计算公式如下所示:
另一种有效计算条件概率的方法称为贝叶斯准则。贝叶斯准则告诉我们如何交换条件概率中
的条件与结果,即如果已知P(x|c),要求P(c|x),那么可以使用下面的计算方法:
指若事件{A1,A2,…,An}构成一个完备事件组且都有正概率,则对任意一个事件B都有:
则有:
结合条件概率可推导出如下公式:
即为贝叶斯公式。把P(A)称为先验概率(Prior probability),即在B事件发生之前,我们对A事件概率的一个判断。
P(A|B)称为后验概率(Posterior probability),即在B事件发生之后,我们对A事件概率的重新评估。
P(B|A)/P(B)称为可能性函数(Likelyhood),这是一个调整因子,使得预估概率更接近真实概率。
所以条件概率可以理解为:后验概率 = 先验概率 × 调整因子
下面举一个用西瓜数据集训练一个朴素贝叶斯分类器,对如下测试例进行分类。
P(好瓜)=8/17=0.471,P(坏瓜)=9/17=0.529
P(色=青|好)=3/8=0.375,P(色=青|坏)=3/9=0.333
P(根=卷|好)=5/8=0.625,P(根=卷|坏)=3/9=0.333
P(敲=浊|好)=6/8=0.75,P(敲=浊|坏)=4/9=0.444
P(纹=清|好)=7/8=0.875,P(纹=清|坏)=2/9=0.222
P(脐=凹|好)=5/8=0.625,P(脐=凹|坏)=2/9=0.222
P(触=硬|好)=6/8=0.75,P(触=硬|坏)=6/9=0.667
P(密度=0.697|好瓜)=1.959,P(密度=0.697|坏瓜)=1.203
P(糖度=0.46|好瓜)=0.788,P(糖度=0.46|坏瓜)=0.066
P1(好瓜|色=青,根=卷,敲=浊,纹=清,脐=凹,触=硬,密度=0.697,糖度=0.46)=6.3X10^-2
P2(坏瓜|色=青,根=卷,敲=浊,纹=清,脐=凹,触=硬,密度=0.697,糖度=0.46)=6.8X10^-5
P1>P2,所以最终结果为好瓜
基本流程:
数据集中有两个文件夹ham和spam,spam文件下的txt文件为垃圾邮件。
代码:
# -*- coding: UTF-8 -*-
import re
# 函数说明:接收一个大字符串并将其解析为字符串列表
def textParse(bigString): # 将字符串转换为字符列表
listOfTokens = re.split(r'\W', bigString) # 将特殊符号作为切分标志进行字符串切分,即非字母、非数字
return [tok.lower() for tok in listOfTokens if len(tok) > 2] # 除了单个字母,例如大写的I,其它单词变成小写
# 函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
def createVocabList(dataSet):
vocabSet = set([]) # 创建一个空的不重复列表
for document in dataSet:
vocabSet = vocabSet | set(document) # 取并集
return list(vocabSet)
if __name__ == '__main__':
docList = [];
classList = []
for i in range(1, 26): # 遍历25个txt文件
wordList = textParse(open('D:/迅雷下载/machinelearninginaction/Ch04/email/spam/%d.txt' % i, 'r').read()) # 读取每个垃圾邮件,并字符串转换成字符串列表
docList.append(wordList)
classList.append(1) # 标记垃圾邮件,1表示垃圾文件
wordList = textParse(open('D:/迅雷下载/machinelearninginaction/Ch04/email/ham/%d.txt' % i, 'r').read()) # 读取每个非垃圾邮件,并字符串转换成字符串列表
docList.append(wordList)
classList.append(0) # 标记非垃圾邮件,1表示垃圾文件
vocabList = createVocabList(docList) # 创建词汇表,不重复
print(vocabList)
运行结果:
使用交叉验证的方式测试朴素贝叶斯分类器的准确性
代码:
# -*- coding: UTF-8 -*-
import numpy as np
import random
import re
# 函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
def createVocabList(dataSet):
vocabSet = set([]) # 创建一个空的不重复列表
for document in dataSet:
vocabSet = vocabSet | set(document) # 取并集
return list(vocabSet)
# 函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList) # 创建一个其中所含元素都为0的向量
for word in inputSet: # 遍历每个词条
if word in vocabList: # 如果词条存在于词汇表中,则置1
returnVec[vocabList.index(word)] = 1
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec # 返回文档向量
# 函数说明:根据vocabList词汇表,构建词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0] * len(vocabList) # 创建一个其中所含元素都为0的向量
for word in inputSet: # 遍历每个词条
if word in vocabList: # 如果词条存在于词汇表中,则计数加一
returnVec[vocabList.index(word)] += 1
return returnVec # 返回词袋模型
# 函数说明:朴素贝叶斯分类器训练函数
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 计算训练的文档数目
numWords = len(trainMatrix[0]) # 计算每篇文档的词条数
pAbusive = sum(trainCategory) / float(numTrainDocs) # 文档属于侮辱类的概率
p0Num = np.ones(numWords);
p1Num = np.ones(numWords) # 创建numpy.ones数组,词条出现数初始化为1,拉普拉斯平滑
p0Denom = 2.0;
p1Denom = 2.0 # 分母初始化为2,拉普拉斯平滑
for i in range(numTrainDocs):
if trainCategory[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 = np.log(p1Num / p1Denom) # 取对数,防止下溢出
p0Vect = np.log(p0Num / p0Denom)
return p0Vect, p1Vect, pAbusive # 返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率
# 函数说明:朴素贝叶斯分类器分类函数
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + np.log(pClass1) # 对应元素相乘。logA * B = logA + logB,所以这里加上log(pClass1)
p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
# 函数说明:朴素贝叶斯分类器训练函数
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 计算训练的文档数目
numWords = len(trainMatrix[0]) # 计算每篇文档的词条数
pAbusive = sum(trainCategory) / float(numTrainDocs) # 文档属于侮辱类的概率
p0Num = np.ones(numWords);
p1Num = np.ones(numWords) # 创建numpy.ones数组,词条出现数初始化为1,拉普拉斯平滑
p0Denom = 2.0;
p1Denom = 2.0 # 分母初始化为2,拉普拉斯平滑
for i in range(numTrainDocs):
if trainCategory[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 = np.log(p1Num / p1Denom) # 取对数,防止下溢出
p0Vect = np.log(p0Num / p0Denom)
return p0Vect, p1Vect, pAbusive # 返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率
# 函数说明:接收一个大字符串并将其解析为字符串列表
def textParse(bigString): # 将字符串转换为字符列表
listOfTokens = re.split(r'\W', bigString) # 将特殊符号作为切分标志进行字符串切分,即非字母、非数字
return [tok.lower() for tok in listOfTokens if len(tok) > 2] # 除了单个字母,例如大写的I,其它单词变成小写
# 函数说明:测试朴素贝叶斯分类器
def spamTest():
docList = [];
classList = [];
fullText = []
for i in range(1, 26): # 遍历25个txt文件
wordList = textParse(open('D:/迅雷下载/machinelearninginaction/Ch04/email/spam/%d.txt' % i, 'r').read()) # 读取每个垃圾邮件,并字符串转换成字符串列表
docList.append(wordList)
fullText.append(wordList)
classList.append(1) # 标记垃圾邮件,1表示垃圾文件
wordList = textParse(open('D:/迅雷下载/machinelearninginaction/Ch04/email/ham/%d.txt' % i, 'r').read()) # 读取每个非垃圾邮件,并字符串转换成字符串列表
docList.append(wordList)
fullText.append(wordList)
classList.append(0) # 标记非垃圾邮件,1表示垃圾文件
vocabList = createVocabList(docList) # 创建词汇表,不重复
trainingSet = list(range(50));
testSet = [] # 创建存储训练集的索引值的列表和测试集的索引值的列表
for i in range(10): # 从50个邮件中,随机挑选出40个作为训练集,10个做测试集
randIndex = int(random.uniform(0, len(trainingSet))) # 随机选取索索引值
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 # 错误计数加1
print("分类错误的测试集:", docList[docIndex])
print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))
if __name__ == '__main__':
spamTest()
运行结果:
函数spamTest()会输出在10封随机选择的电子邮件上的分类错误率。既然这些电子邮件是随机选择的,所以每次的输出结果可能有些差别。如果发现错误的话,函数会输出错分文档的词表,这样就可以了解到底是哪篇文档发生了错误。如果想要更好地估计错误率,那么就应该将上述过程重复多次,比如说10次,然后求平均值。我这么做了一下,获得的平均错误率为10%。
对于分类而言,使用概率有时要比使用硬规则更为有效。贝叶斯概率及贝叶斯准则提供了一种利用已知值来估计未知概率的有效方法。 可以通过特征之间的条件独立性假设,降低对数据量的需求。独立性假设是指一个词的出现概率并不依赖于文档中的其他词。当然我们也知道这个假设过于简单。这就是之所以称为朴素贝叶斯的原因。尽管条件独立性假设并不正确,但是朴素贝叶斯仍然是一种有效的分类器。