機器學習實戰4--樸素貝葉斯

分類器在進行分類的時候會給出一個最優的類別猜測結果,同時給出這個猜測的概率估計值。若p1>p2,那麼屬於類別1,反之屬於類別2。
樸素貝葉斯屬於貝葉斯決策理論的一部分:貝葉斯準則是計算條件概率的方法,若已知P(x|c),要求P(c|x),則p(c|x)=p(x|c)p(c)/p(x)。
p(c1|x,y)表示給定某個由x,y表示的數據點,那麼該數據點來自c1的概率是多少。利用貝葉斯準則求得p(x,y|c1),就可以得到x,y表示的點屬於c1的概率。若是大於屬於c2的概率,則這個點就屬於c1。
利用樸素貝葉斯進行文檔分類一般過程:
收集數據,把數據做成數值型或者布爾型數據,分析數據時因爲有大量的特徵,繪製特徵不好使,使用直方圖比較好。計算不同的獨立特徵的條件概率,對測試數據計算錯誤率,得到文檔分類。

獨立的特徵是指統計意義上的獨立,即一個特徵或者單詞出現的可能性與它和其他單詞相鄰沒有關係。若是1000個單詞,則樣本數就從N的1000次方,減少到1000xN。這也是樸素貝葉斯的一個假設,不同的特徵同等重要。
1:要想從文本中獲取特徵,就要先拆分文本。這裏首先給出將文本轉換爲數字向量的過程,將每一個文本片段表示爲一個詞條向量,值爲1表示詞條出現在文檔中,0表示詞條未出現。在此對類別標籤設置爲侮辱類和非侮辱類,使用0和1表示。

#-*- coding:utf-8 -*-
from numpy import *
#適用於標稱型數據,要求分類器給出一個最優的類別猜測結果
def loadDataSet():#創建一個實驗樣本,以及這些樣本的標籤;用於訓練
    postingList = [['my','dog','has','flea','problem','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,0,1,0,1] #1代表侮辱性文字,0代表正常言論
    return postingList,classVec

創建一些實驗文本,返回的第一個變量是進行詞條切分後的文檔集合,文本被切分成一系列的詞條集合,標點符號從文本中去掉,第二個是類別標籤的集合。
2:創建一個包含在所有文檔中出現的不重複詞的列表,使用set數據類型,set將返回一個不重複詞表。

def createVocabList(dataSet):#創建一個包含在所有文檔中出現的不重複詞的列表,使用set數據集合。
    vocabSet = set([])
    for document in dataSet:
        vocabSet = vocabSet | set(document)
    return list(vocabSet)#就是把原來的樣本全部放入一個集合中,輸出列表,就是詞彙表

3:使用詞彙表或者想要檢查的所有單詞作爲輸入,然後爲其中每個單詞構建一個特徵,一旦給定一篇文檔,該文檔將自動轉化爲詞向量。

def setOfWords2Vec(vocabList,inputSet):#輸入第一個是詞彙表,就是上面那個生成的,第二個是一個文檔
    returnVec = [0]*len(vocabList)#初始化一個和詞彙表相同大小的向量;
    for word in inputSet:#對文檔中每個單詞
        if word in vocabList:#如果這個單詞在詞彙表中,就使返回向量中這個單詞在詞彙表的位置的那個值爲1
            returnVec[vocabList.index(word)] = 1
        else:print "the word:%s is not in my Vocabulary!" %word#否則打印不在列表中
    return returnVec

這裏寫圖片描述
4:上面將一組單詞轉化爲一組數字,接下來就是如何使用這些數字計算概率。由以上的程序可知,一個詞是否出現在一篇文檔中,也知道該文檔所屬的類別。
首先通過類別i(侮辱或非侮辱)中文檔數除以總的文檔數來計算概率p(ci),然後計算p(x|ci),這裏用到樸素貝葉斯假設。如果將x展開爲一個個獨立的特徵,那麼就可以將上述概率寫成p(x0,x1,x2…..xn|ci),所有詞都互相獨立,則稱作條件獨立性假設,意味着可以使用p(x0|ci)p(x1|ci)p(x2|ci)…..p(xn|ci)來計算上述概率。

#當利用貝葉斯分類器對文檔分類時,計算多個概率的乘積以獲得屬於某個類別的概率,把所有詞出現次數初始化爲1,分母初始化爲2,用log避免數太小被約掉
def trainNB0(trainMatrix,trainCategory):#輸入參數是文檔矩陣trainMatrix,以及每篇文檔類別標籤所構成的向量。
    numTrainDocs = len(trainMatrix)#獲取在測試文檔矩陣中有幾篇文檔
    numWords = len(trainMatrix[0])#獲取第一篇文檔的單詞長度
    pAbusive = sum(trainCategory)/float(numTrainDocs)#類別爲1的個數除以總篇數,就得到某一類文檔在總文檔數中所佔的比例
    p0Num = ones(numWords);p1Num = ones(numWords)#初始化求概率的分子變量和分母變量,
    p0Denom = 2.0;p1Denom = 2.0  #這裏防止有一個p(xn|1)爲0,則最後的乘積也爲0,所有將分子初始化爲1,分母初始化爲2。
    #p0Num = zeros(numWords);p1Num = zeros(numWords)
    #p0Denom = 0.0;p1Denom = 0.0
    for i in range(numTrainDocs):#對每一篇訓練文檔
        if trainCategory[i] == 1:#如果這篇文檔的類別是1
            p1Num += trainMatrix[i]#分子就把所有的文檔向量按位置累加,trainMatrix[2] = [1,0,1,1,0,0,0];trainMatrix[3] = [1,1,0,0,0,1,1]
            p1Denom += sum(trainMatrix[i])#這個是分母,把trainMatrix[2]中的值先加起來爲3,再把所有這個類別的向量都這樣累加起來,這個是計算單詞總數目
        else:
            p0Num += trainMatrix[i]#
            p0Denom += sum(trainMatrix[i])
    #防止太多的很小的數相乘造成下溢。對乘積取對數。
    p1Vect =log(p1Num/p1Denom)#change to log()對每個類別的每個單詞的數目除以該類別總數目得條件概率
    p0Vect =log(p0Num/p0Denom) #change to log()#返回每個類別的條件概率,不是常數,是向量,在向量裏面是和詞彙表向量長度相同,每個位置代表這個單詞在這個類別中的概率
    return p0Vect,p1Vect,pAbusive #返回兩個向量和一個概率

這裏寫圖片描述
p0Vect是一個向量,元素是每個詞在該類別中出現的概率。p1Vect同上。
pAbusive是文檔屬於1的概率。
5:構建完整的分類器,就是編寫分類函數。

def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):#輸入是要分類的向量,使用numpy數組計算兩個向量相乘的結果,對應元素相乘,然後將詞彙表中所有值相加,將該值加到類別的對數概率上。比較分類向量在兩個類別中哪個概率大,就屬於哪一類
    p1 = sum(vec2Classify*p1Vec) + log(pClass1)
    p0 = sum(vec2Classify*p0Vec) + log(1-pClass1)
    if p1 > p0:
        return 1
    else:
        return 0

def testingNB():#這是一個測試函數
    listOPosts,listClasses = loadDataSet()#1
    myVocabList = createVocabList(listOPosts)#2
    trainMat = []
    for postinDoc in listOPosts:#3
        trainMat.append(setOfWords2Vec(myVocabList,postinDoc))#這個是把訓練文檔中所有向量都轉換成和詞彙表類似的1,0結構
    p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses))#4,經過四步就把訓練樣本訓練好了,得到了想要的概率
    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)

這裏寫圖片描述

6:現在將每個詞的出現與否作爲一個特徵,這被描述爲詞集模型,但一個詞在文檔中出現不止一次,就要使用詞袋模型,在詞袋中,每個單詞可以出現多次。修改setOfWord2Vec():

def bagOfWords2VecMN(vocabList,inputSet):#這是詞袋功能,就是若相同就加一,可以計算有幾個相同的,上面那個只是相同就等於1,對一個文檔中有相同詞不好操作
    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 Vocabulary!" %word
    return returnVec

7:一個實例,過濾垃圾郵件:
在準備數據時,對一個文本字符串,可以使用string.split()將其切分成詞向量。
regEx = re.compile(‘\W*’)
listOfTokens = regEx.split(mySent)可以去除標點符號,並計算每個字符串的長度,返回大於0的字符串,去除空格。
python中.lower()和.upper()將字符串全部轉換成小寫或大寫。
下面是文件解析和完整的垃圾郵件測試函數:

def textParse(bigString):#這是把字符串分割的函數
    import re
    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):#在文件夾中只有25個文件
        wordList = textParse(open('email/spam/%d.txt' %i).read())#讀文本文檔分割字符串,得到這個文檔所有單詞元素的列表
        docList.append(wordList)#把每個文本文檔都加入到一個大的矩陣裏面
        fullText.extend(wordList)#這個是把文本文檔中所有元素都拉出來,做一個大向量
        classList.append(1)#在這個文件夾中,類別標籤都是1
        wordList = textParse(open('email/ham/%d.txt' %i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)

    vocabList = createVocabList(docList)#統計在矩陣中出現的單詞,做一個向量
    trainingSet = range(50);testSet=[]
    for i in range(10):#就是隨機挑出10個做爲測試文本,其餘的作爲訓練樣本
        randIndex = int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    trainMat=[];trainClasses = []
    for docIndex in trainingSet:#docIndex是0-50的數,去掉了作爲測試文檔的10個隨機數字
        trainMat.append(setOfWords2Vec(vocabList,docList[docIndex]))#組成訓練大矩陣
        trainClasses.append(classList[docIndex])#訓練大矩陣中每個向量的標籤
    p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))#得到概率
    errorCount = 0
    for docIndex in testSet:#測試這十個測試文檔的類別並於給定的類別比較以得出分類錯誤率
        wordVector = setOfWords2Vec(vocabList,docList[docIndex])
        if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
            errorCount += 1
            print "error classify email is:%f.txt " % ((docIndex+1)/2.0)
            print docList[docIndex] 
    print 'the error rate is: ',float(errorCount)/len(testSet)

就是從50個文本中選擇10個作爲測試樣本,其餘作爲訓練樣本。進行測試。

8:分析兩個城市廣告用於,分類這兩個城市的特點用詞。
安裝RSS閱讀器,下載文本。

#一下的程序需要下載RSS程序庫feedparser
def calcMostFreq(vocabList,fullText):
    import operator
    freqDict = {}
    for token in vocabList:
        freqDict[token] = fullText.count(token)#計算每個單詞出現的次數
    sortedFreq = sorted(freqDict.iteritems(),key=operator.itemgetter(1),reverse=True)#按照逆序從大到小對freqDict進行排序
    return sortedFreq[:30]#返回前30個高頻單詞

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'])#每次訪問一條RSS源
        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 = createVocabList(docList)
    top30Words = calcMostFreq(vocabList,fullText)#得到在兩個源中出現次數最高的30個單詞
    for pairW in top30Words:
        if pairW[0] in vocabList:vocabList.remove(pairW[0])#從詞彙表中把高頻的30個詞移除
    trainingSet = range(2*minLen);testSet=[]#
    for i in range(20):#從兩個rss源中挑出20條作爲測試文本
        randIndex = int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    trainMat = []; trainClasses = []
    for docIndex in trainingSet:#訓練文本
        trainMat.append(bagOfWords2VecMN(vocabList,docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
    errorCount = 0
    for docIndex in testSet:#計算分類,和錯誤率
        wordVector = bagOfWords2VecMN(vocabList,docList[docIndex])
        if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
            errorCount += 1
    print 'the error rate is: ',float(errorCount)/len(testSet)
    return vocabList,p0V,p1V

顯示地域相關的用詞:

def getTopWords(ny,sf):#返回頻率大於某個閾值的所有值
    import operator
    vocabList,p0V,p1V=localWords(ny,sf)
    topNY = []; topSF = []
    for i in range(len(p0V)):
        if p0V[i] > -6.0:topSF.append((vocabList[i],p0V[i]))
        if p1V[i] > -6.0:topNY.append((vocabList[i],p1V[i]))
    sortedSF = sorted(topSF,key=lambda pair:pair[1],reverse=True)
    print "SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF"
    for item in sortedSF:
        print item[0]

    sortedNY = sorted(topNY,key=lambda pair:pair[1],reverse=True)
    print "NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY"
    for item in sortedNY:
        print item[0]

總結:對於分類而言,使用概率有時要比使用硬規則更爲有效。貝葉斯概率及貝葉斯準則提供了一種利用已知值來估計未知概率的有效方法。樸素貝葉斯假設就是特徵之間相互獨立,降低了對數據量的需求。下溢出可以用對概率取對數來解決。詞袋模型在解決文檔分類上更有效。
優點:在數據較少的情況下仍然有效,可以處理多類別問題。
缺點:對於輸入數據的準備方式較爲敏感。
適用於數據類型:標稱型數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章