《機器學習實戰》學習筆記之樸素貝葉斯(Naive Bayes)
阿新 • • 發佈:2019-02-17
原理
假如郵箱中有n個單詞,
如果returnVec[i]=0代表這個單詞在這封郵件中不出現,
returnVec[i]=1代表這個單詞在郵件中出現了。
設訓練集中每個郵件都有標記為是垃圾郵件和不是垃圾郵件,是垃圾郵件的分類為1,不是垃圾郵件的分類為0。
演算法原理:
提取郵件內單詞,改寫為小寫單詞輸入字典,過濾長度不大於2的單詞。
利用詞彙表計算出單詞屬於正常詞彙的概率p0V=(正常郵件中該單詞出現次數)/(正常郵件數量)。
辱罵性詞彙的概率p1V=(垃圾郵件中該單詞出現次數)/(垃圾郵件數量)。
然後比較p0V和p1V的概率大小,如果p0V大於p1V就認為他屬於正常郵件,如果p1V大於p0V就認為他屬於垃圾郵件。
pb為(對應類別的郵件數)/(所有郵件總數)。
文章中的改進:設定初始一行的單詞數計數為2,是為了防止下溢,避免因為某個特性下概率為0。計算概率使用了log函式簡化乘除法。
對原文章中的程式碼進行些許改進,添加了中文註釋,加入了主函式,能夠直接執行出結果。因為能力有限,有部分可能解釋不清,希望大牛批評指正!
程式碼如下:
# -*- coding: utf-8 -*- from numpy import * def loadDataSet(): #記錄資料集,使用python的list,一共是6條,每條又包含了若干條 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']] #分類向量,1 代表侮辱性文字,0代表正常言論。 classVec = [0,1,0,1,0,0] #返回的第一個變數是進行詞條切分後的文件集合,第二個變數是一個類別標籤的集合 return postingList,classVec #建立一個包含在所有文件中出現的不重複詞的列表 def createVocabList(dataSet): #建立一個空集 vocabSet = set([]) #迴圈實現如果這個詞出現在資料集中,就建立兩個集合的並集。重複的詞只會被加入進去一次。也就實現了只儲存不重複詞的功能 for document in dataSet: vocabSet = vocabSet | set(document)#建立兩個集合的並集 #返回這個詞彙列表 return list(vocabSet) #這個函式實現的功能:將兩個詞彙表進行對比,如果輸入的資料集在詞彙表中,就讓向量值為1,否則輸出該詞不在文件中。 #輸入的引數為詞彙表及某個文件,輸出的是文件向量。 def wordstoVec(vocabList, inputSet): #建立一個其中所含元素都為0的向量,向量長度與詞彙表長度一致 returnVec = [0]*len(vocabList) #對輸入集中每一個單詞執行,如果單詞在詞彙表中就使這個詞的索引位置相應的的文件向量值為1,否則輸出這個詞不是詞彙表中的 for word in inputSet: if word in vocabList: returnVec[vocabList.index(word)] = 1 else: print "這個單詞: %s 不是我詞彙表裡的!" % word #返回這個轉化後的向量 return returnVec #輸入的引數為文件矩陣trainMatrix,以及由每篇文件類別標籤所構成的向量trainCategory。 #首先計算文件屬於侮辱性文件的概率,即P(1)。因為是二類分類問題,所以可以通過1-P(1)得到p(0)。 def trainNB0(trainMatrix,trainCategory): #訓練文字單詞數為訓練矩陣的長度。 TrainDocsNum = len(trainMatrix) #單詞數為訓練矩陣第一行的長度,這只是為了初始化 Wordsnum = len(trainMatrix[0]) #辱罵性詞彙出現的概率為所有訓練類別相加除以訓練文字單詞總數,因為辱罵性詞彙的類別都是1. pbad = sum(trainCategory)/float(TrainDocsNum) #計算p(wi|c1)和p(wi|c0),p(w|c)指在分類c下,詞語w出現的樣本數與分類c所有樣本中所有詞數 #讓類別為0的單詞出現的次數設為全為1的向量,向量的高度與單詞數相同。 p0 = ones(Wordsnum) #讓類別為1的單詞出現的次數設為全為1的向量,向量的高度與單詞數相同。 p1 = ones(Wordsnum) #設定初始一行的單詞數計數為2,這也是一處改進,為了防止下溢,避免因為某個特性下概率為0 就導致整體概率為0。把計數從1改為2 p0Denom = 2.0; p1Denom = 2.0 #讓所有的向量對應元素相加,一旦某個詞在文件出現,對該詞對應的個數就加1。而且總詞數也加1。 #range代表從0到TrainDocsNum的整數列表 for i in range(TrainDocsNum): #假如訓練種類對應的值為1就使類別1單詞出現的次數加1 if trainCategory[i] == 1: p1 += trainMatrix[i] #假如訓練種類對應的值為0就使類別0單詞出現的次數加1 else: p0 += trainMatrix[i] p1Denom = sum(trainCategory) p0Denom = len(trainCategory) - p1Denom #對每個元素出現的次數除以該類別中對應某行的總詞數,概率進行乘法過後數值會比較小,取log方便比較 p1Vect = log(p1/p1Denom) p0Vect = log(p0/p0Denom) return p0Vect,p1Vect,pbad #樸素貝葉斯分類函式 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(): #載入資料集 listOPosts,listClasses = loadDataSet() #建立詞彙表 myVocabList = createVocabList(listOPosts) #建立一個空集 trainMat = [] #迴圈所有資料集中詞彙,轉化為詞彙向量 for postingDoc in listOPosts: trainMat.append(wordstoVec(myVocabList,postingDoc)) #呼叫函式計算0類詞出現概率和1類詞出現的概率以及辱罵性詞彙出現的概率 p0V,p1V,pb = trainNB0(array(trainMat),array(listClasses)) testEntry = ['I','love','you'] thisDoc = array(wordstoVec(myVocabList,testEntry)) print testEntry, '被分到: ', classifyNB(thisDoc,p0V,p1V,pb),'類\n','是正常詞的概率為:\n',p0V,'\n是辱罵性詞的概率為:\n',p1V,'\n辱罵性詞佔得比例:',pb testEntry = ['stupid','garbage'] thisDoc = array(wordstoVec(myVocabList,testEntry)) print testEntry, '被分到: ', classifyNB(thisDoc,p0V,p1V,pb),'類\n','是正常詞的概率為:\n',p0V,'\n是辱罵性詞的概率為:\n',p1V,'\n辱罵性詞佔得比例:',pb #應用部分:使用樸素貝葉斯過濾垃圾郵件,使用到交叉驗證。 #資料準備會有垃圾郵件資料夾下面全部是標記的垃圾郵件,正常郵件資料夾下是正常郵件。 #textParse函式接受一個字串並將其解析為字串列表,把大寫字元轉化成小寫,並且只儲存大於兩個字母的詞。 #spamTest函式在50封郵件中選取10篇郵件隨機選擇為測試集交叉驗證。 def textParse(bigString): import re listOfTokens = re.split(r'\W*',bigString) return[tok.lower() for tok in listOfTokens if len(tok) > 2] #對貝葉斯垃圾郵件分類器進行自動化處理。匯入資料夾spam和ham下的文字檔案,並將它們解析為詞列表。 def spamTest(): docList = []; classList = []; fullText = [] #匯入並解析文字 for i in range(1,26): wordList = textParse(open('email/垃圾郵件/%d.txt' % i).read()) docList.append(wordList) fullText.extend(wordList) classList.append(1) wordList = textParse(open('email/正常郵件/%d.txt' % i).read()) docList.append(wordList) #extend()方法只接受一個列表作為引數,並將該引數的每個元素都新增到原有的列表中。 fullText.extend(wordList) #append() 方法向列表的尾部新增一個新的元素。只接受一個引數。 classList.append(0) vocabList = createVocabList(docList) #隨機構建訓練集,進行留存交叉驗證 trainingSet = range(50); testSet = [] for i in range(15): #uniform() 方法將隨機生成下一個實數,它在[x,y]範圍內 randIndex = int(random.uniform(0,len(trainingSet))) testSet.append(trainingSet[randIndex]) del(trainingSet[randIndex]) trainMat = []; trainClasses = [] for docIndex in trainingSet: trainMat.append(wordstoVec(vocabList, docList[docIndex])) trainClasses.append(classList[docIndex]) p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses)) errorCount = 0 #對測試集分類 for docIndex in testSet: wordVector = wordstoVec(vocabList, docList[docIndex]) if classifyNB(array(wordVector),p0V,p1V,pSpam)!= classList[docIndex]: errorCount += 1 print "分類錯誤了!分類的錯誤詞是: \n",docList[docIndex],'分類錯誤的數目有: ',errorCount print '分類錯誤率為:',float(errorCount)/len(testSet) if __name__ == "__main__": listOPosts,listClasses = loadDataSet() myVocabList = createVocabList(listOPosts) print ' ————————————————————我的詞彙列表:——————————————————\n',myVocabList trainMat = [] for postinDoc in listOPosts: trainMat.append(wordstoVec(myVocabList, postinDoc)) p0V,p1V,pAb = trainNB0(trainMat, listClasses) print ' ——————————————————————測試案例———————————————————————————\n' testingNB() print ' ———————————————第1次垃圾郵件分類自動化處理結果——————————————————\n' spamTest() print ' ———————————————第2次垃圾郵件分類自動化處理結果——————————————————\n' spamTest() print ' ———————————————第3次垃圾郵件分類自動化處理結果——————————————————\n' spamTest()