1. 程式人生 > >樸素貝葉斯演算法應用——垃圾簡訊分類

樸素貝葉斯演算法應用——垃圾簡訊分類

理解貝葉斯公式其實就只要掌握:1、條件概率的定義;2、乘法原理

P ( c i x )

= P ( x c i
) P ( c i )
P
( x )
P(c_i|x) = \cfrac{P(x|c_i)P(c_i)}{P(x)}

這裡 x x 是一個向量,有幾個特徵,就有幾個維度。樸素貝葉斯就假設這些特徵獨立同分布,即

P ( x c i ) = P ( x 1 c i ) P ( x 2 c i ) P ( x n c i ) 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 ( c i x ) = P ( x c i ) P ( c i ) P ( x ) P(c_i|x) = \cfrac{P(x|c_i)P(c_i)}{P(x)}

P ( x ) P(x) :對所有的資料都一樣,因此我們可以不用計算。
P ( c i ) P(c_i) :這是先驗概率,其實把 y 遍歷一遍,就可以得到了。
P ( x c i ) P(x|c_i) :因為我們假設 P ( x c i ) = P ( x 1 c i ) P ( x 2 c i ) P ( x n 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 ) = P ( x c i ) P ( c i ) P ( x ) = P ( x 1 c i ) P ( x 2 c i ) P ( x n c i ) P ( c i ) P ( x ) 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)}