朴素贝叶斯算法是有监督的学习算法,解决的是分类问题。其分类原理就是利用贝叶斯公式根据某特征的先验概率计算出其后验概率,然后选择具有最大后验概率的类作为该特征所属的类。之所以称之为”朴素”,是因为贝叶斯分类只做最原始、最简单的假设:所有的特征之间是统计独立的。但由于该算法以自变量之间的独立(条件特征独立)性和连续变量的正态性假设为前提,就会导致算法精度在某种程度上受影响。
优点:
对小规模的数据表现很好,能个处理多分类任务,适合增量式训练(即可以实时的对新增的样本进行训练)
朴素贝叶斯模型发源于古典数学理论,有稳定的分类效率
朴素贝叶斯算法的健壮性比较好,对于不同类型的数据集不会呈现出太大的差异性
缺点:
由于我们是通过先验概率和数据来决定后验的概率从而决定分类,所以分类决策存在一定的错误率
对输入数据的表达形式较敏感
属性独立性的条件同时也是朴素贝叶斯分类器的不足之处。数据集属性的独立性在很多情况下是很难满足的,因为数据集的属性之间往往都存在着相互关联,在属性个数比较多或者属性之间相关性较大时,分类效果不好
朴素贝叶斯是贝叶斯决策理论的一部分,所以有必要了解一下贝叶斯决策理论。假设有一个数据集,它由两类数据组成,如下图:
我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中红色圆点表示的类别)的概率,用p2(x,y)表示数据点(x,y)属于类别2(图中蓝色三角形表示的类别)的概率,那么对于一个新数据点(x,y),可以用下面的规则来判断它的类别:
如果p1(x,y) > p2(x,y),那么类别为1
如果p1(x,y) < p2(x,y),那么类别为2
也就是说,我们会选择高概率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策。
一般地,设A,B两个事件,P(A)>0,称已知A发生条件下B发生的概率为B的条件概率,记为P(B|A)。根据上图,可以发现在事件A发生的情况下,事件B发生的概率为P(A∩B)除以P(A),即上图中橙色部分占黄色部分的比例。因此可得条件概率公式为:
同理有:
全概率公式,指若事件{A1,A2,…,An}构成一个完备事件组且都有正概率,则对任意一个事件B都有:
则有:
把P(A)称为先验概率(Prior probability),即在B事件发生之前,我们对A事件概率的一个判断。
P(A|B)称为后验概率(Posterior probability),即在B事件发生之后,我们对A事件概率的重新评估。
P(B|A)/P(B)称为可能性函数(Likelyhood),这是一个调整因子,使得预估概率更接近真实概率。
所以条件概率可以理解为:后验概率 = 先验概率 × 调整因子
如果 “可能性函数”>1,意味着先验概率被增强,事件A的发生的可能性变大;
如果 “可能性函数”=1,意味着B事件无助于判断事件A的可能性;
如果 “可能性函数”<1,意味着"先验概率"被削弱,事件A的可能性变小。
下表收集了15天内某网球爱好者每天是否打网球的数据,其中从左往右每列的属性分别为:天数、天气情况、温度、湿度、风力、当天是否打网球。
现在假设有一个样例 x = {Sunny, Hot, High, Weak},那该种情况下应该去打网球还是不去呢,如何对其进行分类呢?
首先分别统计对15天中去打排球和没去打排球的天数以及各自情况下不同属性分类的的个数,如下图:
上图中的P©表示15天中打网球的概率,|Dc,Xi|表示去打网球的情况下各个属性的不同取值的个数,P(Xi|c)表示表示去打网球的情况下各个属性的不同取值的个数占总属性取值个数的比例。例如,9天去打网球的情况下天气为下雨的概率为 1 3 \frac {1} {3} 31
根据上述方法,统计出是否打网球的情况下各个属性取值的占比,如下图:
此时,根据贝叶斯公式,分别求解去打网球和不去打网球的概率,求解公式如下:
其中,x={Sunny, Hot, High, Weak},由于分母相同,因此只需计算分子的结果来进行比较即可,计算结果如下图所示:
因此对于样例 x = {Sunny, Hot, High, Weak}所述的情况下应该不去打网球,上述的分类过程即为贝叶斯分类器工作的大致过程。下面根据该过程使用贝叶斯分类器对垃圾邮件进行过滤。
(1) 朴素贝叶斯分类器模型的训练: 首先提供两组已经识别好的邮件,一组是正常邮件,另一组是垃圾邮件,然后用这两组邮件对过滤器进行“训练”。首先解析所有邮件,提取每一个词,然后计算每个词语在正常邮件和垃圾邮件中出现的概率。例如,我们假定“cute”这个词在词数(不重复)为4000的垃圾邮件中出现200次,那么该词在垃圾邮件中出现的频率就是5%;而在词数(不重复)为4000的正常邮件中出现1000次,那么该词在正常邮件中出现的频率就是25%。此时就已经初步建立了关于“cute”这个词的过滤模型。
(2) 使用朴素贝叶斯分类器模型进行过滤测试: 读入一封未进行分类的新邮件,然后该邮件进行解析,发现其中包含了“cute”这个词,用P(W|S)和P(W|H)分别表示这个词在垃圾邮件和正常邮件中出现的概率。随后将邮件中解析出来的每个词(已建立对应的训练模型)属于正常邮件和垃圾邮件的概率分别进行累乘,最后比较该邮件属于正常邮件和垃圾邮件的概率大小,可将该邮件归类为概率较大的那一类。
我们把文本看成单词向量或者词条向量,也就是说将句子转换为向量。考虑出现在所有文档中的所有单词,再决定将哪些词纳入词汇表或者说所要的词汇集合,然后必须要将每一篇文档转换为词汇表上的向量。
代码如下:
# 创建实验样本
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]
return postingList,classVec
"""
函数说明:将实验样本中的词条进行合并(剔除重复项)
Parameters:
dataset:样本数据集列表
Rerurns:
vocabSet:包含所有文档中出现的不重复词的列表
"""
def createVocabList(dataset):
vocabSet = set([])
for document in dataset:
# 取并集
vocabSet = vocabSet | set(document)
return list(vocabSet)
"""
函数说明:将样本列表(inputSet)向量化(0或1)
Parameters:
vocabList:词集列表(createVocabList()函数返回后的列表)
inputSet:待向量化的词条列表
Rerurns:
returnVec:inputSet列表向量化后映射到vocabList中的列表
"""
# 将数据集转为词向量(创建一个包含在所有文档中出现的不重复的词集)
def setOfWords2Vec(vocabList, inputSet):
# 创建一个其中所含元素都为0的列表
returnVec = [0]*len(vocabList)
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
if __name__ == '__main__':
postingList, classVec = loadDataSet()
myVocabList = createVocabList(postingList)
print('myVocabList:\n', myVocabList)
print(setOfWords2Vec(myVocabList, postingList[0]))
运行结果:
记w表示一个向量(可理解为经过向量化后的一封邮件),它由多个数值组成。在该例中数值个数与词汇表中的词条个数相同,则贝叶斯公式可表示为:
其中cic_ici表示第i个属性的取值,由于分母不变,因此只用比较分子即可。
代码如下:
"""
函数说明:朴素贝叶斯分类器训练函数
Parameter:
trainMatrix — 训练文档矩阵,即setOfWord2Vec函数返回的returnVec构成的矩阵
trainCategory - 训练类别标签向量,即loadDataSet返回的classVec
Returns:
p0Vect - 非侮辱类的条件概率数组
p1Vect - 侮辱类的条件概率数组
pAbusive - 文档属于侮辱类的概率
"""
def trainNBO(trainMatrix,trainCategory):
#文档数目(6)
numTrainDocs = len(trainMatrix)
#词数
numWords = len(trainMatrix[0])
##初始化概率
pAbusive = sum(trainCategory) / float(numTrainDocs)
#使用zeros()初始化后的数组如果其中一个概率值为0则最后的结果也为0,因此使用ones()
# numpy中的ndarray对象用于存放同类型元素的多维数组。
p0Num = np.ones(numWords)
p1Num = np.ones(numWords)
p0Denom = 2.0
p1Denom = 2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
#向量相加
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
# 分别计算某个词条在某个类别(该类别中所有词条的总数)中的占比
# #避免程序输出下溢,采用自然对数处理
p1Vect = np.log(p1Num/p1Denom)
p0Vect = np.log(p0Num/p0Denom)
return p0Vect,p1Vect,pAbusive
if __name__ == '__main__':
postingList, classVec = loadDataSet()
myVocabList = createVocabList(postingList)
trainMat = []
for postinDoc in postingList:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0Vect, p1Vect, pAbusive = trainNBO(trainMat, classVec)
print('p0Vect:\n', p0Vect)
print('p1Vect:\n', p1Vect)
print('文档属于侮辱性文档的概率:', pAbusive)
代码如下:
"""
函数说明:朴素贝叶斯分类函数
Parameter:
vec2Classify - 待分类的词条数组
p0Vec - 侮辱类的条件概率数组
p1Vec - 非侮辱类的条件概率数组
pClass1 - 文档属于侮辱类的概率
Returns:
1 - 属于侮辱类
0 - 属于非侮辱类
"""
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)
p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
if __name__ == '__main__':
postingList, classVec = loadDataSet()
myVocabList = createVocabList(postingList)
trainMat = []
for postinDoc in postingList:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V,p1V,pAb = trainNBO(np.array(trainMat),np.array(classVec))
testEntry = ['love', 'my', 'dalmation']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'属于侮辱类')
else:
print(testEntry,'属于非侮辱类')
testEntry = ['stupid', 'love', 'cute']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'属于侮辱类')
else:
print(testEntry,'属于非侮辱类')
我们将每个词的出现与否作为一个特征,这可以被描述为词集模型。如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为词袋模型(bag-of-words models)。在词袋中,每个单词可以出现多次 ,而在词集中,每个词只能出现一次。为适应词袋模型,需要对函数setofWords2Vec()稍加修改,修改后的函数称为bagOfWords2Vec()。
"""
函数说明:根据vocabList词汇表,构建词袋模型
Parameter:
vocabList - creatVocabList
inputSet - 切分的词条列表
Returns:
returnVec - 文档向量
"""
def bagOfWords2Vec(vocabList,inputSet):
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
"""
函数说明:对文本进行切分
Parameter:
bigString:长字符串(一封邮件)
Returns:
返回形式统一的、被切分后的词表
"""
def textParse(bigString):
#将非数字、单词外的任意字符串作为切分标志
listOfTokens = re.split('\\W*',bigString)
#为了使所有词的形式统一,除了单个字母,例如大写的I,其余的单词变成小写
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
正则表达式\W表示匹配任何非单词字符,等价于[^A-Za-z0-9_]
正则表达式\w表示匹配包含下画线的任何单词字符,等价于[A-Za-z0-9_]
"""
函数说明: 垃圾邮件测试函数
"""
def spamTest():
# 列表classList用于记录对应邮件的标签
# docList存放切分后的每封邮件的词条的列表
docList = [];
classList = []
# 导入并解析50个文件(垃圾邮件和非垃圾邮件各有25封),并记录对应的标签值,垃圾邮件标记为1
for i in range(1, 26):
# file.read([size]):size未指定每次读取的大小时则返回整个文件
wordList = textParse(open('spam/%d.txt' % i, 'r').read())
docList.append(wordList)
# fullTest.append(wordList)
classList.append(1)
wordList = textParse(open('ham/%d.txt' % i, 'r').read())
docList.append(wordList)
# fullTest.append(wordList)
classList.append(0)
# 获得50个文件构成的词列表(不含重复值)
vocabList = createVocabList(docList)
# 创建一个大小为50的列表,其中的值为对应下标的值
trainingSet = list(range(50))
testSet = []
# 从50封邮件中随机抽取10封作为测试邮件用例
for i in range(10):
# random.uniform(x, y)方法将随机生成一个实数,它在 [x,y] 范围内。
randIndex = int(random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[randIndex])
del (trainingSet[randIndex])
trainMat = [];
trainClass = []
# 构建训练集词向量列表,训练集标签
for docIndex in trainingSet:
trainMat.append(bagOfWords2Vec(vocabList, docList[docIndex]))
trainClass.append(classList[docIndex])
# 训练算法,计算分类所需的概率
p0V, p1V, pSpam = trainNBO(np.array(trainMat), np.array(trainClass))
# 分类错误个数
errorCount = 0
# 遍历测试集,验证算法错误率
for docIndex in testSet:
wordVector = bagOfWords2Vec(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))
if __name__ == '__main__':
spamTest()
本次实验围绕使用贝叶斯公式进行垃圾邮件分类展开,贝叶斯公式的核心是“执果寻因”,是一种典型的后验概率,它基于原有的收集结果对先验概率进行修正并对待求解的事件进行估计。学习贝叶斯公式的同时也对先验概率、后验概率、条件概率和全概率公式进行了简单的回顾,条件概率和全概率公式是推导贝叶斯公式的基础所在。此外,在求解某个词在出某个类中出现的概率时需进行“拉普拉斯”修正,同时也需注意结果的下溢出(过多很小的数相乘,可采用取自然对数的方法来避免)。