樸素貝葉斯詳解及其python實現
- 簡介
貝葉斯定理用Thomas Bayes的名字命名。早在18世紀,英國學者貝葉斯提出計算條件概率的公式用來解決如下問題:
假設B[1]、B[2]…B[n]互斥並且構成一個完備事件組,已知他們的概率P(B[i]),i=1,2,...,n,已知某一事件A與B相伴隨機出現,並且已知條件概率P(A|B[i])的概率,求條件概率p(B[i]|A)
貝葉斯定理具體形式為:
其中P(A|B)是在 B 發生的情況下 A 發生的可能性。P(B|A)是在A發生的情況下B發生的概率可能性。
P(A)是 A 的先驗概率,之所以稱為“先驗”是因為它不考慮任何 B 方面的因素。P(A|B)是已知 B 發生後 A 的條件概率,也由於得自 B 的取值而被稱作 A 的後驗概率。P(B|A)是已知 A 發生後 B 的條件概率,也由於得自 A 的取值而被稱作 B 的後驗概率。P(B)是 B 的先驗概率,也作標淮化常量(normalizing constant)。後驗概率 = (相似度 * 先驗概率)/標淮化常量,既是P(A|B) =P(AB)/P(B),比例P(B|A)/P(B)也有時被稱作標淮相似度(standardised likelihood),Bayes定理可表述為:後驗概率 = 標淮相似度 * 先驗概率
- 樸素貝葉斯
在資料探勘中,樸素貝葉斯是一個比較經典的演算法,給定訓練資料集A,類別B,其中A
的屬性列為Ai,i=1,2,…,n;B={B1,B2,…,Bn};貝葉斯演算法核心思想是給定待判定資料元組,根據訓練資料集進行分類預測,通過貝葉斯定理計算當前待判定元組屬於某一類別的概率,概率最大者即為該待判定元組的類別歸屬。
所謂的樸素貝葉斯是指在給定訓練集資料元組中,每一個屬性列之間是獨立的,相互之間互不影響,A1,A2,…,An之間互相獨立:
通過貝葉斯公式計算後驗概率:
此公式是表名給定元組資料A,其在A的條件下屬於類別Bj的概率,由於A的每一列之間相互獨立的,互不影響,因此
在求出之後,因為P(A)對每一個類別都相同,因此可通過對的數值進行比較,因此就有:得出概率最大的即為相應的預測類別。
在進行貝葉斯演算法應用時候由於資料量較大而且較為複雜,因此經常會遇見概率為0的值,那麼概率為0的值怎麼辦吶?有一個簡單的技巧來避免該問題,可以假定訓練資料庫D很大,以至於對每個計數加1造成的估計概率的變化忽略不計,但可以方便的避免概率值為0.這種概率估計技術成為拉普拉斯校準或者拉普拉斯估計法。對元組中每一列屬性的不重複的資料項計數加1,並且在分母計算上也加1,這樣可以有效地避免概率為0的問題。
同時在運用python進行計算概率時候,我們可以通過取對數進行相應的計算,因為資料量過大的話會造成某項概率過小,但是我們按照數學的方法,y=x是單調遞增,那麼y=lnx也
是單調遞增的這個思想,即x1>x2 那麼lnx1>lnx2;通過對數之間的公式運算,如
- 樸素貝葉斯的python實現
# -*- coding: utf-8 -*- from numpy import * from functools import reduce # 廣告、垃圾標識 adClass = 1 def loadDataSet(): """載入資料集合及其對應的分類""" wordsList = [['週六', '公司', '一起', '聚餐', '時間'], ['優惠', '返利', '打折', '優惠', '金融', '理財'], ['喜歡', '機器學習', '一起', '研究', '歡迎', '貝葉斯', '演算法', '公式'], ['公司', '發票', '稅點', '優惠', '增值稅', '打折'], ['北京', '今天', '霧霾', '不宜', '外出', '時間', '在家', '討論', '學習'], ['招聘', '兼職', '日薪', '保險', '返利']] # 1 是, 0 否 classVec = [0, 1, 0, 1, 0, 1] return wordsList, classVec # python中的& | 是位運算子 and or是邏輯運算子 當and的運算結果為true時候返回的並不是true而是運算結果最後一位變數的值 # 當and返回的結果是false時候,如果A AND B 返回的是第一個false的值,如果a為false 則返回a,如果a不是false,那麼返回b # 如果a or b 為true時候,返回的是第一個真的變數的值,如果a,b都為真時候那麼返回a 如果a為假b為真那麼返回b # a & b a和b為兩個set,返回結果取a和b的交集 a|b a和b為兩個set,返回結果為兩個集合的不重複並集 def doc2VecList(docList): # 從第一個和第二個集合開始進行並集操作,最後返回一個不重複的並集 a = list(reduce(lambda x, y: set(x) | set(y), docList)) return a def words2Vec(vecList, inputWords): """把單子轉化為詞向量""" # 轉化成以一維陣列 resultVec = [0] * len(vecList) for word in inputWords: if word in vecList: # 在單詞出現的位置上的計數加1 resultVec[vecList.index(word)] += 1 else: print('沒有發現此單詞') return array(resultVec) def trainNB(trainMatrix, trainClass): """計算,生成每個詞對於類別上的概率""" # 類別行數 numTrainClass = len(trainClass) # 列數 numWords = len(trainMatrix[0]) # 全部都初始化為1, 防止出現概率為0的情況出現 # 見於韓家煒的資料探勘概念與技術上的講解,避免出現概率為0的狀況,影響計算,因為在數量很大的情況下,在分子和分母同時+1的情況不會 # 影響主要的資料 p0Num = ones(numWords) p1Num = ones(numWords) # 相應的單詞初始化為2 # 為了分子分母同時都加上某個數λ p0Words = 2.0 p1Words = 2.0 # 統計每個分類的詞的總數 # 訓練資料集的行數作為遍歷的條件,從1開始 # 如果當前類別為1,那麼p1Num會加上當前單詞矩陣行資料,依次遍歷 # 如果當前類別為0,那麼p0Num會加上當前單詞矩陣行資料,依次遍歷 # 同時統計當前類別下單詞的個數和p1Words和p0Words for i in range(numTrainClass): if trainClass[i] == 1: # 陣列在對應的位置上相加 p1Num += trainMatrix[i] p1Words += sum(trainMatrix[i]) else: p0Num += trainMatrix[i] p0Words += sum(trainMatrix[i]) # 計算每種型別裡面, 每個單詞出現的概率 # 樸素貝葉斯分類中,y=x是單調遞增函式,y=ln(x)也是單調的遞增的 # 如果x1>x2 那麼ln(x1)>ln(x2) # 在計算過程中,由於概率的值較小,所以我們就取對數進行比較,根據對數的特性 # ln(MN) = ln(M)+ln(N) # ln(M/N) = ln(M)-ln(N) # ln(M**n)= nln(M) # 注:其中ln可替換為log的任意對數底 p0Vec = log(p0Num / p0Words) p1Vec = log(p1Num / p1Words) # 計算在類別中1出現的概率,0出現的概率可通過1-p得到 pClass1 = sum(trainClass) / float(numTrainClass) return p0Vec, p1Vec, pClass1 def classifyNB(testVec, p0Vec, p1Vec, pClass1): # 樸素貝葉斯分類, max(p0, p1)作為推斷的分類 # y=x 是單調遞增的, y=ln(x)也是單調遞增的。 , 如果x1 > x2, 那麼ln(x1) > ln(x2) # 因為概率的值太小了,所以我們可以取ln, 根據對數特性ln(ab) = lna + lnb, 可以簡化計算 # sum是numpy的函式,testVec是一個數組向量,p1Vec是一個1的概率向量,通過矩陣之間的乘機 # 獲得p(X1|Yj)*p(X2|Yj)*...*p(Xn|Yj)*p(Yj) # 其中pClass1即為p(Yj) # 此處計算出的p1是用對數表示,按照上面所說的,對數也是單調的,而貝葉斯分類主要是通過比較概率 # 出現的大小,不需要確切的概率資料,因此下述表述完全正確 p1 = sum(testVec * p1Vec) + log(pClass1) p0 = sum(testVec * p0Vec) + log(1 - pClass1) if p0 > p1: return 0 return 1 def printClass(words, testClass): if testClass == adClass: print(words, '推測為:廣告郵件') else: print(words, '推測為:正常郵件') def tNB(): # 從訓練資料集中提取出屬性矩陣和分類資料 docList, classVec = loadDataSet() # 生成包含所有單詞的list # 此處生成的單詞向量是不重複的 allWordsVec = doc2VecList(docList) # 構建詞向量矩陣 # 計算docList資料集中每一行每個單詞出現的次數,其中返回的trainMat是一個數組的陣列 trainMat = list(map(lambda x: words2Vec(allWordsVec, x), docList)) # 訓練計算每個詞在分類上的概率, p0V:每個單詞在非分類出現的概率, p1V:每個單詞在是分類出現的概率 # 其中概率是以ln進行計算的 # pClass1為類別中是1的概率 p0V, p1V, pClass1 = trainNB(trainMat, classVec) # 測試資料集 testWords = ['公司', '聚餐', '討論', '貝葉斯'] # 轉換成單詞向量,32個單詞構成的陣列,如果此單詞在陣列中,陣列的項值置1 testVec = words2Vec(allWordsVec, testWords) # 通過將單詞向量testVec代入,根據貝葉斯公式,比較各個類別的後驗概率,判斷當前資料的分類情況 testClass = classifyNB(testVec, p0V, p1V, pClass1) # 打印出測試結果 printClass(testWords, testClass) testWords = ['公司', '保險', '金融'] # 轉換成單詞向量,32個單詞構成的陣列,如果此單詞在陣列中,陣列的項值置1 testVec = words2Vec(allWordsVec, testWords) # 通過將單詞向量testVec代入,根據貝葉斯公式,比較各個類別的後驗概率,判斷當前資料的分類情況 testClass = classifyNB(testVec, p0V, p1V, pClass1) # 打印出測試結果 printClass(testWords, testClass) if __name__ == '__main__': tNB()