04機器學習實戰之樸素貝葉斯

樸素貝葉斯 概述

貝葉斯分類是一類分類算法的總稱,這類算法均以貝葉斯定理爲基礎,故統稱爲貝葉斯分類。本章首先介紹貝葉斯分類算法的基礎——貝葉斯定理。最後,咱們經過實例來討論貝葉斯分類的中最簡單的一種: 樸素貝葉斯分類。javascript

貝葉斯理論 & 條件機率

貝葉斯理論

咱們如今有一個數據集,它由兩類數據組成,數據分佈以下圖所示:css

樸素貝葉斯示例數據分佈

咱們如今用 p1(x,y) 表示數據點 (x,y) 屬於類別 1(圖中用圓點表示的類別)的機率,用 p2(x,y) 表示數據點 (x,y) 屬於類別 2(圖中三角形表示的類別)的機率,那麼對於一個新數據點 (x,y),能夠用下面的規則來判斷它的類別:html

  • 若是 p1(x,y) > p2(x,y) ,那麼類別爲1
  • 若是 p2(x,y) > p1(x,y) ,那麼類別爲2

也就是說,咱們會選擇高几率對應的類別。這就是貝葉斯決策理論的核心思想,即選擇具備最高几率的決策。html5

使用條件機率來分類

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

  • 若是 p1(x, y) > p2(x, y), 那麼屬於類別 1;
  • 若是 p2(x, y) > p1(X, y), 那麼屬於類別 2.

這並非貝葉斯決策理論的全部內容。使用 p1() 和 p2() 只是爲了儘量簡化描述,而真正須要計算和比較的是 p(c1|x, y) 和 p(c2|x, y) .這些符號所表明的具體意義是: 給定某個由 x、y 表示的數據點,那麼該數據點來自類別 c1 的機率是多少?數據點來自類別 c2 的機率又是多少?注意這些機率與機率 p(x, y|c1) 並不同,不過可使用貝葉斯準則來交換機率中條件與結果。具體地,應用貝葉斯準則獲得:python

應用貝葉斯準則

使用上面這些定義,能夠定義貝葉斯分類準則爲:jquery

  • 若是 P(c1|x, y) > P(c2|x, y), 那麼屬於類別 c1;
  • 若是 P(c2|x, y) > P(c1|x, y), 那麼屬於類別 c2.

在文檔分類中,整個文檔(如一封電子郵件)是實例,而電子郵件中的某些元素則構成特徵。咱們能夠觀察文檔中出現的詞,並把每一個詞做爲一個特徵,而每一個詞的出現或者不出現做爲該特徵的值,這樣獲得的特徵數目就會跟詞彙表中的詞的數目同樣多。linux

咱們假設特徵之間 相互獨立 。所謂 獨立(independence) 指的是統計意義上的獨立,即一個特徵或者單詞出現的可能性與它和其餘單詞相鄰沒有關係,好比說,「咱們」中的「我」和「們」出現的機率與這兩個字相鄰沒有任何關係。這個假設正是樸素貝葉斯分類器中 樸素(naive) 一詞的含義。樸素貝葉斯分類器中的另外一個假設是,每一個特徵同等重要。android

Note: 樸素貝葉斯分類器一般有兩種實現方式: 一種基於伯努利模型實現,一種基於多項式模型實現。這裏採用前一種實現方式。該實現方式中並不考慮詞在文檔中出現的次數,只考慮出不出現,所以在這個意義上至關於假設詞是等權重的。css3

樸素貝葉斯 場景

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

在文檔分類中,整個文檔(如一封電子郵件)是實例,而電子郵件中的某些元素則構成特徵。咱們能夠觀察文檔中出現的詞,並把每一個詞做爲一個特徵,而每一個詞的出現或者不出現做爲該特徵的值,這樣獲得的特徵數目就會跟詞彙表中的詞的數目同樣多。

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

樸素貝葉斯 原理

樸素貝葉斯 工做原理

提取全部文檔中的詞條並進行去重
獲取文檔的全部類別
計算每一個類別中的文檔數目
對每篇訓練文檔: 
    對每一個類別: 
        若是詞條出如今文檔中-->增長該詞條的計數值(for循環或者矩陣相加)
        增長全部詞條的計數值(此類別下詞條總數)
對每一個類別: 
    對每一個詞條: 
        將該詞條的數目除以總詞條數目獲得的條件機率(P(詞條|類別))
返回該文檔屬於每一個類別的條件機率(P(類別|文檔的全部詞條))

樸素貝葉斯 開發流程

收集數據: 可使用任何方法。
準備數據: 須要數值型或者布爾型數據。
分析數據: 有大量特徵時,繪製特徵做用不大,此時使用直方圖效果更好。
訓練算法: 計算不一樣的獨立特徵的條件機率。
測試算法: 計算錯誤率。
使用算法: 一個常見的樸素貝葉斯應用是文檔分類。能夠在任意的分類場景中使用樸素貝葉斯分類器,不必定非要是文本。

樸素貝葉斯 算法特色

優勢: 在數據較少的狀況下仍然有效,能夠處理多類別問題。
缺點: 對於輸入數據的準備方式較爲敏感。
適用數據類型: 標稱型數據。

相關閱讀:http://www.javashuo.com/article/p-rorgdabv-du.html

樸素貝葉斯 項目案例

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

項目概述

構建一個快速過濾器來屏蔽在線社區留言板上的侮辱性言論。若是某條留言使用了負面或者侮辱性的語言,那麼就將該留言標識爲內容不當。對此問題創建兩個類別: 侮辱類和非侮辱類,使用 1 和 0 分別表示。

開發流程

收集數據: 可使用任何方法
準備數據: 從文本中構建詞向量
分析數據: 檢查詞條確保解析的正確性
訓練算法: 從詞向量計算機率
測試算法: 根據現實狀況修改分類器
使用算法: 對社區留言板言論進行分類

收集數據: 可使用任何方法

本例是咱們本身構造的詞表:

posting_list = [
    ['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', 'gar e'],
    ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
    ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
class_vec = [0, 1, 0, 1, 0, 1]  # 1 is 侮辱性的文字, 0 is not

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

def create_vocab_list(data_set):
    """
    獲取全部單詞的集合
    :param data_set: 數據集
    :return: 全部單詞的集合(即不含重複元素的單詞列表)
    """
    vocab_set = set()  # create empty set
    for item in data_set:
        # | 求兩個集合的並集,# set()返回一個不重複的單詞列表,將該列表加入到
        # vocab集合中
        vocab_set = vocab_set | set(item)  
    return list(vocab_set)
def set_of_words2vec(vocab_list, input_set):
    """
    遍歷查看該單詞是否出現,出現該單詞則將該單詞置1
    :param vocab_list: 全部單詞集合列表
    :param input_set: 輸入一條數據集,如:posting_list[0]
    :return: 匹配列表[0,1,0,1...],其中 1與0 表示詞彙表中的單詞是否出如今輸入的數據集中
    """
    # 建立一個和詞彙表等長的向量,並將其元素都設置爲0
    result = [0] * len(vocab_list)
    # 遍歷文檔中的全部單詞,若是出現了詞彙表中的單詞,則將輸出的文檔向量中的對應值設爲1
    for word in input_set:
        if word in vocab_list:
            result[vocab_list.index(word)] = 1
        else:
            # 這個後面應該註釋掉,由於對你沒什麼用,這只是爲了輔助調試的
            # print('the word: {} is not in my vocabulary'.format(word))
            pass
    return result

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

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

重寫貝葉斯準則

咱們使用上述公式,對每一個類計算該值,而後比較這兩個機率值的大小。

問: 上述代碼實現中,爲何沒有計算P(w)?

答:根據上述公式可知,咱們右邊的式子等同於左邊的式子,因爲對於每一個ci,P(w)是固定的。而且咱們只須要比較左邊式子值的大小來決策分類,那麼咱們就能夠簡化爲經過比較右邊分子值得大小來作決策分類。

首先能夠經過類別 i (侮辱性留言或者非侮辱性留言)中的文檔數除以總的文檔數來計算機率 p(ci)接下來計算 p(w | ci) ,這裏就要用到樸素貝葉斯假設。若是將 w 展開爲一個個獨立特徵,那麼就能夠將上述機率寫做 p(w0, w1, w2...wn | ci) 。這裏假設全部詞都互相獨立,該假設也稱做條件獨立性假設(例如 A 和 B 兩我的拋骰子,機率是互不影響的,也就是相互獨立的,A 拋 2點的同時 B 拋 3 點的機率就是 1/6 * 1/6),它意味着可使用 p(w0 | ci)p(w1 | ci)p(w2 | ci)...p(wn | ci) 來計算上述機率,這樣就極大地簡化了計算的過程。

樸素貝葉斯分類器訓練函數

import numpy as np


def train_naive_bayes(train_mat, train_category):
    """
    樸素貝葉斯分類修正版, 注意和原來的對比,爲何這麼作能夠查看書
    :param train_mat:  type is ndarray
                    總的輸入文本,大體是 [[0,1,0,1], [], []]
    :param train_category: 文件對應的類別分類, [0, 1, 0],
                            列表的長度應該等於上面那個輸入文本的長度
    :return: 兩個條件機率向量,一個機率
    """
    train_doc_num = len(train_mat)
    words_num = len(train_mat[0])
    # 由於侮辱性的被標記爲了1, 因此只要把他們相加就能夠獲得侮辱性的有多少
    # 侮辱性文件的出現機率,即train_category中全部的1的個數,
    # 表明的就是多少個侮辱性文件,與文件的總數相除就獲得了侮辱性文件的出現機率
    pos_abusive = np.sum(train_category) / train_doc_num
    # 單詞出現的次數
    # 原版,變成ones是修改版,這是爲了防止數字太小溢出
    # p0num = np.zeros(words_num)
    # p1num = np.zeros(words_num)
    p0num = np.ones(words_num)
    p1num = np.ones(words_num)
    # 整個數據集單詞出現的次數(原來是0,後面改爲2了)
    p0num_all = 2.0
    p1num_all = 2.0

    for i in range(train_doc_num):
        # 遍歷全部的文件,若是是侮辱性文件,就計算此侮辱性文件中出現的侮辱性單詞的個數
        if train_category[i] == 1:
            p1num += train_mat[i]    # 直接把兩個list相加,對應位置元素相加,
            # 最後直接一除就能夠獲得對應的機率list
            p1num_all += np.sum(train_mat[i])  # 標籤爲1的總詞數
        else:
            p0num += train_mat[i]
            p0num_all += np.sum(train_mat[i])  # 標籤爲0的總詞數
    # 後面改爲取 log 函數
    p1vec = np.log(p1num / p1num_all)
    p0vec = np.log(p0num / p0num_all)
    return p0vec, p1vec, pos_abusive

測試算法: 根據現實狀況修改分類器

在利用貝葉斯分類器對文檔進行分類時,要計算多個機率的乘積以得到文檔屬於某個類別的機率,即計算 p(w0|1) * p(w1|1) * p(w2|1)。若是其中一個機率值爲 0,那麼最後的乘積也爲 0。爲下降這種影響,能夠將全部詞的出現數初始化爲 1,並將分母初始化爲 2 (取1 或 2 的目的主要是爲了保證分子和分母不爲0,你們能夠根據業務需求進行更改)。

另外一個遇到的問題是下溢出,這是因爲太多很小的數相乘形成的。當計算乘積 p(w0|ci) * p(w1|ci) * p(w2|ci)... p(wn|ci) 時,因爲大部分因子都很是小,因此程序會下溢出或者獲得不正確的答案。(用 Python 嘗試相乘許多很小的數,最後四捨五入後會獲得 0)。一種解決辦法是對乘積取天然對數。在代數中有 ln(a * b) = ln(a) + ln(b), 因而經過求對數能夠避免下溢出或者浮點數舍入致使的錯誤。同時,採用天然對數進行處理不會有任何損失。

下圖給出了函數 f(x) 與 ln(f(x)) 的曲線。能夠看出,它們在相同區域內同時增長或者減小,而且在相同點上取到極值。它們的取值雖然不一樣,但不影響最終結果。

函數圖像

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

樸素貝葉斯分類函數

def classify_naive_bayes(vec2classify, p0vec, p1vec, p_class1):
    """
    使用算法:
        # 將乘法轉換爲加法
        乘法: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: 待測數據[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 p_class1: 類別1,侮辱性文件的出現機率
    :return: 類別1 or 0
    """
    # 計算公式  log(P(F1|C))+log(P(F2|C))+....+log(P(Fn|C))+log(P(C))
    # 使用 NumPy 數組來計算兩個向量相乘的結果,這裏的相乘是指對應元素相乘,即先將兩個向量中的第一個元素相乘,而後將第2個元素相乘,以此類推。
    # 個人理解是:這裏的 vec2Classify * p1Vec 的意思就是將每一個詞與其對應的機率相關聯起來
    # 能夠理解爲 1.單詞在詞彙表中的條件下,文件是good 類別的機率 也能夠理解爲 2.在整個空間下,文件既在詞彙表中又是good類別的機率
    p1 = np.sum(vec2classify * p1vec) + np.log(p_class1)
    p0 = np.sum(vec2classify * p0vec) + np.log(1 - p_class1)
    if p1 > p0:
        return 1
    else:
        return 0

測試

def testing_naive_bayes(list_post, list_classes):
    """
    測試樸素貝葉斯算法
    :return: no return 
    """
    # 1. 建立單詞集合
    vocab_list = create_vocab_list(list_post)

    # 2. 計算單詞是否出現並建立數據矩陣
    train_mat = []
    for post_in in list_post:
        train_mat.append(
            # 返回m*len(vocab_list)的矩陣, 記錄的都是0,1信息
            # 其實就是那個東西的句子向量(就是data_set裏面每一行,也不算句子吧)
            set_of_words2vec(vocab_list, post_in)
        )
    # 3. 訓練數據
    p0v, p1v, p_abusive = train_naive_bayes(np.array(train_mat), np.array(list_classes))
    # 4. 測試數據
    test_one = ['love', 'my', 'dalmation']
    test_one_doc = np.array(set_of_words2vec(vocab_list, test_one))
    print('the result is: {}'.format(classify_naive_bayes(test_one_doc, p0v, p1v, p_abusive)))
    test_two = ['stupid', 'garbage']
    test_two_doc = np.array(set_of_words2vec(vocab_list, test_two))
    print('the result is: {}'.format(classify_naive_bayes(test_two_doc, p0v, p1v, p_abusive)))

 

完整代碼:

In [19]:
posting_list = [
    ['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']]
class_vec = [0, 1, 0, 1, 0, 1]  # 1 is 侮辱性的文字, 0 is not
In [20]:
def create_vocab_list(data_set):
    """
    獲取全部單詞的集合
    :param data_set: 數據集
    :return: 全部單詞的集合(即不含重複元素的單詞列表)
    """
    vocab_set = set()  # create empty set
    for item in data_set:
        # | 求兩個集合的並集,# set()返回一個不重複的單詞列表,將該列表加入到
        # vocab集合中
        vocab_set = vocab_set | set(item)  
    return list(vocab_set)
In [21]:
vocab_list = create_vocab_list(posting_list)
vocab_list
Out[21]:
['him',
 'dog',
 'take',
 'cute',
 'ate',
 'love',
 'quit',
 'not',
 'worthless',
 'so',
 'garbage',
 'flea',
 'stop',
 'maybe',
 'licks',
 'how',
 'food',
 'dalmation',
 'has',
 'I',
 'park',
 'posting',
 'help',
 'please',
 'to',
 'problems',
 'stupid',
 'steak',
 'buying',
 'mr',
 'my',
 'is']
In [22]:
def set_of_words2vec(vocab_list, input_set):
    """
    遍歷查看該單詞是否出現,出現該單詞則將該單詞置1
    :param vocab_list: 全部單詞集合列表
    :param input_set: 輸入一條數據集,如:posting_list[0]
    :return: 匹配列表[0,1,0,1...],其中 1與0 表示詞彙表中的單詞是否出如今輸入的數據集中
    """
    # 建立一個和詞彙表等長的向量,並將其元素都設置爲0
    result = [0] * len(vocab_list)
    # 遍歷文檔中的全部單詞,若是出現了詞彙表中的單詞,則將輸出的文檔向量中的對應值設爲1
    for word in input_set:
        if word in vocab_list:
            result[vocab_list.index(word)] = 1
        else:
            # 這個後面應該註釋掉,由於對你沒什麼用,這只是爲了輔助調試的
            # print('the word: {} is not in my vocabulary'.format(word))
            pass
    return result
In [23]:
set_of_words2vec(vocab_list, posting_list[0])
Out[23]:
[0,
 1,
 0,
 0,
 0,
 0,
 0,
 0,
 0,
 0,
 0,
 1,
 0,
 0,
 0,
 0,
 0,
 0,
 1,
 0,
 0,
 0,
 1,
 1,
 0,
 1,
 0,
 0,
 0,
 0,
 1,
 0]
In [24]:
import numpy as np


def train_naive_bayes(train_mat, train_category):
    """
    樸素貝葉斯分類修正版, 注意和原來的對比,爲何這麼作能夠查看書
    :param train_mat:  type is ndarray
                    總的輸入文本,大體是 [[0,1,0,1], [], []]
    :param train_category: 文件對應的類別分類, [0, 1, 0],
                            列表的長度應該等於上面那個輸入文本的長度
    :return: 兩個條件機率向量,一個機率
    """
    train_doc_num = len(train_mat)
    words_num = len(train_mat[0])
    # 由於侮辱性的被標記爲了1, 因此只要把他們相加就能夠獲得侮辱性的有多少
    # 侮辱性文件的出現機率,即train_category中全部的1的個數,
    # 表明的就是多少個侮辱性文件,與文件的總數相除就獲得了侮辱性文件的出現機率
    pos_abusive = np.sum(train_category) / train_doc_num
    # 單詞出現的次數
    # 原版,變成ones是修改版,這是爲了防止數字太小溢出
    # p0num = np.zeros(words_num)
    # p1num = np.zeros(words_num)
    p0num = np.ones(words_num)
    p1num = np.ones(words_num)
    # 整個數據集單詞出現的次數(原來是0,後面改爲2了)
    p0num_all = 2.0
    p1num_all = 2.0

    for i in range(train_doc_num):
        # 遍歷全部的文件,若是是侮辱性文件,就計算此侮辱性文件中出現的侮辱性單詞的個數
        if train_category[i] == 1:
            p1num += train_mat[i]    # 直接把兩個list相加,對應位置元素相加,
            # 最後直接一除就能夠獲得對應的機率list
            p1num_all += np.sum(train_mat[i])  # 標籤爲1的總詞數
        else:
            p0num += train_mat[i]
            p0num_all += np.sum(train_mat[i])  # 標籤爲0的總詞數
    # 後面改爲取 log 函數
    p1vec = np.log(p1num / p1num_all)
    p0vec = np.log(p0num / p0num_all)
    return p0vec, p1vec, pos_abusive
In [25]:
def classify_naive_bayes(vec2classify, p0vec, p1vec, p_class1):
    """
    使用算法:
        # 將乘法轉換爲加法
        乘法: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: 待測數據[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 p_class1: 類別1,侮辱性文件的出現機率
    :return: 類別1 or 0
    """
    # 計算公式  log(P(F1|C))+log(P(F2|C))+....+log(P(Fn|C))+log(P(C))
    # 使用 NumPy 數組來計算兩個向量相乘的結果,這裏的相乘是指對應元素相乘,即先將兩個向量中的第一個元素相乘,而後將第2個元素相乘,以此類推。
    # 個人理解是:這裏的 vec2Classify * p1Vec 的意思就是將每一個詞與其對應的機率相關聯起來
    # 能夠理解爲 1.單詞在詞彙表中的條件下,文件是good 類別的機率 也能夠理解爲 2.在整個空間下,文件既在詞彙表中又是good類別的機率
    p1 = np.sum(vec2classify * p1vec) + np.log(p_class1)
    p0 = np.sum(vec2classify * p0vec) + np.log(1 - p_class1)
    if p1 > p0:
        return 1
    else:
        return 0
In [26]:
def testing_naive_bayes(list_post, list_classes):
    """
    測試樸素貝葉斯算法
    :return: no return 
    """
    # 1. 建立單詞集合
    vocab_list = create_vocab_list(list_post)

    # 2. 計算單詞是否出現並建立數據矩陣
    train_mat = []
    for post_in in list_post:
        train_mat.append(
            # 返回m*len(vocab_list)的矩陣, 記錄的都是0,1信息
            # 其實就是那個東西的句子向量(就是data_set裏面每一行,也不算句子吧)
            set_of_words2vec(vocab_list, post_in)
        )
    # 3. 訓練數據
    p0v, p1v, p_abusive = train_naive_bayes(np.array(train_mat), np.array(list_classes))
    # 4. 測試數據
    test_one = ['love', 'my', 'dalmation']
    test_one_doc = np.array(set_of_words2vec(vocab_list, test_one))
    print('the result is: {}'.format(classify_naive_bayes(test_one_doc, p0v, p1v, p_abusive)))
    test_two = ['stupid', 'garbage']
    test_two_doc = np.array(set_of_words2vec(vocab_list, test_two))
    print('the result is: {}'.format(classify_naive_bayes(test_two_doc, p0v, p1v, p_abusive)))
In [28]:
testing_naive_bayes(posting_list, class_vec)
 
the result is: 0
the result is: 1
相關文章
相關標籤/搜索