机器学习_5:朴素贝叶斯算法

文章目录

  • 实验背景
  • 1.算法原理
    • 1.1.贝叶斯公式
    • 1.1.2.朴素贝叶斯
    • 1.1.3.拉普拉斯修正
  • 2.代码解析
    • 2.1.防溢出策略
    • 2.2.词袋模型
    • 2.3.垃圾邮件分类
  • 3.测试结果
  • 4.总结

实验背景

朴素贝叶斯算法是用于分类问题的一种算法,该算法属于生成式模型,与其他算法的判别式模型不同。比如一封电子邮件是不是垃圾邮件,判别式模型是判断这封邮件是某种类型的概率,生成式模型是某种类型生成这封邮件的概率。
判别式模型:
机器学习_5:朴素贝叶斯算法_第1张图片
生成式模型:
机器学习_5:朴素贝叶斯算法_第2张图片
以判断电子邮件是否垃圾为例,如果采用判别式模型来判断,那么对于这个二分类(是垃圾,不是垃圾)问题来说,如果是垃圾邮件的可能性只有10%,那么根据简单的非A即B,这个邮件大概率不是垃圾邮件。但是如果这封邮件是一种全新的垃圾邮件,我们之前没有见过,那么这个分类就是错误的。生成式模型则不同,一封全新的垃圾邮件,如果是垃圾邮件的概率为10%,不是垃圾邮件的概率只有5%,那么很显然,这封邮件就是垃圾邮件。

1.算法原理

1.1.贝叶斯公式

机器学习_5:朴素贝叶斯算法_第3张图片
已知两个独立事件A和B,事件B发生的前提下,事件A发生的概率可以表示为P(A|B),即上图中橙色部分占红色部分的比例,即:
机器学习_5:朴素贝叶斯算法_第4张图片
其中P(A)为先验概率,通过训练数据集得出,比如10封邮件,里面有3封垃圾邮件和7封正常邮件,那么假设A代表垃圾邮件,P(A)就为30%。P(A|B)为后验概率,也就给定一封邮件B,判断是否为垃圾邮件的概率。P(B)为这封邮件在所有邮件中出现的概率,这个概率与我们判断是否为垃圾邮件无关,我们可以不去考虑,即
在这里插入图片描述
那么核心问题转化为求P(B|A),即垃圾邮件产生邮件B的概率,很显然P(B|A)是个联合分布概率,邮件B里有很多个单词,这些单词共同决定B是否为一封垃圾邮件。

1.1.2.朴素贝叶斯

很显然,如果我们要找到单词之间的关联需要大量的数据支持,为了减少数据量,我们认为单词之间是没有联系的,每个单词单独分布,由这些单词概率之积来决定联合概率,这就是朴素贝叶斯算法。
扩展到其他分类问题上,意味着属性条件独立分布,即每个属性独立地对分类结果发生影响。
假设分类结果为c,x为实例,那么可以将贝叶斯公式表达为:
机器学习_5:朴素贝叶斯算法_第5张图片
其中d为属性数目,xi为 x 在第i个属性上的取值
举个例子:
机器学习_5:朴素贝叶斯算法_第6张图片
那么对于这么一个样例x
在这里插入图片描述
他是否能够Play Tennis呢
机器学习_5:朴素贝叶斯算法_第7张图片
首先我们统计下每个属性出现的次数。然后将其转换为概率。
机器学习_5:朴素贝叶斯算法_第8张图片
对于YES和NO的概率来说,因为分母都是P(X),所以我们只需比较分子的大小,也就是说
机器学习_5:朴素贝叶斯算法_第9张图片
因为NO的概率 比YES的概率大,所以我们把这个样本x分类为NO

1.1.3.拉普拉斯修正

有细心的朋友会发现,对于outlook属性中,overcast只在yes的样本中出现过,那么一个新的带有overcast属性的样本x,因为p(Overcast|NO)=0的关系,分类为NO的概率一定为0,也就是说只要新的样本x带有overcast属性,那么就一定会被分类为YES。这就是过拟合现象,过度地适应训练数据,不符合现实应用。
机器学习_5:朴素贝叶斯算法_第10张图片
因此,我们采取最简单的修正方法,也就是拉普拉斯提出的加1法,即每个类别的属性数量都加上1,总数再加上类别的数量。
在这里插入图片描述

2.代码解析

2.1.防溢出策略

对于计算机来说因为属性数量的增多,属性概率过小,所以很可能会出现累乘结果下溢出的现象,而在代数中有ln(a*b)=ln(a)+ ln(b),因此我们可以把条件概率累乘转换为对数累加。这并不影响结果的判断。
机器学习_5:朴素贝叶斯算法_第11张图片

2.2.词袋模型

一般来说,对于涉及文本的数据类型,我们都会选择词袋模型。
机器学习_5:朴素贝叶斯算法_第12张图片
顾名思义,我们将一段话中的每个单词单独提取出来,统计每个单词出现的次数,因为只统计出现过的单词,所以最小值为1,同样,将统计完成的数据转化成概率后对其进行判断。还有一种模型叫词集模型,只统计单词是否出现,很显然,不同单词的出现频率很大程度上决定一封邮件是否为垃圾邮件,所以本次实验我们抛弃了这种模型。

2.3.垃圾邮件分类

from numpy import *
import re
#切割文本,将非单个字母的单词转换成小写字母
def textParse(bigString):
    listOfTokens = re.split(r'\W+', bigString)
    return [tok.lower() for tok in listOfTokens if len(tok) > 2] 

#删除重复单词,构建词袋模型
def createVocabList(dataSet):
    vocabSet = set([])
    for document in dataSet:
        #删除重复单词
        vocabSet = vocabSet | set(document)
    return list(vocabSet)

#构造词集
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

#朴素贝叶斯分类器
def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)
    #统计元素长度
    numWords = len(trainMatrix[0])
    #计算先验概率
    pAbusive = sum(trainCategory)/float(numTrainDocs)
    #拉普拉斯修正,创建全1矩阵和初始值为2的分母
    p0Num = ones(numWords)
    p1Num = 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 = log(p1Num/p1Denom)
    p0Vect = log(p0Num/p0Denom)
    return p0Vect,p1Vect,pAbusive
#变量分类
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else: 
        return 0
    
def spamTest():
	docList = []
	classList = []
	fullText = []
    #遍历25个txt文件  
	for i in range(1, 26):                                                                         
        #读取每个垃圾邮件,并字符串转换成字符串列表
		wordList = textParse(open('D:/vscode/python/.vscode/email/spam/%d.txt' % i, 'r').read())
		docList.append(wordList)
		fullText.append(wordList)
        #标记垃圾邮件,1表示垃圾邮件
		classList.append(1)                                                                        
        #读取每个非垃圾邮件,并字符串转换成字符串列表  
		wordList = textParse(open('D:/vscode/python/.vscode/email/ham/%d.txt' % i, 'r').read())    
		docList.append(wordList)
		fullText.append(wordList)
        #标记非垃圾邮件,0表示非垃圾邮件
		classList.append(0)                                                                         
    #创建不重复的词汇表
	vocabList = createVocabList(docList)                                
    #创建存储训练集的索引值的列表和测试集的索引值的列表                            
	trainingSet = list(range(50))
	testSet = []                                                     
    #从50个邮件中,随机挑选出40个作为训练集,10个做测试集
	for i in range(10):                                                                             
        #随机选取索索引值
		randIndex = int(random.uniform(0, len(trainingSet)))                
        #添加测试集的索引值                        
		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(array(trainMat), array(trainClasses))        
    #错误分类计数
	errorCount = 0                                                          
    #遍历测试集
	for docIndex in testSet:                                                
        #测试集的词集模型
		wordVector = setOfWords2Vec(vocabList, docList[docIndex])           
        #如果分类错误
		if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:    
            #错误计数加1
			errorCount += 1                                                 
	print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))
    #返回错误率
	return float(errorCount)/len(testSet)

if __name__ == '__main__':
	error=0.0
    #统计十次测试结果
	for i in range(10):
		error+=spamTest()
	error/=10
	print('平均错误率:%.2f%%' %(error * 100))

3.测试结果

机器学习_5:朴素贝叶斯算法_第13张图片

4.总结

对于分类而言,有时候使用概率会比使用硬规则更为有效。当我们缺少足够数据的时候,我们可以假设特征之间是条件独立的来减少数据量的需求。当然,这个假设并不适用于所有的情况,但针对文本分类而言,朴素贝叶斯仍然是一种有效的分类器。
对于编程语言来说,我们需要考虑计算机特有的问题,比如下溢出等,这些都可以通过数学的变化来得到数据之间关系不变但便于我们存储的数据。对于计算机人来说,数学不要求非常优秀,但起码得看得懂公式,且懂得如何降低存储难度来进行数据变换。
同时,本次实验还有很多可以改进的地方,比如文本中的连词,人称词等,有些词汇对于文本分类是没有意义的,对于如何更好的切分文本构建词袋是个很好的研究方向。

你可能感兴趣的:(开发语言,python,机器学习)