1. 程式人生 > >寫程式學ML:樸素貝葉斯演算法原理及實現(二)

寫程式學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]
(未完待續)