前言
本文介紹機器學習分類演算法中的樸素貝葉斯分類演算法並給出虛擬碼,Python程式碼實現。
詞向量
樸素貝葉斯分類演算法常常用於文件的分類,而且實踐證明效果挺不錯的。
在說明原理之前,先介紹一個叫詞向量的概念。 --- 它一般是一個布林型別的集合,該集合中每個元素都表示其對應的單詞是否在文件中出現。
比如說,詞彙表只有三個單詞:'apple', 'orange', 'melo',某文件中,apple和melo出現過,那麼其對應的詞向量就是 {1, 0, 1}。
這種模型通常稱為詞集模型,如果詞向量元素是整數型別,每個元素表示相應單詞在文件中出現的次數(0表示不出現),那這種模型就叫做詞袋模型。
如下部分程式碼可用於由文件構建詞向量以及測試結果:
1 #==================================== 2 # 輸入: 3 # 空 4 # 輸出: 5 # postingList: 文件列表 6 # classVec: 分類標籤列表 7 #==================================== 8 def loadDataSet(): 9 '建立測試資料' 10 11 # 這組資料是從斑點狗論壇獲取的 12 postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], 13 ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'], 14 ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'], 15 ['stop', 'posting', 'stupid', 'worthless', 'garbage'], 16 ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'], 17 ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']] 18 19 # 1 表示帶敏感詞彙 20 classVec = [0,1,0,1,0,1] 21 22 return postingList,classVec 23 24 #==================================== 25 # 輸入: 26 # dataSet: 文件列表 27 # 輸出: 28 # list(vocabSet): 詞彙表 29 #==================================== 30 def createVocabList(dataSet): 31 '建立詞彙表' 32 33 vocabSet = set([]) 34 for document in dataSet: # 遍歷文件列表 35 # 首先將當前文件的單詞唯一化,然後以交集的方式加入到儲存詞彙的集合中。 36 vocabSet = vocabSet | set(document) 37 38 return list(vocabSet) 39 40 #==================================== 41 # 輸入: 42 # vocabList: 詞彙表 43 # inputSet: 待轉換文件 44 # 輸出: 45 # returnVec: 轉換結果 - 詞向量 46 #==================================== 47 def setOfWords2Vec(vocabList, inputSet): 48 '將文件轉換為詞向量' 49 50 returnVec = [0]*len(vocabList) 51 for word in inputSet: 52 if word in vocabList: 53 returnVec[vocabList.index(word)] = 1 54 else: print "單詞: %s不在詞彙表當中" % word 55 return returnVec 56 57 def test(): 58 '測試' 59 60 listOPosts, listClasses = loadDataSet() 61 myVocabList = createVocabList(listOPosts) 62 print setOfWords2Vec(myVocabList, listOPosts[0])
測試結果:
演算法原理
不論是用於訓練還是分類的文件,首先一致處理為詞向量。
通過貝葉斯演算法對資料集進行訓練,從而統計出所有詞向量各種分類的概率。
對於待分類的文件,在轉換為詞向量之後,從訓練集中取得該詞向量為各種分類的概率,概率最大的分類就是所求分類結果。
訓練演算法剖析:如何計算某個詞向量的概率
由貝葉斯準則可知,某詞向量X為分類 Ci 的概率可用如下公式來進行計算:
p(ci)表示該文件為分類ci的概率;p(w)為該文件對應詞向量為w的概率;這兩個量是很好求的,這裡不多解釋。關鍵要解決的是 p(w|ci),也即在文件為分類 ci 的條件下,詞向量為w的概率。
這裡就要談到為什麼本文講解的演算法名為 "樸素" 貝葉斯。所謂樸素,就是整個形式化過程只做最原始假設。也就是說,假設不同的特徵是相互獨立的。但這和現實世界不一致,也導致了其他各種形形色色的貝葉斯演算法。
在這樣的假設前提下: p(w|ci) = p(w0|ci) * p(w1|ci) * p(w2|ci) * .... * p(wn|ci)。
而前面提到了w是指詞向量,這裡wn的含義就是詞向量中的某個單詞。
可使用如下虛擬碼計算條件概率 p(wn|ci):
1 對每篇訓練文件:
2 對每個類別:
3 增加該單詞計數值
4 增加所有單詞計數值
5 對每個類別:
6 對每個單詞:
7 將該單詞的數目除以單詞總數得到條件概率
8 返回所有單詞在各個類別下的條件概率
請注意如下的具體程式碼中,對應上述虛擬碼的第2行,第8行的部分都採用了向量來計算:
1 #============================================= 2 # 輸入: 3 # trainMatrix: 文件矩陣 4 # trainCategory: 分類標籤集 5 # 輸出: 6 # p0Vect: 各單詞在分類0的條件下出現的概率 7 # p1Vect: 各單詞在分類1的條件下出現的概率 8 # pAbusive: 文件屬於分類1的概率 9 #============================================= 10 def trainNB0(trainMatrix,trainCategory): 11 '樸素貝葉斯分類演算法' 12 13 # 文件個數 14 numTrainDocs = len(trainMatrix) 15 # 文件詞數 16 numWords = len(trainMatrix[0]) 17 # 文件屬於分類1的概率 18 pAbusive = sum(trainCategory)/float(numTrainDocs) 19 # 屬於分類0的詞向量求和 20 p0Num = numpy.zeros(numWords); 21 # 屬於分類1的詞向量求和 22 p1Num = numpy.zeros(numWords) 23 24 # 分類 0/1 的所有文件內的所有單詞數統計 25 p0Denom = .0; p1Denom = .0 26 for i in range(numTrainDocs): # 遍歷各文件 27 28 # 若文件屬於分類1 29 if trainCategory[i] == 1: 30 # 詞向量累加 31 p1Num += trainMatrix[i] 32 # 分類1文件單詞數累加 33 p1Denom += sum(trainMatrix[i]) 34 35 # 若文件屬於分類0 36 else: 37 # 詞向量累加 38 p0Num += trainMatrix[i] 39 # 分類0文件單詞數累加 40 p0Denom += sum(trainMatrix[i]) 41 42 p1Vect = p1Num/p1Denom 43 p0Vect = p0Num/p0Denom 44 45 return p0Vect,p1Vect,pAbusive 46 47 def test(): 48 '測試' 49 50 listOPosts, listClasses = loadDataSet() 51 myVocabList = createVocabList(listOPosts) 52 53 # 建立文件矩陣 54 trainMat = [] 55 for postinDoc in listOPosts: 56 trainMat.append(setOfWords2Vec(myVocabList, postinDoc)) 57 58 # 對文件矩陣進行樸素貝葉斯分類並返回各單詞在各分類條件下的概率及文件為類別1的概率 59 p0V, p1V, pAb = trainNB0(trainMat, listClasses) 60 61 print p0V
測試結果:
樸素貝葉斯分類演算法的完整實現
對於此公式:
上一步做的工作僅僅是將各個分量求出來了(p(w)為1),而沒有進行p(w0|ci) * p(w1|ci) * p(w2|ci) * .... * p(wn|ci)的累乘,也沒有進行概率大小的比較。
剩下的工作看似簡單但在具體實現上也涉及到兩個問題。
問題一:p(wn|ci) 中有一個為0,導致整個累乘結果也為0。這是錯誤的結論。
解決方法:將所有詞的出現次數初始化為1,並將分母初始化為2。
問題二:即使 p(wn|ci) 不為0了,可是它的值也許會很小,這樣會導致浮點數值型別的下溢位等精度問題錯誤。
解決方法:用 p(wn|ci) 的對數進行計算。
具體實現請參考下面程式碼。針對這兩個問題,它對上一步的函式做了一點修改:
特別說明:在下面的程式碼實現中,w只包含在待分類文件中出現了的單詞的特徵位。
1 #============================================= 2 # 輸入: 3 # trainMatrix: 文件矩陣 4 # trainCategory: 分類標籤集 5 # 輸出: 6 # p0Vect: 各單詞在分類0的條件下出現的概率 7 # p1Vect: 各單詞在分類1的條件下出現的概率 8 # pAbusive: 文件屬於分類1的概率 9 #============================================= 10 def trainNB0(trainMatrix,trainCategory): 11 '樸素貝葉斯分類演算法' 12 13 # 文件個數 14 numTrainDocs = len(trainMatrix) 15 # 文件詞數 16 numWords = len(trainMatrix[0]) 17 # 文件屬於分類1的概率 18 pAbusive = sum(trainCategory)/float(numTrainDocs) 19 # 屬於分類0的詞向量求和 20 p0Num = numpy.ones(numWords); 21 # 屬於分類1的詞向量求和 22 p1Num = numpy.ones(numWords) 23 24 # 分類 0/1 的所有文件內的所有單詞數統計 25 p0Denom = 2.0; p1Denom = 2.0 26 for i in range(numTrainDocs): # 遍歷各文件 27 28 # 若文件屬於分類1 29 if trainCategory[i] == 1: 30 # 詞向量累加 31 p1Num += trainMatrix[i] 32 # 分類1文件單詞數累加 33 p1Denom += sum(trainMatrix[i]) 34 35 # 若文件屬於分類0 36 else: 37 # 詞向量累加 38 p0Num += trainMatrix[i] 39 # 分類0文件單詞數累加 40 p0Denom += sum(trainMatrix[i]) 41 42 p1Vect = numpy.log(p1Num/p1Denom) 43 p0Vect = numpy.log(p0Num/p0Denom) 44 45 return p0Vect,p1Vect,pAbusive
完善公式的實現,並編寫測試程式碼:
1 #============================================= 2 # 輸入: 3 # vec2Classify: 目標物件的詞向量的陣列形式 4 # p0Vect: 各單詞在分類0的條件下出現的概率 5 # p1Vect: 各單詞在分類1的條件下出現的概率 6 # pClass1: 文件屬於分類1的概率 7 # 輸出: 8 # 分類結果 0/1 9 #============================================= 10 def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): 11 '完成貝葉斯公式剩餘部分得到最終分類概率' 12 13 # 為分類1的概率 14 p1 = sum(vec2Classify * p1Vec) + numpy.log(pClass1) 15 # 為分類0的概率 16 p0 = sum(vec2Classify * p0Vec) + numpy.log(1.0 - pClass1) 17 if p1 > p0: 18 return 1 19 else: 20 return 0 21 22 def test(): 23 '測試' 24 25 listOPosts,listClasses = loadDataSet() 26 myVocabList = createVocabList(listOPosts) 27 28 # 建立文件矩陣 29 trainMat=[] 30 for postinDoc in listOPosts: 31 trainMat.append(setOfWords2Vec(myVocabList, postinDoc)) 32 33 # 對文件矩陣進行樸素貝葉斯分類並返回各單詞在各分類條件下的概率及文件為類別1的概率 34 p0V,p1V,pAb = trainNB0(numpy.array(trainMat),numpy.array(listClasses)) 35 36 # 測試一 37 testEntry = ['love', 'my', 'dalmation'] 38 thisDoc = numpy.array(setOfWords2Vec(myVocabList, testEntry)) 39 print testEntry,'分類結果: ',classifyNB(thisDoc,p0V,p1V,pAb) 40 41 # 測試二 42 testEntry = ['stupid', 'garbage'] 43 thisDoc = numpy.array(setOfWords2Vec(myVocabList, testEntry)) 44 print testEntry,'分類結果: ',classifyNB(thisDoc,p0V,p1V,pAb)
測試結果:
小結
1. 為突出重點,本文示例僅採用兩個分類做測試。更多分類是同理的。
2. 程式設計中應儘量以矩陣或者向量為單位來處理資料。這樣能簡化程式碼,增加效率,也能提高程式可讀性。
3. 該分類演算法的優點是對於資料集較少的情況也能適用,能處理多類別問題;然而不足之處在於對資料集的準備方式比較敏感。