【機器學習實戰(zhàn)】第4章 樸素貝葉斯(NaiveBayes)

第4章 基于概率論的分類方法:樸素貝葉斯

樸素貝葉斯首頁

樸素貝葉斯 概述

貝葉斯分類是一類分類算法的總稱,這類算法均以貝葉斯定理為基礎(chǔ),故統(tǒng)稱為貝葉斯分類。本章首先介紹貝葉斯分類算法的基礎(chǔ)——貝葉斯定理。最后,我們通過實例來討論貝葉斯分類的中最簡單的一種: 樸素貝葉斯分類。

貝葉斯理論 & 條件概率

貝葉斯理論

我們現(xiàn)在有一個數(shù)據(jù)集,它由兩類數(shù)據(jù)組成,數(shù)據(jù)分布如下圖所示:

樸素貝葉斯示例數(shù)據(jù)分布

我們現(xiàn)在用 p1(x,y) 表示數(shù)據(jù)點 (x,y) 屬于類別 1(圖中用圓點表示的類別)的概率,用 p2(x,y) 表示數(shù)據(jù)點 (x,y) 屬于類別 2(圖中三角形表示的類別)的概率,那么對于一個新數(shù)據(jù)點 (x,y),可以用下面的規(guī)則來判斷它的類別:

  • 如果 p1(x,y) > p2(x,y) ,那么類別為1
  • 如果 p2(x,y) > p1(x,y) ,那么類別為2

也就是說,我們會選擇高概率對應(yīng)的類別。這就是貝葉斯決策理論的核心思想,即選擇具有最高概率的決策。

條件概率

如果你對 p(x,y|c1) 符號很熟悉,那么可以跳過本小節(jié)。

有一個裝了 7 塊石頭的罐子,其中 3 塊是白色的,4 塊是黑色的。如果從罐子中隨機取出一塊石頭,那么是白色石頭的可能性是多少?由于取石頭有 7 種可能,其中 3 種為白色,所以取出白色石頭的概率為 3/7 。那么取到黑色石頭的概率又是多少呢?很顯然,是 4/7 。我們使用 P(white) 來表示取到白色石頭的概率,其概率值可以通過白色石頭數(shù)目除以總的石頭數(shù)目來得到。

7塊石頭的集合

如果這 7 塊石頭如下圖所示,放在兩個桶中,那么上述概率應(yīng)該如何計算?

7塊石頭放入兩個桶中

計算 P(white) 或者 P(black) ,如果事先我們知道石頭所在桶的信息是會改變結(jié)果的。這就是所謂的條件概率(conditional probablity)。假定計算的是從 B 桶取到白色石頭的概率,這個概率可以記作 P(white|bucketB) ,我們稱之為“在已知石頭出自 B 桶的條件下,取出白色石頭的概率”。很容易得到,P(white|bucketA) 值為 2/4 ,P(white|bucketB) 的值為 1/3 。

條件概率的計算公式如下:

P(white|bucketB) = P(white and bucketB) / P(bucketB)

首先,我們用 B 桶中白色石頭的個數(shù)除以兩個桶中總的石頭數(shù),得到 P(white and bucketB) = 1/7 .其次,由于 B 桶中有 3 塊石頭,而總石頭數(shù)為 7 ,于是 P(bucketB) 就等于 3/7 。于是又 P(white|bucketB) = P(white and bucketB) / P(bucketB) = (1/7) / (3/7) = 1/3 。

另外一種有效計算條件概率的方法稱為貝葉斯準則。貝葉斯準則告訴我們?nèi)绾谓粨Q條件概率中的條件與結(jié)果,即如果已知 P(x|c),要求 P(c|x),那么可以使用下面的計算方法:

計算 P(c|x)

使用條件概率來分類

上面我們提到貝葉斯決策理論要求計算兩個概率 p1(x, y) 和 p2(x, y):

  • 如果 p1(x, y) > p2(x, y), 那么屬于類別 1;
  • 如果 p2(x, y) > p1(X, y), 那么屬于類別 2.

這并不是貝葉斯決策理論的所有內(nèi)容。使用 p1() 和 p2() 只是為了盡可能簡化描述,而真正需要計算和比較的是 p(c1|x, y) 和 p(c2|x, y) .這些符號所代表的具體意義是: 給定某個由 x、y 表示的數(shù)據(jù)點,那么該數(shù)據(jù)點來自類別 c1 的概率是多少?數(shù)據(jù)點來自類別 c2 的概率又是多少?注意這些概率與概率 p(x, y|c1) 并不一樣,不過可以使用貝葉斯準則來交換概率中條件與結(jié)果。具體地,應(yīng)用貝葉斯準則得到:

應(yīng)用貝葉斯準則

使用上面這些定義,可以定義貝葉斯分類準則為:

  • 如果 P(c1|x, y) > P(c2|x, y), 那么屬于類別 c1;
  • 如果 P(c2|x, y) > P(c1|x, y), 那么屬于類別 c2.

在文檔分類中,整個文檔(如一封電子郵件)是實例,而電子郵件中的某些元素則構(gòu)成特征。我們可以觀察文檔中出現(xiàn)的詞,并把每個詞作為一個特征,而每個詞的出現(xiàn)或者不出現(xiàn)作為該特征的值,這樣得到的特征數(shù)目就會跟詞匯表中的詞的數(shù)目一樣多。

我們假設(shè)特征之間 相互獨立 。所謂 獨立(independence) 指的是統(tǒng)計意義上的獨立,即一個特征或者單詞出現(xiàn)的可能性與它和其他單詞相鄰沒有關(guān)系,比如說,“我們”中的“我”和“們”出現(xiàn)的概率與這兩個字相鄰沒有任何關(guān)系。這個假設(shè)正是樸素貝葉斯分類器中 樸素(naive) 一詞的含義。樸素貝葉斯分類器中的另一個假設(shè)是,每個特征同等重要。

Note: 樸素貝葉斯分類器通常有兩種實現(xiàn)方式: 一種基于伯努利模型實現(xiàn),一種基于多項式模型實現(xiàn)。這里采用前一種實現(xiàn)方式。該實現(xiàn)方式中并不考慮詞在文檔中出現(xiàn)的次數(shù),只考慮出不出現(xiàn),因此在這個意義上相當于假設(shè)詞是等權(quán)重的。

樸素貝葉斯 場景

機器學習的一個重要應(yīng)用就是文檔的自動分類。

在文檔分類中,整個文檔(如一封電子郵件)是實例,而電子郵件中的某些元素則構(gòu)成特征。我們可以觀察文檔中出現(xiàn)的詞,并把每個詞作為一個特征,而每個詞的出現(xiàn)或者不出現(xiàn)作為該特征的值,這樣得到的特征數(shù)目就會跟詞匯表中的詞的數(shù)目一樣多。

樸素貝葉斯是上面介紹的貝葉斯分類器的一個擴展,是用于文檔分類的常用算法。下面我們會進行一些樸素貝葉斯分類的實踐項目。

樸素貝葉斯 原理

樸素貝葉斯 工作原理

提取所有文檔中的詞條并進行去重
獲取文檔的所有類別
計算每個類別中的文檔數(shù)目
對每篇訓練文檔: 
    對每個類別: 
        如果詞條出現(xiàn)在文檔中-->增加該詞條的計數(shù)值(for循環(huán)或者矩陣相加)
        增加所有詞條的計數(shù)值(此類別下詞條總數(shù))
對每個類別: 
    對每個詞條: 
        將該詞條的數(shù)目除以總詞條數(shù)目得到的條件概率(P(詞條|類別))
返回該文檔屬于每個類別的條件概率(P(類別|文檔的所有詞條))

樸素貝葉斯 開發(fā)流程

收集數(shù)據(jù): 可以使用任何方法。
準備數(shù)據(jù): 需要數(shù)值型或者布爾型數(shù)據(jù)。
分析數(shù)據(jù): 有大量特征時,繪制特征作用不大,此時使用直方圖效果更好。
訓練算法: 計算不同的獨立特征的條件概率。
測試算法: 計算錯誤率。
使用算法: 一個常見的樸素貝葉斯應(yīng)用是文檔分類??梢栽谌我獾姆诸悎鼍爸惺褂脴闼刎惾~斯分類器,不一定非要是文本。

樸素貝葉斯 算法特點

優(yōu)點: 在數(shù)據(jù)較少的情況下仍然有效,可以處理多類別問題。
缺點: 對于輸入數(shù)據(jù)的準備方式較為敏感。
適用數(shù)據(jù)類型: 標稱型數(shù)據(jù)。

樸素貝葉斯 項目案例

項目案例1: 屏蔽社區(qū)留言板的侮辱性言論

項目概述

構(gòu)建一個快速過濾器來屏蔽在線社區(qū)留言板上的侮辱性言論。如果某條留言使用了負面或者侮辱性的語言,那么就將該留言標識為內(nèi)容不當。對此問題建立兩個類別: 侮辱類和非侮辱類,使用 1 和 0 分別表示。

開發(fā)流程

收集數(shù)據(jù): 可以使用任何方法
準備數(shù)據(jù): 從文本中構(gòu)建詞向量
分析數(shù)據(jù): 檢查詞條確保解析的正確性
訓練算法: 從詞向量計算概率
測試算法: 根據(jù)現(xiàn)實情況修改分類器
使用算法: 對社區(qū)留言板言論進行分類

收集數(shù)據(jù): 可以使用任何方法

本例是我們自己構(gòu)造的詞表:

def loadDataSet():
    """
    創(chuàng)建數(shù)據(jù)集
    :return: 單詞列表postingList, 所屬類別classVec
    """
    postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], #[0,0,1,1,1......]
                   ['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 is abusive, 0 not
    return postingList, classVec

準備數(shù)據(jù): 從文本中構(gòu)建詞向量

def createVocabList(dataSet):
    """
    獲取所有單詞的集合
    :param dataSet: 數(shù)據(jù)集
    :return: 所有單詞的集合(即不含重復元素的單詞列表)
    """
    vocabSet = set([])  # create empty set
    for document in dataSet:
        # 操作符 | 用于求兩個集合的并集
        vocabSet = vocabSet | set(document)  # union of the two sets
    return list(vocabSet)


def setOfWords2Vec(vocabList, inputSet):
    """
    遍歷查看該單詞是否出現(xiàn),出現(xiàn)該單詞則將該單詞置1
    :param vocabList: 所有單詞集合列表
    :param inputSet: 輸入數(shù)據(jù)集
    :return: 匹配列表[0,1,0,1...],其中 1與0 表示詞匯表中的單詞是否出現(xiàn)在輸入的數(shù)據(jù)集中
    """
    # 創(chuàng)建一個和詞匯表等長的向量,并將其元素都設(shè)置為0
    returnVec = [0] * len(vocabList)# [0,0......]
    # 遍歷文檔中的所有單詞,如果出現(xiàn)了詞匯表中的單詞,則將輸出的文檔向量中的對應(yīng)值設(shè)為1
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            print "the word: %s is not in my Vocabulary!" % word
    return returnVec

分析數(shù)據(jù): 檢查詞條確保解析的正確性

檢查函數(shù)執(zhí)行情況,檢查詞表,不出現(xiàn)重復單詞,需要的話,可以對其進行排序。

>>> listOPosts, listClasses = bayes.loadDataSet()
>>> myVocabList = bayes.createVocabList(listOPosts)
>>> myVocabList
['cute', 'love', 'help', 'garbage', 'quit', 'I', 'problems', 'is', 'park', 
'stop', 'flea', 'dalmation', 'licks', 'food', 'not', 'him', 'buying', 'posting', 'has', 'worthless', 'ate', 'to', 'maybe', 'please', 'dog', 'how', 
'stupid', 'so', 'take', 'mr', 'steak', 'my']

檢查函數(shù)有效性。例如:myVocabList 中索引為 2 的元素是什么單詞?應(yīng)該是是 help 。該單詞在第一篇文檔中出現(xiàn)了,現(xiàn)在檢查一下看看它是否出現(xiàn)在第四篇文檔中。

>>> bayes.setOfWords2Vec(myVocabList, listOPosts[0])
[0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1]

>>> bayes.setOfWords2Vec(myVocabList, listOPosts[3])
[0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]

訓練算法: 從詞向量計算概率

現(xiàn)在已經(jīng)知道了一個詞是否出現(xiàn)在一篇文檔中,也知道該文檔所屬的類別。接下來我們重寫貝葉斯準則,將之前的 x, y 替換為 <b>w</b>. 粗體的 <b>w</b> 表示這是一個向量,即它由多個值組成。在這個例子中,數(shù)值個數(shù)與詞匯表中的詞個數(shù)相同。

重寫貝葉斯準則

我們使用上述公式,對每個類計算該值,然后比較這兩個概率值的大小。

首先可以通過類別 i (侮辱性留言或者非侮辱性留言)中的文檔數(shù)除以總的文檔數(shù)來計算概率 p(ci) 。接下來計算 p(<b>w</b> | ci) ,這里就要用到樸素貝葉斯假設(shè)。如果將 w 展開為一個個獨立特征,那么就可以將上述概率寫作 p(w0, w1, w2...wn | ci) 。這里假設(shè)所有詞都互相獨立,該假設(shè)也稱作條件獨立性假設(shè)(例如 A 和 B 兩個人拋骰子,概率是互不影響的,也就是相互獨立的,A 拋 2點的同時 B 拋 3 點的概率就是 1/6 * 1/6),它意味著可以使用 p(w0 | ci)p(w1 | ci)p(w2 | ci)...p(wn | ci) 來計算上述概率,這樣就極大地簡化了計算的過程。

樸素貝葉斯分類器訓練函數(shù)

def _trainNB0(trainMatrix, trainCategory):
    """
    訓練數(shù)據(jù)原版
    :param trainMatrix: 文件單詞矩陣 [[1,0,1,1,1....],[],[]...]
    :param trainCategory: 文件對應(yīng)的類別[0,1,1,0....],列表長度等于單詞矩陣數(shù),其中的1代表對應(yīng)的文件是侮辱性文件,0代表不是侮辱性矩陣
    :return:
    """
    # 文件數(shù)
    numTrainDocs = len(trainMatrix)
    # 單詞數(shù)
    numWords = len(trainMatrix[0])
    # 侮辱性文件的出現(xiàn)概率,即trainCategory中所有的1的個數(shù),
    # 代表的就是多少個侮辱性文件,與文件的總數(shù)相除就得到了侮辱性文件的出現(xiàn)概率
    pAbusive = sum(trainCategory) / float(numTrainDocs)
    # 構(gòu)造單詞出現(xiàn)次數(shù)列表
    p0Num = zeros(numWords) # [0,0,0,.....]
    p1Num = zeros(numWords) # [0,0,0,.....]

    # 整個數(shù)據(jù)集單詞出現(xiàn)總數(shù)
    p0Denom = 0.0
    p1Denom = 0.0
    for i in range(numTrainDocs):
        # 是否是侮辱性文件
        if trainCategory[i] == 1:
            # 如果是侮辱性文件,對侮辱性文件的向量進行加和
            p1Num += trainMatrix[i] #[0,1,1,....] + [0,1,1,....]->[0,2,2,...]
            # 對向量中的所有元素進行求和,也就是計算所有侮辱性文件中出現(xiàn)的單詞總數(shù)
            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類別下,每個單詞出現(xiàn)的概率
    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類別下,每個單詞出現(xiàn)的概率
    p0Vect = p0Num / p0Denom
    return p0Vect, p1Vect, pAbusive

測試算法: 根據(jù)現(xiàn)實情況修改分類器

在利用貝葉斯分類器對文檔進行分類時,要計算多個概率的乘積以獲得文檔屬于某個類別的概率,即計算 p(w0|1) * p(w1|1) * p(w2|1)。如果其中一個概率值為 0,那么最后的乘積也為 0。為降低這種影響,可以將所有詞的出現(xiàn)數(shù)初始化為 1,并將分母初始化為 2 (取1 或 2 的目的主要是為了保證分子和分母不為0,大家可以根據(jù)業(yè)務(wù)需求進行更改)。

另一個遇到的問題是下溢出,這是由于太多很小的數(shù)相乘造成的。當計算乘積 p(w0|ci) * p(w1|ci) * p(w2|ci)... p(wn|ci) 時,由于大部分因子都非常小,所以程序會下溢出或者得到不正確的答案。(用 Python 嘗試相乘許多很小的數(shù),最后四舍五入后會得到 0)。一種解決辦法是對乘積取自然對數(shù)。在代數(shù)中有 ln(a * b) = ln(a) + ln(b), 于是通過求對數(shù)可以避免下溢出或者浮點數(shù)舍入導致的錯誤。同時,采用自然對數(shù)進行處理不會有任何損失。

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

函數(shù)圖像
def trainNB0(trainMatrix, trainCategory):
    """
    訓練數(shù)據(jù)優(yōu)化版本
    :param trainMatrix: 文件單詞矩陣
    :param trainCategory: 文件對應(yīng)的類別
    :return:
    """
    # 總文件數(shù)
    numTrainDocs = len(trainMatrix)
    # 總單詞數(shù)
    numWords = len(trainMatrix[0])
    # 侮辱性文件的出現(xiàn)概率
    pAbusive = sum(trainCategory) / float(numTrainDocs)
    # 構(gòu)造單詞出現(xiàn)次數(shù)列表
    # p0Num 正常的統(tǒng)計
    # p1Num 侮辱的統(tǒng)計
    p0Num = ones(numWords)#[0,0......]->[1,1,1,1,1.....]
    p1Num = ones(numWords)

    # 整個數(shù)據(jù)集單詞出現(xiàn)總數(shù),2.0根據(jù)樣本/實際調(diào)查結(jié)果調(diào)整分母的值(2主要是避免分母為0,當然值可以調(diào)整)
    # p0Denom 正常的統(tǒng)計
    # p1Denom 侮辱的統(tǒng)計
    p0Denom = 2.0
    p1Denom = 2.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            # 累加辱罵詞的頻次
            p1Num += trainMatrix[i]
            # 對每篇文章的辱罵的頻次 進行統(tǒng)計匯總
            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

使用算法: 對社區(qū)留言板言論進行分類

樸素貝葉斯分類函數(shù)

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    """
    使用算法:
        # 將乘法轉(zhuǎn)換為加法
        乘法:P(C|F1F2...Fn) = P(F1F2...Fn|C)P(C)/P(F1F2...Fn)
        加法:P(F1|C)*P(F2|C)....P(Fn|C)P(C) -> log(P(F1|C))+log(P(F2|C))+....+log(P(Fn|C))+log(P(C))
    :param vec2Classify: 待測數(shù)據(jù)[0,1,1,1,1...],即要分類的向量
    :param p0Vec: 類別0,即正常文檔的[log(P(F1|C0)),log(P(F2|C0)),log(P(F3|C0)),log(P(F4|C0)),log(P(F5|C0))....]列表
    :param p1Vec: 類別1,即侮辱性文檔的[log(P(F1|C1)),log(P(F2|C1)),log(P(F3|C1)),log(P(F4|C1)),log(P(F5|C1))....]列表
    :param pClass1: 類別1,侮辱性文件的出現(xiàn)概率
    :return: 類別1 or 0
    """
    # 計算公式  log(P(F1|C))+log(P(F2|C))+....+log(P(Fn|C))+log(P(C))
    # 大家可能會發(fā)現(xiàn),上面的計算公式,沒有除以貝葉斯準則的公式的分母,也就是 P(w) (P(w) 指的是此文檔在所有的文檔中出現(xiàn)的概率)就進行概率大小的比較了,
    # 因為 P(w) 針對的是包含侮辱和非侮辱的全部文檔,所以 P(w) 是相同的。
    # 使用 NumPy 數(shù)組來計算兩個向量相乘的結(jié)果,這里的相乘是指對應(yīng)元素相乘,即先將兩個向量中的第一個元素相乘,然后將第2個元素相乘,以此類推。
    # 我的理解是:這里的 vec2Classify * p1Vec 的意思就是將每個詞與其對應(yīng)的概率相關(guān)聯(lián)起來
    p1 = sum(vec2Classify * p1Vec) + log(pClass1) # P(w|c1) * P(c1) ,即貝葉斯準則的分子
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1) # P(w|c0) * P(c0) ,即貝葉斯準則的分子·
    if p1 > p0:
        return 1
    else:
        return 0


def testingNB():
    """
    測試樸素貝葉斯算法
    """
    # 1. 加載數(shù)據(jù)集
    listOPosts, listClasses = loadDataSet()
    # 2. 創(chuàng)建單詞集合
    myVocabList = createVocabList(listOPosts)
    # 3. 計算單詞是否出現(xiàn)并創(chuàng)建數(shù)據(jù)矩陣
    trainMat = []
    for postinDoc in listOPosts:
        # 返回m*len(myVocabList)的矩陣, 記錄的都是0,1信息
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    # 4. 訓練數(shù)據(jù)
    p0V, p1V, pAb = trainNB0(array(trainMat), array(listClasses))
    # 5. 測試數(shù)據(jù)
    testEntry = ['love', 'my', 'dalmation']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb)
    testEntry = ['stupid', 'garbage']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb)

完整代碼地址: https://github.com/apachecn/MachineLearning/blob/master/src/python/4.NaiveBayes/bayes.py

項目案例2: 使用樸素貝葉斯過濾垃圾郵件

項目概述

完成樸素貝葉斯的一個最著名的應(yīng)用: 電子郵件垃圾過濾。

開發(fā)流程

使用樸素貝葉斯對電子郵件進行分類

收集數(shù)據(jù): 提供文本文件
準備數(shù)據(jù): 將文本文件解析成詞條向量
分析數(shù)據(jù): 檢查詞條確保解析的正確性
訓練算法: 使用我們之前建立的 trainNB() 函數(shù)
測試算法: 使用樸素貝葉斯進行交叉驗證
使用算法: 構(gòu)建一個完整的程序?qū)σ唤M文檔進行分類,將錯分的文檔輸出到屏幕上

收集數(shù)據(jù): 提供文本文件

文本文件內(nèi)容如下:

Hi Peter,

With Jose out of town, do you want to
meet once in a while to keep things
going and do some interesting stuff?

Let me know
Eugene

準備數(shù)據(jù): 將文本文件解析成詞條向量

使用正則表達式來切分文本

>>> mySent = 'This book is the best book on Python or M.L. I have ever laid eyes upon.'
>>> import re
>>> regEx = re.compile('\\W*')
>>> listOfTokens = regEx.split(mySent)
>>> listOfTokens
['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python', 'or', 'M.L.', 'I', 'have', 'ever', 'laid', 'eyes', 'upon', '']

分析數(shù)據(jù): 檢查詞條確保解析的正確性

訓練算法: 使用我們之前建立的 trainNB0() 函數(shù)

def trainNB0(trainMatrix, trainCategory):
    """
    訓練數(shù)據(jù)優(yōu)化版本
    :param trainMatrix: 文件單詞矩陣
    :param trainCategory: 文件對應(yīng)的類別
    :return:
    """
    # 總文件數(shù)
    numTrainDocs = len(trainMatrix)
    # 總單詞數(shù)
    numWords = len(trainMatrix[0])
    # 侮辱性文件的出現(xiàn)概率
    pAbusive = sum(trainCategory) / float(numTrainDocs)
    # 構(gòu)造單詞出現(xiàn)次數(shù)列表
    # p0Num 正常的統(tǒng)計
    # p1Num 侮辱的統(tǒng)計
    p0Num = ones(numWords)#[0,0......]->[1,1,1,1,1.....]
    p1Num = ones(numWords)

    # 整個數(shù)據(jù)集單詞出現(xiàn)總數(shù),2.0根據(jù)樣本/實際調(diào)查結(jié)果調(diào)整分母的值(2主要是避免分母為0,當然值可以調(diào)整)
    # p0Denom 正常的統(tǒng)計
    # p1Denom 侮辱的統(tǒng)計
    p0Denom = 2.0
    p1Denom = 2.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            # 累加辱罵詞的頻次
            p1Num += trainMatrix[i]
            # 對每篇文章的辱罵的頻次 進行統(tǒng)計匯總
            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

測試算法: 使用樸素貝葉斯進行交叉驗證

文件解析及完整的垃圾郵件測試函數(shù)

# 切分文本
def textParse(bigString):
    '''
    Desc:
        接收一個大字符串并將其解析為字符串列表
    Args:
        bigString -- 大字符串
    Returns:
        去掉少于 2 個字符的字符串,并將所有字符串轉(zhuǎn)換為小寫,返回字符串列表
    '''
    import re
    # 使用正則表達式來切分句子,其中分隔符是除單詞、數(shù)字外的任意字符串
    listOfTokens = re.split(r'\W*', bigString)
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]

def spamTest():
    '''
    Desc:
        對貝葉斯垃圾郵件分類器進行自動化處理。
    Args:
        none
    Returns:
        對測試集中的每封郵件進行分類,若郵件分類錯誤,則錯誤數(shù)加 1,最后返回總的錯誤百分比。
    '''
    docList = []
    classList = []
    fullText = []
    for i in range(1, 26):
        # 切分,解析數(shù)據(jù),并歸類為 1 類別
        wordList = textParse(open('input/4.NaiveBayes/email/spam/%d.txt' % i).read())
        docList.append(wordList)
        classList.append(1)
        # 切分,解析數(shù)據(jù),并歸類為 0 類別
        wordList = textParse(open('input/4.NaiveBayes/email/ham/%d.txt' % i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    # 創(chuàng)建詞匯表    
    vocabList = createVocabList(docList)
    trainingSet = range(50)
    testSet = []
    # 隨機取 10 個郵件用來測試
    for i in range(10):
        # random.uniform(x, y) 隨機生成一個范圍為 x - y 的實數(shù)
        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)

使用算法: 構(gòu)建一個完整的程序?qū)σ唤M文檔進行分類,將錯分的文檔輸出到屏幕上

完整代碼地址: https://github.com/apachecn/MachineLearning/blob/master/src/python/4.NaiveBayes/bayes.py

項目案例3: 使用樸素貝葉斯分類器從個人廣告中獲取區(qū)域傾向

項目概述

廣告商往往想知道關(guān)于一個人的一些特定人口統(tǒng)計信息,以便能更好地定向推銷廣告。

我們將分別從美國的兩個城市中選取一些人,通過分析這些人發(fā)布的信息,來比較這兩個城市的人們在廣告用詞上是否不同。如果結(jié)論確實不同,那么他們各自常用的詞是那些,從人們的用詞當中,我們能否對不同城市的人所關(guān)心的內(nèi)容有所了解。

開發(fā)流程

收集數(shù)據(jù): 從 RSS 源收集內(nèi)容,這里需要對 RSS 源構(gòu)建一個接口
準備數(shù)據(jù): 將文本文件解析成詞條向量
分析數(shù)據(jù): 檢查詞條確保解析的正確性
訓練算法: 使用我們之前簡歷的 trainNB0() 函數(shù)
測試算法: 觀察錯誤率,確保分類器可用??梢孕薷那蟹殖绦?,以降低錯誤率,提高分類結(jié)果
使用算法: 構(gòu)建一個完整的程序,封裝所有內(nèi)容。給定兩個 RSS 源,改程序會顯示最常用的公共詞

收集數(shù)據(jù): 從 RSS 源收集內(nèi)容,這里需要對 RSS 源構(gòu)建一個接口

也就是導入 RSS 源,我們使用 python 下載文本,在http://code.google.com/p/feedparser/ 下瀏覽相關(guān)文檔,安裝 feedparse,首先解壓下載的包,并將當前目錄切換到解壓文件所在的文件夾,然后在 python 提示符下輸入:

>>> python setup.py install

準備數(shù)據(jù): 將文本文件解析成詞條向量

文檔詞袋模型

我們將每個詞的出現(xiàn)與否作為一個特征,這可以被描述為 <b>詞集模型(set-of-words model)</b>。如果一個詞在文檔中出現(xiàn)不止一次,這可能意味著包含該詞是否出現(xiàn)在文檔中所不能表達的某種信息,這種方法被稱為 <b>詞袋模型(bag-of-words model)</b>。在詞袋中,每個單詞可以出現(xiàn)多次,而在詞集中,每個詞只能出現(xiàn)一次。為適應(yīng)詞袋模型,需要對函數(shù) setOfWords2Vec() 稍加修改,修改后的函數(shù)為 bagOfWords2Vec() 。

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

def bagOfWords2VecMN(vocaList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in inputSet:
            returnVec[vocabList.index(word)] += 1
    return returnVec
#創(chuàng)建一個包含在所有文檔中出現(xiàn)的不重復詞的列表
def createVocabList(dataSet):
    vocabSet=set([])    #創(chuàng)建一個空集
    for document in dataSet:
        vocabSet=vocabSet|set(document)   #創(chuàng)建兩個集合的并集
    return list(vocabSet)
def setOfWords2VecMN(vocabList,inputSet):
    returnVec=[0]*len(vocabList)  #創(chuàng)建一個其中所含元素都為0的向量
    for word in inputSet:
        if word in vocabList:
                returnVec[vocabList.index(word)]+=1
    return returnVec

#文件解析
def textParse(bigString):
    import re
    listOfTokens=re.split(r'\W*',bigString)
    return [tok.lower() for tok in listOfTokens if len(tok)>2]

分析數(shù)據(jù): 檢查詞條確保解析的正確性

訓練算法: 使用我們之前簡歷的 trainNB0() 函數(shù)

def trainNB0(trainMatrix, trainCategory):
    """
    訓練數(shù)據(jù)優(yōu)化版本
    :param trainMatrix: 文件單詞矩陣
    :param trainCategory: 文件對應(yīng)的類別
    :return:
    """
    # 總文件數(shù)
    numTrainDocs = len(trainMatrix)
    # 總單詞數(shù)
    numWords = len(trainMatrix[0])
    # 侮辱性文件的出現(xiàn)概率
    pAbusive = sum(trainCategory) / float(numTrainDocs)
    # 構(gòu)造單詞出現(xiàn)次數(shù)列表
    # p0Num 正常的統(tǒng)計
    # p1Num 侮辱的統(tǒng)計 
    # 避免單詞列表中的任何一個單詞為0,而導致最后的乘積為0,所以將每個單詞的出現(xiàn)次數(shù)初始化為 1
    p0Num = ones(numWords)#[0,0......]->[1,1,1,1,1.....]
    p1Num = ones(numWords)

    # 整個數(shù)據(jù)集單詞出現(xiàn)總數(shù),2.0根據(jù)樣本/實際調(diào)查結(jié)果調(diào)整分母的值(2主要是避免分母為0,當然值可以調(diào)整)
    # p0Denom 正常的統(tǒng)計
    # p1Denom 侮辱的統(tǒng)計
    p0Denom = 2.0
    p1Denom = 2.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            # 累加辱罵詞的頻次
            p1Num += trainMatrix[i]
            # 對每篇文章的辱罵的頻次 進行統(tǒng)計匯總
            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

測試算法: 觀察錯誤率,確保分類器可用??梢孕薷那蟹殖绦?,以降低錯誤率,提高分類結(jié)果

#RSS源分類器及高頻詞去除函數(shù)
def calcMostFreq(vocabList,fullText):
    import operator
    freqDict={}
    for token in vocabList:  #遍歷詞匯表中的每個詞
        freqDict[token]=fullText.count(token)  #統(tǒng)計每個詞在文本中出現(xiàn)的次數(shù)
    sortedFreq=sorted(freqDict.iteritems(),key=operator.itemgetter(1),reverse=True)  #根據(jù)每個詞出現(xiàn)的次數(shù)從高到底對字典進行排序
    return sortedFreq[:30]   #返回出現(xiàn)次數(shù)最高的30個單詞
def localWords(feed1,feed0):
    import feedparser
    docList=[];classList=[];fullText=[]
    minLen=min(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])    #去掉出現(xiàn)次數(shù)最高的那些詞
    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=trainNBO(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

#樸素貝葉斯分類函數(shù)
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

使用算法: 構(gòu)建一個完整的程序,封裝所有內(nèi)容。給定兩個 RSS 源,改程序會顯示最常用的公共詞

函數(shù) localWords() 使用了兩個 RSS 源作為參數(shù),RSS 源要在函數(shù)外導入,這樣做的原因是 RSS 源會隨時間而改變,重新加載 RSS 源就會得到新的數(shù)據(jù)

>>> reload(bayes)
<module 'bayes' from 'bayes.pyc'>
>>> import feedparser
>>> ny=feedparser.parse('http://newyork.craigslist.org/stp/index.rss')
>>> sy=feedparser.parse('http://sfbay.craigslist.org/stp/index.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

為了得到錯誤率的精確估計,應(yīng)該多次進行上述實驗,然后取平均值

接下來,我們要分析一下數(shù)據(jù),顯示地域相關(guān)的用詞

可以先對向量pSF與pNY進行排序,然后按照順序打印出來,將下面的代碼添加到文件中:

#最具表征性的詞匯顯示函數(shù)
def getTopWords(ny,sf):
    import operator
    vocabList,p0V,p1V=localWords(ny,sf)
    topNY=[];topSF=[]
    for i in range(len(p0V)):
        if p0V[i]>-6.0:topSF.append((vocabList[i],p0V[i]))
        if p1V[i]>-6.0:topNY.append((vocabList[i],p1V[i]))
    sortedSF=sorted(topSF,key=lambda pair:pair[1],reverse=True)
    print "SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**"
    for item in sortedSF:
        print item[0]
    sortedNY=sorted(topNY,key=lambda pair:pair[1],reverse=True)
    print "NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**"
    for item in sortedNY:
        print item[0]

函數(shù) getTopWords() 使用兩個 RSS 源作為輸入,然后訓練并測試樸素貝葉斯分類器,返回使用的概率值。然后創(chuàng)建兩個列表用于元組的存儲,與之前返回排名最高的 X 個單詞不同,這里可以返回大于某個閾值的所有詞,這些元組會按照它們的條件概率進行排序。

保存 bayes.py 文件,在python提示符下輸入:

>>> reload(bayes)
<module 'bayes' from 'bayes.pyc'>
>>> bayes.getTopWords(ny,sf)
the error rate is: 0.55
SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**
how
last
man
...
veteran
still
ends
late
off
own
know
NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**
someone
meet
...
apparel
recalled
starting
strings

當注釋掉用于移除高頻詞的三行代碼,然后比較注釋前后的分類性能,去掉這幾行代碼之后,錯誤率為54%,,而保留這些代碼得到的錯誤率為70%。這里觀察到,這些留言中出現(xiàn)次數(shù)最多的前30個詞涵蓋了所有用詞的30%,vocabList的大小約為3000個詞,也就是說,詞匯表中的一小部分單詞卻占據(jù)了所有文本用詞的一大部分。產(chǎn)生這種現(xiàn)象的原因是因為語言中大部分都是冗余和結(jié)構(gòu)輔助性內(nèi)容。另一個常用的方法是不僅移除高頻詞,同時從某個預定高頻詞中移除結(jié)構(gòu)上的輔助詞,該詞表稱為停用詞表。

最后輸出的單詞,可以看出程序輸出了大量的停用詞,可以移除固定的停用詞看看結(jié)果如何,這樣做的花,分類錯誤率也會降低。

完整代碼地址: https://github.com/apachecn/MachineLearning/blob/master/src/python/4.NaiveBayes/bayes.py


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內(nèi)容

  • 【博客的主要內(nèi)容主要是自己的學習筆記,并結(jié)合個人的理解,供各位在學習過程中參考,若有疑問,歡迎提出;若有侵權(quán),請告...
    Paullu閱讀 2,272評論 0 11
  • 忘光了概率統(tǒng)計的知識還想學樸素貝葉斯算法?這一篇就是為你準備的。雖然如此,作為初學者,別指望 5 分鐘就能完全理解...
    kamidox閱讀 2,730評論 4 7
  • 各位小伙伴們大家好,前些日子,我看了一些關(guān)于貝葉斯方法的文章,其中以今天這一篇文章覺得最好,不僅講的簡單通俗易懂并...
    云時之間閱讀 5,647評論 4 72
  • 我猜,風是甜的 因為它曾飄過你的耳畔 唱著歌謠; 我猜,雨是咸的 因為它曾落在你的眉心 鐘情于思; 我猜,光是酸的...
    陳斤默閱讀 802評論 2 4
  • 這世間再也沒有比光更讓人產(chǎn)生安全感的東西了,除了家。 我小時候喜歡偷偷的看聊齋志異,嚇得半夜不敢睡覺,總覺得黑暗的...
    蘇敘閱讀 593評論 10 18