04机器学习实战之朴素贝叶斯
樸素貝葉斯 概述
貝葉斯分類是一類分類算法的總稱,這類算法均以貝葉斯定理為基礎,故統稱為貝葉斯分類。本章首先介紹貝葉斯分類算法的基礎——貝葉斯定理。最后,我們通過實例來討論貝葉斯分類的中最簡單的一種: 樸素貝葉斯分類。
貝葉斯理論 & 條件概率
貝葉斯理論
我們現在有一個數據集,它由兩類數據組成,數據分布如下圖所示:
我們現在用 p1(x,y) 表示數據點 (x,y) 屬于類別 1(圖中用圓點表示的類別)的概率,用 p2(x,y) 表示數據點 (x,y) 屬于類別 2(圖中三角形表示的類別)的概率,那么對于一個新數據點 (x,y),可以用下面的規則來判斷它的類別:
- 如果 p1(x,y) > p2(x,y) ,那么類別為1
- 如果 p2(x,y) > p1(x,y) ,那么類別為2
也就是說,我們會選擇高概率對應的類別。這就是貝葉斯決策理論的核心思想,即選擇具有最高概率的決策。
使用條件概率來分類
上面我們提到貝葉斯決策理論要求計算兩個概率 p1(x, y) 和 p2(x, y):
- 如果 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) 并不一樣,不過可以使用貝葉斯準則來交換概率中條件與結果。具體地,應用貝葉斯準則得到:
使用上面這些定義,可以定義貝葉斯分類準則為:
- 如果 P(c1|x, y) > P(c2|x, y), 那么屬于類別 c1;
- 如果 P(c2|x, y) > P(c1|x, y), 那么屬于類別 c2.
在文檔分類中,整個文檔(如一封電子郵件)是實例,而電子郵件中的某些元素則構成特征。我們可以觀察文檔中出現的詞,并把每個詞作為一個特征,而每個詞的出現或者不出現作為該特征的值,這樣得到的特征數目就會跟詞匯表中的詞的數目一樣多。
我們假設特征之間?相互獨立?。所謂?獨立(independence)?指的是統計意義上的獨立,即一個特征或者單詞出現的可能性與它和其他單詞相鄰沒有關系,比如說,“我們”中的“我”和“們”出現的概率與這兩個字相鄰沒有任何關系。這個假設正是樸素貝葉斯分類器中 樸素(naive) 一詞的含義。樸素貝葉斯分類器中的另一個假設是,每個特征同等重要。
Note:?樸素貝葉斯分類器通常有兩種實現方式: 一種基于伯努利模型實現,一種基于多項式模型實現。這里采用前一種實現方式。該實現方式中并不考慮詞在文檔中出現的次數,只考慮出不出現,因此在這個意義上相當于假設詞是等權重的。
樸素貝葉斯 場景
機器學習的一個重要應用就是文檔的自動分類。
在文檔分類中,整個文檔(如一封電子郵件)是實例,而電子郵件中的某些元素則構成特征。我們可以觀察文檔中出現的詞,并把每個詞作為一個特征,而每個詞的出現或者不出現作為該特征的值,這樣得到的特征數目就會跟詞匯表中的詞的數目一樣多。
樸素貝葉斯是上面介紹的貝葉斯分類器的一個擴展,是用于文檔分類的常用算法。下面我們會進行一些樸素貝葉斯分類的實踐項目。
樸素貝葉斯 原理
樸素貝葉斯 工作原理
提取所有文檔中的詞條并進行去重 獲取文檔的所有類別 計算每個類別中的文檔數目 對每篇訓練文檔: 對每個類別: 如果詞條出現在文檔中-->增加該詞條的計數值(for循環或者矩陣相加)增加所有詞條的計數值(此類別下詞條總數) 對每個類別: 對每個詞條: 將該詞條的數目除以總詞條數目得到的條件概率(P(詞條|類別)) 返回該文檔屬于每個類別的條件概率(P(類別|文檔的所有詞條))樸素貝葉斯 開發流程
收集數據: 可以使用任何方法。 準備數據: 需要數值型或者布爾型數據。 分析數據: 有大量特征時,繪制特征作用不大,此時使用直方圖效果更好。 訓練算法: 計算不同的獨立特征的條件概率。 測試算法: 計算錯誤率。 使用算法: 一個常見的樸素貝葉斯應用是文檔分類。可以在任意的分類場景中使用樸素貝葉斯分類器,不一定非要是文本。樸素貝葉斯 算法特點
優點: 在數據較少的情況下仍然有效,可以處理多類別問題。 缺點: 對于輸入數據的準備方式較為敏感。 適用數據類型: 標稱型數據。相關閱讀:https://blog.csdn.net/u012162613/article/details/48323777
樸素貝葉斯 項目案例
項目案例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 setfor 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 表示詞匯表中的單詞是否出現在輸入的數據集中"""# 創建一個和詞匯表等長的向量,并將其元素都設置為0result = [0] * len(vocab_list)# 遍歷文檔中的所有單詞,如果出現了詞匯表中的單詞,則將輸出的文檔向量中的對應值設為1for word in input_set:if word in vocab_list:result[vocab_list.index(word)] = 1else:# 這個后面應該注釋掉,因為對你沒什么用,這只是為了輔助調試的# print('the word: {} is not in my vocabulary'.format(word))passreturn 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 npdef 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.0p1num_all = 2.0for i in range(train_doc_num):# 遍歷所有的文件,如果是侮辱性文件,就計算此侮辱性文件中出現的侮辱性單詞的個數if train_category[i] == 1:p1num += train_mat[i] # 直接把兩個list相加,對應位置元素相加,# 最后直接一除就可以得到對應的概率listp1num_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 1else: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 setfor 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 表示詞匯表中的單詞是否出現在輸入的數據集中 """# 創建一個和詞匯表等長的向量,并將其元素都設置為0result = [0] * len(vocab_list)# 遍歷文檔中的所有單詞,如果出現了詞匯表中的單詞,則將輸出的文檔向量中的對應值設為1for word in input_set:if word in vocab_list:result[vocab_list.index(word)] = 1else:# 這個后面應該注釋掉,因為對你沒什么用,這只是為了輔助調試的# print('the word: {} is not in my vocabulary'.format(word))passreturn 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 npdef 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.0p1num_all = 2.0for i in range(train_doc_num):# 遍歷所有的文件,如果是侮辱性文件,就計算此侮辱性文件中出現的侮辱性單詞的個數if train_category[i] == 1:p1num += train_mat[i] # 直接把兩個list相加,對應位置元素相加,# 最后直接一除就可以得到對應的概率listp1num_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 1else: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轉載于:https://www.cnblogs.com/xinmomoyan/p/10494444.html
總結
以上是生活随笔為你收集整理的04机器学习实战之朴素贝叶斯的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MPLS笔记
- 下一篇: VMWare共享文件