貝葉斯分類是一類分類算法的總稱,這類算法均以貝葉斯定理爲基礎,故統稱爲貝葉斯分類。本章首先介紹貝葉斯分類算法的基礎——貝葉斯定理。最後,咱們經過實例來討論貝葉斯分類的中最簡單的一種: 樸素貝葉斯分類。
javascript
咱們如今有一個數據集,它由兩類數據組成,數據分佈以下圖所示:css
咱們如今用 p1(x,y) 表示數據點 (x,y) 屬於類別 1(圖中用圓點表示的類別)的機率,用 p2(x,y) 表示數據點 (x,y) 屬於類別 2(圖中三角形表示的類別)的機率,那麼對於一個新數據點 (x,y),能夠用下面的規則來判斷它的類別:html
也就是說,咱們會選擇高几率對應的類別。這就是貝葉斯決策理論的核心思想,即選擇具備最高几率的決策。html5
上面咱們提到貝葉斯決策理論要求計算兩個機率 p1(x, y) 和 p2(x, y):java
這並非貝葉斯決策理論的全部內容。使用 p1() 和 p2() 只是爲了儘量簡化描述,而真正須要計算和比較的是 p(c1|x, y) 和 p(c2|x, y) .這些符號所表明的具體意義是: 給定某個由 x、y 表示的數據點,那麼該數據點來自類別 c1 的機率是多少?數據點來自類別 c2 的機率又是多少?注意這些機率與機率 p(x, y|c1) 並不同,不過可使用貝葉斯準則來交換機率中條件與結果。具體地,應用貝葉斯準則獲得:python
使用上面這些定義,能夠定義貝葉斯分類準則爲:jquery
在文檔分類中,整個文檔(如一封電子郵件)是實例,而電子郵件中的某些元素則構成特徵。咱們能夠觀察文檔中出現的詞,並把每一個詞做爲一個特徵,而每一個詞的出現或者不出現做爲該特徵的值,這樣獲得的特徵數目就會跟詞彙表中的詞的數目同樣多。linux
咱們假設特徵之間 相互獨立 。所謂 獨立(independence) 指的是統計意義上的獨立,即一個特徵或者單詞出現的可能性與它和其餘單詞相鄰沒有關係,好比說,「咱們」中的「我」和「們」出現的機率與這兩個字相鄰沒有任何關係。這個假設正是樸素貝葉斯分類器中 樸素(naive) 一詞的含義。樸素貝葉斯分類器中的另外一個假設是,每一個特徵同等重要。android
Note: 樸素貝葉斯分類器一般有兩種實現方式: 一種基於伯努利模型實現,一種基於多項式模型實現。這裏採用前一種實現方式。該實現方式中並不考慮詞在文檔中出現的次數,只考慮出不出現,所以在這個意義上至關於假設詞是等權重的。css3
機器學習的一個重要應用就是文檔的自動分類。
在文檔分類中,整個文檔(如一封電子郵件)是實例,而電子郵件中的某些元素則構成特徵。咱們能夠觀察文檔中出現的詞,並把每一個詞做爲一個特徵,而每一個詞的出現或者不出現做爲該特徵的值,這樣獲得的特徵數目就會跟詞彙表中的詞的數目同樣多。
樸素貝葉斯是上面介紹的貝葉斯分類器的一個擴展,是用於文檔分類的經常使用算法。下面咱們會進行一些樸素貝葉斯分類的實踐項目。
提取全部文檔中的詞條並進行去重 獲取文檔的全部類別 計算每一個類別中的文檔數目 對每篇訓練文檔: 對每一個類別: 若是詞條出如今文檔中-->增長該詞條的計數值(for循環或者矩陣相加) 增長全部詞條的計數值(此類別下詞條總數) 對每一個類別: 對每一個詞條: 將該詞條的數目除以總詞條數目獲得的條件機率(P(詞條|類別)) 返回該文檔屬於每一個類別的條件機率(P(類別|文檔的全部詞條))
收集數據: 可使用任何方法。 準備數據: 須要數值型或者布爾型數據。 分析數據: 有大量特徵時,繪製特徵做用不大,此時使用直方圖效果更好。 訓練算法: 計算不一樣的獨立特徵的條件機率。 測試算法: 計算錯誤率。 使用算法: 一個常見的樸素貝葉斯應用是文檔分類。能夠在任意的分類場景中使用樸素貝葉斯分類器,不必定非要是文本。
優勢: 在數據較少的狀況下仍然有效,能夠處理多類別問題。 缺點: 對於輸入數據的準備方式較爲敏感。 適用數據類型: 標稱型數據。
構建一個快速過濾器來屏蔽在線社區留言板上的侮辱性言論。若是某條留言使用了負面或者侮辱性的語言,那麼就將該留言標識爲內容不當。對此問題創建兩個類別: 侮辱類和非侮辱類,使用 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)))
完整代碼:
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
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)
vocab_list = create_vocab_list(posting_list)
vocab_list
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
set_of_words2vec(vocab_list, posting_list[0])
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
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)))
testing_naive_bayes(posting_list, class_vec)