一、樸素貝葉斯分類器原理
這一部分的内容來自《機器學習-周志華》。
(1)樸素貝葉斯表達式的推導
1.假設有分類器h(x)->c,将樣本x歸類為類c,那麼最優的貝葉斯分類器為

2.上式可以轉化為
3.由于P(x)對于所有類都相同,是以求h*(x)=arg maxP(c|x)就是求如下式子
(2)求解樸素貝葉斯分類器
(3)例子
(4)拉普拉斯修正
二、使用樸素貝葉斯分類器對文本進行分類
本例子是手工制作六個短文本及其對應标簽(标簽1-侮辱性,标簽0-非侮辱性),然後使用該6條資料訓練樸素貝葉斯分類進,然後進行分類測試:測試某條文本資訊是否具有侮辱性。
(1)将6個短文本資料轉化為詞向量
'''
def loadDataSet():建立6條短文本,并給這些短文本貼标簽(侮辱類 或 非侮辱類)
'''
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代表侮辱類
return postingList,classVec
'''
def createVocabList(dataSet):該函數的作用是建立詞彙表vocabSet。
dataSet的格式是函數loadDataSet()傳回的格式,可以将
dataSet出現的所有單詞聚集到一個set中(每種單詞隻會出現一次)
'''
def createVocabList(dataSet):
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):inputSet隻是某個短文本,該函數将inputSet按照詞彙表vocabList的格式轉化為詞向量。
例如vocabList=['cute','love','help'],當短文本出現單詞'cute',但沒出息單詞'love'、'help',則
該行短文本對應的詞向量為[1,0,0]
'''
def setOfWords2Vec(vocabList, inputSet):
returnVec = []*len(vocabList)
for word in inputSet:
if word in vocabList: #當該word在詞彙表vocabList中,則對應位置标記為1,注意如果目前短文本inputSet某個單詞
# 出現了多次,那麼最後該單詞在詞向量對應位置還是标記為1
returnVec[vocabList.index(word)] =
else: print ("the word: %s is not in my Vocabulary!" % word)
return returnVec
展示結果:
(2)使用資料集對應的詞向量集訓練貝葉斯分類器
訓練貝葉斯分類器,實質就是分别統計在标簽為1(侮辱性)的短文本中各種單詞出現的頻率、在标簽為0(非侮辱性)的短文本中各種單詞出現的機率。
所用函數:
'''
def trainNB(trainMatrix, trainCategory): 其中trainMatrix是詞向量矩陣, trainCategory是每個詞向量對應的标簽。
傳回p0Vect(向量p0Vect表示在非侮辱性文檔中,各種單詞出現的頻率),
p1Vect(向量p1Vect表示在侮辱性文檔中,各種單詞出現的頻率),
pAbusive(侮辱性的短文本在總體短文本數量中的比例)
'''
def trainNB(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) #擷取詞向量矩陣的行數,就是擷取短文本的個數(每個短文本對應一個詞向量)
numWords = len(trainMatrix[]) #詞向量的長度就是單詞表的長度,這也是擷取單詞表的單詞個數
pAbusive = sum(trainCategory) / float(numTrainDocs) #sum(trainCategory)用來統計标簽中1(表示侮辱性)的個數,這句
# 代碼用來計算侮辱性的短文本在總體短文本數量中的比例
p0Num = zeros(numWords); #建立長度等于詞向量長度的0向量p0Num
p1Num = zeros(numWords) #建立長度等于詞向量長度的0向量p1Num
p0Denom = ;
p1Denom =
for i in range(numTrainDocs):
if trainCategory[i] == :
p1Num += trainMatrix[i] #統計侮辱性的文檔中,各個單詞出現的次數。最終傳回的向量格式與詞彙表格式一樣,每個
#單元對應一種單詞,每個單元對應的數值就是該種單詞出現的次數
p1Denom += sum(trainMatrix[i]) #統計侮辱性的文檔中的單詞總數
else:
p0Num += trainMatrix[i] #統計非侮辱性的文檔中,各個單詞出現的次數。
p0Denom += sum(trainMatrix[i]) #統計非侮辱性的文檔中的單詞總數
p1Vect = p1Num / p1Denom #向量p1Vect表示在侮辱性文檔中,各種單詞出現的頻率
p0Vect = p0Num / p0Denom #向量p0Vect表示在非侮辱性文檔中,各種單詞出現的頻率
return p0Vect, p1Vect, pAbusive
結果:
(3)使用貝葉斯分類器進行分類
這裡測試的測試短文本[‘love’, ‘my’, ‘dalmation’]、[‘stupid’, ‘garbage’],要求也貝斯分類器能夠将第一個文本分類為非侮辱性(标簽為0),将第二個文本分類為侮辱性(标簽為1)。
所用函數:
'''
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):vec2Classify是待分類短文本對應的詞向量。p0Vec表示在非侮辱
性文檔中,各種單詞出現的頻率。p1Vec表示在侮辱性文檔中,各種單詞出現的頻率。
該函數可能判斷vec2Classify是屬于侮辱性文本還是非侮辱性文本
'''
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
p1=prod(vec2Classify*p1Vec)*pClass1 #vec2Classify*p1Vec表示在p1Vec中,将沒有在待分類詞向量vec2Classify中出現的單
#詞的頻率置0。prod(vec2Classify*p1Vec)表示将向量(vec2Classify*p1Vec)的各個元素相乘。
#這句代碼是求vec2Classify屬于侮辱性短文本的機率
p0=prod(vec2Classify*p0Vec)*(-pClass1) #這句代碼是求vec2Classify屬于非侮辱性短文本的機率
if p1>p0:
return
else:
return
'''
testingNB():使用函數loadDataSet()的傳回值作為訓練集,分别測試短文本['love', 'my', 'dalmation']、['stupid', 'garbage']
是屬于侮辱性文本還是非侮辱性文本
'''
def testingNB():
listOPosts,listClasses = loadDataSet() #擷取短文本訓練集
myVocabList = createVocabList(listOPosts) #擷取訓練集對應的單詞表
trainMat=[]
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc)) #将短文本訓練集轉化對應的詞向量集
p0V,p1V,pAb = trainNB(array(trainMat),array(listClasses))
testEntry = ['love', 'my', 'dalmation']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print (testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb))
testEntry = ['stupid', 'garbage']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print (testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb))
結果:
結果不盡人意,貝斯分類器能夠将第一、二個文本都分類為非侮辱性(标簽為0)【具體原因,參見《周志華-機器學習》中樸素貝葉斯分類器需要拉普拉斯修正的原因】,這就需要作拉普拉斯修正,如何修正如下文。
(4)對貝葉斯分類器進行修改,使其更适用于實際情況
1.對函數trainNB()進行拉普拉斯修正(具體原理見“一、樸素貝葉斯分類器原理-(4)拉普拉斯修正”)
p0Num=ones(numWords); p1Num=ones(numWords)
p0Denom=2.0; p1Denom=2.0
2.将函數trainNB()中的詞頻率向量 p1Vect、p0Vect中各個元素取對數
p1Vect = log(p1Num/p1Denom)
p0Vect = log(p0Num/p0Denom)
詞向量中各元素對應的是每種詞出現的頻率,在貝葉斯求機率時,需要将這些詞頻率相乘,但是當這些頻率很小時,程式會下溢出(下溢出:指當要表示的資料的絕對值小于計算機所能表示的最小絕對值時,程式會四舍五入得到0)。為了解決下溢出,可以将頻率取對數解決。例如兩個頻率為a,b,則取對數後,乘法需要變成加法:ln(a*b) = ln(a)+ln(b),将函數classifyNB(vec2Classify,p0Vec,p1Vec,pClass1),修改為classifyNB0(vec2Classify, p0Vec, p1Vec, pClass1)。最後分類的準則是比較向量p1Vect與p0Vect的中各元素相乘後的大小關系,取對數後,雖然向量p1Vect與p0Vect的中各元素的值發生了改變,但是不影響最終的判斷結果。
以下是取對數前後的函數圖象,它們有相同的單調區間,且在相同的x值上取得極值。
3.修改後對應的函數
'''
def trainNB0(trainMatrix, trainCategory): 其中trainMatrix是詞向量矩陣, trainCategory是每個詞向量對應的标簽。
傳回p0Vect(向量p0Vect表示在非侮辱性文檔中,各種單詞出現的頻率的對數),
p1Vect(向量p1Vect表示在侮辱性文檔中,各種單詞出現的頻率的對數),
pAbusive(侮辱性的短文本在總體短文本數量中的比例)
'''
def trainNB0(trainMatrix,trainCategory):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[])
pAbusive = sum(trainCategory)/float(numTrainDocs)
p0Num = ones(numWords); p1Num = ones(numWords) #進行拉普拉斯修正,change to ones()
p0Denom = ; p1Denom = #進行拉普拉斯修正,change to 2.0
for i in range(numTrainDocs):
if trainCategory[i] == :
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = log(p1Num/p1Denom) #防止相乘的因子過小,造成相乘結果為0,是以取對數
p0Vect = log(p0Num/p0Denom) #防止相乘的因子過小,造成相乘結果為0,是以取對數
return p0Vect,p1Vect,pAbusive
'''
classifyNB0(vec2Classify,p0Vec,p1Vec,pClass1):vec2Classify是待分類短文本對應的詞向量。這裡的p0Vec,p1Vec是從函數
trainNB0(trainMatrix,trainCategory)獲得,是以p0Vec表示在非侮辱性文檔中,各種單詞出現的頻率的對數。p1Vec表示在侮辱性文
檔中,各種單詞出現的頻率的對數。該函數可能判斷vec2Classify是屬于侮辱性文本還是非侮辱性文本
'''
def classifyNB0(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1)#vec2Classify*p1Vec表示在p1Vec中,将沒有在待分類詞向量vec2Classify中出現的單
#詞的頻率的對數置0。由于p1Vec是單詞出現頻率的對數,是以sum()将這些對數相加,相
#當于将頻率相乘。該函數原理與函數classifyNB(vec2Classify,p0Vec,p1Vec,pClass1)相同
p0 = sum(vec2Classify * p0Vec) + log( - pClass1) #與上一句代碼原理相同,不過這句代碼是計算屬于非侮辱性短文本的機率
if p1 > p0:
return
else:
return
'''
testingNB0():使用函數loadDataSet()的傳回值作為訓練集,分别測試短文本['love', 'my', 'dalmation']、['stupid', 'garbage']
是屬于侮辱性文本還是非侮辱性文本
'''
def testingNB0():
listOPosts,listClasses = loadDataSet() #擷取短文本訓練集
myVocabList = createVocabList(listOPosts) #擷取訓練集對應的單詞表
trainMat=[]
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc)) #将短文本訓練集轉化對應的詞向量集
p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses))
testEntry = ['love', 'my', 'dalmation']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print (testEntry,'classified as: ',classifyNB0(thisDoc,p0V,p1V,pAb))
testEntry = ['stupid', 'garbage']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print (testEntry,'classified as: ',classifyNB0(thisDoc,p0V,p1V,pAb))
4.運作結果
(5)将詞集模型(set-of-words model)修改為詞袋模型(bag-of-words model)
'''
def bagOfWords2VecMN(vocabList, inputSet):該函數是基于詞袋模型的,與函數setOfWords2Vec(vocabList, inputSet)唯一
不同的是,每當遇到一個單詞則增加詞向量的對應值,而不隻是将對應的值置為1
'''
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = []*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] +=
return returnVec
三、使用樸素貝葉斯過濾垃圾郵件
本例子的資料集為50封郵件(25封垃圾郵件、25封非垃圾郵件),随機抽取10封作為測試集,剩下的40封作為訓練集來訓練貝葉斯分類器。
1. 分詞函數函數textParse(bigString)
'''
def textParse(bigString): 輸入長字元串bigString,傳回一個單詞清單(每個單詞的長度>2)
'''
def textParse(bigString):
import re
listOfTokens = re.split(r'\W*', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > ]
2.對垃圾郵件使用交叉驗證法測試分類結果
交叉驗證法:這個函數随機從50封郵件中(25封垃圾郵件、25封非垃圾郵件)中随機抽取10封作為測試集,剩下的40封作為訓練集。
'''
spamTest():從檔案夾email下擷取25個垃圾郵件和25個非垃圾郵件的txt檔案,随機地從這50個郵件中抽取10個作為測試集,剩下的40個
作為訓練集,然後使用基于詞袋模型的詞向量進行貝葉斯分類。
'''
def spamTest():
docList=[]; classList = []; fullText =[]
for i in range(,):
wordList = textParse(open('email/spam/%d.txt' % i).read())
docList.append(wordList)
fullText.extend(wordList)
classList.append()
wordList = textParse(open('email/ham/%d.txt' % i).read())
docList.append(wordList)
fullText.extend(wordList)
classList.append()
vocabList = createVocabList(docList)#create vocabulary
trainingSet = list(range()); #trainingSet表示docList中參與訓練的短文本詞向量的下标
testSet=[]
for i in range(): #從訓練集trainingSet中随機抽取10個下标添加到測試集,并将這10個下标從訓練集中删除
randIndex = int(random.uniform(,len(trainingSet)))
testSet.append(trainingSet[randIndex])
del(trainingSet[randIndex])
trainMat=[]; trainClasses = []
for docIndex in trainingSet:#将docList[]中參與訓練的短文本轉化為基于詞袋模型的詞向量
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex]) #将參與訓練的短文本對應的标簽添加到數組trainClasses中
p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
errorCount =
for docIndex in testSet: #對10個用于測試短文本進行貝葉斯分類
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
if classifyNB0(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
errorCount +=
print ("classification error",docList[docIndex])
print ('the error rate is: ',float(errorCount)/len(testSet))
#return vocabList,fullText
四、使用樸素貝葉斯分類器分析征婚廣告資訊
征婚廣告資訊分别從來與兩個城市(newyork、sfbay)的RSS源獲得,本例子共有兩個任務:使用貝葉斯分類器測試某條資料是來自newyork還是sfbay的、分别找出newyork和sfbay最具有代表性的頻繁用詞。
(1)擷取RSS源資料 —- feedparser包的parse函數示例
從浏覽器中輸入”https://newyork.craigslist.org/stp/index.rss“,頁面如下面白色圖檔。黑色圖檔中的ny[‘entries’][0][‘summary’]表示擷取0号item的description(下圖紅框部分)
(2)測試某條資料是來自newyork還是sfbay的
首先使用函數calcMostFreq(vocabList,fullText)去除高頻詞,因為高頻詞是兩個不同地區的共同點,難以作為某條資訊來源的區分依據。然後使用函數localWords(feed1,feed0)進行區分。
'''
calcMostFreq(vocabList,fullText):vocabList是詞彙表,fullText是所有的文本。該函數傳回出現頻率最高的前30個單詞。
'''
def calcMostFreq(vocabList,fullText):
import operator
freqDict = {}
for token in vocabList:
freqDict[token]=fullText.count(token)
sortedFreq = sorted(freqDict.items(), key=operator.itemgetter(), reverse=True)
return sortedFreq[:]
'''
def localWords(feed1,feed0):原理和函數spamTest()一樣,不同的是從兩個不同的RSS源feed1,feed0擷取文本資訊。
該函數将總體資料集文本fullText中,統計出前30個出現頻率最高的單詞,從詞彙表vocabList中删除,之後的貝葉斯分類中這30個單詞
不給予考慮(因為,出現頻率高,說明該單詞的出現與否,不太能表明該文本是來自feed1還是feed0)。
該函數從資料集當中随機抽取20個作為測試集,剩下的資料作為訓練集。
'''
def localWords(feed1,feed0):
import feedparser
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() #NY is class 1
wordList = textParse(feed0['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append()
vocabList = createVocabList(docList)#create vocabulary
top30Words = calcMostFreq(vocabList,fullText) #remove top 30 words
for pairW in top30Words:
if pairW[] in vocabList:
vocabList.remove(pairW[])
trainingSet = list(range(*minLen)); testSet=[] #create test set
for i in range():
randIndex = int(random.uniform(,len(trainingSet)))
testSet.append(trainingSet[randIndex])
del(trainingSet[randIndex])
trainMat=[]; trainClasses = []
for docIndex in trainingSet:#train the classifier (get probs) trainNB0
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
errorCount =
for docIndex in testSet: #classify the remaining items
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
if classifyNB0(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
errorCount +=
print ('the error rate is: ',float(errorCount)/len(testSet))
return vocabList,p0V,p1V
(3)找出newyork、sfbay兩個城市最具有表征性的詞語
經過測試,p0V向量代表的城市使用門檻值-4.7(由于之前将詞頻率取對數了,是以詞頻率的對數為負數)最能代表sfbay,p1V向量代表的城市使用門檻值-4.6最能代表newyork。
1.所用函數
'''
def getTopWords(ny,sf):通過函數localWords(feed1,feed0)擷取去除出現頻率最高的前30個單詞所對應的兩個
詞頻率向量p0V(對應的詞來源于sf),p1V(對應的詞來源于ny)。該函數将向量p0V,p1V中出現的對數頻率>-6.0的單詞輸出,這
些單詞分别最能代表城市ny和城市sf
'''
def getTopWords(ny,sf):
import operator
vocabList,p0V,p1V=localWords(ny,sf)
topNY=[]; topSF=[]
for i in range(len(p0V)):
if p0V[i] > - : topSF.append((vocabList[i],p0V[i]))
if p1V[i] > - : topNY.append((vocabList[i],p1V[i]))
sortedSF = sorted(topSF, key=lambda pair: pair[], reverse=True)
print ("最能代表sfbay的單詞為:")
listSF=[]
for item in sortedSF:
listSF.append(item[])
print (listSF)
sortedNY = sorted(topNY, key=lambda pair: pair[], reverse=True)
print ("最能代表newyork的單詞為:")
listNY=[]
for item in sortedNY:
listNY.append(item[])
print(listNY)
2.結果