機器學習樸素貝葉斯演算法
樸素貝葉斯屬於監督學習的生成模型,實現簡單,沒有迭代,學習效率高,在大樣本量下會有較好表現。但因為假設太強——特徵條件獨立,在輸入向量的特徵條件有關聯的場景下,並不適用。
樸素貝葉斯演算法:主要思路是通過聯合概率建模,運用貝葉斯定理求解後驗概率;將後驗概率最大者對應的類別作為預測類別。
首先定義訓練集,其類別。
輸入待預測的資料,預測類別:
由貝葉斯定理可知:
由於p(x)是恆等的,因此預測類別等價於:
最後,貝葉斯將分類問題轉化成了求條件概率與先驗概率的最大乘積問題。由於做了條件獨立性的假設,求解公式可以轉化為:
選取後驗概率醉的的類別作為預測類別,是因為後驗概率最大化,可以使得期望風險最小化。
極大似然估計:
在樸素貝葉斯學習中,需要估計先驗概率與條件概率,一般時採用極大似然估計。先驗概率的極大似然估計:
其中,是指示函式,滿足括號內條件時為1否則為0;可以看作為計數
設第 j 維特徵的取值空間為,且輸入變數的第 j 維
貝葉斯估計:
在估計先驗概率與條件概率時,有可能出現為0的情況,則計算得到的後驗概率亦為0,從而影響分類的效果。因此,需要在估計時做平滑,這種方法被稱為貝葉斯估計。先驗概率的貝葉斯估計:
後驗概率的貝葉斯估計為:
當的時候,被稱為拉普拉斯平滑。
以上就是樸素貝葉斯演算法的數學原理。接下來附上案例程式碼。(詳細過程見註釋)
import numpy as np from functools import reduce #建立資料樣本 #postingList的每一行,代表一個樣本資料 #classVec是每個樣本資料所對應的標籤,0是非侮辱類,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] return postingList,classVec #根據vocabList詞彙表,將inputSet向量化,向量的每個元素為1或0 #引數裡,vocabList就是createVocabList方法最後返回的元素不重複的陣列(詞彙表), #inputSet是取的原始資料集裡的某一行,比如:['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'] def setOfWords2Vec(vocabList,inputSet): returnVec = [0]*len(vocabList) #建立一個所有元素都是0的陣列,長度和vocabList一樣,returnVec=[0,0,0,0..........0,0,0] for word in inputSet: #將['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']裡的每個元素遍歷 if word in vocabList: #如果某個元素在詞彙表裡 returnVec[vocabList.index(word)] = 1 #那就找到該元素在詞彙表中的對應索引,並在returnVec裡的同樣索引位置賦值,值為1 # else: print("the word: %s is not in my Vocabulary!" % word) return returnVec # 該方法最終返回的結果如下: # [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0] # 其中"1"代表的索引位置,就是['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']裡每個元素在我的詞彙表vocabList裡的索引位置 #將切分的實驗樣本詞條整理成不重複的此條例列表,也就是詞彙表 #dataSet是最初的資料樣本,也就是loadDataSet方法裡的postingList資料集 def createVocabList(dataSet): vocabSet = set([]) #先建立一個空的字典(set建立字典,注意字典是不能有重複元素的) for document in dataSet: #將dataSet的每一行都進行遍歷 vocabSet = vocabSet | set(document) #set(document)表示取每行的不重複資料,每次迭代後vocabSet返回值類似於:['my','dog','flea',.....] print('詞彙表為:\n',vocabSet) #"" | "是並集符號,表示將每次迭代的不重複資料集都和之前的vocabSet進行合併 return list(vocabSet) #將所有行的不重複資料都進行合併之後,最後得到整個dataSet的不重複的資料集,也就是詞彙表 # 該方法最後返回的list(vocabSet)詞彙表l類似如下結構: # ['dog', 'help', 'ate', 'is', 'buying', 'him', 'mr', # 'maybe', 'park', 'quit', 'so', 'stupid', 'stop', 'steak', # 'licks', 'food', 'please', 'problems', 'cute', 'I', 'flea', # 'posting', 'love', 'how', 'dalmation', 'take', 'garbage', 'not', # 'my', 'to', 'has', 'worthless'] #分類器訓練函式 # 其中trainMatrix是setOfWords2Vec方法返回的returnVec所組成的矩陣,如下: # [[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,1,0,1,0], # [1,0,0,0,0,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0], # [0,0,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,1,0,1,0,0,0,1,0,0,0], # [0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1], # [0,0,1,0,0,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,0,0], # [1,0,0,0,1,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]] # trainCategory表示類別標籤向量,即loadDataSet方法裡的classVec,classVec=[0,1,0,1,0,1] #該方法最終將出現錯誤 def trainNB0(trainMatrix,trainCategory): numTrainDocs = len(trainMatrix) #返回文件數目,numTrainDocs = 6 numWords = len(trainMatrix[0]) #計算詞彙表裡的詞條數,numWords = 32 pAbusive = sum(trainCategory)/float(numTrainDocs) #計算侮辱類文件佔總文件的概率(比例)pAbusive = 3/6 = 0.5 p0Num = np.zeros(numWords);p1Num = np.zeros(numWords) #建立兩個numpy陣列,每個元素都是0,長度為32 p0Denom = 0.0; p1Denom = 0.0 #初始化兩個數,這兩個數是之後算條件概率的分母,p0Denom是非侮辱性相對應的條件概率的分母,p1Denom則是侮辱性的分母 for i in range(numTrainDocs): #對類別標籤進行遍歷,也就是對[0,1,0,1,0,1]的每個元素進行遍歷,0代表非侮辱,1代表侮辱 if trainCategory[i]==1: #如果是侮辱的 p1Num += trainMatrix[i] #那就把trainMatrix矩陣中對應的那一行的標籤累加到p1Num上, p1Denom += sum(trainMatrix[i]) #然後,將所有詞出現的次數累加到p1Denom上 #最後p1Num得到的結果是: # p1Num = [2. 0. 0. 0. 1. 1. 0. 1. 1. 1. 0. 3. 1. 0. 0. 1. 0. 0. 0. 0. 0. 1. 0. 0. 0. 1. 1. 1. 0. 1. 0. 2.] # 該陣列的意思:在整個詞條中,第一個詞出現的頻次為2,第二個詞頻次為0,以此類推 #p1Denom=19,意思是侮辱性文件中所有詞彙出現的總次數,也就是p1Num中所有元素相加,p1Denom = 19 else: #如果是非侮辱的,同上所述,將得到非侮辱性文件的結果, p0Num += trainMatrix[i] #p0Num = [1. 1. 1. 1. 0. 2. 1. 0. 0. 0. 1. 0. 1. 1. 1. 0. 1. 1. 1. 1. 1. 0. 1. 1. 1. 0. 0. 0. 3. 1. 1. 0.] p0Denom += sum(trainMatrix[i]) #p0Denom = 24 p1Vect = p1Num/p1Denom #最後需要分別計算在侮辱類和非侮辱類中,每個詞出現的概率, p0Vect = p0Num/p0Denom #比如對於侮辱類,第一個詞出現兩次,除以所有侮辱類的次數19.得到2/19=0.10526316 return p0Vect,p1Vect,pAbusive #p0Vect是非侮辱類每個詞出現的概率,p1Vect是侮辱類每個詞出現的概率,pAbusive是侮辱類文件佔總文件的比例 #該方法最後返回的結果將是: # p1Vect = [0.10526316 0. 0. 0. 0.05263158 0.05263158 # 0. 0.05263158 0.05263158 0.05263158 0. 0.15789474 # 0.05263158 0. 0. 0.05263158 0. 0. # 0. 0. 0. 0.05263158 0. 0. # 0. 0.05263158 0.05263158 0.05263158 0. 0.05263158 # 0. 0.10526316] # p0Vect = [0.04166667 0.04166667 0.04166667 0.04166667 0. 0.08333333 # 0.04166667 0. 0. 0. 0.04166667 0. # 0.04166667 0.04166667 0.04166667 0. 0.04166667 0.04166667 # 0.04166667 0.04166667 0.04166667 0. 0.04166667 0.04166667 # 0.04166667 0. 0. 0. 0.125 0.04166667 # 0.04166667 0. ] # pAbusive = 3/6 = 0.5 #修正後的貝葉斯分類器訓練函式 def trainNB1(trainMatrix,trainCategory): numTrainDocs = len(trainMatrix) #計算訓練的文件數目 numWords = len(trainMatrix[0]) #計算每篇文件的詞條數 pAbusive = sum(trainCategory)/float(numTrainDocs) #文件屬於侮辱類的概率 p0Num = np.ones(numWords); p1Num = np.ones(numWords) #建立numpy.ones陣列,詞條出現數初始化為1,拉普拉斯平滑 p0Denom = 2.0; p1Denom = 2.0 #分母初始化為2,拉普拉斯平滑 for i in range(numTrainDocs): if trainCategory[i] == 1: p1Num += trainMatrix[i] #p1Num=[1. 1. 2. 2. 1. 2. 2. 1. 1. 3. 1. 2. 1. 1. 1. 1. 3. 2. 2. 1. 1. 1. 2. 2. 1. 2. 2. 1. 1. 4. 1. 2.] p1Denom += sum(trainMatrix[i]) #p1Denom = 21 else: p0Num += trainMatrix[i] #p0Num=[2. 2. 1. 1. 2. 1. 2. 2. 2. 1. 2. 1. 2. 2. 2. 2. 2. 1. 1. 2. 2. 2. 1. 2. 2. 1. 3. 4. 2. 1. 2. 1.] p0Denom += sum(trainMatrix[i]) #p0Denom = 26 p1Vect = np.log(p1Num/p1Denom) #取對數,防止下溢位 print('p1Vect:\n',p1Vect) p0Vect = np.log(p0Num/p0Denom) print('p0Vect:\n',p0Vect) return p0Vect,p1Vect,pAbusive #該函式最後返回的p0Vect,p1Vect結果是:(相比之前的訓練器,將初始陣列的元素0改成1,分母初始值由0改為2,然後概率取對數,就得到以下結果 # 元素0改為1,屬於拉普拉斯平滑,概率取對數是防止下溢位) # p0Vect = [-2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.56494936 -3.25809654 # -2.56494936 -2.56494936 -2.56494936 -3.25809654 -2.56494936 -3.25809654 # -2.56494936 -2.56494936 -2.56494936 -2.56494936 -2.56494936 -3.25809654 # -3.25809654 -2.56494936 -2.56494936 -2.56494936 -3.25809654 -2.56494936 # -2.56494936 -3.25809654 -2.15948425 -1.87180218 -2.56494936 -3.25809654 # -2.56494936 -3.25809654] # p0Vect = [-3.04452244 -3.04452244 -2.35137526 -2.35137526 -3.04452244 -2.35137526 # -2.35137526 -3.04452244 -3.04452244 -1.94591015 -3.04452244 -2.35137526 # -3.04452244 -3.04452244 -3.04452244 -3.04452244 -1.94591015 -2.35137526 # -2.35137526 -3.04452244 -3.04452244 -3.04452244 -2.35137526 -2.35137526 # -3.04452244 -2.35137526 -2.35137526 -3.04452244 -3.04452244 -1.65822808 # -3.04452244 -2.35137526] #樸素貝葉斯分類器分類函式 #vec2Classify是待測試的詞條 #p0Vec是非侮辱類的條件概率陣列 #p1Vec是侮辱類的條件概率陣列 #pClass1是文件屬於侮辱類的概率 def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1): p1 = sum(vec2Classify * p1Vec) + np.log(pClass1) #計算該文件屬於侮辱類的概率 p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1) #計算該文件屬於非侮辱類的概率 print('屬於p0的概率取對數:',p0) #注意上面兩行的計算公式省略了貝葉斯公式裡的分母 print('屬於p1的概率取對數:',p1) #因為不管是計算p0還是p1,分母都是一樣的,而我們要比較概率的大小,只需要比較分子即可 if p1 > p0: return 1 #如果p1大於p0,說明屬於侮辱類的可能性更大,返回1,(1代表侮辱性) else: return 0 #反之,則返回0(代表非侮辱性) def testingNB(): listOPosts,listClasses = loadDataSet() #建立實驗樣本 myVocabList = createVocabList(listOPosts) #建立詞彙表 trainMat=[] for postinDoc in listOPosts: trainMat.append(setOfWords2Vec(myVocabList, postinDoc)) #將實驗樣本向量化 p0V,p1V,pAb = trainNB1(np.array(trainMat),np.array(listClasses)) #訓練樸素貝葉斯分類器 testEntry = ['love', 'my', 'dalmation'] #測試樣本1 thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) #測試樣本向量化 if classifyNB(thisDoc,p0V,p1V,pAb): print(testEntry,'屬於侮辱類') #執行分類並列印分類結果 else: print(testEntry,'屬於非侮辱類') #執行分類並列印分類結果 testEntry = ['stupid', 'garbage'] #測試樣本2 thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) #測試樣本向量化 if classifyNB(thisDoc,p0V,p1V,pAb): print(testEntry,'屬於侮辱類') #執行分類並列印分類結果 else: print(testEntry,'屬於非侮辱類') if __name__ == '__main__': testingNB()
最後列印資訊,並分類得出的結果是:
詞彙表為:
{'to', 'problems', 'help', 'buying', 'park', 'dalmation', 'how', 'I', 'is', 'posting', 'stupid', 'ate', 'mr', 'has', 'stop', 'take', 'quit', 'him', 'garbage', 'my', 'steak', 'not', 'cute', 'maybe', 'flea', 'love', 'dog', 'worthless', 'food', 'so', 'licks', 'please'}
p1Vect:
[-2.35137526 -3.04452244 -3.04452244 -2.35137526 -2.35137526 -3.04452244
-3.04452244 -3.04452244 -3.04452244 -2.35137526 -1.65822808 -3.04452244
-3.04452244 -3.04452244 -2.35137526 -2.35137526 -2.35137526 -2.35137526
-2.35137526 -3.04452244 -3.04452244 -2.35137526 -3.04452244 -2.35137526
-3.04452244 -3.04452244 -1.94591015 -1.94591015 -2.35137526 -3.04452244
-3.04452244 -3.04452244]
p0Vect:
[-2.56494936 -2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.56494936
-2.56494936 -2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.56494936
-2.56494936 -2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.15948425
-3.25809654 -1.87180218 -2.56494936 -3.25809654 -2.56494936 -3.25809654
-2.56494936 -2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.56494936
-2.56494936 -2.56494936]
屬於p0的概率取對數: -7.694848072384611
屬於p1的概率取對數: -9.826714493730215
['love', 'my', 'dalmation'] 屬於非侮辱類
屬於p0的概率取對數: -7.20934025660291
屬於p1的概率取對數: -4.702750514326955
['stupid', 'garbage'] 屬於侮辱類