目录
一、朴素贝叶斯理论
1.1基于贝叶斯决策理论的分类方法
1.2条件概率
编辑
1.3全概率公式
编辑
1.4朴素贝叶斯分类器
二、贝叶斯分类器简单应用
三、使用朴素贝叶斯进行文档分类
3.1准备数据:从文本中构建词向量
3.2训练算法:从词向量计算概率
3.3测试算法:根据现实情况修改分类器
3.4准备数据:文档词袋模型
四、使用朴素贝叶斯过滤垃圾邮件
4.1准备数据:切分文本
4.2测试算法:使用朴素贝叶斯进行交叉验证
编辑
五、总结
朴素贝叶斯是经典的机器学习算法之一,也是为数不多的基于概率论的分类算法。对于大多数的分类算法,在所有的机器学习分类算法中,朴素贝叶斯和其他绝大多数的分类算法都不同。比如决策树,KNN,逻辑回归,支持向量机等,他们都是判别方法,也就是直接学习出特征输出Y和特征X之间的关系,要么是决策函数,要么是条件分布。但是朴素贝叶斯却是生成方法,该算法原理简单,也易于实现。
朴素贝叶斯算法以贝叶斯理论为理论基础,通过计算样本属于不同类别的概率来进行分类,是一种经典的分类算法。朴素贝叶斯是贝叶斯分类器里的一种方法,之所以称它朴素,原因在于它采用了特征生命条件全部独立的假设。
贝叶斯理论是指基于能获得的最好证据(观察、数据和信息等),来计算信念度(或者假说、主张、命题)的有效方法。信念度即为对事物的真实性和正确性所具有的信心。朴素贝叶斯的核心便是朴素贝叶斯公式,在了解朴素贝叶斯公式之前不妨先了解条件概率、全概率公式、先验概率和后验概率等相关概念。
我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中用圆点表示的类别)的概率,用p2(x,y)表示数据点(x,y)属于类别2(图中用三角形表示的类别)的概率,那么对于一个新数据点(x,y),可以用下面的规则来判断它的类别:
也就是说,我们会选择高概率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有 最高概率的决策。看图1,如果该图中的整个数据使用6个浮点数来表示,并且计算类别概率的Python代码只有两行,那么我们应该使用下面哪种方法来对该数据点进行分类?
1.使用kNN算法,则需要进行1000次距离计算,和简单的概率计算相比,kNN的计算量太大;
2.使用决策树,则需要分别沿x轴、y轴划分数据,似乎效果并不是很好;
3.还有就是计算数据点属于每个类别的概率,并进行比较,这是最佳选择。
如上图,可以知道在事件B发生的情况下,事件A发生的概率是部分占B部分的比例,即 :
因此:
同理可得:
所以:
假设事件A和事件A'共同构成样本空间S,在这种情况下,事件B可以划分成两部分,即:
因为:
结合上面两个式子,可以得到全概率公式:
先验概率P(X):先验概率是指根据以往经验和分析得到的概率。
后验概率P(Y|X):事情已发生,要求这件事情发生的原因是由某个因素引起的可能性的大小,后验分布P(Y|X)表示事件X已经发生的前提下,事件Y发生的概率,称事件X发生下事件Y的条件概率。
基于属性条件独立性假设,,其中d为属性数目,
为x在第i个属性上的取值。
由于对所有类别来说P(x)相同,因此贝叶斯判断准则有
这就是朴素贝叶斯分类器的表达式
朴素贝叶斯分类器的训练器的训练过程就是基于训练集D估计类先验概率 ,并为每个属性估计条件概率
。
令 表示训练集D中第c类样本组合的集合,则类先验概率:
拉普拉斯修正:若某个属性值在训练集中没有与某个类同时出现过,则训练后的模型会出现 over-fitting 现象。比如训练集中没有该样例,因此连乘式计算的概率值为0,这显然不合理。因为样本中不存在(概率为0),不代该事件一定不可能发生。所以为了避免其他属性携带的信息,被训练集中未出现的属性值“ 抹去” ,在估计概率值时通常要进行“拉普拉斯修正”。
在超市挑选苹果,一般颜色红润,形状较圆的为好果。根据之前购买的苹果的大小、颜色、形状这三个特征来区分好坏:
编号 |
大小 | 颜色 | 形状 | 好果 |
1 | 小 | 青色 | 非规则 | 否 |
2 | 大 | 红色 | 非规则 | 是 |
3 | 大 | 红色 | 圆形 | 是 |
4 | 大 | 青色 | 圆形 | 否 |
5 | 大 | 青色 | 非规则 | 否 |
6 | 小 | 红色 | 圆形 | 是 |
7 | 大 | 青色 | 非规则 | 否 |
8 | 小 | 红色 | 非规则 | 否 |
9 | 小 | 青色 | 圆形 | 否 |
10 | 大 | 红色 | 圆形 | 是 |
现在一个苹果的特征如下:
大小 | 颜色 | 形状 | 好果 |
大 | 红色 | 圆形 | ? |
先验概率P(c),简化的求解方法:c类样本的个数除以样本总数
P(c=好果)=4/10 P(c=一般)=6/10
每个属性的类条件概率,可以初步这么求解:这个类别下的样本中对应这个属性的样本个数除以这个类别下的样本个数,因此:
P(大小=大 | c=好果) = 3/4
P(颜色=红色 | c=好果) = 4/4
P(形状=圆形 | c=好果) = 3/4
P(大小=大 | c=一般) = 3/6
P(颜色=红色 | c=一般) = 1/6
P(形状=圆形 | c=一般) = 2/6
因此:
P(c=好果) * P(大小=大 | c=好果) * P(颜色=红色 | c=好果) * P(形状=圆形 | c=好果)
= 4/10 * 3/4 * 4/4 * 3/4
= 0.225
P(c=一般) * P(大小=大 | c=一般) * P(颜色=红色 | c=一般) * P(形状=圆形 | c=一般)
= 6/10 * 3/6 * 1/6 * 2/6
= 0.0167
0.225>0.0167,所以这个苹果为好果
# 创建实验样本
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) # 返回不重复的词条列表
# 输出文档向量
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("单词 %s 不在词汇表中!" % word)
return returnVec
# 测试函数效果
# 创建实验样本
listPosts, listClasses = loadDataSet()
print('数据集\n', listPosts)
# 创建词汇表
myVocabList = createVocabList(listPosts)
print('词汇表:\n', myVocabList)
# 输出文档向量
print(setOfWords2Vec(myVocabList, listPosts[5]))
一个单词在词汇表中出现过一次,那么就在相应位置记作1,如果没有出现就在相应位置记作0。
# 朴素贝叶斯分类器训练函数
from gettext import npgettext
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 获得训练的文档总数
numWords = len(trainMatrix[0]) # 获得每篇文档的词总数
pAbusive = sum(trainCategory) / float(numTrainDocs) # 计算文档是侮辱类的概率
p0Num = ones(numWords) # 创建numpy.ones数组,词条出现次数初始化为1,拉普拉斯平滑
p1Num = ones(numWords)
p0Denom = 2.0 # 分母初始化为2,拉普拉斯平滑
p1Denom = 2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i] # 向量相加,统计侮辱类的条件概率的数据,即P(w0|1),P(w1|1),P(w2|1)···
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i] # 向量相加,统计非侮辱类的条件概率的数据,即P(w0|0),P(w1|0),P(w2|0)···
p0Denom += sum(trainMatrix[i])
p1Vect = np.log(p1Num / p1Denom) # 侮辱类,每个元素除以该类别中的总词数
p0Vect = np.log(p0Num / p0Denom) # 非侮辱类,每个元素除以该类别中的总词数
return p0Vect, p1Vect, pAbusive # p0Vect非侮辱类的条件概率数组、p1Vect侮辱类的条件概率数组、pAbusive文档属于侮辱类的概率
# 测试代码
listPosts, listClasses = loadDataSet() # 创建实验样本
print('数据集\n', listPosts)
myVocabList = createVocabList(listPosts) # 创建词汇表
print('词汇表:\n', myVocabList)
trainMat = []
for postinDoc in listPosts: # for循环使用词向量来填充trainMat列表
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB0(trainMat, listClasses)
print('p0V:\n', p0V)
print('p1V:\n', p1V)
print('classVec:\n',listClasses)
print('pAb:\n', pAb)
运行结果如图:p0V存放的是每个单词属于类别0,也就是非侮辱类词汇的概率。 p1V存放的是每个单词属于类别1,也就是侮辱类词汇的概率。例如词汇表中第五个stupid,非侮辱类的概率就是-3.25809654,侮辱类概率为-1.65822808。pAb是所有侮辱类的样本占所有样本的概率,从classVec中可以看出,一共有三个侮辱类,所以pAb概率为0.5。
# 测试朴素贝叶斯分类器
def testingNB():
listOPosts, listClasses = loadDataSet() # 创建实验样本
myVocabList = createVocabList(listOPosts) # 创建词汇表
trainMat = [] # 文档矩阵
for postinDoc in listOPosts: # for循环使用词向量来填充trainMat列表
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB0(array(trainMat), array(listClasses)) # 获得概率数组及先验概率
testEntry = ['love', 'my', 'dalmation'] # 输入测试1
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'属于侮辱类')
else:
print(testEntry,'属于非侮辱类')
testEntry = ['stupid', 'garbage'] # 输入测试2
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'属于侮辱类')
else:
print(testEntry,'属于非侮辱类')
if __name__ == '__main__':
testingNB()
# 朴素贝叶斯词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1 # 加1
else:
print("the word: %s is not in my vocabulary!" % word)
return returnVec
普通邮件
垃圾邮件
数据集:链接: https://pan.baidu.com/s/14evrKNZQ6h0mmJCwsRGRhA?pwd=q4p6 提取码: q4p6
# 例子
mySent = 'This book is the best book on Python or M.L. I have ever laid eyes upon.'
print(mySent.split())
# 文件解析
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, 22): # 遍历垃圾邮件和非垃圾邮件各21个
wordList = textParse(open('email/spam/%d.txt' % i).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) # 0表示垃圾邮件,标记非垃圾邮件,
vocabList = createVocabList(docList) # 创建不重复的词汇表
trainingSet = list(range(42)) # 为训练集添加索引
testSet = [] # 创建测试集
for i in range(8): # 目的为了从42个邮件中,随机挑选出34个作为训练集,8个做测试集
randIndex = int(random.uniform(0, len(trainingSet))) # 随机产生索引
testSet.append(trainingSet[randIndex]) # 添加测试集的索引值
del (trainingSet[randIndex]) # 在训练集中,把加入测试集的索引删除
trainMat = [] # 创建训练集矩阵训练集类别标签系向量
trainClasses = []
#print(vocabList)
#print(docList[docIndex]) # 训练集类别标签
for docIndex in trainingSet: # for循环使用词向量来填充trainMat列表t
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex])) # 把词集模型添加到训练矩阵中
trainClasses.append(classList[docIndex]) # 把类别添加到训练集类别标签中
p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses)) # 朴素贝叶斯分类器训练函数
#print('词表:\n', vocabList)
#print('p0V:\n', p0V)
#print('p1V:\n', p1V)
#print('pSpam:\n', pSpam)
errorCount = 0 # 用于计数错误分类
for docIndex in testSet: # 循环遍历训练集
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex]) # 获得测试集的词集模型
if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1 # 预测值和真值不一致,则错误分类计数加1
print("分类错误集", docList[docIndex])
print('错误率: ', float(errorCount) / len(testSet))
函数spamTest()会输出在8封随机选择的电子邮件上的分类错误概率。既然这些电子邮件是随机选择的,所以每次的输出结果可能有些差别。如果发现错误的话,函数会输出错误的文档的此表,这样就可以了解到底是哪篇文档发生了错误。如果想要更好地估计错误率,那么就应该将上述过程重复多次,比如说10次,然后求平均值。相比之下,将垃圾邮件误判为正常邮件要比将正常邮件归为垃圾邮件好。
完整代码:链接: https://pan.baidu.com/s/1wAN8wFmuAjU8YDZgewtUuQ?pwd=swcr 提取码: swcr