一、朴素贝叶斯算法的起源和发展
朴素贝叶斯算法源于贝叶斯定理,由于其简单而有效的特性,成为机器学习中常用的分类算法之一。其名中的“朴素”表示该算法对于特征之间的条件独立性做了朴素的假设,即假设给定类别的情况下,特征之间是相互独立的。尽管这一假设在现实数据中并不总是成立,但朴素贝叶斯的高效性使其在实际应用中表现出色。
二、贝叶斯定理基本概念
贝叶斯定理是基于概率论的一项重要原理,它提供了一种根据新的观察数据来更新我们对事件概率的估计的方法。贝叶斯定理的核心思想是通过先验概率和新观察到的数据来计算后验概率。下面是一些贝叶斯定理的基本概念:
-
先验概率: 在考虑新的观察数据之前,我们对事件的概率的初始估计。这是基于以往知识或经验得出的。
-
似然度: 衡量观察数据在给定假设下发生的可能性。似然度描述了观察到的数据对不同假设的支持程度。
贝叶斯定理
条件概率:就是事件A在另外一个事件B已经发生条件下的发生概率。条件概率表示为P(A|B),读作“在B发生的条件下A发生的概率”。
联合概率:表示两个事件共同发生(数学概念上的交集)的概率。A与B的联合概率表示为联合概率。
P(AB)=P(A∣B)P(B)=P(B∣A)P(A),若AB相互独立,P(AB)=P(A)P(B)
贝叶斯公式:
通俗来讲在朴素贝叶斯算法中即不同类别之间进行大小比较,其分母是相同的,所有朴素贝叶斯的朴素代表属性独立性的假设因此式子也可以改写为这样:
P(类别∣特征)∝P(特征1∣类别)P(特征2∣类别)……P(特征n|类别)
先验概率
在朴素贝叶斯算法中,先验概率指的是在考虑任何特征信息之前,某个样本属于某一类别的概率。数学上表示为 P(),其中Ci 表示第i 个类别。
先验概率的计算通常基于训练数据中每个类别出现的频率。例如,如果有 N 个样本,其中有 个属于类别
,那么先验概率可以估计为:
后验概率
后验概率是指在考虑了观察到的特征信息后,样本属于某一类别的概率。根据贝叶斯定理,后验概率 可以通过先验概率和似然度的乘积计算得到
拉普拉斯修正
在实际应用中,由于可能存在某些特征在某个类别下未出现的情况,导致似然度为零,从而使后验概率无法计算。为了解决这个问题,引入了拉普拉斯修正(Laplace smoothing)。
拉普拉斯修正通过在似然度的计算中添加一个小的常数值来避免概率为零的情况。修正后的似然度计算公式为:
其中,是类别
下特征 Xi 出现的次数,
是类别
下所有特征出现的总次数,而 ∣V∣ 是特征的可能取值的总数。
拉普拉斯修正确保了每个特征在每个类别下都有一个非零的概率,使得算法更加稳健和泛化能力更强。
三、垃圾邮件分类
构建一个用于文本分类的数据集,并生成相应的词汇表:
import numpy as np
#1准备数据:从文本中构建词向量
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:
returnVec[vocabList.index(word)]=1
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec
#创建实验样本
listPosts,listClasses=loadDataSet()
print('数据集\n',listPosts)
#创建词汇表
myVocabList= createVocabList(listPosts)
print('词汇表:\n',myVocabList)
loadDataSet 函数:
此函数用于准备数据,其中 postingList 包含了一组文档,每个文档表示一条帖子或评论,classVec 是对应的标签向量,其中1表示侮辱性文字,0表示正常言论。
函数返回 postingList 和 classVec。
createVocabList 函数:
该函数接受一个数据集作为输入,通过迭代数据集中的每个文档,创建了一个词汇表(vocabSet),其中包含了数据集中所有不重复的词条。
最后,函数返回一个不重复的词条列表。
setOfWords2Vec 函数:
此函数用于将文档转换成词向量。给定一个词汇表 vocabList 和一个文档 inputSet,函数返回一个与词汇表对应的词向量。
返回的词向量中,如果词汇表中的词在文档中出现,则对应位置的元素为1,否则为0。
结果:
实现朴素贝叶斯文本分类器,并包含了训练和测试两个阶段:
#训练算法:从词向量计算概率
from math import log
#改进后的朴素贝叶斯分类器训练函数
def trainNB0(trainMatrix,trainCategory):
numTrainDOcs=len(trainMatrix) #获得训练的文档总数
numWords=len(trainMatrix[0]) #获得每篇文档的词总数
pAbusive=sum(trainCategory)/float(numTrainDOcs) #计算文档是侮辱类的概率
p0Num=np.ones(numWords) #创建numpy.ones数组,初始化概率
p1Num=np.ones(numWords) #创建numpy.ones数组,初始化概率
p0Denom=2.0 #初始化为2.0
p1Denom=2.0 #初始化为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文档属于侮辱类的概率
listOposts,listClasses=loadDataSet()
myVocabList=createVocabList(listOposts) #创建不重复的词表
trainMat=[]
for postinDoc in listOposts: #按小list计数
trainMat.append(setOfWords2Vec(myVocabList,postinDoc)) #在trainmat中不断加入训练数据,加入的是词表list里面每个小list在总词表中的有无
#append加入也不是直接加入。而是把每次加入的对象单独作为一个新的list元素
p0V,p1V,pAb=trainNB0(trainMat,listClasses)
print(pAb)
print(p0V)
print(p1V)
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
p1=sum(vec2Classify*p1Vec)+np.log(pClass1) #对应元素相乘log(A*B)=log(A)+log(B)所以这里+log(pClass1)
p0=sum(vec2Classify*p0Vec)+np.log(1.0-pClass1) #这里计算的p(w|Ci)*p(Ci)=p(wo,w1,w2....|Ci)*p(Ci)=p(w0|Ci)p(w1|Ci)……p(Ci)
if p1>p0:
return 1
else:
return 0
#测试朴素贝叶斯分类器
def testingNB():
listOPosts,listClasses=loadDataSet()
myVocabList=createVocabList(listOPosts) #创建词汇表
trainMat=[]
for postinDoc in listOPosts: #得到训练集
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
p0V,p1V,pAb=trainNB0(np.array(trainMat),np.array(listClasses))#训练分类器,注意列表到array格式的转换
testEntry=['love','my','dalmation']
thisDoc=np.array(setOfWords2Vec(myVocabList,testEntry))
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'属于侮辱类')
else:
print(testEntry,'属于非侮辱类')
testEntry=['stupid','garbage']
thisDoc=np.array(setOfWords2Vec(myVocabList,testEntry))
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'属于侮辱类')
else:
print(testEntry,'属于非侮辱类')
testingNB()
训练阶段: trainNB0 函数:
输入:
trainMatrix:训练数据的词向量矩阵,每一行表示一个文档的词向量。
trainCategory:训练数据对应的标签,1表示侮辱性文字,0表示正常言论。
输出:
p0Vect:非侮辱类的条件概率数组。
p1Vect:侮辱类的条件概率数组。
pAbusive:文档属于侮辱类的概率。
测试阶段:classifyNB
函数:
输入:
vec2Classify
:待分类的文档的词向量。
p0Vec
:非侮辱类的条件概率数组。
p1Vec
:侮辱类的条件概率数组。
pClass1
:文档属于侮辱类的概率。
输出:
根据朴素贝叶斯分类规则,返回文档属于侮辱类(1)或非侮辱类(0)。
结果
使用朴素贝叶斯分类器对垃圾邮件进行分类:
#基于词袋模型
def bagofwords2VecMN(vocabList, inputset):
returnVec=[0]*len(vocabList)
for word in inputSet:
returnVec[vocabList.index(word)]+=1 #词汇每出现一次就加1
return returnVec
import re
def textParse(bigString):
listOfTokens = re.split(r'\W+', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 0]
import random
def spamTest():
docList=[]
classList=[]
fullText=[]
for i in range(1,26): #遍历25个文件
wordList=textParse(open ( 'C:/Users/Brenty/Desktop/email/spam/%d.txt '%i, 'r', encoding='latin1').read())
docList.append(wordList)
fullText.append(wordList)
classList.append(1) #垃圾邮件标记为1
wordList=textParse(open ( 'C:/Users/Brenty/Desktop/email/ham/%d.txt '%i, 'r', encoding='latin1').read())
docList.append(wordList)
fullText.append(wordList)
classList.append(0) #垃圾邮件标记为0
vocabList=createVocabList(docList) #创建不重复词汇表
trainingset=list(range(50)) ##共50个文件,索引值e-se,列表格式
testSet=[]
for i in range(10):
randIndex=int(random.uniform(0,len(trainingset))) #随机选择10个作为测试集
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 #统计分类错误的样本数量
print('分类错误的测试集:',docList[docIndex])
print('错误率:%.2f%%'%(float(errorCount)/len(testSet)*100))
spamTest()
bagofwords2VecMN 函数:
输入:
vocabList:词汇表,包含了所有文档中出现的不重复词汇。
inputSet:待转换为词袋模型的文档。
输出:
返回一个词袋模型的向量,向量的每个元素表示对应词汇在文档中出现的次数。
textParse 函数:
输入:
bigString:一段文本。
输出:
返回经处理的文本,通过正则表达式进行分词,将文本转换成小写,并去除长度小于等于0的词汇。
结果
四、朴素贝叶斯的局限性与改进
局限性:
假设特征独立: 朴素贝叶斯算法中最重要的假设是所有特征之间相互独立,即给定类别的情况下,特征之间不存在相关性。在实际数据中,这个假设经常不成立,特征之间可能存在一定的依赖关系。
对零概率问题敏感: 当某个特征在某个类别中没有出现过时,朴素贝叶斯会给该特征赋予零概率,导致整个后验概率为零。为了解决这个问题,通常采用拉普拉斯平滑或其他平滑方法。
无法处理连续型数据: 朴素贝叶斯算法假设特征是离散的,因此对于连续型数据需要进行离散化处理,这可能损失一些信息。
对文本分类的不适应: 在文本分类任务中,朴素贝叶斯通常基于词袋模型,忽略了单词在文本中的顺序和结构信息,这在某些应用场景下可能不够灵活。
改进方法:
特征选择: 选择合适的特征对朴素贝叶斯的性能有重要影响。可以采用特征选择方法来去除一些不相关或冗余的特征,提高模型的泛化能力。
使用其他平滑方法: 除了拉普拉斯平滑,还有其他平滑方法,如Lidstone平滑等,可以在一定程度上缓解零概率问题。
处理连续型数据: 对于连续型数据,可以使用高斯朴素贝叶斯模型,该模型假设特征的分布符合高斯分布,从而适应连续型数据。
考虑特征相关性: 对于特征之间存在相关性的情况,可以考虑使用更为复杂的贝叶斯网络等模型,以更准确地表示特征之间的关系。
深度学习方法: 深度学习模型如循环神经网络(RNN)和长短时记忆网络(LSTM)在处理序列数据和文本分类任务上取得了显著的成就,可以作为替代或改进朴素贝叶斯的选择。
五、总结与收获
-
简单而高效: 朴素贝叶斯算法是一种简单而高效的分类算法。其基本原理清晰,易于理解和实现,使其成为许多应用场景中的首选算法之一。
-
适用于小规模数据: 朴素贝叶斯在小规模数据集上表现出色,尤其是在文本分类等任务中,它通常能够取得令人满意的结果。
-
处理高维数据: 由于其对特征条件独立性的假设,朴素贝叶斯在高维数据上的计算效率较高,适用于包含大量特征的问题。
-
文本分类应用: 在自然语言处理领域,朴素贝叶斯广泛用于文本分类任务。基于词袋模型,它能够有效地处理文本数据,被广泛应用于垃圾邮件过滤、情感分析等场景。
-
处理离散型数据: 朴素贝叶斯擅长处理离散型数据,对于分类问题,特别是分类标签之间存在概率关系的问题,其表现优异。
-
概率框架: 朴素贝叶斯算法基于概率框架,提供了对不确定性的建模,对于需要估计不同类别概率的问题具有独特优势。
尽管朴素贝叶斯算法在某些方面存在假设的局限性,但其简单性和良好的性能使其在实际应用中得到了广泛的采用。在大数据时代,朴素贝叶斯的高效性使其成为处理实时、高维数据的理想选择。其在文本分类等任务中的成功应用也为其在更多领域的发展提供了坚实基础。