贝叶斯的数学基础和理论就不写了,很基础,网上博客也一大堆。这里只写实现的具体过程
(代码复制可以直接使用,没有缺少,里面会有一些测试性的语句)
总的来说实现的过程分成四个步骤
第一部分:一些基础函数的实现
loadDataSet()函数创建了一些实验样本,这个是我们自己写的,用来对代码编写后进行简单的测试,其中postingList是样本,classVec是样本的标签(1 是侮辱性留言,0是非侮辱性留言)
createVocaList()函数会创建一个包含在所有文档中出现的不重复的列表,得到基于数据集的一个词集
setOfWord2Vec()函数 输入参数为词汇表及某个文档,输出的是文档向量,向量的每个元素为1或者0,代表单词是否在文档中出现。这个称为词集模型
bagOfWord2Vec()函数 和setOfWord2Vec()函数差不多,不过这个是统计词在文档中出现的次数,因为一个词可能在文档中出现不止一次,这个称为词袋模型
最后一些语句就是一些测试性代码,看看函数有没有编写成功。
import numpy as np import re import feedparser as fp import operator as op def loadDataSet(): postingList = [['my','dog','has','flea','problems','help','please','haha'], ['maybe','not','take','him','to','dog','park','stupid'], ['my','dalmation','is','so','cute','I','love','him','haha'], ['stop','posting','stupid','worthless','garbage'], ['mr','licks','ate','my','steak','how','to','stop','him','haha'], ['quit','buying','worthless','dog','food','stupid'] ] classVec = [0,1,0,1,0,1] return postingList,classVec #创建一个包含在所有文档中不重复的词的列表,为Python的 set数据类型 def createVocaList(dataSet): vocabSet = set([]) for docunment in dataSet: vocabSet = vocabSet | set(docunment) return list(vocabSet) #词集模型,将每个词的出现与否作为一个特征,创建一个词向量 def setOfWords2Vec(vocabList,inputSet): 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 vocabList!" % word) return returnVec #如果一个词在文档中出现不止一次,称为词袋模型 def bagOfWords2Vec(vocabList,inputSet): 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 vocabList!" % word) return returnVec '''listOPosts,listClasses = loadDataSet() vocaSet = createVocaList(listOPosts) print (vocaSet) #listOPosts[0].append('hhh') print (setOfWords2Vec(vocaSet,listOPosts[0]))'''
第二部分:是贝叶斯算法的代码实现:
trainNB0()函数是贝叶斯训练函数,通过样本得到一些条件概率,输出结果就是p0Vect代表是p(W(i) | 0)也就是p(某词 | 非侮辱性)的概率,p1Vect代表是p(W(i) | 0)也就是 p(某词 | 侮辱性)概率,pAbusive代表是训练集中侮辱性的样本数目的概率。其中会对得到的初步概率进行对数处理,原因代码里简单写了一下。
classifyNB()函数通过上面的函数得到的概率,输入要输入的预测的样本得到分类
testingNB()函数是用来测试用的,简单封装到了一个函数里
#朴素贝叶斯训练函数 def trainNB0(trainMatrix,trainCategory): numTrainDocs = len(trainMatrix) numWords = len(trainMatrix[0])#这里直接用第一个例子的长度作为词的长度会不会不合适 pAbusive = sum(trainCategory)/float(numTrainDocs) #p0Num = np.zeros(numWords) #p1Num = np.zeros(numWords) #p0Denom = 0.0 #p1Denom = 0.0 #其中一个概率值为0,那么最后的乘积也为0,所以在初始化的时候把所有的词出现的次数初始化为1 #并将分母初始化为2(为什么要初始化为2呢?,可能要考虑极端情况吧,比如如果训练集中的数据标签 #全是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: 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 '''listOPosts,listClasses = loadDataSet() myVocabList = createVocaList(listOPosts) #print (myVocabList) trainMat = [] for postinDoc in listOPosts: trainMat.append(setOfWords2Vec(myVocabList,postinDoc)) p0V, p1V, pAb = trainNB0(trainMat, listClasses) print ("the p0V is :",p0V) print ("the p1V is :",p1V) print ("the pAb is :",pAb)''' #要理解为什么要对p0V, p1V取对数,以及ln(a*b) = lna + lnb,是为了解决下溢出问题,由于太多很小 #的数相乘。 #p0V就是p(w(i)|c0)的概率,p1V就是p(w(i)|c1)的概率,pClass1就是训练数据中p(c1)的概率 def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1): #ln(a*b) = lna + lnb #p(w)的概率是一定的,只需要比较分子的大小 p1 = sum(vec2Classify * p1Vec) + np.log(pClass1) p0 = sum(vec2Classify * p0Vec) + np.log(1.0-pClass1) if p1 > p0: return 1 else: return 0 def testingNB(): listOPosts,listClasses = loadDataSet() myVocabList = createVocaList(listOPosts) trainMat = [] for postinDoc in listOPosts: 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)) #testingNB()
第三部分:使用朴素贝叶斯过滤垃圾邮件
textParse()函数是将每一个样本解析成一个字符串列表,好进行使用
spamTest()函数是用来测试的函数,将样本全部读取出来,然后抽取一部分样本用来测试算法,然后调用第一部分和第二部分的代码进行测试
#垃圾邮件检测实例 #将邮件内容解析成字符串列表。//将一个大字符串解析成字符串列表 def textParse(bigString): listofTokens = re.split(r'\W*',bigString) return [tok.lower() for tok in listofTokens if len(tok) > 2] def spamTest(): docList = [] classList = [] fullText = []
#读取样本数据 for i in range(1,26): #spam是垃圾邮件 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 = createVocaList(docList) trainingSet = list(range(50)) testSet = [] #选出来十条数据作为测试数据集 for i in range(10): randIndex = int(np.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(np.array(trainMat),np.array(trainClasses)) #print ("diyige:",p0V,"dierge:",p1V,"disange",pSpam) errorCount = 0 for docIndex in testSet: wordVector = setOfWords2Vec(vocabList,docList[docIndex]) if classifyNB(wordVector,p0V,p1V,pSpam) != classList[docIndex]:
第四部分:使用贝叶斯分类器从个人广告找那个获取区域倾向
这部分数据从网上获取,需要收集数据:导入RSS源,了解一下RSS源的用法,有一个feedparser包可以用来处理RSS源
calcMostFreq()函数统计文本中的高频词汇,然后用来提出,以为例如英文中的 is ,as, the等词出现的频率很高,但是对于用来分类毫无用处,甚至会降低分类的准确性。
localWords()函数用来获取数据和测试样本,和spamTest()函数功能上基本一样,就是获取数据的方式不一样(读懂代码的话需要了解一下RSS的编写规则)
#实例:从个人广告中获取区域倾向 #遍历词汇表中的每个词并统计它在文本中出现的次数,然后从高到低排序 def calcMostFreq(vocabList,fullText): freqDict = {} for token in vocabList: freqDict[token] = fullText.count(token) sortedFreq = sorted(freqDict.items(),key = op.itemgetter(1),reverse = True) return sortedFreq[:30] def localWords(feed1,feed0): docList = [] classList = [] fullText = [] minLen = min(len(feed1['entries']),len(feed0['entries'])) for i in range(minLen): 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 = createVocaList(docList) top30Words = calcMostFreq(vocabList,fullText) for pairW in top30Words: if pairW in vocabList: vocabList.remove(pairW) trainingSet = list(range(2*minLen)) testSet = [] for i in range(20): randIndex = int(np.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(np.array(trainMat),np.array(trainClasses)) errorCount = 0 for docIndex in testSet: wordVector = bagOfWords2Vec(vocabList,docList[docIndex]) if classifyNB(np.array(wordVector),p0V,p1V,pSpam) != classList[docIndex]: errorCount +=1 print ('the error rate is :',float(errorCount)/len(testSet)) return vocabList,p0V,p1V ny = fp.parse('http://newyork.craigslist.org/stp/index.rss') sf = fp.parse('http://sfbay.craigslist.org/stp/index.rss') #print (ny['entries']) vocabList, psf,pny = localWords(ny,sf)
总结:使用概率有时要比使用硬规则更有效,这里假设每个词(特征)之间独立,但是现实中往往不是这样的,每个词的出现往往和周围的词有关,但是这里我们只进行了简单的实现。可以优化的方面还有很多。