朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法。对于给定的训练数据集,首先基于特征条件独立假设学习输入/输出的联合概率密度分布;然后基于此模型,对于给定的输入x,利用贝叶斯定理求出后验概率最大的输出y。朴素贝叶斯法实现简单,学习与预测的效率都很高,是一种常用的方法。
一、朴素贝叶斯
1.1 基本方法
朴素贝叶斯法对条件概率分布作了条件独立性的假设。由于这是一个较强的假设,朴素贝叶斯由此得名。具体的,条件独立性假设是:
朴素贝叶斯法实际上学习到生成数据的机制,属于生成模型。条件独立假设等于是说用于分类的特征在类确定的条件下都是条件独立的。这一假设使得朴素贝叶斯法变得简单,但有时会牺牲一定的分类准确率。
朴素贝叶斯法分类时,对于给定的输入x,通过学习到的模型计算后验概率分布,将后验概率最大的类作为x的类的输出:
ps:后验概率最大化,等价于期望风险最小化,这就是贝叶斯所采用的原理。
1.2 朴素贝叶斯法的参数估计
1.2.1 极大似然估计
1.2.2 贝叶斯估计
二、使用朴素贝叶斯进行文档分类
机器学习的一个重要应用就是文档的自动分类。在文档分类中,整个文档(如一封电子邮件)是实例,而电子邮件中的某些元素则构成特征。虽然电子邮件是一种会不断增加的文本, 但我们同样也可以对新闻报道、用户留言、 政府公文等其他任意类型的文本进行分类。我们可以观察文档中出现的词, 并把每个词的出现或者不出现作为一个特征,这样得到的特征数目就会跟词汇表中的词目一样多。朴素贝叶斯是用于文档分类的常用算法。
要得到好的概率分布,就需要足够的数据样本,假定样本数为N。由统计学知,如果每个特征需要N个样本,那么对于10个特征将需要 N^10个样本,对于包含1000个特征的词汇表将需要N^1000个样本。可以看到,所需要的样本数会随着特征数目增大而迅速增长。如果特征之间相互独立,那么样本数就可以从N^1000减少到1000*N况。所谓独立指的是统计意义上的独立,即一个特征或者单词出现的可能性与它和其他单词相邻没有关系。
朴素贝叶斯分类器通常有两种实现方式:一种基于贝努利模型的实现,一种是基于多项式模型的实现。前者是只考虑词在文档中出现的次数,只考虑出不出现,因此在这个意义上相当于假设词是等权重的;后者考虑词在文档中出现的次数。
2.1 朴素贝叶斯的一般过程:
2.2 使用朴素贝叶斯过滤垃圾邮件:
2.2.1 代码实现:
# -*- coding: utf-8 -*- """ Created on Mon Apr 16 20:18:51 2018 file name:bayes.py goal:使用朴素贝叶斯过滤恶意留言和垃圾邮件 @author: lizihua """ import numpy as np from numpy import log import re #加载数据 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) #词集模型(set-of-words model),不考虑词在文档中出现次数,只考虑出不出现 #输入词汇表vocabList和文档inputSet,输出文档向量returnVec #returnVec的元素为1或0,分别表示词汇表中单词在输入文档中是否出现,出现为1,否则为0 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 Vacabulary!" % word) return returnVec #词袋模型(bag-of-words model),考虑词在文档中出现的次数 def bagOfWords2Vec(vocabList, inputSet): returnVec =[0]*len(vocabList) for word in inputSet: if word in vocabList: returnVec[vocabList.index(word)] += 1 return returnVec #朴素贝叶斯分类器训练函数 #参数估计,需要得到:p(class=1),p(wi,class=1)和p(wi,class=0),其中wi代表第i个特征,即词汇表中第i个元素 #输入参数为文档矩阵trainMatrix(即上面函数输出的returnVec),和每篇文档类别标签所构成的向量trainCategory #类别标签有两个:侮辱性文档(class=1)和非侮辱性文档(class=0) def trainNB0(trainMatrix,trainCategory): #numTrainDocs是trainMatrix的行数,代表有几句话 numTrainDocs = len(trainMatrix) #numWords是trainMatrix的列数,一句话有多少字 numWords = len(trainMatrix[0]) #pAbusive即p(class=1),且p(class=0)=1-p(class=1) pAbusive = sum(trainCategory)/float(numTrainDocs) #初始化概率 """ p0Num=np.zeros(numWords) p1Num=np.zeros(numWords) p0Denom = 0.0 p1Denom = 0.0 """ #改进1: #考虑到,贝叶斯分类器对文档进行分类时,需计算多个概率的乘积。若其中一个为0,则会导致最终乘积为0, #因此,将所有词的出现数初始化1,并将分母初始化2 p0Num=np.ones(numWords) p1Num=np.ones(numWords) p0Denom = 2.0 p1Denom = 2.0 for i in range(numTrainDocs): if trainCategory[i] == 1: #class=1时,所有词汇表中的侮辱性词汇各自统计个数 p1Num += trainMatrix[i] #class=1时,所有侮辱性词汇的总数 p1Denom += sum(trainMatrix[i]) #class=0的情况 else: p0Num += trainMatrix[i] p0Denom += sum(trainMatrix[i]) """ #求p(w,class=1),其中w代表所有特征(词汇表)构成的向量 p1Vect = p1Num/p1Denom #求p(w,class=0) p0Vect = p0Num/p0Denom """ #改进2: #由于需要计算多个概率的乘积。但这些概率大部分都非常小,可能会导致下溢或得出出错误结果 #例如:许多很小的数相乘,最后四舍五入会得到0 #因此,采用对乘积取自然对数,ln(a*b)=lna+lnb p1Vect = log(p1Num/p1Denom) p0Vect = log(p0Num/p0Denom) return p0Vect,p1Vect,pAbusive #朴素贝叶斯分类函数 #p1=ln(p(class=1|X=x))=ln(p(class=1)*p(X=x|class=1)) //取ln后,*变加号 #p0=ln(p(class=0|X=x))=ln(p(class=0)*p(X=x|class=0)) #class=arg max(p1,p2) //概率最大的类作为输出 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 testingNB(): listOfPosts,listClasses = loadDataSet() myVocabList = createVocabList(listOfPosts) print(myVocabList) print(setOfWords2Vec(myVocabList,listOfPosts[0])) print(setOfWords2Vec(myVocabList,listOfPosts[3])) #将文档转换成训练矩阵 trainMat = [] for postinDoc in listOfPosts: trainMat.append(setOfWords2Vec(myVocabList,postinDoc)) p0V,p1V,pAb=trainNB0(trainMat,listClasses) #测试文本分类 testEntry = ['love','my','dalmation'] thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) print(testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb)) testEntry = ['stupid', 'garbage'] thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) print(testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb)) #文本解析及完整的垃圾邮件测试函数 def textParse(bigString): listOfTokens = re.split(r'\W*',bigString) #全部小写,且过滤掉长度小于3的字符串 return [tok.lower() for tok in listOfTokens if len(tok)>2] def spamTest(): #导入并解析文本 docList = [] classList = [] fullText = [] for i in range(1,26): wordList = textParse(open('email/spam/%d.txt' % i).read()) docList.append(wordList) fullText.extend(wordList) classList.append(1) wordList = textParse(open('email/ham/%d.txt' % i).read()) docList.append(wordList) fullText.extend(wordList) classList.append(0) vocabList = createVocabList(docList) #range(50)返回的是range对象,不是list trainingSet = list(range(50)) #随机构建训练集,本例总共50封电子邮件,随机选择其中的10个文件 testSet = [] for i in range(10): randIndex = int(np.random.uniform(0,len(trainingSet))) testSet.append(trainingSet[randIndex]) #range对象没有del方法,所以,将trainingSet改成list对象 del(trainingSet[randIndex]) #将训练样本利用setOfWords2Vec函数转换成训练矩阵 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("错误率:",float(errorCount)/len(testSet)) #测试 if __name__ == "__main__": #过滤恶意留言 testingNB() #过滤垃圾邮件 spamTest()
2.2.2 出现的问题:
Q1:
answer:email/ham/23.txt文本中存在错误,打开将?删除
Q2:
answer:将trainingSet = range(50)改成trainingSet = list(range(50)),这是因为range(50)返回的是range对象,没有del方法
2.2.3 结果显示:
由于测试集是随机抽取10封,因此,多次运行程序,结果可能不同:
结果1:
结果2:
2.3 使用贝叶斯分类器从个人广告中获取区域倾向
注意:原文给定网站不能用,重新找了两个能用的RSS源,且,一个源只能抓取25个entries,数量优点过小。
实现代码:
# -*- coding: utf-8 -*- """ Created on Tue Apr 17 17:23:24 2018 file name:bayes_ad.py goal:使用朴素贝叶斯从个人广告中获取区域倾向 @author:lizihua """ import feedparser import operator from bayes import * from numpy import random,array #计算出现频率 def calcMostFreq(vocabList,fullText): freqDict = {} for token in vocabList: freqDict[token] = fullText.count(token) sortedFreq = sorted(freqDict.items(),key=operator.itemgetter(1),reverse=True) return sortedFreq[:30] #与bayes中的spamTest几乎相同 def localWords(feed1,feed0): docList=[] classList=[] fullText=[] minLen=min(len(feed1['entries']),len(feed0['entries'])) for i in range(minLen): #每次访问一条RSS源 wordList = textParse(feed1['entries'][i]['summary']) docList.append(wordList) fullText.extend(wordList) classList.append(1) wordList = textParse(feed0['entries'][i]['summary']) docList.append(wordList) fullText.extend(wordList) classList.append(0) vocabList = createVocabList(docList) #去掉出现次数最高的那些词 top30Words = calcMostFreq(vocabList,fullText) for pairW in top30Words: if pairW[0] in vocabList: vocabList.remove(pairW[0]) trainingSet = list(range(2*minLen)) #训练集数目:25*2 #随机选取30%的数据作为测试数据集,50*0.3=15 testSet= [] for i in range(15): randIndex = int(random.uniform(0,len(trainingSet))) testSet.append(trainingSet[randIndex]) del(trainingSet[randIndex]) trainMat = [] trainClasses = [] for docIndex in trainingSet: #采用词袋模型 trainMat.append(bagOfWords2Vec(vocabList,docList[docIndex])) trainClasses.append(classList[docIndex]) p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses)) errorCount = 0 for docIndex in testSet: wordVector = bagOfWords2Vec(vocabList,docList[docIndex]) if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]: errorCount += 1 print("错误率:",float(errorCount)/len(testSet)) return vocabList,p0V,p1V #分析数据:显示地域相关用词 def getTopWords(ny,sf): vocabList,p0V,p1V=localWords(ny,sf) #创建两个列表用于存储元组(vocabList[i],p0V[i])等 topNY=[]; topSF=[] for i in range(len(p0V)): #返回概率大于阈值-6.0的所有词 if p0V[i] > -5.0 : topSF.append((vocabList[i],p0V[i])) if p1V[i] > -5.0 : topNY.append((vocabList[i],p1V[i])) #按照元组的第1个元素p0V,从大到小将topSF排序 sortedSF = sorted(topSF, key=lambda pair: pair[1], reverse=True) print("SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**") for item in sortedSF: #按p0V从大到小打印元组第0个元素vocabList print(item[0]) sortedNY = sorted(topNY, key=lambda pair: pair[1], reverse=True) print("NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**") for item in sortedNY: print(item[0]) #测试 if __name__ == "__main__": ny=feedparser.parse('https://newyork.craigslist.org/search/sss?format=rss') print(len(ny['entries'])) sf=feedparser.parse('https://sfbay.craigslist.org/search/sss?format=rss') print(len(sf['entries'])) #vocabList,pSF,pNY = localWords(ny,sf) getTopWords(ny,sf)
部分结果显示: