了解貝葉斯公式其實就隻要掌握:1、條件機率的定義;2、乘法原理
\[P(c_i|x) = \cfrac{P(x|c_i)P(c_i)}{P(x)}
\]
這裡 \(x\) 是一個向量,有幾個特征,就有幾個次元。樸素貝葉斯就假設這些特征獨立同分布,即
\[P(x|c_i) = P(x_1|c_i)P(x_2|c_i) \cdots P(x_n|c_i)
在實作樸素貝葉斯的時候,還要注意一寫技巧:
1、資料平滑處理;
2、在計算機中,多個小數相乘趨于 0 ,是以,常常對每一個機率取對數(這種情況很多書籍上稱之為“下溢”)。
資料下載下傳:http://archive.ics.uci.edu/ml/datasets.html
關于短信的分類在這個網頁下載下傳:http://archive.ics.uci.edu/ml/datasets/SMS+Spam+Collection
資料如下:每一行表示一條資訊和真實的分類結果。一行的開始不是 ham 就是 spam,其中,ham 表示合理合法的郵件,spam 表示是一些廣告,即垃圾短信。

資料預處理
每一行按照分隔符 "\t" 分割成前後兩部分:
第 1 部分:辨別短信是否是垃圾短信;
第 2 部分:一條短信的具體内容。
第 2 部分還要繼續做處理:
1、按照空格進行分割,即分詞,如果是中文短信,就要使用一些中文分詞庫了;
2、把分割以後的單詞全部處理成小寫;
文法上的大小寫不應該被算法認為是兩個詞。
3、去除停用詞:
這一步,實際上就是把一些常見的詞“你”、“我”、“他”、“是”、“的”之内的去掉,這些詞很大很長度上也隻是撐起了句子的結構,對表達句子的情感來說,沒有幫助。
- 我們的做法比較粗暴,把長度小于 3 的單詞全部去掉了。
參考代碼:
class FileOperate:
def __init__(self, data_path, label):
self.data_path = data_path
self.label = label
def load_data(self):
with open(self.data_path, 'r', encoding='utf-8') as fr:
content = fr.readlines()
print("一共 {} 條資料。".format(len(content)))
X = list()
y = list()
for line in content:
result = line.split(self.label, maxsplit=2)
X.append(FileOperate.__clean_data(result[1]))
y.append(1 if result[0]=='spam' else 0)
return X, y
@staticmethod
def __clean_data(origin_info):
'''
清洗資料,去掉非字母的字元,和位元組長度小于 2 的單詞
:return:
'''
# 先轉換成小寫
# 把标點符号都替換成空格
temp_info = re.sub('\W', ' ', origin_info.lower())
# 根據空格(大于等于 1 個空格)
words = re.split(r'\s+', temp_info)
return list(filter(lambda x: len(x) >= 3, words))
經過上面的處理,得到的一條短信的實際上是下面這樣一個單詞清單:
['until', 'jurong', 'point', 'crazy', 'available', 'only', 'bugis', 'great', 'world', 'buffet', 'cine', 'there', 'got', 'amore', 'wat']
接下來,把全部的資料集分成訓練資料集和測試資料集
開始訓練
根據公式
\(P(x)\) :對所有的資料都一樣,是以我們可以不用計算。
\(P(c_i)\):這是先驗機率,其實把 y 周遊一遍,就可以得到了。
\(P(x|c_i)\):因為我們假設 \(P(x|c_i) = P(x_1|c_i)P(x_2|c_i) \cdots P(x_n|c_i)\),是以就要對兩個類别都去做詞頻統計。具體細節如下:
1、首先建立單詞表,這個單詞表是從所有的資料中得到;
2、然後針對兩個類别,分别統計單詞表出現的次數,其實就是 word count,用一個 map(Python 中叫 dict)去統計詞頻;
這裡有個細節:
- 很可能在某個類别中,某個單詞不出現,即頻數為 0,那麼頻率也為 0,于是連乘以後積就為 0 ,在這裡就要做拉普拉斯平滑。同理,對類别也要做拉普拉斯平滑。
參考代碼(包含了預測的代碼,看這一部分的時候可以暫時略過,隻看 fit 的部分,fit 其實就是在做單詞頻數統計):
class NaiveBayes:
def __init__(self):
self.__ham_count = 0 # 非垃圾短信數量
self.__spam_count = 0 # 垃圾短信數量
self.__ham_words_count = 0 # 非垃圾短信單詞總數
self.__spam_words_count = 0 # 垃圾短信單詞總數
self.__ham_words = list() # 非垃圾短信單詞清單
self.__spam_words = list() # 垃圾短信單詞清單
# 訓練集中不重複單詞集合
self.__word_dictionary_set = set()
self.__word_dictionary_size = 0
self.__ham_map = dict() # 非垃圾短信的詞頻統計
self.__spam_map = dict() # 垃圾短信的詞頻統計
self.__ham_probability = 0
self.__spam_probability = 0
def fit(self, X_train, y_train):
self.build_word_set(X_train, y_train)
self.word_count()
def predict(self, X_train):
return [self.predict_one(sentence) for sentence in X_train]
def build_word_set(self, X_train, y_train):
'''
第 1 步:建立單詞集合
:param X_train:
:param y_train:
:return:
'''
for words, y in zip(X_train, y_train):
if y == 0:
# 非垃圾短信
self.__ham_count += 1
self.__ham_words_count += len(words)
for word in words:
self.__ham_words.append(word)
self.__word_dictionary_set.add(word)
if y == 1:
# 垃圾短信
self.__spam_count += 1
self.__spam_words_count += len(words)
for word in words:
self.__spam_words.append(word)
self.__word_dictionary_set.add(word)
# print('非垃圾短信數量', self.__ham_count)
# print('垃圾短信數量', self.__spam_count)
# print('非垃圾短信單詞總數', self.__ham_words_count)
# print('垃圾短信單詞總數', self.__spam_words_count)
# print(self.__word_dictionary_set)
self.__word_dictionary_size = len(self.__word_dictionary_set)
def word_count(self):
# 第 2 步:不同類别下的詞頻統計
for word in self.__ham_words:
self.__ham_map[word] = self.__ham_map.setdefault(word, 0) + 1
for word in self.__spam_words:
self.__spam_map[word] = self.__spam_map.setdefault(word, 0) + 1
# 【下面兩行計算先驗機率】
# 非垃圾短信的機率
self.__ham_probability = self.__ham_count / (self.__ham_count + self.__spam_count)
# 垃圾短信的機率
self.__spam_probability = self.__spam_count / (self.__ham_count + self.__spam_count)
def predict_one(self, sentence):
ham_pro = 0
spam_pro = 0
for word in sentence:
# print('word', word)
ham_pro += math.log(
(self.__ham_map.get(word, 0) + 1) / (self.__ham_count + self.__word_dictionary_size))
spam_pro += math.log(
(self.__spam_map.get(word, 0) + 1) / (self.__spam_count + self.__word_dictionary_size))
ham_pro += math.log(self.__ham_probability)
spam_pro += math.log(self.__spam_probability)
# print('垃圾短信機率', spam_pro)
# print('非垃圾短信機率', ham_pro)
return int(spam_pro >= ham_pro)
預測
我們再看看樸素貝葉斯公式:
\[P(c_i|x) = \cfrac{P(x|c_i)P(c_i)}{P(x)} = \cfrac{P(x_1|c_i)P(x_2|c_i) \cdots P(x_n|c_i) \cdot P(c_i)}{P(x)}
對于一條預測資料,分别針對兩個類,計算分子的對數,然後比較大小即可,即
\[\log P(x_1|c_i)P(x_2|c_i) \cdots P(x_n|c_i) \cdot P(c_i) = \log P(x_1|c_i) + \log P(x_2|c_i) \cdots + \log P(x_n|c_i) + \log \cdot P(c_i)
樸素貝葉斯其實就是這麼簡單。在這個資料集上,我們可以得到準确率:0.9755922469490309。
(把代碼傳到 GitHub 上。)
我們這個例子隻是對于二分類問題而言,并且特征都是離散型的。樸素貝葉斯在 scikit-learn 上有伯努利樸素貝葉斯(就是我們這個例子使用到的模型),多項式樸素貝葉斯、高斯樸素貝葉斯,這些在劉建平的文章《scikit-learn 樸素貝葉斯類庫使用小結》(https://www.cnblogs.com/pinard/p/6074222.html)中有介紹。
參考資料
1、李航《統計學習方法》
關鍵詞:貝葉斯估計、拉普拉斯平滑
2、周志華《機器學習》
這兩本教材上都給出了詳細的講解和例子。
3、《機器學習實戰》
這本書上給出了參考的代碼,但是代碼比較冗長,看起來會有點累。
其它在網絡上的參考資料:
樸素貝葉斯分類器詳解及中文文本輿情分析(附代碼實踐)
https://mp.weixin.qq.com/s/Pi30jA1xUbXg3dSdlvdU-g
深入了解樸素貝葉斯(Naive Bayes)
https://blog.csdn.net/li8zi8fa/article/details/76176597
劉建平:樸素貝葉斯算法原理小結
https://www.cnblogs.com/pinard/p/6069267.html