天天看點

【機器學習】樸素貝葉斯分類器

一、樸素貝葉斯分類器原理

  這一部分的内容來自《機器學習-周志華》。

(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.結果

【機器學習】樸素貝葉斯分類器
【機器學習】樸素貝葉斯分類器

繼續閱讀