寫程式學ML:樸素貝葉斯演算法原理及實現(二)
[題外話]近期申請了一個微信公眾號:平凡程式人生。有興趣的朋友可以關注,那裡將會涉及更多更新機器學習、OpenCL+OpenCV以及影象處理方面的文章。
2、樸素貝葉斯演算法的實現
2.1 樸素貝葉斯演算法的實現
按照樸素貝葉斯演算法的原理,我們需要實現一個樸素貝葉斯分類器。首先,需要使用文字樣例對貝葉斯分類器進行訓練。可以按照下面的流程進行處理:
1、 定義函式:deftrainNB0(trainMatrix, trainCategory)來實現貝葉斯分類器相關資料的訓練。
2、 收入引數:trainMatrix:儲存每個文件樣本中各個詞彙在詞彙表裡出現情況的資料,與樣本數目相同。trainCategory: 儲存每個文件樣本所屬類別的標籤,即分類資訊。當前我們分為類別1和類別0兩大類。類別1在trainCategory中用1表示,類別0用0表示。
3、 獲取trainMatrix中樣本個數、詞彙表的長度以及樣本中類別1所佔的比例。
4、 利用貝葉斯分類器對文件進行分類時,要計算多個概率的乘積以獲得文件屬於某個類別的概率,即計算p(w0|1)p(w1|1)p(w2|1)。如果其中一個概率值為0,那麼最後的乘積也為0。為降低這種影響,將所有詞的出現數初始化為1,並將分母初始化為2。
5、 對trainMatrix中的每個樣本資料進行處理,如果該樣本為類別1,將樣本中對應詞彙出現的次數累加起來,同時統計該類別詞彙出現的總數;如果是類別0,也做類似的處理。
6、 對類別1和類別0中,將各個詞彙出現概率計算出來,並以對數方式計算。採用對數方式計算是為了避免計算下溢。當計算乘積p(w0|ci)p(w1|ci)p(w2|ci)...p(wN|ci)時,由於大部分因子都非常小,所以程式會下溢位或者得到不正確的答案。一種解決辦法是對乘積取自然對數。在代數中有ln(a*b)=ln(a)*ln(b),於是通過求對數可以避免下溢位或者浮點數舍入導致的錯誤。同時,採用自然對數進行處理不會有任何損失。
7、 最後返回類別1和類別0的樣本中各個詞彙出現的概率,及樣本集中類別1的概率。
具體程式碼實現如下:
#trainMatrix: 儲存每個文件樣本在詞彙表中各個詞彙出現情況的集合,與樣本數目相同 #這是一個二維陣列,第一維對應各個文件樣本id,第二維對應該樣本中詞彙在詞彙表中出現情況 #trainCategory: 儲存每個文件樣本所屬類別的標籤,即分類資訊 #獲取侮辱性和非侮辱性樣本中各個詞彙出現的概率,及樣本集的侮辱性概率 def trainNB0(trainMatrix, trainCategory): numTrainDocs = len(trainMatrix) #獲取文件樣本個數 numWords = len(trainMatrix[0]) #獲取詞彙表的長度 pAbusive = sum(trainCategory) / float(numTrainDocs) #獲取所有樣本的分類概率 #利用貝葉斯分類器對文件進行分類時,要計算多個概率的乘積以獲得文件屬於某個類別的概率, #即計算p(w0|1)p(w1|1)p(w2|1)。如果其中一個概率值為0,那麼最後的乘積也為0。 #為降低這種影響,將所有詞的出現數初始化為1,並將分母初始化為2。 #p0Num = zeros(numWords) #建立有numWords個元素的陣列,且每個元素初始化為0 #p1Num = zeros(numWords) #建立有numWords個元素的陣列,且每個元素初始化為0 #p0Denom = 0.0 #儲存所有樣本中非侮辱性詞彙出現次數總和 #p1Denom = 0.0 #儲存所有樣本中侮辱性詞彙出現次數總和 p0Num = ones(numWords) #建立有numWords個元素的陣列,且每個元素初始化為1 p1Num = ones(numWords) #建立有numWords個元素的陣列,且每個元素初始化為1 p0Denom = 2.0 #儲存所有樣本中非侮辱性詞彙出現次數總和 p1Denom = 2.0 #儲存所有樣本中侮辱性詞彙出現次數總和 for i in range(numTrainDocs): #逐個樣本進行迴圈處理 if trainCategory[i] == 1: #如果當前樣本的分類結果為1,即abusive,則對p1xx進行調整 #p1Num和trainMatrix[i]為含相同數目元素的list,此處的+是對應位置上元素的相加 p1Num += trainMatrix[i] #sum(trainMatrix[i])求得第i個樣本中所有侮辱性詞彙出現的個數 p1Denom += sum(trainMatrix[i]) else: p0Num += trainMatrix[i] p0Denom += sum(trainMatrix[i]) #當計算乘積p(w0|ci)p(w1|ci)p(w2|ci)...p(wN|ci)時,由於大部分因子都非常小,所以程式會下溢位或者得到不正確的答案。 #一種解決辦法是對乘積取自然對數。在代數中有ln(a*b)=ln(a)*ln(b),於是通過求對數可以避免下溢位或者浮點數舍入導致的錯誤。 #同時,採用自然對數進行處理不會有任何損失。 #對list變數p1Num中每個元素都除以p1Denom,求得詞彙表中每個詞彙在所有侮辱性樣本中出現的概率 #p1Vect = p1Num / p1Denom p1Vect = log(p1Num / p1Denom) #對list變數p0Num中每個元素都除以p0Denom,求得詞彙表中每個詞彙在所有非侮辱性樣本中出現的概率 #p0Vect = p0Num / p0Denom p0Vect = log(p0Num / p0Denom) return p0Vect, p1Vect, pAbusive
為了產生函式trainNB0()的輸入引數,還實現瞭如下幾個關鍵的函式。
函式createVocabList()
該函式定義為:def createVocabList(dataSet)
該函式用來建立詞彙表,即文件樣本集dataSet中所有不重複的單詞。
具體程式碼實現如下:
#dataSet:文件樣本集
#建立dataSet包含的所有文件中出現的不重複詞的列表
def createVocabList(dataSet):
vocabSet = set([]) #建立一個空集合變數
for document in dataSet:
#set(document)獲得document中所有不重複的詞
#vocabSet | set(document)求集合的並集,返回不重複的詞集合
#在數學符號表示上,按位或操作與集合求並操作使用相同記號"|"
vocabSet = vocabSet | set(document)
return list(vocabSet) #將集合轉換為列表並返回
函式setOfWords2Vec()
函式定義為:def setOfWords2Vec(vocabList, inputSet)
該函式用來獲取測試樣本inputSet中單詞在詞彙表中是否出現的列表,如果出現記錄為1,否則為0。
具體程式碼實現如下:
#vocabList: 詞彙表
#inputSet: 測試樣本文件
#獲取測試樣本中單詞在詞彙表中是否出現的列表
def setOfWords2Vec(vocabList, inputSet):
#定義一個列表變數,其長度與vocabList一樣,其內容初始化為0
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList: #判斷測試文件中的單詞是否存在於詞彙表
#index() 函式用於從列表中找出某個值第一個匹配項的索引位置
#尋找當前單詞在詞彙表vocabList中的位置,在列表returnVec對應位置寫1,表示該單詞存在於詞彙表
returnVec[vocabList.index(word)] = 1
else:
print "the word: %s is not in my Vocabulary!" % word
return returnVec
函式bagOfWords2Vec()
函式定義為:def bagOfWords2Vec(vocabList, inputSet)
該函式用來獲取測試樣本inputSet中單詞在詞彙表中出現次數的列表。如果出現記錄出現的次數,否則為0。
具體程式碼實現如下:
#vocabList: 詞彙表
#inputSet: 測試樣本文件
#獲取表示測試樣本中單詞在詞彙表中出現次數的列表
def bagOfWords2Vec(vocabList, inputSet):
#定義一個列表變數,其長度與vocabList一樣,其內容初始化為0
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList: #判斷測試文件中的單詞是否存在於詞彙表
#index() 函式用於從列表中找出某個值第一個匹配項的索引位置
#尋找當前單詞在詞彙表vocabList中的位置,在列表returnVec對應位置累加1,記錄該單詞在詞彙表中出現的次數
returnVec[vocabList.index(word)] += 1
else:
print "the word: %s is not in my Vocabulary!" % word
return returnVec
函式classifyNB()
函式定義為:def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1)
該函式用來判斷輸入樣本vec2Classify是否為類別1。
具體程式碼實現如下:
#vec2Classify:測試樣本中單詞在詞彙表中出現情況資訊
#p0Vec:非侮辱性詞彙出現的概率
#p1Vec: 侮辱性詞彙出現的概率
#pClass1: 訓練樣本中侮辱性樣本的概率
#判斷輸入樣本資訊是否為侮辱性樣本
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
#sum(vec2Classify * p1Vec): 求兩個向量對應元素乘積之和,計算測試樣本中侮辱性詞彙的概率
p1 = sum(vec2Classify * p1Vec) + log(pClass1)
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0: #比較侮辱性概率和非侮辱性概率,p1大,則為侮辱性樣本;反之,為非侮辱性樣本
return 1
else:
return 0
函式textParse ()
函式定義為:deftextParse(bigString)
該函式用來解析英文句子bigString,將其中長度大於2單詞抽取出來,並將其全部轉換為小寫。
具體程式碼實現如下:
#bigString:英文句子
#解析英文句子,將其中長度大於2單詞抽取出來,並將其全部轉換為小寫
def textParse(bigString):
#分隔bigString中的單詞,分隔符為除單詞、數字外的任意字串
listOfTokens = re.split(r'\W*', bigString)
#選出長度大於2的單詞變為小寫
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
(未完待續)