python機器學習實戰(三)

原文鏈接:www.cnblogs.com/fydeblog/p/7277205.html

前言

這篇博客是關於機器學習中基於概率論的分類方法--樸素貝葉斯,內容包括樸素貝葉斯分類器,垃圾郵件的分類,解析RSS源數據以及用樸素貝葉斯來分析不同地區的態度.

操作系統:ubuntu14.04 運行環境:anaconda-python2.7-jupyter notebook 參考書籍:機器學習實戰和源碼,機器學習(周志華) notebook writer ----方陽

注意事項:在這裏說一句,默認環境python2.7的notebook,用python3.6的會出問題,還有我的目錄可能跟你們的不一樣,你們自己跑的時候記得改目錄,我會把notebook和代碼以及數據集放到結尾的百度雲盤,方便你們下載!

1. 基於貝葉斯決策理論的分類方法

樸素貝葉斯的特點:

  • 優 點: 在數據較少的情況下仍然有效,可以處理多類別問題。
  • 缺 點: 對於輸入數據的準備方式較爲敏感。
  • 適用數據類型:標稱型數據。

貝葉斯決策理論的核心思想:選擇具有最高概率的決策。(最小化每個樣本的條件風險,則總體風險也就最小,就是選擇最高概率,減小風險)

2. 條件概率

2.1 簡單回顧

條件概率在樸素貝葉斯里面是必不可少的一環,下面來簡單介紹介紹:

假設現在有一個裝了7塊石頭的罐子,其中3塊是灰色的, 4塊是黑色的 。如果從罐子中隨機取出一塊石頭,那麼是灰色石頭的可能性是多少? 由於取石頭有 7 種可能 ,其中 3種爲灰色 ,所以取出灰色石頭的概率爲 3/7 。那麼取到黑色石頭的概率又是多少呢?很顯然 ,是4/7 。

如果這7塊石頭放在兩個桶中,那麼上述概率應該如何計算? (設兩個桶分爲A,B,A桶裝了2個灰色和2個黑色的石頭,B桶裝了1個灰色和2個黑色的石頭)

要計算P(gray)或者P(black) ,事先得知道石頭所在桶的信息會不會改變結果?你有可能巳經想到計算從B桶中取到灰色石頭的概率的辦法,這就是所謂的條件概率.

來計算P(gray|bucketB),這個是條件概率,在已知是從B桶拿出石頭的條件下,拿到灰色石頭的概率。

計算公式:P(gray|bucketB) = P(gray and bucketB) / P(bucketB) (將兩者同時發生的概率除以前提條件發生的概率)

我們知道P(bucketB)就是3/7,B桶的石頭數/總石頭數, P(gray and bucketB) 是1/7,B桶中的灰色石頭數/總石頭數,所以P(gray|bucketB) = 1/3。

這裏說一下P(gray and bucketB) ,它等於P(bucketB|gray)乘以P(gray)的,先發生gray,然後在gray的基礎上發生bucketB,就是gray and bucketB。

所以這裏的公式還可以變一下,*P(gray|bucketB) = P(gray and bucketB) / P(bucketB) =P(bucketB|gray) P(gray) / P(bucketB)**。

一般情況下,寫成 *p(c|x) = p(x|c) p(c) / p(x)** 這就是貝葉斯準則。

2.2 使用條件概率進行分類

貝葉斯決策論中真正比較的是條件概率p(c1|x,y)和p(c2|x,y),這些符號所代表的具體意義是,給定某個由x,y表示的數據點,想知道該數據點來自類別c1的概率是多少?數據點來自類別c2的概率又是多少?

如果 p(c1|x,y) > p(c2|x,y) ,屬於類別c1 如果 p(c2|x,y) > p(c1|x,y) ,屬於類別c2。

這些概率可以有2.1的貝葉斯準則計算。

3. 使用樸素貝葉斯進行留言分類

樸素貝葉斯的一般過程
(1) 收集數據:可以使用任何方法。本章使用RSS源。
(2) 準備數據:需要數值型或者布爾型數據。
(3) 分析數據:有大量特徵時,繪製特徵作用不大,此時使用直方圖效果更好。
(4) 訓練算法:計算不同的獨立特徵的條件概率。
(5) 測試算法:計算錯誤率。
(6) 使用算法:一個常見的樸素貝葉斯應用是文檔分類。可以在任意的分類場景中使用樸素貝葉斯命類器,不一定非要是文本

樸素貝葉斯的兩個假設
(1) 特徵之間是統計獨立的,即一個特徵或者單詞出現的可能性與它和其他單詞相鄰沒有關係。
(2) 每個特徵同等重要。

以上兩個假設是有問題的,不夠嚴謹,但處理方便,實際效果卻很好。

3.1 準備數據:從文本中構建詞向量

詞表到向量的轉換函數如下:

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,0,1,0,1]    #1 is abusive, 0 not
    return postingList,classVec

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):
    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
  • 第一個loadDataSet函數是返回詞條切分後的文檔集合postlist(選自斑點犬愛好者留言板)和類別標籤集合classvec(1代表侮辱,0則是正常言論)。
  • 第二個createVocabList函數會返回輸入數據集所有不重複詞彙的列表。
  • 第三個setOfWords2Vec函數的功能是遍歷輸入vocablist的所有單詞,如果當初出現了InputSet中的單詞,returnVec對應位數的值返回1,無則返回0。

簡單來講,第一個函數的作用是界定訓練類別,看之後的文檔是否含有類別中的詞彙,第二個函數的作用是將一篇文檔做成列表,方便後面進行標記。第三個函數則是將第二個函數生成的列表根據第一個類別詞彙進行標記,將單詞轉化成數字,方便後面計算條件概率。

測試一下吧(所有函數都放在bayes中)。

cd 桌面/machinelearninginaction/Ch04
/home/fangyang/桌面/machinelearninginaction/Ch04
import bayes
listOPosts,listClasses = bayes.loadDataSet()
myVocabList = bayes.createVocabList(listOPosts)
myVocabList

bayes.setOfWords2Vec(myVocabList,listOPosts[0])

python機器學習實戰(三)

bayes.setOfWords2Vec(myVocabList,listOPosts[3])

python機器學習實戰(三)

3.2 訓練算法 :從詞向量計算概率

根據上面介紹的三個函數,我們知道如何將一組單詞轉換爲一組數字,也知道一個詞是否出現在一篇文檔中。現在已知文檔的類別,讓我們使用轉換得到的數字來計算條件概率吧。

還是根據上面的貝葉斯準則來計算條件概率,不過公式會有一點不一樣。

*p(ci|w) = p(w|ci) p(ci) / p(w)** (這裏的ci表示所屬類別,這裏有兩種可能性1和0,w爲向量,由多個數值組成)

我們根據上面的公式對每個類進行計算,然後比較這兩個概率值的大小。計算過程如下:

首先可以通過類別 i ( 侮辱性留言或非侮辱性留言)中文檔數除以總的文檔數來計算概率p(ci),接下來計算p(w|ci),由於p(w|ci) = p(w0,w1,w2..wn|ci),又因爲所有詞都相互獨立,所以p(w|ci) = p(w0|ci)p(w1|ci)p(w2|ci)...p(wn|ci)

於是函數的僞代碼相應如下:

計算每個類別中的文檔數目
對每篇訓練文檔:
       對每個類別:
              如果詞條出現文檔中―增加該詞條的計數值
              增加所有詞條的計數值
對每個類別:
       對每個詞條:
              將該詞條的數目除以總詞條數目得到條件概率
返回每個類別的條件概率

參考代碼如下:

def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pAbusive = sum(trainCategory)/float(numTrainDocs)
    p0Num = zeros(numWords); p1Num = zeros(numWords)      
    p0Denom = 0.0; p1Denom = 0.0                        
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = p1Num/p1Denom         
    p0Vect = p0Num/p0Denom         
    return p0Vect,p1Vect,pAbusive

輸入的trainMatrix是文檔經過setOfWords2Vec函數轉換後的列表,trainCategory是每篇文檔構成類別標籤向量。輸出是返回每個類別的概率,pAbusive等於類別和除以訓練的樣本數,這個就是說明一下文檔類別的概率分佈,沒有什麼其他意思。

由於要算每一個詞語的概率,這裏用到裏numpy的array數組,可以很方便的計算每個詞語的概率,即是用p0Num和p1Num來統計不同類別樣本的詞語所出現的次數,最後對每個元素除以該類別中的總詞數。

來測試一下吧。

from numpy import *
reload(bayes)
<module 'bayes' from 'bayes.py'>
listOPosts,listClasses = bayes.loadDataSet()
myVocabList = bayes.createVocabList(listOPosts)
trainMat = []

for postinDoc in listOPosts:
       trainMat.append(bayes.setOfWords2Vec(myVocabList,postinDoc))

p0V , p1V, pAb = bayes.trainNB0(trainMat,listClasses)
p0V

python機器學習實戰(三)

p1V

python機器學習實戰(三)

看一看在給定文檔類別條件下詞彙表中單詞的出現概率, 看看是否正確.
詞彙表中的第一個詞是cute , 其在類別 0中出現1次 ,而在類別1中從未出現。對應的條件概率分別爲 0.04166667 與 0.0,該計算是正確的。
我們找找所有概率中的最大值,該值出現在p(1)數組第21個下標位置,大小爲 0.15789474.可以查到該單詞是stupid,這意味着它最能表徵類別1的單詞。

3.3 測試算法:根據現實情況修改分類器

利用貝葉斯分類器進行文檔文類時,要計算每個元素的條件概率並相乘,若其中有一個概率值等於0,那麼最後的乘積也爲0,爲降低這種影響,可以將所有詞的出現數初始化爲1 ,並將分母初始化爲2 。

相應的trainNB0()的第4行和第5行修改爲:

p0Num = ones(numWords);  p1Num = ones(numWords)      #change to ones() 
p0Denom = 2.0; p1Denom = 2.0                                             #change to 2.0

另一個問題是向下溢出,乘積p(w0|ci)p(w1|ci)p(w2|ci)...p(wn|ci)太小的緣故 解決的辦法是對乘積取對數

相應的trainNB0()的第13行和第14行修改爲:

 p1Vect = log(p1Num/p1Denom)        #change to log()
 p0Vect = log(p0Num/p0Denom)        #change to log()

將更改好的函數命名爲trainNB0_change.

現在已經準備好構建完整的分類器了。當使用numpy向量處理功能時 , 這一切變得十分簡單.

參考代碼如下:

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)    #element-wise mult
    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 postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0V,p1V,pAb = trainNB0_change(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)

第一個函數就是兩個類別的條件概率進行比較,輸出最終的類別信息。 第二個函數就是一個測試函數,函數前面部分跟上面一樣,後面引入兩個測試樣本,進行分類。

reload(bayes)
<module 'bayes' from 'bayes.pyc'>
bayes.testingNB()
['love', 'my', 'dalmation'] classified as: 0
['stupid', 'garbage'] classified as: 1

3.4 文檔詞袋模型

我們將每個詞的出現與否作爲一個特徵,這可以被描述爲詞集模型,上面就是詞集模型。
如果一個詞在文檔中出現不止一次,這可能意味着包含該詞是否出現在文檔中所不能表達的某種信息,這種方法被稱爲詞袋模型。
詞集和詞袋的區別:在詞袋中,每個單詞可以出現多次 ,而在詞集中,每個詞只能出現一次。

爲適應詞袋模型 ,需要對函數setOfWords2Vec稍加修改,修改後的函數爲bagOfWords2Vec,代碼如下:

def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec

這個返回的列表表現的是單詞出現的次數,還不再是是否出現

4. 使用樸素貝葉斯過濾垃圾郵件

4.1 準備數據:切分文本

前面介紹的詞向量是直接給定的,下面來介紹如何從文本中構建自己的詞列表.

先從一個文本字符串介紹

mySent = ' This book is the best book on python or M.L. I have ever laid eyes upon.'
mySent.split()

python機器學習實戰(三)

可以看到, 切分的結果不錯, 但是標點符號也被當成了詞的一部分.

解決方法:可以使用正則表示式來切分句子 ,其中分隔符是除單詞、數字外的任意字符串.

import re
regEx = re.compile('\\W*')
listOfTokens = regEx.split(mySent)
listOfTokens

python機器學習實戰(三)![6]()

可以看到裏面的標點沒有了,但剩下一些空字符,還要進行一步,去掉這些空字符。

[tok for tok in listOfTokens if len(tok) &gt;0]

python機器學習實戰(三)![7]()

空字符消掉了,我們可以看到,有的詞首字母是大寫的,這對句子查找很有用,但我們是構建詞袋模型,所以還是希望格式統一,還要處理一下.

[tok.lower() for tok in listOfTokens if len(tok) &gt;0]

python機器學習實戰(三)![8]()

可以看到大寫全部變成了小寫,如果是想從小寫變成大寫,只需將tok.lower()改成top.upper()即可.

我們構建一個testParse函數,來切分文本,代碼如下

def textParse(bigString):    #input is big string, #output is word list
    import re
    listOfTokens = re.split(r'\W*', bigString)
    return [tok.lower() for tok in listOfTokens if len(tok) > 2] 

4.2 測試算法:使用樸素貝葉斯進行交叉驗證

參考代碼如下:

def spamTest():
    docList=[]; classList = []; fullText =[]
    for i in range(1,26):
        wordList = textParse(open('email/spam/%d.txt' % i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        wordList = textParse(open('email/ham/%d.txt' % i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList = createVocabList(docList)#create vocabulary
    trainingSet = range(50); testSet=[]           #create test set
    for i in range(10):
        randIndex = int(random.uniform(0,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 = 0
    for docIndex in testSet:        #classify the remaining items
        wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
        if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
            errorCount += 1
            print "classification error",docList[docIndex]
    print 'the error rate is: ',float(errorCount)/len(testSet)
    #return vocabList,fullText
  • 第一個循環是對垃圾郵件和非垃圾郵件進行切分,然後生成詞列表和類標籤
  • 第二個循環是0到50個數中隨機生成10個序號
  • 第三個循環是將第二個循環得到的序號映射到詞列表,得到訓練集和相應的類別,然後進行訓練算法
  • 第四個循環是進行錯誤率計算,分類出的類別與實際類別相比較,累計錯誤的樣本數,最後除以總數,得到錯誤率

bayes.spamTest()
the error rate is: 0.0
bayes.spamTest()

python機器學習實戰(三)![9]()

每次運行得出的結果可能不太一樣,因爲是隨機選的序號.

5. 使用樸素貝葉斯分類器從個人廣告中獲取區域傾向

在這個最後的例子當中,我們將分別從美國的兩個城市中選取一些人,通過分析這些人發佈的徵婚廣告信息,來比較這兩個城市的人們在廣告用詞上是否不同。如果結論確實是不同,那麼他們各自常用的詞是哪些?從人們的用詞當中,我們能否對不同城市的人所關心的內容有所瞭解?

下面將使用來自不同城市的廣告訓練一個分類器,然後觀察分類器的效果。我們的目的並不是使用該分類器進行分類,而是通過觀察單詞和條件概率值來發現與特定城市相關的內容。

5.1 收集數據:導入RSS源

接下來要做的第一件事是使用python下載文本,而利用RSS,這很容易得到,而Universal Feed Parser 是python最常用的RSS程序庫。

由於python默認不會安裝feedparser,所以需要自己手動安裝,這裏附上ubuntu下的安裝方法

具體可以看到這個鏈接:blog.csdn.net/tinkle181129/article/details/45343267
相關文檔:http://code.google.com/p/feedparser/

import feedparser
ny = feedparser.parse('http://newyork.craigslist.org/stp/index.rss')
上面是打開了Craigslist上的RSS源,要訪問所有條目的列表,輸入以下代碼

ny['entries']

python機器學習實戰(三)![10]()

len(ny['entries'])
Out:25

可以構建一個類似spamTest的函數來對測試過程自動化

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) 
    return sortedFreq[: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'])
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1) #NY is class 1
        wordList = textParse(feed0['entries'][i]['summary'])
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList = createVocabList(docList)#create vocabulary
    top30Words = calcMostFreq(vocabList,fullText)   #remove top 30 words
    for pairW in top30Words:
        if pairW[0] in vocabList: vocabList.remove(pairW[0])
    trainingSet = range(2*minLen); testSet=[]           #create test set
    for i in range(20):
        randIndex = int(random.uniform(0,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 = 0
    for docIndex in testSet:        #classify the remaining items
        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

localWords函數與之前介紹的spamTest函數類似,不同的是它是使用兩個RSS作爲參數。

上面還新增了一個輔助函數calcMostFreq,該函數遍歷詞彙表中的每個詞並統計它在文本中出現的次數,然後根據出現次數從高到低對詞典進行排序 , 最後返回排序最高的30個單詞

下面來測試一下

cd 桌面/machinelearninginaction/Ch04
/home/fangyang/桌面/machinelearninginaction/Ch04
import bayes
import feedparser
ny = feedparser.parse('http://newyork.craigslist.org/stp/index.rss')
sf = feedparser.parse('http://sfbay.craigslist.org/stp/index.rss')
vocabList,pSF,pNY = bayes.localWords(ny,sf)
the error rate is :0.15
vocabList,pSF,pNY = bayes.localWords(ny,sf)
the error rate is :0.4

我們會發現這裏的錯誤率要遠高於垃圾郵件中的錯誤率,這是因爲這裏關注的是單詞概率而不是實際分類,可以通過calcMostFreq函數改變移除單詞數,降低錯誤率,因爲次數最多的前30個單詞涵蓋了所有用詞的30%,產生這種現象的原因是語言中大部分都是冗餘和結構輔助性內容。

5.2 分析數據:顯示地域相關的用詞

將pSF和pNY進行排序,然後按照順序將詞打印出來,這裏用getTopWords函數表示這個功能

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**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**NY**NY**NY**NY**NY**"
    for item in sortedNY:
        print item[0]

輸入是兩個RSS源,然後訓練並測試樸素貝葉斯分類器,返回使用的概率值然後創建兩個列表用於元組的存儲。與之前返回排名最高的x個單詞不同,這裏可以返回大於某個閾值的所有詞。這些元組會按照它們的條件概率進行排序。

bayes.getTopWords(ny,sf)

python機器學習實戰(三)![11]()

值得注意的現象是,程序輸出了大量的停用詞。移除固定的停用詞(比如 there等等)看看結果會如何變化,依本書作者的經驗來看,這樣會使分類錯誤率降低。

小結

(1)對於分類而言,使用概率有時要比使用硬規則更爲有效
(2)貝葉斯概率及貝葉斯準則提供了一種利用已知值來估計未知概率的有效方法
(3)獨立性假設是指一個詞的出現概率並不依賴於文檔中的其他詞,這個假設過於簡單。這就是之所以稱爲樸素貝葉斯的原因。
(4)下溢出就是其中一個問題,它可以通過對概率取對數來解決
(5)詞袋模型在解決文檔分類問題上比詞集模型有所提高
(6)移除停用詞,可降低錯誤率
(7)花大量時間對切分器進行優化

百度雲鏈接:https://pan.baidu.com/s/1LgKUL7f4ja7mz0js-y62qg

               

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