【机器学习】k近邻算法(KNN)【机器学习】决策树(Decision Tree)【机器学习】朴素贝叶斯(Naive Bayes)
1. 概述
贝叶斯分类算法是统计学的一种概率分类方法,朴素贝叶斯分类(Naive Bayes)是贝叶斯分类中最简单的一种。
分类原理:利用贝叶斯公式根据某特征的先验概率计算出其后验概率,然后选择具有最大后验概率的类作为该特征所属的类。
之所以称之为”朴素”,是因为贝叶斯分类只做最原始、最简单的假设:所有的特征之间是统计独立的。
相关概念:
(1)条件概率
条件概率(Condittional probability),就是指在事件B发生的情况下,事件A发生的概率,用P(A|B)来表示。
表达如下:
(2)全概率
如果事件A1,A2,...,An构成一个完备事件且都有正概率,那么对于任意一个事件B则有:
(3)贝叶斯推断
根据条件概率和全概率公式,可以得到贝叶斯公式如下:
P(A)称为"先验概率"(Prior probability),即在B事件发生之前,我们对A事件概率的一个判断。
P(A|B)称为"后验概率"(Posterior probability),即在B事件发生之后,我们对A事件概率的重新评估。
P(B|A)/P(B)称为"可能性函数"(Likely hood),这是一个调整因子,使得预估概率更接近真实概率。
因此,我们就是通过先验概率乘以调整因子来计算其后验概率。即:
条件概率可以理解为:后验概率=先验概率*调整因子
其中:
调整因子>1,表示事件B发生时,事件A发生的可能性变大,先验概率被增强;调整因子=1,表示事件B无助于判断事件A的可能性;调整因子<1,表示事件B发生时,事件A发生的可能性变小,先验概率呗削弱。
根据贝叶斯公式:
转换成分类任务的表达式:
那我们结合这个公式,就可以在相同特征下,对于不同类别的概率大小的判断
若有以下数据:
假如某男(帅,性格不好,不上进)向女生求婚,该女生嫁还是不嫁?
那实际上我们需要计算P(嫁 | 帅 性格不好 不上进)和P(不嫁 | 帅 性格不好 不上进),即:
通过全概率公式计算分母,公式如下:
对表中数据统计可得:
则对于类别“嫁”的贝叶斯分子为:
对于类别“不嫁”的贝叶斯分子为:
最终计算结果:
因此选择不嫁
2. 朴素贝叶斯算法种类
在scikit-learn中,一共有3个朴素贝叶斯的分类算法。
分别是GaussianNB,MultinomialNB和BernoulliNB。
(1)GaussianNB
GaussianNB就是先验为高斯分布(正态分布)的朴素贝叶斯,假设每个标签的数据都服从简单的正态分布。
调用scikit-learn的包实现GaussianNB:
数据集为4中特征,3种类别的数据。
# 导入包import pandas as pdfrom sklearn.naive_bayes import GaussianNBfrom sklearn.model_selection import train_test_splitfrom sklearn.metrics import accuracy_scorefrom sklearn import datasets# 导入数据集iris = datasets.load_iris()# 切分数据集,将数据的内容余目标切分成训练集和测试集Xtrain, Xtest, ytrain, ytest = train_test_split(iris.data,iris.target,random_state=12)# 建模clf = GaussianNB()clf.fit(Xtrain, ytrain)# 在测试集上执行预测,proba导出的是每个样本属于某类的概率pre_result=clf.predict(Xtest) # 预测结果pro_result=clf.predict_proba(Xtest) # 预测的每一类的百分比# 测试准确率accuracy_value=accuracy_score(ytest, pre_result)# 打印输出结果print(ytest) # 测试集的正确值print(pre_result) # 测试集的预测值print(pro_result) # 测试集的计算结果 每一类的可能性百分比print(accuracy_value) # 最后预测准确率
准确率为0.9736842105263158
(2)MultinomialNB
MultinomialNB就是先验为多项式分布的朴素贝叶斯。它假设特征是由一个简单多项式分布生成的。
多项分布可以描述各种类型样本出现次数的概率,因此多项式朴素贝叶斯非常适合用于描述出现次数或者出现次数比例的特征。
该模型常用于文本分类,特征表示的是次数,例如某个词语的出现次数。
多项式分布公式如下:
(3)BernoulliNB
BernoulliNB就是先验为伯努利分布的朴素贝叶斯。
假设特征的先验概率为二元伯努利分布,即如下式:
此时只有两种取值, 只能取值0或者1。
在伯努利模型中,每个特征的取值是布尔型的,即true和false,或者1和0。
在文本分类中,就是一个特征有没有在一个文档中出现。
总结:
如果样本特征的分布大部分是连续值,使用GaussianNB会比较好。如果样本特征的分布大部分是多元离散值,使用MultinomialNB比较合适(常用于文本分类)。如果样本特征是二元离散值或者很稀疏的多元离散值,应该使用BernoulliNB。3. 使用朴素贝叶斯进行文档分类
朴素贝叶斯一个很重要的应用就是文本分类
目的:构建一个快速过滤器屏蔽掉侮辱性的言论
对此问题建立两个类型:侮辱类和非侮辱类,使用1和0分别表示
思路:我们把文本看成单词向量或者词条向量,也就是说将句子转换为向量。考虑出现所有文档中的单词,再决定将哪些单词纳入词汇表或者说所要的词汇集合,然后必须要将每一篇文档转换为词汇表上的向量。简单起见,我们先假设已经将本文切分完毕,存放到列表中,并对词汇向量进行分类标注。
此案例所有的函数:
loadDataSet:创建实验数据集createVocabList:生成词汇表setOfWords2Vec:生成词向量get_trainMat:所有词条向量列表trainNB:朴素贝叶斯分类器训练函数classifyNB:朴素贝叶斯分类器分类函数testingNB:朴素贝叶斯测试函数
(1)创建实验数据集 loadDataSet
"""函数功能:创建实验数据集参数说明:无参数返回:postingList:切分好的样本词条classVec:类标签向量"""def loadDataSet():dataSet = [['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 dataSet, classVec
(2)生成词汇表createVocabList
"""函数功能:生成词汇表:将切分的样本词条整理成词汇表(不重复)参数说明:dataSet:切分好的样本词条返回:vocabList:不重复的词汇表"""def createVocabList(dataSet):vocabSet = set() # 创建一个空的集合for doc in dataSet: # 遍历dataSet中的每一条言论vocabSet = vocabSet | set(doc) # 取并集vocabList = list(vocabSet)return vocabList
(3)生成词向量setOfWords2Vec
"""函数功能:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0参数说明:vocabList:词汇表inputSet:切分好的词条列表中的一条返回:returnVec:文档向量,词集模型"""def setOfWords2Vec(vocabList, inputSet):returnVec = [0] * len(vocabList) # 创建一个其中所含元素都为0的向量for word in inputSet: # 遍历每个词条if word in vocabList: # 如果词条存在于词汇表中,则变为1returnVec[vocabList.index(word)] = 1else:print(f" {word} is not in my Vocabulary!")return returnVec # 返回文档向量
(4)所有词条向量列表get_trainMat
"""函数功能:生成训练集向量列表参数说明:dataSet:切分好的样本词条返回:trainMat:所有的词条向量组成的列表"""def get_trainMat(dataSet):trainMat = [] # 初始化向量列表vocabList = createVocabList(dataSet) # 生成词汇表for inputSet in dataSet: # 遍历样本词条中的每一条样本returnVec = setOfWords2Vec(vocabList, inputSet) # 将当前词条向量化trainMat.append(returnVec) # 追加到向量列表中return trainMat
(5)朴素贝叶斯分类器训练函数trainNB
"""函数功能:朴素贝叶斯分类器训练函数参数说明:trainMat:训练文档矩阵classVec:训练类别标签向量返回:p0V:非侮辱类的条件概率数组p1V:侮辱类的条件概率数组pAb:文档属于侮辱类的概率"""def trainNB(trainMat, classVec):n = len(trainMat) # 计算训练的文档数目m = len(trainMat[0]) # 计算每篇文档的词条数pAb = sum(classVec) / n # 文档属于侮辱类的概率p0Num = np.zeros(m) # 词条出现数初始化为0p1Num = np.zeros(m) # 词条出现数初始化为0p0Denom = 0 # 分母初始化为0p1Denom = 0 # 分母初始化为0for i in range(n): # 遍历每一个文档if classVec[i] == 1: # 统计属于侮辱类的条件概率所需的数据p1Num += trainMat[i]p1Denom += sum(trainMat[i])else: # 统计属于非侮辱类的条件概率所需的数据p0Num += trainMat[i]p0Denom += sum(trainMat[i])p1V = p1Num / p1Denomp0V = p0Num / p0Denomreturn p0V, p1V, pAb # 返回属于非侮辱类,侮辱类和文档属于侮辱类的概率
(6)朴素贝叶斯分类器分类函数classifyNB
"""函数功能:朴素贝叶斯分类器分类函数参数说明:vec2Classify:待分类的词条数组p0V:非侮辱类的条件概率数组p1V:侮辱类的条件概率数组pAb:文档属于侮辱类的概率返回:0:属于非侮辱类1:属于侮辱类"""def classifyNB(vec2Classify, p0V, p1V, pAb):p1 = reduce(lambda x, y: x * y, vec2Classify * p1V) * pAb # 对应元素相乘p0 = reduce(lambda x, y: x * y, vec2Classify * p0V) * (1 - pAb)print('p0:', p0)print('p1:', p1)if p1 > p0:return 1else:return 0
(7)朴素贝叶斯测试函数testingNB
"""函数功能:朴素贝叶斯测试函数参数说明:testVec:测试样本返回:测试样本的类别"""def testingNB(testVec):dataSet, classVec = loadDataSet() # 创建实验样本vocabList = createVocabList(dataSet) # 创建词汇表trainMat = get_trainMat(dataSet) # 将实验样本向量化p0V, p1V, pAb = trainNB(trainMat, classVec) # 训练朴素贝叶斯分类器thisone = setOfWords2Vec(vocabList, testVec) # 测试样本向量化if classifyNB(thisone, p0V, p1V, pAb):print(testVec, '属于侮辱类') # 执行分类并打印分类结果else:print(testVec, '属于非侮辱类') # 执行分类并打印分类结果
最后测试:
# 测试样本1testVec1 = ['love', 'my', 'dalmation']testingNB(testVec1)# 测试样本2testVec2 = ['stupid', 'garbage']testingNB(testVec2)
结果:
这是出现问题,明显‘stupid’与‘garbage’属于侮辱词汇,而最后判断为非侮辱的,探究一下原因。
由于最后的概率是非侮辱和侮辱概率相乘,因此打印样本2的这两个的概率看看:
可以看到,即使这两个词汇侮辱类的概率值是存在的,而非侮辱值由于初始化设置为零因此相乘之后也为 零,所以判断为非侮辱类
利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算p(w0|1)p(w1|1)p(w2|1)。如果其中有一个概率值为0,那么最后的成绩也为0
处理该问题的方法:
可以将所有词的出现数初始化为1,并将分母初始化为2。这种做法就叫做拉普拉斯平滑(Laplace Smoothing)又被称为加1平滑,是比较常用的平滑方法,它就是为了解决0概率问题。
因此修改
trainNB:朴素贝叶斯分类器训练函数testingNB:朴素贝叶斯测试函数
# 使用拉普拉斯平滑处理的def trainNB(trainMat,classVec):n = len(trainMat) #计算训练的文档数目m = len(trainMat[0]) #计算每篇文档的词条数pAb = sum(classVec)/n #文档属于侮辱类的概率p0Num = np.ones(m) #词条出现数初始化为1p1Num = np.ones(m) #词条出现数初始化为1p0Denom = 2 #分母初始化为2p1Denom = 2 #分母初始化为2for i in range(n): #遍历每一个文档if classVec[i] == 1: #统计属于侮辱类的条件概率所需的数据p1Num += trainMat[i]p1Denom += sum(trainMat[i])else: #统计属于非侮辱类的条件概率所需的数据p0Num += trainMat[i]p0Denom += sum(trainMat[i])p1V = np.log(p1Num/p1Denom)p0V = np.log(p0Num/p0Denom)return p0V,p1V,pAb #返回属于非侮辱类,侮辱类和文档属于侮辱类的概率# 使用拉普拉斯平滑处理的def classifyNB(vec2Classify, p0V, p1V, pAb):p1 = sum(vec2Classify * p1V) + np.log(pAb) # 对应元素相乘p0 = sum(vec2Classify * p0V) + np.log(1 - pAb) # 对应元素相乘if p1 > p0:return 1else:return 0
计算概率不再是零:
运行结果正确:
学习来源:菊安酱的机器学习实战