- Python版本: Python3.x
- 运行平台: Windows
- IDE: PyCharm
- 参考资料:《机器学习》(西瓜书)《机器学习实战》(王斌)
- 转载请标明出处:https://blog.csdn.net/tian121381/category_9748511.html
- 资料下载,提取码:j2l5
一、前言
前两章我们要求分类器做出艰难决策,给出“该数据实例属于哪⼀类”这类问题的明确答案。不过,分类器有时会产⽣错误结果,这时可以要求分类器给出⼀个最优的类别猜测结果,同时给出这个猜测的概率估计值。 概率论是许多机器学习算法的基础,所以深刻理解这一主题就显得⼗分重要。第3章在计算特征值取某个值的概率时涉及了⼀些概率知识,在那里我们先统计特征在数据集中取某个特定值的次数,然后除以数据集的实例总数,就得到了特征取该值的概率。我们将在此基础上深⼊讨论(概率论的相关知识忘记的可以去复习下,只用到了条件概率)。 本章会给出⼀些使⽤概率论进⾏分类的⽅法。⾸先从 ⼀个最简单的概率分类器开始,然后给出⼀些假设来学习朴素贝叶斯分类器。我们称之为“朴素”,是因为整个形式化过程只做最原始、最简单的假设,这章咱们主要讲的也是朴素贝叶斯。
二、贝叶斯的理论知识
1. 链接学习
贝叶斯决策论(Bayesian decision theory)是概率框架下实施决策的基本方法。我看了两篇文章,感觉很好,很适合第一次接触贝叶斯分类器的同学,它可以让我们直观的理解,肯定比我说的要好,可以去看一下。
非数学语言讲解贝叶斯定理
朴素贝叶斯分类算法
2. 手算预测
看完上面链接的内容,相信你们对贝叶斯有了个大体的理解了。我再举个例子加深一下印象(随便补充点)。(~ ̄▽ ̄)~
有如下数据集。
假定通过上面的数据集训练了一个贝叶斯分类器,再给你一个测试集,你会判断它的类型嘛?如下面的测试集。
可否知道这个瓜是不是好瓜呢?先说一下思路,咱们的目的是分别求好瓜的条件下满足上述条件的,坏瓜的条件下满足上述条件的。再判断谁的概率大,就是什么瓜。
通过分享的链接的内容知,第一步要先计算先验概率 P( c ), 显然有
然后,为每个属性估计条件概率 P(xi |c) :
要注意到密度和含糖率不是离散型的数据,而是连续的。
对连续属性可考虑概率密度函数,如下
其中 μ和σ^2分别是第 c 类样本在第 i个属性上取值的均值和方差。(还要求方差,好麻烦的有没有)
于是,有
由于 0.038 > 6.80 x 10^-5,因此7,朴素贝叶斯分类器将测试样本"测 1" 判别为 “好瓜”。
3. 拉普拉斯修正
需注意,若某个属性值在训练集中没有与某个类同时出现过,则直接进行概率估计,进行判别将出现问题。例如,在使用西瓜数据集中,训练朴素贝叶斯分类器时,对一个"敲声=情脆"的测试例,有
那代入累乘后,不就全是0了嘛,没意义了。
为了避免其他属性携带的信息被训练集中未出现的属性值"抹去’,在估计概率值时通常要进行"平滑" (smoothing),常用"拉普拉斯修正" 。具体来说,令 N 表示训练集 D 中可能的类别数,Ni表示第 i个属性可能的取值数,那公式就修正为
也是这个例子,它们就变成了
显然,拉普拉斯修正避免了因训练集样本不充分而导致概率估值为零的问题, 并且在训练集变大时,修正过程所引入的先验(prior)的影响也会逐渐变得可忽略,使得估值渐趋向于实际概率值。
到目前为止,咱们已经了解了足够的知识,可以开始编写代码了。如果还不清楚,那么了解代码的实际效果会有助于理解。
三、 构造简单的朴素贝叶斯
朴素贝叶斯的⼀般过程:
- 收集数据:置顶资料下载处。
- 准备数据:需要数值型或者布尔型数据
- 分析数据:有⼤量特征时,绘制特征作用不大,此时使⽤直⽅图效果更好。
- 训练算法:计算不同的独立特征的条件概率。
- 测试算法:计算错误率。
- 使⽤算法:⼀个常见的朴素贝叶斯应用是文档分类。可以在任意的分类场景中使用朴素贝叶斯分类器,不⼀定非要是文本。
以在线社区的留⾔板为例。为了不影响社区的发展,我们要屏蔽侮辱性的⾔论,所以要构建⼀个快速过滤器,如果某条留⾔使⽤了负⾯或者侮辱性的语⾔,那么就将该留⾔标识为内容不当。过滤这类内容是⼀个 很常见的需求。对此问题建⽴两个类别:侮辱类和非侮辱类,使⽤1和0分别表⽰。 接下来首先给出将⽂本转换为数字向量的过程,然后介绍如何基于这些向量来计算条件概率,并在此基础上构建分类器,最后还要介绍⼀些利⽤Python实现朴素贝叶斯过程中需要考虑的问题。
1. 收集数据
本例自写了几个简单数据。
2. 准备数据
先引入相关包
import numpy as np
from functools import reduce
因为咱们一开始构造简单的,先自己写几个句子,和它们是否是侮辱性的标签向量。
def loadDataSet():
"""
Parameters:
无
Returns:
postingList - 实验样本切分的词条
classVec - 类别标签向量
"""
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
#------测试-------------------------------------------------------
if __name__ == '__main__':
postingLIst, classVec = loadDataSet()
for each in postingLIst:
print(each)
print(classVec)
结果:
['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']
[0, 1, 0, 1, 0, 1]
将切分的实验样本词条整理成不重复的词条列表,也就是词汇表。
def createVocabList(dataSet):
vocabSet = set([]) #创建空的不重复的列表
for document in dataSet: #遍历所有句子,找并集
#vocabSet = set(vocabSet.union(document)) #取并集
vocabSet = vocabSet | set(document) # 取并集
#print('aaa',vocabSet)
return list(vocabSet)
#-------测试-----------------------------------------
if __name__ == '__main__':
postingList, classVec = loadDataSet()
print('postingList:\n',postingList)
myVocabList = createVocabList(postingList)
print('myVocabList:\n',myVocabList)
结果:
根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0。简单点的意思就是我们把输入的句子放入前面求得的有所有单词的词汇表。将输入句子里有的单词,在词汇表里置1,没有的为默认0。代码如下
def setOfWords2Vec(vocabList,inputSet):
returnVec = [0] * len(vocabList) #创建一个其中所含元素都为0的向量,默认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
#----------测试-----------------------------------------------------
if __name__ == '__main__':
postingList, classVec = loadDataSet()
print('postingList:\n',postingList)
myVocabList = createVocabList(postingList)
print('myVocabList:\n',myVocabList)
trainMat = []
for postinDoc in postingList: #将我们写的句子改写为0 1格式
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
print('trainMat:\n', trainMat)
结果:
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']]
myVocabList:
['cute', 'maybe', 'problems', 'steak', 'love', 'buying', 'posting', 'ate', 'so', 'I', 'has', 'my', 'flea', 'not', 'to', 'mr', 'how', 'stupid', 'take', 'dog', 'is', 'licks', 'him', 'worthless', 'dalmation', 'park', 'quit', 'please', 'help', 'stop', 'food', 'garbage']
trainMat:
[[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1],
[0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0]]
和前面的对一下,看看对吗,0,1的位置与单词位置是否一致。
从运行结果可以看出,postingList是原始的词条列表,myVocabList是词汇表。myVocabList是所有单词出现的集合,没有重复的元素。词汇表是用来干什么的?没错,它是用来将词条向量化的,一个单词在词汇表中出现过一次,那么就在相应位置记作1,如果没有出现就在相应位置记作0。trainMat是所有的词条向量组成的列表。它里面存放的是根据myVocabList向量化的词条向量。
3. 训练算法
前⾯介绍了如何将⼀组单词转换为⼀组数字,接下来看看如何使⽤这些数字计算概率。。现在已经知道⼀个词是否出现在⼀则留言中,也知道该留言所属的类别。通过贝叶斯相关公式。
其中。w表示这是⼀个向量,即它由多个数值组成。
我们将使用上述公式,对每个类计算该值,然后⽐较这两个概率值的大小。如何计算呢?首先可以通过类别 i(侮辱性留⾔或⾮侮辱性留⾔)中⽂档数除以总的⽂档数来计算概率p(ci)。接下来计算p(w|ci),这里就要⽤到朴素贝叶斯假设。如果将w展开为⼀个个独立特征,那么就可以将上述概率写 作p(w0,w1,w2…wN|ci)。这⾥假设所有词都互相独⽴, 该假设也称作条件独立性假设,它意味着可以使用p(w0|ci)p(w1|ci)p(w2|ci)…p(wN|ci)来计算上述概率,这就极大地简化了计算的过程。
该函数的伪代码如下:
知道思路和流程了,下面就是代码了。
def trainNB0(trainMatrix,trainCategory):
"""
Parameters:
trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵
trainCategory - 训练类别标签向量,即loadDataSet返回的classVec
Returns:
p0Vect - 侮辱类的条件概率数组
p1Vect - 非侮辱类的条件概率数组
pAbusive - 文档属于侮辱类的概率
"""
#文档的数量
numtrainDocs = len(trainMatrix)
#每篇文档的词条数
numWords = len(trainMatrix[0])
#文档属于侮辱类的概率
pAbusive =