上一篇文章我们简单介绍了朴素贝叶斯的前提条件以及实现过程,并介绍了几个流行的朴素贝叶斯分类法,实现了最基本的文本分类,这篇文章将继续介绍朴素贝叶斯分类,这次主要通过垃圾邮件过滤的程序实现,深化贝叶斯分类的过程,并通过sklearn库实现上文中三种朴素分类器的效果,并比较几种朴素贝叶斯分类算法的准确性。
对于垃圾邮件过滤而言,我们首先要对邮件数据进行一定的处理,才能供算法使用,第一步是对文本切分,将文本切分为一个一个独立的字符,但是这其中可能有类似无用字符或者是标点符号,我们首先需要讲这些字符从读取文本中剔除,除此之外,为了保持文本数据的一致性,我们希望所有字符都是小写状态,这样更方便统计分类识别,所以我们调用了python自带命令lower()实现了提取字符的小写化。经过这样的数据前期处理后,我们就可以按照之前的思想,对垃圾邮件进行过滤分类了。
1.首先我们把上一篇文章中的一些函数导入,这一篇垃圾邮件过滤同样需要他们大展身手,函数功能上一篇已经介绍过,这里就简单说一下:
createVocablist(dataSet):从dataSet中提取字符,返回一个无重复的包含全部dataSet中字符的列表。
set0fWords2Vec(vocablist,inputSet):根据上一函数生成的无重复列表,在输入字符列表中,将输入列表中个字符的出现次数分别添加到无重复列表的对应位置上。
trainNB0(trainMatrix,trainCategory):根据转换好的训练集和训练集标签,分别求出贝叶斯公式需要的几个概率值。
classifyNB(vec2Classifiy,p0Vec,p1Vec,p1class):通过转换为测试格式的输入字符vec2classifiy和求出的p0v,p1v,p1class,根据后验概率,推测文本分类。
import re import random from numpy import * def createVocabList(dataSet): # """ # :params: # :dataSet:输入字符列表,提取字符列表中的唯一值 # :return:包含datSet中不重复词条的列表 # """ vocabSet = set([]) #create empty set for document in dataSet: vocabSet = vocabSet | set(document) #union of the two sets return list(vocabSet) def setOfWords2Vec(vocabList, inputSet): # """ # :params: # :vocablist:文本全部无重复词条 # :inputSet:输入待检测词条 # return:bool形列表,显示vocablist中词频 # """ 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): #""" #:params: #:trainMatri训练集 #:trainCategory训练集分类 #return p(C0|wi) p(C1|wi) p(Ci) #""" numTrainDocs = len(trainMatrix) numWords = len(trainMatrix[0]) pAbusive = sum(trainCategory)/float(numTrainDocs) p0Num = ones(numWords); p1Num = ones(numWords)#修改后的拉普拉斯修正 p0Denom = 2.0; p1Denom = 2.0 #类别数 因为类别为好和不好 所以类别数N为2 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) #只有两种情况发生,所以另一个用1减 p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1) if p1 > p0:#根据后验概率判断分类类别,与传统的贝叶斯学习不同 return 1 else: return 0
接下来就是测试算法,用朴素贝叶斯进行交叉验证:
def textParse(bigString): import re listOfTokens=re.split(r'\W*',bigString)#除单词,数字外,均分割 return [tok.lower() for tok in listOfTokens if len(tok)>2] #长度小于2的字符将会被剔除
函数textParse主要对输入的数据进行文章开头说到的数据预处理,剔除不需要的符号字符,并根据字符长度,舍弃掉一些太短意义不大的字符,返回一个理想的数据集。
def spamTest(): docList = []; classList = []; fullText = [] for i in range(1,26):#垃圾邮件和正常邮件的文件数都为25 wordList = textParse(open('email/spam/%d.txt' % i ,'r').read())#逐text文件读取 docList.append(wordList)#添加到wordlist fullText.extend(wordList) classList.append(1) wordList = textParse(open('email/ham/%d.txt' % i, 'r').read())#逐text文件读取 docList.append(wordList)#添加到wordlist fullText.extend(wordList) classList.append(0) vocabList = createVocabList(docList)#返回全部字符无重复列表 trainingSet = list(range(50));testSet= [] for i in list(range(10)):#随机构造训练集,测试集总数为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('the error rate is : ',float(errorCount)/len(testSet))#得到预测率 return float(errorCount)/len(testSet)
第二个函数spamTset是对第一篇文章的调用,首先将textParse函数返回的数据集提取了无重复列表,并在classlist中添加对应的类别,接下来就是交叉验证,从50封邮件中提取10封作为测试集,40封作为训练集,验证算法准确性。如果不调库的化,这里实现交叉验证的方法感觉今后也可以借鉴到,通过限定范围内使用random函数随机生成索引,从而分出训练集与测试集。下面开始验证算法分类准确率,首先导入下列代码:
ratio = [] for i in range(50): ratio.append(spamTest()) print('the total right rate is : ',(1.0-float(sum(array(ratio)))/50.0)*100,'%')
这里我们将交叉验证的程序循环50次,通过50次的计算得到分类的平均准确率。
the total right rate by <<<orginal naive_bayes>>> is : 96.39999999999999 %
我们通过计算得到了50次的平均预测率是96.39%,分类准确度已经算不错了,当然由于交叉验证以及random函数的随机性,这个值可能每次跑程序都不一样,但是大致都会在这个值附近波动,不会出现太大偏差。
2.接下来我们将就上一篇文章中提到的GaussianNB高斯朴素贝叶斯,MultinomialNB多项分布朴素贝叶斯以及BernoulliNB多重伯努利分布朴素贝叶斯算法在sklearn.cross_validation交叉验证基础下进行算法分类验证,并比较包括上述算法在内四种算法的准确率高低。
A.高斯朴素贝叶斯
def sklearnNB(test_size = 0.2):#高斯朴素贝叶斯 from sklearn.naive_bayes import GaussianNB from sklearn.cross_validation import train_test_split docList = []; classList = []; fullText = [] for i in range(1,26): wordList = textParse(open('email/spam/%d.txt' % i ,'r').read())#逐text文件读取 docList.append(wordList)#添加到wordlist fullText.extend(wordList) classList.append(1) wordList = textParse(open('email/ham/%d.txt' % i, 'r').read())#逐text文件读取 docList.append(wordList)#添加到wordlist fullText.extend(wordList) classList.append(0) vocabList = createVocabList(docList)#返回全部字符无重复列表 total_Mat = []#处理每一份数据并放到一个list中 for data in docList: train_data =setOfWords2Vec(vocabList,data) total_Mat.append(train_data) #交叉验证划分数据训练集,测试集 X_train,X_test, y_train, y_test = train_test_split(total_Mat,classList,test_size=test_size) errorCount = 0#初始化分类错误数 gnb = GaussianNB()#建立高斯bayes模型 gnb.fit(X_train,y_train)#训练模型 email_predicted = gnb.predict(X_test) for i in range(int(len(total_Mat)*test_size)): if email_predicted[i] != y_test[i]: errorCount += 1 # print('the total right rate is : ',(1.0-float(errorCount)/50.0)*100,'%') return float(errorCount)/len(y_test)
这里与之前程序主要区别在建模和交叉验证处,这里都直接从Sklearn调用库实现,数据预处理以及转向量均和之前相同,为了保持与之前程序相同的训练测试比例,这里我默认了test_size = 0.2,大家也可以根据自己需求调整,不过在一般情况下,模型的训练效果都不错,下面我们看一下准确率表现如何:
ratio = [] for i in range(50): ratio.append(sklearnNB()) print('the total right rate by <<<GaussianNB>>> is : ',(1.0-float(sum(array(ratio)))/50.0)*100,'%')
我了比较不同算法,这里的重复数也设置为与之前相同的50次。
the total right rate by <<<GaussianNB>>> is : 96.8 %
整体来说比杠杆的算法要高一点,不过还是存在一些偶然性原因,想获得更稳定的准确率,可以增加循环次数。
接下来是MultinomialNB多项分布朴素贝叶斯以及BernoulliNB多重伯努利分布朴素贝叶斯,由于代码程序在实现时只是改变了建立朴素贝叶斯模型的调用参数,所以我们直接看两个算法的准确率如何,其他的部分都相同。
B.多项分布朴素贝叶斯
def sklearnNB(test_size = 0.2): from sklearn.naive_bayes import MultinomialNB from sklearn.cross_validation import train_test_split docList = []; classList = []; fullText = [] for i in range(1,26): wordList = textParse(open('email/spam/%d.txt' % i ,'r').read())#逐text文件读取 docList.append(wordList)#添加到wordlist fullText.extend(wordList) classList.append(1) wordList = textParse(open('email/ham/%d.txt' % i, 'r').read())#逐text文件读取 docList.append(wordList)#添加到wordlist fullText.extend(wordList) classList.append(0) vocabList = createVocabList(docList)#返回全部字符无重复列表 total_Mat = []#处理每一份数据并放到一个list中 for data in docList: train_data =setOfWords2Vec(vocabList,data) total_Mat.append(train_data) #交叉验证划分数据训练集,测试集 X_train,X_test, y_train, y_test = train_test_split(total_Mat,classList,test_size=test_size) errorCount = 0#初始化分类错误数 gnb = MultinomialNB()#建立多项式bayes模型 gnb.fit(X_train,y_train)#训练模型 email_predicted = gnb.predict(X_test) for i in range(int(len(total_Mat)*test_size)): if email_predicted[i] != y_test[i]: errorCount += 1 # print('the total right rate is : ',(1.0-float(errorCount)/50.0)*100,'%') return float(errorCount)/len(y_test) ratio = [] for i in range(50): ratio.append(sklearnNB()) print('the total right rate by <<<MultinomialNB>>> is : ',(1.0-float(sum(array(ratio)))/50.0)*100,'%')
the total right rate by <<<MultinomialNB>>> is : 96.39999999999999 %
比较巧~和最一开始的准确率相同,看最后一个。
C.BernoulliNB多重伯努利分布
def sklearnNB(test_size = 0.2): from sklearn.naive_bayes import BernoulliNB from sklearn.cross_validation import train_test_split, cross_val_score docList = []; classList = []; fullText = [] for i in range(1,26): wordList = textParse(open('email/spam/%d.txt' % i ,'r').read())#逐text文件读取 docList.append(wordList)#添加到wordlist fullText.extend(wordList) classList.append(1) wordList = textParse(open('email/ham/%d.txt' % i, 'r').read())#逐text文件读取 docList.append(wordList)#添加到wordlist fullText.extend(wordList) classList.append(0) vocabList = createVocabList(docList)#返回全部字符无重复列表 total_Mat = []#处理每一份数据并放到一个list中 for data in docList: train_data =setOfWords2Vec(vocabList,data) total_Mat.append(train_data) #交叉验证划分数据训练集,测试集 X_train,X_test, y_train, y_test = train_test_split(total_Mat,classList,test_size=test_size) errorCount = 0#初始化分类错误数 gnb = BernoulliNB()#建立伯努利bayes模型 gnb.fit(X_train,y_train)#训练模型 email_predicted = gnb.predict(X_test) for i in range(int(len(total_Mat)*test_size)): if email_predicted[i] != y_test[i]: errorCount += 1 # print('the total right rate is : ',(1.0-float(errorCount)/50.0)*100,'%') return float(errorCount)/len(y_test) ratio = [] for i in range(50): ratio.append(sklearnNB()) print('the total right rate by <<<BernoulliNB>>> is : ',(1.0-float(sum(array(ratio)))/50.0)*100,'%')
the total right rate by <<<BernoulliNB>>> is : 90.60000000000001 %
伯努利分布朴素贝叶斯的准确率相对来说比较低,尝试了几次,准确率均在89%-91%徘徊,我猜想可能是上文提到的BernoulliNB明确惩罚类C中没有出现作为预测因子的特征i,而MultinomialNB则是简单的忽略了未出现的特征,所以造成了分类的更加严格,从而分类准确率相对较低。大家也可以多多尝试不同算法比较算法准确度,这里只是简单的运行了几次,还不具备说服力。
总结:
总的来说,朴素贝叶斯分类器是在比较理想的假设前提下进行分类的,而分类基础则是基于概率论统计的贝叶斯公式,通过先验信息和先验概率寻找样本的最大后验概率,判断样本类别,这里模型参数的估计采用了极大似然估计,大家可以参考《统计学完全教程》了解极大似然估计相关性质以及参数估计EM算法;除朴素贝叶斯分类器之外,还有半朴素贝叶斯分类器,贝叶斯网等也是基于贝叶斯公式对样本进行分类,只不过前提假设做了修改,大家也可以参考相关内容。最后欢迎大家提出意见和交流~