--樸素貝葉斯實戰(二)

  

一 前言

  上一篇文章介紹了樸素貝葉斯的基本原理, 現在就來實踐一下吧, 閱讀了部分<機器學習實戰>上的代碼, 自己也敲了一遍, 做了一下驗證, 現在就在這裏分享一下.
  環境:
  Ubuntu 16.04
  Python 3.5.2

  

二 使用樸素貝葉斯進行文檔分類


2.1 準備數據: 從文本中構建詞向量

   加載數據

'''
加載訓練數據, postingList是所有的訓練集, 每一個列表代表一條言論, 一共有8條言論
            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', 'hime'], 
                  ['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

看一下運行結果:
這裏寫圖片描述

  統計文檔中的單詞, 生成詞彙表, 詞彙表中每一個單詞只出現一次, 沒有重複的. (就是把文檔中的所有單詞放在一塊, 然後去重.)

'''
創建詞彙表, 就是把這個文檔中所有的單詞不重複的放在一個列表裏面
'''
def createVocabList(dataSet):
    vocabSet = set([])           # 新建一個set集合, 保證裏面的數據不重複
    for document in dataSet:     # 獲得每一個文檔
        vocabSet = vocabSet | set(document)   # 文檔去重之後和詞彙表求並集
    return list(vocabSet)                     # 詞彙錶轉換爲列表

這樣我們就生成了一個詞彙表. 看一下詞彙表:
這裏寫圖片描述

  文檔轉換爲詞向量, 對於每一個文檔, 我們都要把他轉換爲詞向量, 也就是由數字組成的一個向量, 此處的轉換很簡單. 上一步我們已經創建了一個詞彙表, 對於一個文檔, 首先我們生成一個和該文檔長度一致的全0列表returnVec, 然後遍歷該文當中的每一個單詞, 如果這個單詞在詞彙表中出現過, 就在returnVec中相應位置變爲1, 如果沒出現過, 就仍然保持爲0. 最後返回這個列表returnVec.

'''
vocabList是由createVocabList產生的詞彙表
inputSet是輸入新的文檔
'''
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)  # 生成一個全0列表, 個數爲輸入文檔的長度
    for word in inputSet:      # 遍歷輸入文檔中的每一個單詞
        if word in vocabList:  # 如果這個單詞在詞彙表中
            returnVec[vocabList.index(word)] = 1   # 置1
        else:                               # 否則依然爲0
            print("the word %s is not in my Vocabulary" % word) 
    return returnVec

看一下第一個文檔轉換爲詞向量後是什麼樣子:
這裏寫圖片描述
詞向量就是由0和1組成的數組.

2.2 計算先驗概率

  計算先驗概率, 接下來就是計算各種先驗概率了, 還記得甚麼是先驗概率嗎? 不記得的同學可以去前面文章裏面看看甚麼是先驗概率. P(x1|Y=ck),P(x2|Y=ck)...P(Y=ck) , 在此處我們使用了拉普拉斯平滑, 注意看代碼裏面的初始化.

  首先統計一共有多少個文檔, 然後統計詞向量的長度, 接着計算侮辱性文檔的先驗概率, 再初始化p0Num, p0Denom, p0Num就是一個array(numpy), 大小是詞向量的長度, 它用於記錄當前文檔的每一個單詞是否在詞向量中存在, 當然它是初始化爲全1, 也就是拉普拉斯平滑. 請看for循環, 遍歷每一個文檔, 首先判斷當前文檔的label是否爲侮辱性的, 是侮辱性的就執行p1, 不是就執行p0. 我們看
p1Num += trainMatrix[i] , 這句話是兩個array之間的相加, 也就是下圖這種情況:
這裏寫圖片描述
p1Denom += 1這個就是統計當前文檔中有多少是屬於侮辱性的, <機器學習實戰>上寫的是p1Denom += sum(trainMatrix[i]), 但是按照計算先驗概率的公式, 我認爲是加一即可. 最後p1Vect = log(p1Num / p1Denom), 取log是爲了防止多個小數相乘出現下溢. 這樣就計算出了每一個單詞的先驗概率, 以及每一個類別的先驗概率.

'''
計算先驗概率
trainMatrix: 詞向量矩陣
trainCategory: 每一個詞向量的類別
返回每一個單詞屬於侮辱性和非侮辱性詞彙的先驗概率, 以及訓練集包含侮辱性文檔的概率
'''
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)    # 由訓練集生成的詞向量矩陣
    numWords = len(trainMatrix[0])     # 每一個詞向量的長度
    pAbusive  = sum(trainCategory) / float(numTrainDocs)    # 計算侮辱性文檔的先驗概率
    p0Num = ones(numWords)          # 生成全1 array, 長度爲詞向量的長度, 用於統計每個單詞在整個矩陣中出現的次數(分子)
    p1Num = ones(numWords)
    p0Denom = 2.0                   # 初始化爲2(分母), 拉普拉斯平滑
    p1Denom = 2.0
    for i in range(numTrainDocs):   # 遍歷每一個詞向量
        if trainCategory[i] == 1:   # 如果該詞向量的類別爲1
            p1Num += trainMatrix[i] # 計算P(x0)..P(xn)
            p1Denom += 1            # 統計侮辱性文檔的個數
        else:
            p0Num += trainMatrix[i] # 計算P(x0)..P(xn)
            p0Denom += 1            # 統計非侮辱性文檔個數
    p0Vect = log(p0Num / p0Denom)   # 計算P(x0|0)P(xn|0)
    p1Vect = log(p1Num / p1Denom)   # 計算P(x0|1) P(x1|1) P(xn|1)   取對數是防止多個小數相乘出現下溢
    return p0Vect, p1Vect, pAbusive

  我們看一下計算結果:

p0V, p1V, pAb = trainNB0(trainMat, listClasses)
p0V, p1V, pAb

這裏寫圖片描述

  接下來就是將訓練集裏面的文檔轉換爲詞向量了. 代碼很簡單:

'''
製作詞向量矩陣
將每一個文檔轉換爲詞向量, 然後放入矩陣中
'''
trainMat = []
for postinDoc in listOPosts:
    trainMat.append(setOfWords2Vec(myVocabList, postinDoc))

2.3 製作分類器, 測試

  接下來就是根據上面計算出來的每一個單詞的先驗概率, 來預測一個未知文檔是否具有侮辱性了. 代碼如下:

'''
製作貝葉斯分類器
vec2Classify: 測試樣本的詞向量
p0Vec: P(x0|Y=0) P(x1|Y=0) P(xn|Y=0)
p1Vec: P(x0|Y=1) P(x1|Y=1) P(xn|Y=1)
pClass1: P(y)
# log(P(x1|1)*P(x2|1)*P(x3|1)P(1))=log(P(x1|1))+log(P(x2|1))+log(P(1))
'''
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)       
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0

註釋部分已經解釋了爲什麼要加上log(pClass1)了.
  檢驗效果的時候到了, 讀入一個文檔, 根據我們計算的先驗概率, 分別計算他屬於侮辱性文檔的概率和屬於非侮辱性文檔的概率, 比較兩個概率的大小, 大的那一類就是該文檔所屬於的類.

'''
測試貝葉斯分類器
'''
def testingNB():
    listOPosts, listClasses =  loadDataSet()   # 加載數據
    myVocabList = createVocabList(listOPosts)  # 詞彙表
    trainMat = []                          # 訓練集詞向量
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0V, p1V, pAb = trainNB0(trainMat, listClasses) # 計算先驗概率
    testEntry = ['love', 'my', 'dalmation']   # 測試文檔1
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as :', classifyNB(thisDoc, p0V, p1V, pAb))
    testEntry = ['stupid', 'garbage', 'stupid'] # 測試文檔2
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as : ', classifyNB(thisDoc, p0V, p1V, pAb))

  看一下結果吧,
這裏寫圖片描述

可以看出, 第一個屬於非侮辱性的文檔, 第二個屬於侮辱性的文檔. 因爲文檔中存在stupid這樣的單詞, 也就是罵人是傻子的意思.所以被判定爲侮辱性的.
  文章主要參考<機器學習實戰>和<統計學習方法>這兩本書, 自己也是一個初學者, 文中有紕漏或者不當的地方, 歡迎各位朋友指出來, 咱們共同進步. 謝謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章