1. 程式人生 > >一步步教你輕鬆學樸素貝葉斯模型實現篇2

一步步教你輕鬆學樸素貝葉斯模型實現篇2

導讀:樸素貝葉斯模型是機器學習常用的模型演算法之一,其在文字分類方面簡單易行,且取得不錯的分類效果。所以很受歡迎,對於樸素貝葉斯的學習,本文首先介紹理論知識即樸素貝葉斯相關概念和公式推導,為了加深理解,採用一個維基百科上面性別分類例子進行形式化描述。然後通過程式設計實現樸素貝葉斯分類演算法,並在遮蔽社群言論、垃圾郵件、個人廣告中獲取區域傾向等幾個方面進行應用,包括建立資料集、資料預處理、詞集模型和詞袋模型、樸素貝葉斯模型訓練和優化等。然後結合復旦大學新聞語料進行樸素貝葉斯的應用。最後,大家熟悉其原理和實現之後,採用機器學習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中已經構建完成,並進行了闡述。

分析資料

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

訓練演算法

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

測試演算法

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

'''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

參考文獻

完整程式碼下載

原始碼請進【機器學習和自然語言QQ群:436303759】檔案下載:


作者宣告