《机器学习实战》笔记——朴素贝叶斯
闲来无事最近复习了一下ID3决策树算法,并凭着理解用pandas实现了一遍,加了些自己的理解(链接如下)。相比本篇博文,更简明清晰,更适合复习用。
https://github.com/DianeSoHungry/ShallowMachineLearningCodeItOut/blob/master/Naive%20Bayes.ipynb
第一步:分类器的建立、训练、以及测试
运用贝叶斯公式(朴素贝叶斯假设每个特征每个特征都是独立的)可以解决的问题有,已知某些特征,用来判断某情况发生的可能性大小,设置可能性最大的情况作为预测值。
是一种监督算法。
广泛应用于垃圾邮件检测等等。
1 # _*_coding:utf-8_*_ 2 from numpy import * 3 4 # 4-1 词表到向量的转换函数(实验样本) 5 def loadDataSet(): 6 postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], 7 ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'], 8 ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'], 9 ['stop', 'posting', 'stupid', 'worthless', 'garbage'], 10 ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'], 11 ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']] 12 classVec = [0, 1, 0, 1, 0, 1] # 1 is abusive, 0 not 13 return postingList, classVec 14 15 # 返回一个dataSet中所有出现过的词条列表 16 def createVocabList(dataSet): 17 vocabSet = set([]) 18 for document in dataSet: 19 vocabSet = vocabSet | set(document) #用于求两个集合的并集 20 return list(vocabSet) 21 22 # # 输入参数为词汇表(vocabSet)和某个文档(inputSet),返回的是元素和词汇表元素一一对应的向量returnVec 23 # # vocabSet中的元素在inputSet中出现过则向量中元素为1,反之为0 24 def setOfWords2Vec(vocabList, inputSet): 25 returnVec = [0] * len(vocabList) # [1,2,3]*3 结果等于[1,2,3,1,2,3,1,2,3] 26 for word in inputSet: 27 if word in vocabList: 28 returnVec[vocabList.index(word)] = 1 29 else: 30 print ("the word: %s is not in my Vocabulary!" % word) # 为什么有这一步? 31 return returnVec 32 33 # 4-4 朴素贝叶斯词袋模型 34 # 修改setOfWords2Vec函数 35 def bagOfWords2Vec(vocabList, inputSet): 36 returnVec = [0] * len(vocabList) # [1,2,3]*3 结果等于[1,2,3,1,2,3,1,2,3] 37 for word in inputSet: 38 if word in vocabList: 39 returnVec[vocabList.index(word)] += 1 40 return returnVec 41 42 # 4-2 朴素贝叶斯分类器训练函数 43 # 注意: 44 # trainMatrix不是最原始的文档,而是由原始文档列表得到的矩阵, 45 # 矩阵的列分别对应了所有出现过的词,行对应了各个不同的句子 46 # 上面函数的输出returnVec就是trainMatrix其中的一行 47 # trainCategory为该行是否为侮辱性言论。1为是 48 def trainNB0(trainMatrix, trainCategory): # 49 numTrainDocs = len(trainMatrix) # 有几句话 50 numWords = len(trainMatrix[0]) # 每句话有几个词条 51 pAbusive = sum(trainCategory)/float(numTrainDocs) # trainCategory中元素为1的表示是侮辱性语言 52 p0Num = zeros(numWords)+0.00000001 # 累计所有非侮辱性言论中,各个词条出现过的次数 53 p1Num = zeros(numWords)+0.00000001 # 累计所有侮辱性言论中,各个词条出现过的次数 加一个极小量是为了避免在侮辱性言论的条件下,某个词条 54 # 的概率为0的情况,此时,最终结果也会为0,显然不对。 55 # p0Denom = 0.0 # 书上代码是被我注释掉了,最终是要得到(非)侮辱性言论中所有出现过的词汇的总和(包括重复值), 56 # p1Demon = 0.0 # 不如在最后将(非)侮辱性言论中每个词汇出现过的次数相加。 57 for i in range(numTrainDocs): 58 if trainCategory[i] == 1: 59 p1Num += trainMatrix[i] 60 # p1Demon += sum(trainMatrix[i]) 61 else: 62 p0Num += trainMatrix[i] 63 # p0Denom += sum(trainMatrix[i]) 64 p1Demon=sum(p1Num) 65 p0Denom=sum(p0Num) 66 # p1Vect = p1Num/p1Demon 67 # p0Vect = p0Num/p0Denom 68 p1Vect = log(p1Num / p1Demon) # 利用log()函数避免多个概率连乘过小的问题 69 p0Vect = log(p0Num / p0Denom) 70 return p0Vect, p1Vect, pAbusive 71 72 # 4-3 朴素贝叶斯分类函数 73 # 对于一个测试点,p(ci)几乎不变,p(w|ci)=p(w0|ci)*p(w1|ci)...p(wn|ci) 74 # 而p(w0|ci)的概率又等于pow( ci发生的条件下,下标为0的特征=1的概率, w0发生的次数) 75 76 def classifyNB(vec2Classify, p0Vec,p1Vec, pClass1): 77 p1 = sum(vec2Classify * p1Vec) + log(pClass1) 78 p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1) 79 if p1 > p0: 80 return 1 81 else: 82 return 0 83 84 def testingNB(): 85 listOPosts,listClasses = loadDataSet() 86 myVocabList = createVocabList(listOPosts) 87 trainMat = [] 88 for postinDoc in listOPosts: 89 trainMat.append(bagOfWords2Vec(myVocabList, postinDoc)) 90 p0V, p1V, pAb =trainNB0(array(trainMat), array(listClasses)) 91 testEntry = ['love', 'my', 'dalmation'] 92 thisDoc = array(bagOfWords2Vec(myVocabList,testEntry)) 93 print (testEntry, 'classified as:', classifyNB(thisDoc, p0V, p1V, pAb)) 94 testEntry = ['stupid','garbage'] 95 thisDoc = array(bagOfWords2Vec(myVocabList,testEntry)) 96 print (testEntry,'classified as:',classifyNB(thisDoc,p0V,p1V,pAb)) 97 98 99 # 4-5 文件解析及完整的垃圾邮件测试函数 100 def textParse(bigString): 101 import re 102 listOfTokens = re.split(r'\W*', bigString) #正则表达式中\W表示\w的相反,后者表示的是字母数字下划线,将正则表达式作为split()函数的第一个参数,比表示以此为分隔符 103 return [tok.lower() for tok in listOfTokens if len(tok) > 2] 104 105 def spamTest(): 106 docList=[] #共50个元素,每个元素为指向一个文本的引用,有序 107 classList=[] #共50个元素,与docList元素一一对应,有序 108 fullText=[] #把50个文件内容连起来的一个文本文件 109 for i in range(1,26): #从第1个txt文件到第25个,依次遍历 110 wordList = textParse(open('email/spam/%d.txt'%i).read()) 111 docList.append(wordList) 112 fullText.extend(wordList) 113 classList.append(1) #spam文件里的标签都为1 ham文件里的标签都为0 114 wordList = textParse(open('email/ham/%d.txt'%i).read()) 115 docList.append(wordList) 116 fullText.extend(wordList) 117 classList.append(0) 118 vocabList = createVocabList(docList) 119 trainingSet = range(50) #存储的是docList的下标 120 testSet=[] #存储的是docList的下标 121 for i in range(10): # 随机选取从训练集里选10个放到测试集 122 randIndex = int(random.uniform(0,len(trainingSet))) 123 testSet.append(trainingSet[randIndex]) 124 del(trainingSet[randIndex]) 125 trainMat=[] 126 trainClasses=[] 127 for docIndex in trainingSet: 128 trainMat.append(setOfWords2Vec(vocabList,docList[docIndex])) 129 trainClasses.append(classList[docIndex]) 130 p0v,p1v,pSpam = trainNB0(array(trainMat), array(trainClasses)) 131 132 errorCount = 0 133 for docIndex in testSet: 134 wordVector = setOfWords2Vec(vocabList, docList[docIndex]) 135 if classifyNB(array(wordVector),p0v,p1v,pSpam) != classList[docIndex]: 136 errorCount += 1 137 print"the error rate is: ", float(errorCount) / len(testSet)
到目前为止,已经建好了一个朴素贝叶斯分类器并用txt文件进行训练和测试。
第二步:应用——通过婚恋网上的求偶信息判断所在地域
需要从婚恋网上获得实时信息,这需要RSS阅读器,python中最常用的RSS程序库是Universal Feed Parser。下载、安装。
在第一步的代码中,继续加入以下代码,并运行。
1 # 4-6 RSS源分类器及高频词去除函数 2 3 # 从fullText中找vacabList中每个词条出现的次数,选取次数最多的前30项,形成排好序的字典 4 def calcMostFreq(vocabList, fullText): 5 import operator 6 freqDict = {} 7 for token in vocabList: 8 freqDict[token] = fullText.count(token) 9 sortedFreq = sorted(freqDict.iteritems(), key=operator.itemgetter(1),reverse=True) 10 return sortedFreq[:30] 11 12 # 训练分类器 13 def localWords(feed1, feed0): #这里feed1,feed0不是两个不同标签的文本文件的引用,二是指向两个网址 14 import feedparser 15 docList=[] # 最终用来装所有的文章 16 classList=[] # docList对应的标签(是哪个网站的) 17 fullText=[] # 选取的所有文章的词(有重复) 18 minLen = min(len(feed1['entries']), len(feed0['entries'])) # 为了在两个网站上取相同数量的文章数 19 for i in range(minLen): 20 wordList = textParse(feed1['entries'][i]['summary']) # 把feed1中文章简介给切分成词条列表 21 docList.append(wordList) 22 fullText.extend(wordList) 23 classList.append(1) 24 wordList = textParse(feed0['entries'][i]['summary']) 25 docList.append(wordList) 26 fullText.extend(wordList) 27 classList.append(0) 28 vocabList = createVocabList(docList) 29 top30Words = calcMostFreq(vocabList,fullText) 30 for pairW in top30Words: # 删除30个高频词条,据经验这样可以提高预测结果。因为这样可以减小冗余词汇的影响 31 if pairW[0] in vocabList: vocabList.remove(pairW[0]) # pairW不是一个字符串,而是字典元素。按道理不需要判断的 32 trainingSet = range(2*minLen) 33 testSet = [] 34 for i in range(20): 35 randIndex = int (random.uniform(0, len(trainingSet))) 36 testSet.append(trainingSet[randIndex]) 37 del(trainingSet[randIndex]) 38 trainMat = [] 39 trainClasses = [] 40 for docIndex in trainingSet: 41 trainMat.append(bagOfWords2Vec(vocabList, docList[docIndex])) # 书上这块估计是有排版印刷的问题的 42 trainClasses.append(classList[docIndex]) 43 p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses)) 44 errorCount = 0 45 for docIndex in testSet: 46 wordVector = bagOfWords2Vec(vocabList, docList[docIndex]) 47 if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]: 48 errorCount += 1 49 print 'the error rate is: ', float(errorCount)/len(testSet) 50 return vocabList, p0V, p1V 51 52 import feedparser 53 ny=feedparser.parse('http://newyork.craigslist.org/stp/index.rss') 54 print len(ny['entries']) 55 sf=feedparser.parse('http://sfbay.craigslist.org/stp/index.rss') 56 print len(sf['entries']) 57 vocabList, pSF, pNY = localWords(ny,sf)