天天看點

實作 | 樸素貝葉斯模型算法研究與執行個體分析

實作 | 樸素貝葉斯模型算法研究與執行個體分析

(

白甯超

  2018年9月4日10:28:49)

導讀:樸素貝葉斯模型是機器學習常用的模型算法之一,其在文本分類方面簡單易行,且取得不錯的分類效果。是以很受歡迎,對于樸素貝葉斯的學習,本文首先介紹理論知識即樸素貝葉斯相關概念和公式推導,為了加深了解,采用一個維基百科上面性别分類例子進行形式化描述。然後通過程式設計實作樸素貝葉斯分類算法,并在屏蔽社群言論、垃圾郵件、個人廣告中擷取區域傾向等幾個方面進行應用,包括建立資料集、資料預處理、詞集模型和詞袋模型、樸素貝葉斯模型訓練和優化等。然後結合複旦大學新聞語料進行樸素貝葉斯的應用。最後,大家熟悉其原理和實作之後,采用機器學習sklearn包進行實作和優化。由于篇幅較長,采用理論了解、案例實作、sklearn優化三個部分進行學習。(本文原創,轉載必須注明出處:樸素貝葉斯模型算法研究與執行個體分析)

案例場景1: 屏蔽社群留言闆的侮辱性言論

項目概述

建構一個快速過濾器來屏蔽線上社群留言闆上的侮辱性言論。如果某條留言使用了負面或者侮辱性的語言,那麼就将該留言辨別為内容不當。對此問題建立兩個類别: 侮辱類和非侮辱類,使用 1 和 0 分别表示。

本案例開發流程如下:

  1. 收集資料: 可以是文本資料、資料庫資料、網絡爬取的資料、自定義資料等等
  2. 資料預處理: 對采集資料進行格式化處理,文本資料的格式一緻化,網絡資料的分析抽取等,包括中文分詞、停用詞處理、詞袋模型、建構詞向量等。
  3. 分析資料: 檢查詞條確定解析的正确性,根據特征進行模型選擇、特征抽取等。
  4. 訓練算法: 從詞向量計算機率
  5. 測試算法: 根據現實情況修改分類器
  6. 使用算法: 對社群留言闆言論進行分類

收集資料

本案例我們采用自定義的資料集,我們選擇6條社群評論,然後進行資料處理後以list形式存儲在文檔清單postingList中。其中每個詞代表一個特征。将每條評論進行分類(即1代表侮辱性文字,0代表非侮辱文字)存在在類别清單classVec中。最後傳回資料集和類标簽。代碼實作如下:

'''建立資料集:單詞清單postingList, 所屬類别classVec'''
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]  # 1代表侮辱性文字,0代表非侮辱文字
    return postingList, classVec
           

代碼分析:postingList清單存儲6條評論資訊,classVec清單存儲每條資訊類别(1代表侮辱性文字,0代表非侮辱文字)。最後傳回文檔清單和類别清單。

資料預處理

資料預處理包括對樣本進行分詞、詞性篩選、停用詞處理等,最後形成規範化幹淨的資料樣本。由于本案例收集資料時預設進行了資料預處理,是以本節不在介紹(複旦新聞語料文本分類案例會詳細介紹)。目前,我們采集的資料還是文本類型,計算機還不能直接處理,需要将文本資料轉化成詞向量進行處理。這裡面需要擷取特征的詞彙集合(如果暫時不了解,先看看代碼實作,下面會進行形式化描述)。其實作過程如下:

'''擷取所有單詞的集合:傳回不含重複元素的單詞清單'''
def createVocabList(dataSet):
    vocabSet = set([])
    for document in dataSet:
        vocabSet = vocabSet | set(document)  # 操作符 | 用于求兩個集合的并集
    # print(vocabSet)
    return list(vocabSet)
           

代碼分析:方法參數dataSet即加載資料集傳回的文檔清單。vocabSet是定義的不重複的資料集合。然後for循環對文檔清單每條資料進行周遊處理,将不重複的詞彙添加到vocabSet中,最終形成整個文檔的詞彙集,然後以list形式傳回。

上面的方法已經擷取了整個文檔詞彙集合,接着建構資料矩陣,代碼實作如下:

'''詞集模型建構資料矩陣'''
def setOfWords2Vec(vocabList, inputSet):
    # 建立一個和詞彙表等長的向量,并将其元素都設定為0
    returnVec = [0] * len(vocabList)
    # 周遊文檔中的所有單詞,如果出現了詞彙表中的單詞,則将輸出的文檔向量中的對應值設為1
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            print("單詞: %s 不在詞彙表之中!" % word)
    # print(returnVec)
    return returnVec
           

代碼分析:本方法提供兩個參數分别是整個訓練文檔詞彙集(即全部訓練文檔6條評論不重複的單詞集合),輸入的資料清單。以整個詞彙集等長的0向量。我們周遊輸入資料清單,如果詞特征在詞彙集則标記1,不在詞彙集保持為0.最後傳回詞向量矩陣。

與詞集模型對應的,有個詞袋模型。兩者都是建構詞向量,隻是方式不一樣,詞袋模型也是推薦使用的詞向量化方法,其實作如下:

'''文檔詞袋模型建構資料矩陣'''
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    # print(returnVec)
    return returnVec
           

分析資料

運作詞集模型setOfWords2Vec(vocabList, dataSet[0])運作結果如下:

['dog', 'to', 'take', 'park', 'licks', 'has', 'help', 'stupid', 'him', 'so', 'not', 'love', 'buying', 'problems', 'cute', 'stop', 'steak', 'how', 'flea', 'maybe', 'food', 'I', 'please', 'dalmation', 'mr', 'posting', 'ate', 'garbage', 'worthless', 'my', 'is', 'quit']
[1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0]
           

結果分析:我們将dataSet[0]即第一條資訊['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']建構詞集模型,詞特征集為['dog', 'to', 'take', 'park', 'licks', 'has', 'help', 'stupid', 'him', 'so', 'not', 'love', 'buying', 'problems', 'cute', 'stop', 'steak', 'how', 'flea', 'maybe', 'food', 'I', 'please', 'dalmation', 'mr', 'posting', 'ate', 'garbage', 'worthless', 'my', 'is', 'quit']。結果顯示[1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0]。即詞特征集dog在dataSet[0]中,标記為1,to不在則保留原始的0.以此類推。我們也可以檢視dataSet[1]等資料結果。

資料樣本分析僅僅如上所述?當然不是,本例子中資料量比較小,容易分析。當資料量比較大,特征數以萬計之時,人工分析就顯得捉襟見肘了。我們可以采用圖形化分析方法,根據具體業務需求,可以選擇基于python自帶的matplotlib可視化分析、或者其他圖形可視化工具進行平面或多元資料分析,然後便于特征的選擇。

如果是中文分詞,我們還可以對詞性進行分析,然後選擇相應的詞性特征,比如名詞、動詞、地名、人名、機構名等等,對虛詞、助詞等進行過濾,一方面達到資料降維另一方面防止模型訓練拟合化等問題。

訓練模型

現在已經知道了一個詞是否出現在一篇文檔中,也知道該文檔所屬的類别。接下來我們重寫貝葉斯準則,将之前的 x, y 替換為 w. 粗體的 w 表示這是一個向量,即它由多個值組成。在這個例子中,數值個數與詞彙表中的詞個數相同。

實作 | 樸素貝葉斯模型算法研究與執行個體分析

我們使用上述公式,對每個類計算該值,然後比較這兩個機率值的大小。根據上述公式可知,我們右邊的式子等同于左邊的式子,由于對于每個ci,P(w)是固定的。并且我們隻需要比較左邊式子值的大小來決策分類,那麼我們就可以簡化為通過比較右邊分子值得大小來做決策分類。

首先可以通過類别 i (侮辱性留言或者非侮辱性留言)中的文檔數除以總的文檔數來計算機率 

實作 | 樸素貝葉斯模型算法研究與執行個體分析

。接下來計算

實作 | 樸素貝葉斯模型算法研究與執行個體分析

 ,這裡就要用到樸素貝葉斯假設。如果将 w 展開為一個個獨立特征,那麼就可以将上述機率寫作

實作 | 樸素貝葉斯模型算法研究與執行個體分析

。這裡假設所有詞都互相獨立,該假設也稱作條件獨立性假設(例如 A 和 B 兩個人抛骰子,機率是互不影響的,也就是互相獨立的,A 抛 2點的同時 B 抛 3 點的機率就是 1/6 * 1/6),它意味着可以使用

實作 | 樸素貝葉斯模型算法研究與執行個體分析

來計算上述機率,這樣就極大地簡化了計算的過程。具體代碼實作如下:

'''樸素貝葉斯分類器訓練函數'''
def _trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix) # 檔案數
    numWords = len(trainMatrix[0]) # 單詞數
    # 侮辱性檔案的出現機率,即trainCategory中所有的1的個數,
    # 代表的就是多少個侮辱性檔案,與檔案的總數相除就得到了侮辱性檔案的出現機率
    pAbusive = sum(trainCategory) / float(numTrainDocs)

    # 構造單詞出現次數清單
    p0Num = zeros(numWords) # [0,0,0,.....]
    p1Num = zeros(numWords) # [0,0,0,.....]
    p0Denom = 0.0;p1Denom = 0.0 # 整個資料集單詞出現總數
    for i in range(numTrainDocs):
        # 周遊所有的檔案,如果是侮辱性檔案,就計算此侮辱性檔案中出現的侮辱性單詞的個數
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i] #[0,1,1,....]->[0,1,1,...]
            p1Denom += sum(trainMatrix[i])
        else:
            # 如果不是侮辱性檔案,則計算非侮辱性檔案中出現的侮辱性單詞的個數
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    # 類别1,即侮辱性文檔的[P(F1|C1),P(F2|C1),P(F3|C1),P(F4|C1),P(F5|C1)....]清單
    # 即 在1類别下,每個單詞出現次數的占比
    p1Vect = p1Num / p1Denom# [1,2,3,5]/90->[1/90,...]
    # 類别0,即正常文檔的[P(F1|C0),P(F2|C0),P(F3|C0),P(F4|C0),P(F5|C0)....]清單
    # 即 在0類别下,每個單詞出現次數的占比
    p0Vect = p0Num / p0Denom
    return p0Vect, p1Vect, pAbusive
           

代碼分析:本方法參數分别是文檔特征向量矩陣和文檔類别向量矩陣。首先計算侮辱性文檔占總文檔的機率,然後計算正常文檔下特征詞的機率向量和侮辱性特征詞的向量,為了更好了解上面的代碼我們看下運作p0V,p1V,pAb=_trainNB0(trainMatrix,Classlabels)結果:

詞彙表集
['I', 'cute', 'help', 'dalmation', 'please', 'has', 'my', 'him', 'worthless', 'problems', 'so', 'mr', 'flea', 'love', 'take', 'stupid', 'dog', 'park', 'how', 'quit', 'buying', 'posting', 'steak', 'maybe', 'to', 'is', 'ate', 'not', 'garbage', 'food', 'stop', 'licks']
各條評論特征向量,其中1,3,5條為類别0;2,4,6條為類别1
[0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0]
[1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0]
[0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1]
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]
類别0下特征詞條件機率
[0.04166667 0.04166667 0.04166667 0.04166667 0.04166667 0.04166667
 0.125      0.08333333 0.         0.04166667 0.04166667 0.04166667
 0.04166667 0.04166667 0.         0.         0.04166667 0.
 0.04166667 0.         0.         0.         0.04166667 0.
 0.04166667 0.04166667 0.04166667 0.         0.         0.
 0.04166667 0.04166667] 
 類别1下特征詞條件機率
[0.         0.         0.         0.         0.         0.
 0.         0.05263158 0.10526316 0.         0.         0.
 0.         0.         0.05263158 0.15789474 0.10526316 0.05263158
 0.         0.05263158 0.05263158 0.05263158 0.         0.05263158
 0.05263158 0.         0.         0.05263158 0.05263158 0.05263158
 0.05263158 0.        ] 
 0.5
           

結果分析:結合結果我們去了解上面的訓練模型代碼。首先最後一個是0.5代表侮辱性文檔占全部文檔的50%即一半,實際上我們标記3個正常評論詞條,3個非正常的,這個顯然正确。其次,第一次詞I在類别1中出現0次,在類别0中出現1次。對應的條件機率分别是0.04166667和0

測試算法

在利用貝葉斯分類器對文檔進行分類時,要計算多個機率的乘積以獲得文檔屬于某個類别的機率,即計算 

實作 | 樸素貝葉斯模型算法研究與執行個體分析

。如果其中一個機率值為 0,那麼最後的乘積也為 0。為降低這種影響,可以将所有詞的出現數初始化為 1,并将分母初始化為 2 (取1 或 2 的目的主要是為了保證分子和分母不為0,大家可以根據業務需求進行更改)。

另一個遇到的問題是下溢出,這是由于太多很小的數相乘造成的。當計算乘積 

實作 | 樸素貝葉斯模型算法研究與執行個體分析

 時,由于大部分因子都非常小,是以程式會下溢出或者得到不正确的答案。(用 Python 嘗試相乘許多很小的數,最後四舍五入後會得到 0)。一種解決辦法是對乘積取自然對數。在代數中有 ln(a * b) = ln(a) + ln(b), 于是通過求對數可以避免下溢出或者浮點數舍入導緻的錯誤。同時,采用自然對數進行處理不會有任何損失。

下圖給出了函數 f(x) 與 ln(f(x)) 的曲線。可以看出,它們在相同區域内同時增加或者減少,并且在相同點上取到極值。它們的取值雖然不同,但不影響最終結果。

實作 | 樸素貝葉斯模型算法研究與執行個體分析

根據樸素貝葉斯公式,我們觀察分子

實作 | 樸素貝葉斯模型算法研究與執行個體分析

進行條件機率連乘時候,由于有條件機率極小或者為0,最後導緻結果為0 ,顯然不符合我們預期結果,是以對訓練模型進行優化,其優化代碼如下:

'''訓練資料優化版本'''
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix) # 總檔案數
    numWords = len(trainMatrix[0]) # 總單詞數
    pAbusive = sum(trainCategory) / float(numTrainDocs) # 侮辱性檔案的出現機率
    # 構造單詞出現次數清單,p0Num 正常的統計,p1Num 侮辱的統計
    # 避免單詞清單中的任何一個單詞為0,而導緻最後的乘積為0,是以将每個單詞的出現次數初始化為 1
    p0Num = ones(numWords)#[0,0......]->[1,1,1,1,1.....],ones初始化1的矩陣
    p1Num = ones(numWords)

    # 整個資料集單詞出現總數,2.0根據樣本實際調查結果調整分母的值(2主要是避免分母為0,當然值可以調整)
    # p0Denom 正常的統計
    # p1Denom 侮辱的統計
    p0Denom = 2.0
    p1Denom = 2.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]  # 累加辱罵詞的頻次
            p1Denom += sum(trainMatrix[i]) # 對每篇文章的辱罵的頻次 進行統計彙總
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    # 類别1,即侮辱性文檔的[log(P(F1|C1)),log(P(F2|C1)),log(P(F3|C1)),log(P(F4|C1)),log(P(F5|C1))....]清單,取對數避免下溢出或浮點舍入出錯
    p1Vect = log(p1Num / p1Denom)
    # 類别0,即正常文檔的[log(P(F1|C0)),log(P(F2|C0)),log(P(F3|C0)),log(P(F4|C0)),log(P(F5|C0))....]清單
    p0Vect = log(p0Num / p0Denom)
    return p0Vect, p1Vect, pAbusive
           

我們再看生成條件機率結果如下:

[-2.56494936 -2.15948425 -3.25809654 -2.56494936 -3.25809654 -3.25809654
 -2.56494936 -2.56494936 -3.25809654 -2.56494936 -3.25809654 -3.25809654
 -2.56494936 -2.56494936 -2.56494936 -3.25809654 -2.56494936 -2.56494936
 -2.56494936 -2.56494936 -1.87180218 -2.56494936 -3.25809654 -2.56494936
 -2.56494936 -2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.56494936
 -3.25809654 -2.56494936] 
 [-3.04452244 -2.35137526 -2.35137526 -3.04452244 -2.35137526 -2.35137526
 -3.04452244 -3.04452244 -1.94591015 -2.35137526 -2.35137526 -2.35137526
 -3.04452244 -2.35137526 -3.04452244 -1.65822808 -1.94591015 -3.04452244
 -3.04452244 -3.04452244 -3.04452244 -3.04452244 -2.35137526 -3.04452244
 -3.04452244 -3.04452244 -3.04452244 -2.35137526 -2.35137526 -3.04452244
 -2.35137526 -3.04452244] 
 0.5
           

使用算法對社群留言闆言論進行分類

建構樸素貝葉斯分類函數

将乘法轉換為加法

乘法:

實作 | 樸素貝葉斯模型算法研究與執行個體分析

加法:

實作 | 樸素貝葉斯模型算法研究與執行個體分析
'''
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    # 計算公式  log(P(F1|C))+log(P(F2|C))+....+log(P(Fn|C))+log(P(C))
    # 使用 NumPy 數組來計算兩個向量相乘的結果,這裡的相乘是指對應元素相乘,即先将兩個向量中的第一個元素相乘,然後将第2個元素相乘,以此類推。這裡的 vec2Classify * p1Vec 的意思就是将每個詞與其對應的機率相關聯起來
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0
           
測試樸素貝葉斯算法

結合上面分析流程和實作方法,我們綜合測試樸素貝葉斯對評論資訊分類如下:

'''樸素貝葉斯算法屏蔽社群留言闆的侮辱性言論的應用'''
def testingNB():
    # 1. 加載資料集
    dataSet, Classlabels = loadDataSet()
    # 2. 建立單詞集合
    myVocabList = createVocabList(dataSet)
    # 3. 計算單詞是否出現并建立資料矩陣
    trainMat = []
    for postinDoc in dataSet:
        # 傳回m*len(myVocabList)的矩陣, 記錄的都是0,1資訊
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    # print('test',len(array(trainMat)[0]))
    # 4. 訓練資料
    p0V, p1V, pAb = trainNB0(array(trainMat), array(Classlabels))
    # 5. 測試資料
    testEntry = ['love', 'my', 'dalmation']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, '分類結果是: ', classifyNB(thisDoc, p0V, p1V, pAb))
    testEntry = ['stupid', 'garbage']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, '分類結果是: ', classifyNB(thisDoc, p0V, p1V, pAb))
           

運作結果如下:

['love', 'my', 'dalmation'] 分類結果是:  0
['stupid', 'garbage'] 分類結果是:  1
           

案例場景2: 對社群留言闆言論進行分類

我們運作樸素貝葉斯分類進行電子郵件垃圾過濾。在文檔分類中,整個文檔(如一封電子郵件)是執行個體,而電子郵件中的某些元素則構成特征。我們可以觀察文檔中出現的詞,并把每個詞作為一個特征,而每個詞的出現或者不出現作為該特征的值,這樣得到的特征數目就會跟詞彙表中的詞的數目一樣多。

本項目開發流程如下:

收集資料: 提供文本檔案
準備資料: 将文本檔案解析成詞條向量
分析資料: 檢查詞條確定解析的正确性
訓練算法: 使用我們之前建立的 trainNB() 函數
測試算法: 使用樸素貝葉斯進行交叉驗證
使用算法: 建構一個完整的程式對一組文檔進行分類,将錯分的文檔輸出到螢幕上
           

收集資料并預處理

郵件格式内容如下:

實作 | 樸素貝葉斯模型算法研究與執行個體分析

對郵件進行讀取實作如下:

'''讀取文本'''
def testParseTest():
    print(textParse(open('./email/ham/1.txt').read()))
           

準備資料

對讀取的文本進行詞條向量化,其實作如下:

'''接收一個大字元串并将其解析為字元串清單'''
def textParse(bigString):
    import re
    # 使用正規表達式來切分句子,其中分隔符是除單詞、數字外的任意字元串
    listOfTokens = re.split(r'\W*', bigString)
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]
           

這個部分在案例場景1進行了較長的描述,此處不在贅述。

訓練算法

此處,我們去調用在場景1優化過的樸素貝葉斯訓練模型trainNB0() 函數,這裡也是一勞永逸的方法。還可以對資料預處理等進行封裝。

本測試方法中使用的資料集,即文檔可以參見下文代碼下載下傳。采用方法跟場景1基本類似,這裡不作代碼解析。具體實作代碼如下:

'''對貝葉斯垃圾郵件分類器進行自動化處理。'''
def spamTest():
    docList = [];classList = [];fullText = [] # 文檔清單、類别清單、文本特征
    for i in range(1, 26): # 總共25個文檔
        # 切分,解析資料,并歸類為 1 類别
        wordList = textParse(open('./email/spam/%d.txt' % i).read())
        docList.append(wordList)
        classList.append(1)
        # 切分,解析資料,并歸類為 0 類别
        wordList = textParse(open('./email/ham/%d.txt' % i,encoding='UTF-8').read())
        docList.append(wordList)
        classList.append(0)
        fullText.extend(wordList)
    # 建立詞彙表
    vocabList = createVocabList(docList)
    trainingSet = list(range(50)) # 詞彙表文檔索引
    testSet = []
    # 随機取 10 個郵件用來測試
    for i in range(10):
        # random.uniform(x, y) 随機生成一個範圍為 x - y 的實數
        randIndex = int(random.uniform(0, len(trainingSet)))
        testSet.append(trainingSet[randIndex]) # 随機抽取測試樣本
        del(trainingSet[randIndex]) # 訓練集中删除選擇為測試集的文檔

    trainMat = [];trainClasses = [] # 訓練集合訓練标簽
    for docIndex in trainingSet:
        trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])

    p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses))
    errorCount = 0
    for docIndex in testSet:
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])
        if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
            errorCount += 1
    print('the errorCount is: ', errorCount)
    print('the testSet length is :', len(testSet))
    print('the error rate is :', float(errorCount)/len(testSet))
           
the errorCount is:  2
the testSet length is : 10
the error rate is : 0.2
           

案例場景3: 使用樸素貝葉斯分類器從個人廣告中擷取區域傾向

廣告商往往想知道關于一個人的一些特定人口統計資訊,以便能更好地定向推銷廣告。我們将分别從美國的兩個城市中選取一些人,通過分析這些人釋出的資訊,來比較這兩個城市的人們在廣告用詞上是否不同。如果結論确實不同,那麼他們各自常用的詞是哪些,從人們的用詞當中,我們能否對不同城市的人所關心的内容有所了解。

開發流程如下:

收集資料: 從 RSS 源收集内容,這裡需要對 RSS 源建構一個接口
準備資料: 将文本檔案解析成詞條向量
分析資料: 檢查詞條確定解析的正确性
訓練算法: 使用我們之前建立的 trainNB0() 函數
測試算法: 觀察錯誤率,確定分類器可用。可以修改切分程式,以降低錯誤率,提高分類結果
使用算法: 建構一個完整的程式,封裝所有内容。給定兩個 RSS 源,改程式會顯示最常用的公共詞
           

從 RSS 源收集内容,這裡需要對 RSS 源建構一個接口,也就是導入 RSS 源,我們使用 python 下載下傳文本,在

http://code.google.com/p/feedparser/

 下浏覽相關文檔,安裝 feedparse,首先解壓下載下傳的包,并将目前目錄切換到解壓檔案所在的檔案夾,然後在 python 提示符下輸入:python setup.py install

文檔詞袋模型

我們将每個詞的出現與否作為一個特征,這可以被描述為 詞集模型(set-of-words model)。如果一個詞在文檔中出現不止一次,這可能意味着包含該詞是否出現在文檔中所不能表達的某種資訊,這種方法被稱為 詞袋模型(bag-of-words model)。在詞袋中,每個單詞可以出現多次,而在詞集中,每個詞隻能出現一次。為适應詞袋模型,需要對函數 setOfWords2Vec() 稍加修改,修改後的函數為 bagOfWords2Vec() 。

如下給出了基于詞袋模型的樸素貝葉斯代碼。它與函數 setOfWords2Vec() 幾乎完全相同,唯一不同的是每當遇到一個單詞時,它會增加詞向量中的對應值,而不隻是将對應的數值設為 1 。

這部分在場景1中已經建構完成,并進行了闡述。

觀察錯誤率,確定分類器可用。可以修改切分程式,以降低錯誤率,提高分類結果。其具體實作如下:

'''RSS源分類器及高頻詞去除函數'''
def calcMostFreq(vocabList,fullText):
    import operator
    freqDict={}
    for token in vocabList:  #周遊詞彙表中的每個詞
        freqDict[token]=fullText.count(token)  #統計每個詞在文本中出現的次數
    sortedFreq=sorted(freqDict.items(),key=operator.itemgetter(1),reverse=True)  #根據每個詞出現的次數從高到底對字典進行排序
    return sortedFreq[:30]   #傳回出現次數最高的30個單詞



def localWords(feed1,feed0):
    # import feedparser # feedparser是python中最常用的RSS程式庫
    docList=[];classList=[];fullText=[]
    minLen=min(len(feed1['entries']),len(feed0['entries'])) # entries内容無法抓取,網站涉及反爬蟲技術
    print(len(feed1['entries']),len(feed0['entries']))
    for i in range(minLen):
        wordList=textParse(feed1['entries'][i]['summary'])   #每次通路一條RSS源
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        wordList=textParse(feed0['entries'][i]['summary'])
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList=createVocabList(docList)
    top30Words=calcMostFreq(vocabList,fullText)
    for pairW in top30Words:
        if pairW[0] in vocabList:vocabList.remove(pairW[0])    #去掉出現次數最高的那些詞
    trainingSet=range(2*minLen);testSet=[]
    for i in range(20):
        randIndex=int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    trainMat=[];trainClasses=[]
    for docIndex in trainingSet:
        trainMat.append(bagOfWords2VecMN(vocabList,docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V,p1V,pSpam=trainNB0(array(trainMat),array(trainClasses))
    errorCount=0
    for docIndex in testSet:
        wordVector=bagOfWords2VecMN(vocabList,docList[docIndex])
        if classifyNB(array(wordVector),p0V,p1V,pSpam)!=classList[docIndex]:
            errorCount+=1
    print('the error rate is:',float(errorCount)/len(testSet))
    return vocabList,p0V,p1V
           

運作結果:

ny = feedparser.parse('http://newyork.craigslist.org/stp/index.rss')
sf = feedparser.parse('http://sfbay.craigslist.org/stp/index.rss')
# print(ny)
vocabList,pSF,pNY=localWords(ny,sf)
           

由于如上兩個位址抓取,得到feed0['entries']為空,是以沒有進行結果分析,讀者可以試用其他rss位址進行處理。如下是采用之前網站反爬蟲抓取前的分析結果:

vocabList,pSF,pNY=bayes.localWords(ny,sf)
the error rate is: 0.2
vocabList,pSF,pNY=bayes.localWords(ny,sf)
the error rate is: 0.3
vocabList,pSF,pNY=bayes.localWords(ny,sf)
the error rate is: 0.55
           

參考文獻

  1. scikit中文社群: http://sklearn.apachecn.org/cn/0.19.0/
  2. 中文維基百科: https://zh.wikipedia.org/wiki/
  3. 文本分類特征選擇: https://www.cnblogs.com/june0507/p/7601001.html
  4. GitHub: https://github.com/BaiNingchao/MachineLearning-1
  5. 圖書:《機器學習實戰》
  6. 圖書:《自然語言處理理論與實戰》

完整代碼下載下傳

源碼請進【機器學習和自然語言QQ群:436303759】檔案下載下傳:
實作 | 樸素貝葉斯模型算法研究與執行個體分析

作者聲明

本文版權歸作者白甯超所有,本文原創,旨在學術和科研使用。文章同步如下: