機器學習之樸素貝葉斯:基於概論論的分類方法(四)

主要內容:

  • 使用概率分布進行分類
  • 學習樸素貝葉斯分類器
  • 解析RSS源數據
  • 使用樸素貝葉斯來分析不同地區的態度

概率論是機器學習演算法的基礎,所以深刻理解概率論這個主題就十分重要。

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

樸素貝葉斯

優點:在數據較少的情況下仍然有效,可以處理多類問題。

缺點:對於輸入的數據的準備方式較為敏感。

適用數據類型:標稱型數據。

我們先來了解一下貝葉斯決策理論。

假設我們現在有一個數據集,它由兩類數據組成,數據分布如下圖所示。

假設我們圖中有兩類數據統計參數。

用p1(x,y)表示數據點(x,y)屬於類別1(圖中圓點表示的類別)的概率,

用p2(x,y)表示數據點(x,y)屬於類別2(圖中三角形表示類別的)的概率。

  • 如果p1(x,y) > p2(x,y),那麼類別為1;
  • 如果p2(x,y) > p1(x,y),那麼類別為2.。

以上也說明了貝葉斯決策理論的核心思想,我們選擇具有最高概率的決策。

如果上面這個圖中,使用6個參數來表示,你會更加想使用哪種方法類進行分類呢?

如果是kNN演算法,計算量就比較大;如果是使用決策樹,不會非常成功。

那麼最佳的方法其實是使用貝葉斯決策理論。

4.2 條件概率

我們來複習一下本科時候學習的概率論中的條件概率。

假設現在有一個裝了7塊石頭的的罐子,其中3塊是白的,4塊是黑色的。如果我們從罐子中隨機取出一塊石頭,那麼白色石頭的可能性是多少?黑色的又是多少?

顯然,取得黑色是概率是4/7 ; 取得白色的概率是 3/7 ;

如果我們的石頭放在兩個桶中,那麼上述的概率又該如何計算呢?

我們先設,取得黑色的概率為P(black) , 取得白色的概率為P(white) 。

那麼如何計算黑色或者白色取得的概率問題,就是條件概率。

假設計算的是從B桶取到白色石頭的概率,此概率可以記為P(white | bucketsB) , 並稱之為「在已知石頭出自B桶的條件下,取出白色石頭的概率」。

此時

P(white | bucketsA) = 2/4 ; P(white | bucketsB) = 1/3 ;

那麼條件概率的計算公式就如下:

P(white | bucketsB) = P(white and bucketsB) / P bucketsB)

我們是需要驗證摺合後公式的合理性,

P(white and bucketsB) = 1/7 這個就是B桶的白色石頭除以兩個桶的石頭數量總和。再次,B桶有3個石頭,總石頭數是7,那麼 公式就是

P(white | bucketsB) = P(white and bucketsB) / P bucketsB) = (1/7) /(3/7) = 1/3

另外一種計算條件概率的方法稱之為貝葉斯準則。公式如下:

P (c|x ) = P (x|c) * p(c) / p(x)

4.3 使用條件概率來分類

假設我們圖中有兩類數據統計參數。

用p1(x,y)表示數據點(x,y)屬於類別1(圖中圓點表示的類別)的概率,

用p2(x,y)表示數據點(x,y)屬於類別2(圖中三角形表示類別的)的概率。

  • 如果p1(x,y) > p2(x,y),那麼類別為C1;
  • 如果p2(x,y) > p1(x,y),那麼類別為C2.。

上面只是為了簡化問題。

使用貝葉斯準則來交換概率中條件與結果:

應用貝葉斯準則得到,如下:

使用這些定義,可以定義貝葉斯分類:

  • 如果 P(c1,x|y)>P(c2|x,y),那麼屬於類別c1 。
  • 如果 P(c1,x|y)<P(c2|x,y) ,那麼屬於類別c2 。

4.4 使用樸素貝葉斯進行文檔分類

機器學習的一個重要作用就是文檔的自動分類。

樸素貝葉斯的一般流程:

1.收集數據:可以使用任何方法。本章使用RSS源。

2.準備數據:需要數值型或者布爾型數據。

3.分析數據:有大量特徵時,繪製特徵作用不大,此時使用直方圖效果更好。

4.訓練演算法:計算錯誤率。

6.使用演算法:一個常見的樸素貝葉斯應用是文檔分類。可以在任意的分類場景中使用樸素貝葉斯分類器,不一定非要是文本。

獨立:指的是統計意義上的獨立,即一個特徵的出現或者單詞出現的可能性與它和其他單詞相鄰沒有任何關係。

4.5 使用python進行文本分類

要從文本中獲取特徵,需要先拆分文本。

例如,我們為了管理社區,我們要屏蔽侮辱性言論,構建一個快速過濾器。

對於這個問題,我們隊此問題建立以下兩個類別:侮辱類和非侮辱類。,使用0和1來分別表示。

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

我們將文本看成單詞向量,也就是將句子轉換為向量。

我們需要建立一個bayes.py的文件。

程序4-1 詞表向量的轉換函數

def loanDataSet(): 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代表侮辱性文字,0代表正常言論 return postingList,classVecdef createVocabList(dataSet): #創建一個空集 vocabSet = set ([]) for document in dataSet: #創建兩個集合的並集 vocabSet = vocabSet | set (document) return list(vocabSet)def setOfWords2Vec(VocabList,inputSet): #創建一個其中所含元素都為0的向量並與辭彙表等長 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

分析:

1.第一個函數創建了一些實驗樣本。其中第一個變數是進行詞條切分後的文檔集合,第二個變數是一個類別標籤的集合。

2.第二個函數會創建一個包含在所有文檔中出現的不重複詞的列表。set也會返回一個不重複詞表。

3.第三個函數的輸入參數為辭彙表以及某個文檔。創建一個其中所含元素都為0的向量並與辭彙表等長,

然後函數會創建一個遍歷文檔中所有單詞,如果出現了辭彙中的表中的單詞,輸出向量為1.

來測試代碼:

In [6]: import bayesIn [7]: reload(bayes)Out[7]: <module bayes from bayes.py>In [8]: listOposts,listClasses = bayes.loadDataSet()In [10]: myVocabList = bayes.createVocabList(listOposts)In [11]: myVocabList Out[11]: [cute, love, help,garbage, quit, I, problems, is, park, stop, flea, dalmation, licks, food, not, him,buying, posting, has, worthless, ate, to, maybe, please, dog, how, stupid, so,take, mr,steak, my]In [13]: bayes.setOfWords2Vec(myVocabList,listOposts[0])Out[13]: [0,0,1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0,0, 0, 1, 0, 0, 0, 0, 1, 1, 0,0,0, 0, 0, 0, 1]In [15]: bayes.setOfWords2Vec(myVocabList,listOposts)the word:[my, dog, has, flea, problems, help, please] is not in my Vocabulary!the word:[maybe, not, take, him, to, dog, park, stupid] is not in my Vocabulary!the word:[my, dalmation, is, so, cute, I, love, him] is not in my Vocabulary!the word:[stop, posting, stupid, worthless, garbage] is not in my Vocabulary!the word:[mr, licks, ate, my, steak, how, to, stop, him] is not in my Vocabulary!the word:[quit, buying, worthless, dog, food, stupid] is not in my Vocabulary!Out[15]: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0,0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0]

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

函數的偽代碼如下:

計算每個類別中的文檔數目:

對每篇訓練文檔:

對每個類別:

如果詞條出現在文檔中: 增加該詞條的計數值

增加所有詞條的計數值

對每個類別:

對每個詞條:

將該詞條的數目除以總詞數得到條件概率

返回每個類別的條件概率

代碼如下:

# 樸素貝葉斯的=分類器訓練函數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

解析:

1.代碼函數中輸入參數為文檔矩陣 trainMatrix ,以及由每篇文檔類別標籤的所構成的向量trainCategory

2.計算文檔屬於侮辱性文檔(class=1)的概率,即p(1)

3.計對每個元素的除以該類別中的總詞數。

In [1]: from numpy import *In [4]: import bayesIn [5]: reload(bayes)Out[5]: <module bayes from bayes.pyc>In [6]: listOposts,listClasses = bayes.loadDataSet()In [7]: myVocabList = bayes.createVocabList(listOposts)In [8]: trainMat = []In [10]: for postinDoc in listOposts: ...: trainMat.append(bayes.setOfWords2Vec(myVocabList,postinDoc))

...:

#給出侮辱性文檔的概率以及兩個類別的概率向量In [11]: pov,p1v,pAb = bayes.trainNB0(trainMat,listClasses)In [12]: pAbOut[12]: 0.5

#任意文檔的侮辱性概率值In [13]: povOut[13]: array([ 0.04166667, 0.04166667, 0.04166667, ..., 0.04166667, 0.04166667, 0.125 ])In [14]: p1vOut[14]: array([ 0., 0., 0.,0.053 ..., 0.0520., 0., 0.])

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

貝葉斯分類器對文檔進行分類的時,要計算多個概率的乘積以獲得文檔屬於某個類別的概率。

即是計算 p(w0|1)p(w1|1)p(w2|1) ,如果其中一個概率為0,那麼最終的乘積也為0,為了降低這種影響,我們可以將所有詞的出現次數初始化為1,將分母初始化為2。

我們改變bayes.py文件的第4行和第5行,修改為:

#初始化概率 p0Num = ones(numWords) p1Num = ones(numWords) p0Denom = 2.0 p1Denom = 2.0

另外一個問題是下溢出(這是由於太多的很小的數相乘,最後四捨五入就會得到0)。解決辦法就是取自然對數。

我們將採取取自然對數來處理。(通過求對數可以避免)

至此,我們可以修改bayes.py文件中return前的兩行代碼:

p1Vect = log(p1Num/p1Denom) p0Vect = log(p0Num/p0Denom)

我們繼續增加代碼:

#樸素貝葉斯分類函數 def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1): p1 = sum(vec2Classify * p1Vec) + log(pClass1) p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1) if p1 > p0: return 1 else: return 0def testingNB(): 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: ,classifyNB(thisDoc,p0v,p1v,pAb) testEntry = [stupid,garbage] thisDoc = array(setOfWords2Vec(myVocabList,testEntry)) print testEntry,classified as: ,classifyNB(thisDoc,p0v,p1v,pAb)

testingNB()函數是一個便利函數,這個函數封裝所有操作,以節省輸入上述代碼的時間。

運行代碼,看看結果:

In [14]: reload(bayes)Out[14]: <module bayes from bayes.py>In [15]: bayes.testingNB()[love, my, dalmation] classified as: 0[stupid, garbage] classified as: 1

4.5.4 準備數據:文檔詞袋模型

  • (1)詞集模型:Set Of Words(SOW),單詞構成的集合,集合自然每個元素都只有一個,也即詞集中的每個單詞都只有一個
  • (2)詞袋模型:Bag Of Words(BOW),如果一個單詞在文檔中出現不止一次,並統計其出現的次數(頻數)

在詞集中,每個詞只能出現一次。

在詞袋中,每個單詞可以出現多次。

代碼4-4 樸素貝葉斯詞袋模型(基於詞袋模型的樸素貝葉斯代碼)

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

通過上述代碼,我們已經構建好了分類器。

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

使用樸素貝葉斯對電子郵件進行分類:

步驟:

1.收集數據:提供文本文件

2.準備數據:將文本文件解析成詞條向量

3.分析數據:檢查詞條確保解析的正確性

4.訓練演算法:使用我們之前建立的trainNB0()函數

5.測試演算法:使用classifyNB(),構建一個新的測試函數來計算文檔集的錯誤率

6.使用演算法:構建一條完整的程序對一組文檔進行分類,將錯分的文檔輸出到屏幕上

4.6.1 準備數據:切分文本

對於一個文本文檔,我們可以使用python的string.split()方法將其切分。

In [17]: mysent = This book is the best book on python or M.L. I have ever laid eyes upon.In [18]: mysent.split()Out[18]:[This,book,is,the,best,book,on,python,or,M.L.,I,have,ever,laid,eyes,upon.]

我們看到已經切分開了,但是標點符號被成了詞的一部分,我們利用正則表達式來切分,去除除了單詞或者數字之外的任意字元串。

In [19]: import reIn [20]: regEx = re.compile(\W*)In [21]: listOfTokens = regEx.split(mysent)In [22]: listOfTokensOut[22]:[This,book,is,the,best,book,on,python,or,M,LI,have,ever,laid,eyes,upon,]

我們看到有很多空字元串,我們可以,先轉換為小寫,然後返回長度大於0的字元

In [23]: [i.lower() for i in listOfTokens if len(i) > 0]Out[23]:[this,bookis,the,best,book,on,python,or,m,l,i,have,ever,laid,eyes,upon]

我們導入一封電子郵件,然後用正則表達式簡單的處理一下,最終的結果,我們可以進入spyder裡面看看。

In [27]: emailTxt = open(rE:MLML_source_codemliaCh04emailham6.txt).read()In [28]: listof = regEx.split(emailTxt)In [29]: listofo = [i.lower() for i in listof if len(i) > 3]

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

# 文本解析及完整的垃圾郵件的測試函數def textParse(bigString): listOfTokens = re.split(rW*,bigString) return [x.lower() for x in listOfTokens if len(x) > 2]def spamTest(): docList = []; classList = []; fullText = [] #導入文件並解析文本 for i in range(1,26): wordList = textParse(open(rE:MLML_source_codemliaCh04emailspam%d.txt % i).read()) docList.append(wordList) fullText.extend(wordList) classList.append(1) wordList = textParse(open(rE:MLML_source_codemliaCh04emailham%d.txt % i).read()) docList.append(wordList) fullText.extend(wordList) classList.append(0) vocabList = createVocabList(docList) traningSet = range(50); testSet = [] #隨機的構建訓練集 for i in range(10): randIndex = int(random.uniform(0,len(traningSet))) testSet.append(traningSet[randIndex]) del(traningSet[randIndex]) trainMat = []; trainClasses = [] for docIndex in traningSet: 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 the error rate is:,float(errorCount)/len(testSet)

  • textParse() 函數接受一個大字元串並將其解析為字元串列表。
  • spamTest()函數對貝葉斯垃圾郵件分類器進行自動化處理。
  • 演算法的訓練方法是從總的數據集中隨機選擇數字,將其添加到測試集中,同時將其從測試機中刪除。這種隨機選擇數據的一部分作為訓練集,而剩餘部分作為測試集的過程稱之為 留存交叉驗證。

In [42]: reload(bayes)Out[42]: <module bayes from bayes.py>In [43]: bayes.spamTest()the error rate is: 0.0In [44]: reload(bayes)Out[44]: <module bayes from bayes.py>In [45]: bayes.spamTest()the error rate is: 0.1

代碼會對10封電子郵件進行分類,獲得的錯誤率0.1,0.2,0.1等,我做了10詞最終平均了一下,錯誤了吧大概為5% 。這裡一直出現的錯誤率其實就是將垃圾郵件誤判為正常郵件。

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

我們分別從美國的兩個城市中選取一些人,通過分析他們發布的徵婚信息,來比較兩個城市的人們在徵婚廣告用詞上有什麼不同。

步驟:

  1. 收集數據:從RSS源收集內容,這裡需要對RSS源構建一個介面
  2. 準備數據:將文本文件解析成詞條向量
  3. 分析數據:檢查詞條確保解析的正確性
  4. 訓練演算法:使用我們之前建立的trainNB0()函數
  5. 測試演算法:觀察錯誤率,確保分類器可用
  6. 使用演算法:構建一個完整的程序,封裝所有內容

4.7.1 收集數據:導入RSS源

先安裝feedparser這個模塊。然後進入這個RSS源

In [62]: import feedparserIn [63]: ny = feedparser.parse(http://newyork.craigslist.org/stp/index.rss)In [64]: len(ny[entries])Out[64]: 25

# 使用RSS源分類器及高頻詞去除函數ny = feedparser.parse(http://newyork.craigslist.org/stp/index.rss)sf = feedparser.parse(http://sfbay.craigslist.org/stp/index.rss)def calcMostFre(vocabList,fullText): """ 函數遍歷辭彙表中出現的每個詞語的次數, 並排序,返回次數最高的30個詞 """ 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): docList = [];classList = [];fullText = [] minLen = min(len(feed1[entries]),len(feed0[entries])) for i in range(minLen): #每次只去訪問一條RSS源 wordList = textParse(feed1[entries][i][summary]) 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 = calcMostFre(vocabList,fullText) for pairW in top30Words: if pairW[0] in vocabList: vocabList.remove(pairW[0]) trainingSet = range(2*minLen); testSet = [] 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: 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

  • 輔助函數calcMostFre遍歷辭彙表中出現的每個詞語的次數, 並排序,返回次數最高的30個詞
  • 函數localWords()與spamTest()最大的區別在於,函數裡面訪問的RSS源,不是文件。
  • 函數calcMostFre()獲取次數最高的30個詞隨後移除。

如果我們注釋掉 移除移除高頻詞的三行代碼,比較注釋前後的分類性能會發現,錯誤率會大大的提升的。比如注釋之前大概是50%左右,注釋後,就是70%左右。

主要原因是這些高頻詞佔了我們所有用詞的30% 。主要原因是因為英文語言本身大部分都是冗餘或小黃人結構性輔助詞。

In [3]: import bayesIn [4]: reload(bayes)Out[4]: <module bayes from bayes.pyc>In [8]: ny = feedparser.parse(http://newyork.craigslist.org/stp/index.rss) ...: sf = feedparser.parse(http://sfbay.craigslist.org/stp/index.rss)In [14]: vocabList,pSF,pNY = bayes.locWords(ny,sf)the error rate is: 0.45In [15]: vocabList,pSF,pNY = bayes.locWords(ny,sf)the error rate is: 0.3In [16]: vocabList,pSF,pNY = bayes.locWords(ny,sf)the error rate is: 0.4In [17]: vocabList,pSF,pNY = bayes.locWords(ny,sf)the error rate is: 0.5In [18]: vocabList,pSF,pNY = bayes.locWords(ny,sf)the error rate is: 0.25

我們發現這裡的錯誤率是比郵件分類的錯誤率是要高的,這裡關注的是單詞概率而不是實際分類,可以通過輔助函數改變要移除的單詞數目,相應的,錯誤率就會降低。

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

可以先對向量pSF與pNY進行排序,然後按照順序將詞列印出來。

# 最具表徵性的辭彙顯示函數def getTopWords(ny,sf): 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**NY**NY**NY" for item in sortedNY: print item[0]

這個代碼之前的排名最高的X個單詞不同,這裡可以返回大於某個閾值的所有詞。

In [29]: reload(bayes)Out[29]: <module bayes from bayes.pyc>In [30]: bayes.getTopWords(ny,sf)the error rate is: 0.45SF**SF****SF**SF**SF****SF**SF**SF****SF**SF**SF****SF**SF**SF****SF**SFfeltstaydevelopschrisfriendspoolyoungeragehellodepthNY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NYchrispoolagoyoungerlongertogethercurvydepthlives

4.8 總結

對於分類而言,使用概率的方法要比使用硬規則更為有效。貝葉斯概率以及貝葉斯準則提供了一套利用已知的值來估計位置概率的有效方法。


推薦閱讀:

想要做好社團招新?——會利用數據就好啦
超簡單的熱力地圖教程來襲,各位小主快翻牌啊~
泰坦尼克號倖存預測
Excel--速成路線
文| Fish(梁靜茹)歌詞分析

TAG:机器学习 | 数据分析 |