原文連接:www.cnblogs.com/fydeblog/p/…html
這篇博客是關於機器學習中基於機率論的分類方法--樸素貝葉斯,內容包括樸素貝葉斯分類器,垃圾郵件的分類,解析RSS源數據以及用樸素貝葉斯來分析不一樣地區的態度.python
操做系統:ubuntu14.04 運行環境:anaconda-python2.7-jupyter notebook 參考書籍:機器學習實戰和源碼,機器學習(周志華) notebook writer ----方陽算法
注意事項:在這裏說一句,默認環境python2.7的notebook,用python3.6的會出問題,還有個人目錄可能跟大家的不同,大家本身跑的時候記得改目錄,我會把notebook和代碼以及數據集放到結尾的百度雲盤,方便大家下載!ubuntu
樸素貝葉斯的特色:數組
貝葉斯決策理論的核心思想:選擇具備最高几率的決策。(最小化每一個樣本的條件風險,則整體風險也就最小,就是選擇最高几率,減少風險)bash
條件機率在樸素貝葉斯里面是必不可少的一環,下面來簡單介紹介紹:app
假設如今有一個裝了7塊石頭的罐子,其中3塊是灰色的, 4塊是黑色的 。若是從罐子中隨機取出一塊石頭,那麼是灰色石頭的可能性是多少? 因爲取石頭有 7 種可能 ,其中 3種爲灰色 ,因此取出灰色石頭的機率爲 3/7 。那麼取到黑色石頭的機率又是多少呢?很顯然 ,是4/7 。less
若是這7塊石頭放在兩個桶中,那麼上述機率應該如何計算? (設兩個桶分爲A,B,A桶裝了2個灰色和2個黑色的石頭,B桶裝了1個灰色和2個黑色的石頭)dom
要計算P(gray)或者P(black) ,事先得知道石頭所在桶的信息會不會改變結果?你有可能巳經想到計算從B桶中取到灰色石頭的機率的辦法,這就是所謂的條件機率.python2.7
來計算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)* 這就是貝葉斯準則。
貝葉斯決策論中真正比較的是條件機率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的貝葉斯準則計算。
樸素貝葉斯的通常過程 (1) 收集數據:可使用任何方法。本章使用RSS源。 (2) 準備數據:須要數值型或者布爾型數據。 (3) 分析數據:有大量特徵時,繪製特徵做用不大,此時使用直方圖效果更好。 (4) 訓練算法:計算不一樣的獨立特徵的條件機率。 (5) 測試算法:計算錯誤率。 (6) 使用算法:一個常見的樸素貝葉斯應用是文檔分類。能夠在任意的分類場景中使用樸素貝葉斯命類器,不必定非要是文本
樸素貝葉斯的兩個假設 (1) 特徵之間是統計獨立的,即一個特徵或者單詞出現的可能性與它和其餘單詞相鄰沒有關係。 (2) 每一個特徵同等重要。
以上兩個假設是有問題的,不夠嚴謹,但處理方便,實際效果卻很好。
詞表到向量的轉換函數以下:
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
複製代碼
簡單來說,第一個函數的做用是界定訓練類別,看以後的文檔是否含有類別中的詞彙,第二個函數的做用是將一篇文檔作成列表,方便後面進行標記。第三個函數則是將第二個函數生成的列表根據第一個類別詞彙進行標記,將單詞轉化成數字,方便後面計算條件機率。
測試一下吧(全部函數都放在bayes中)。
cd 桌面/machinelearninginaction/Ch04
/home/fangyang/桌面/machinelearninginaction/Ch04 import bayes
listOPosts,listClasses = bayes.loadDataSet()
myVocabList = bayes.createVocabList(listOPosts)
myVocabList
bayes.setOfWords2Vec(myVocabList,listOPosts[0])
bayes.setOfWords2Vec(myVocabList,listOPosts[3])
根據上面介紹的三個函數,咱們知道如何將一組單詞轉換爲一組數字,也知道一個詞是否出如今一篇文檔中。如今已知文檔的類別,讓咱們使用轉換獲得的數字來計算條件機率吧。
仍是根據上面的貝葉斯準則來計算條件機率,不過公式會有一點不同。
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
p1V
看一看在給定文檔類別條件下詞彙表中單詞的出現機率, 看看是否正確. 詞彙表中的第一個詞是cute , 其在類別 0中出現1次 ,而在類別1中從未出現。對應的條件機率分別爲 0.04166667 與 0.0,該計算是正確的。 咱們找找全部機率中的最大值,該值出如今p(1)數組第21個下標位置,大小爲 0.15789474.能夠查到該單詞是stupid,這意味着它最能表徵類別1的單詞。
利用貝葉斯分類器進行文檔文類時,要計算每一個元素的條件機率並相乘,若其中有一個機率值等於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
咱們將每一個詞的出現與否做爲一個特徵,這能夠被描述爲詞集模型,上面就是詞集模型。 若是一個詞在文檔中出現不止一次,這可能意味着包含該詞是否出如今文檔中所不能表達的某種信息,這種方法被稱爲詞袋模型。 詞集和詞袋的區別:在詞袋中,每一個單詞能夠出現屢次 ,而在詞集中,每一個詞只能出現一次。
爲適應詞袋模型 ,須要對函數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
複製代碼
這個返回的列表表現的是單詞出現的次數,還再也不是是否出現
前面介紹的詞向量是直接給定的,下面來介紹如何從文本中構建本身的詞列表.
先從一個文本字符串介紹
mySent = ' This book is the best book on python or M.L. I have ever laid eyes upon.'
mySent.split()
能夠看到, 切分的結果不錯, 可是標點符號也被當成了詞的一部分.
解決方法:可使用正則表示式來切分句子 ,其中分隔符是除單詞、數字外的任意字符串.
import re
regEx = re.compile('\\W*')
listOfTokens = regEx.split(mySent)
listOfTokens
能夠看到裏面的標點沒有了,但剩下一些空字符,還要進行一步,去掉這些空字符。
[tok for tok in listOfTokens if len(tok) >0]
空字符消掉了,咱們能夠看到,有的詞首字母是大寫的,這對句子查找頗有用,但咱們是構建詞袋模型,因此仍是但願格式統一,還要處理一下.
[tok.lower() for tok in listOfTokens if len(tok) >0]
能夠看到大寫所有變成了小寫,若是是想從小寫變成大寫,只需將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]
複製代碼
參考代碼以下:
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
複製代碼
bayes.spamTest()
the error rate is: 0.0 bayes.spamTest()
每次運行得出的結果可能不太同樣,由於是隨機選的序號.
在這個最後的例子當中,咱們將分別從美國的兩個城市中選取一些人,經過分析這些人發佈的徵婚廣告信息,來比較這兩個城市的人們在廣告用詞上是否不一樣。若是結論確實是不一樣,那麼他們各自經常使用的詞是哪些?從人們的用詞當中,咱們可否對不一樣城市的人所關心的內容有所瞭解?
下面將使用來自不一樣城市的廣告訓練一個分類器,而後觀察分類器的效果。咱們的目的並非使用該分類器進行分類,而是經過觀察單詞和條件機率值來發現與特定城市相關的內容。
接下來要作的第一件事是使用python下載文本,而利用RSS,這很容易獲得,而Universal Feed Parser 是python最經常使用的RSS程序庫。
因爲python默認不會安裝feedparser,因此須要本身手動安裝,這裏附上ubuntu下的安裝方法
具體能夠看到這個連接:blog.csdn.net/tinkle18112… 相關文檔:code.google.com/p/feedparse…
import feedparser
ny = feedparser.parse('http://newyork.craigslist.org/stp/index.rss')
上面是打開了Craigslist上的RSS源,要訪問全部條目的列表,輸入如下代碼
ny['entries']
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%,產生這種現象的緣由是語言中大部分都是冗餘和結構輔助性內容。
將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)
值得注意的現象是,程序輸出了大量的停用詞。移除固定的停用詞(好比 there等等)看看結果會如何變化,依本書做者的經驗來看,這樣會使分類錯誤率下降。
(1)對於分類而言,使用機率有時要比使用硬規則更爲有效 (2)貝葉斯機率及貝葉斯準則提供了一種利用已知值來估計未知機率的有效方法 (3)獨立性假設是指一個詞的出現機率並不依賴於文檔中的其餘詞,這個假設過於簡單。這就是之因此稱爲樸素貝葉斯的緣由。 (4)下溢出就是其中一個問題,它能夠經過對機率取對數來解決 (5)詞袋模型在解決文檔分類問題上比詞集模型有所提升 (6)移除停用詞,可下降錯誤率 (7)花大量時間對切分器進行優化
百度雲連接:pan.baidu.com/s/1LgKUL7f4…
圓方圓學院聚集 Python + AI 名師,打造精品的 Python + AI 技術課程。 在各大平臺都長期有優質免費公開課,歡迎報名收看。
公開課地址:ke.qq.com/course/3627…
加入python學習討論羣 78486745 ,獲取資料,和廣大羣友一塊兒學習。