機器學習實戰教程(五):樸素貝葉斯實戰篇之新浪新聞分類

原文連接: Jack-Cui,cuijiahua.com/blog/2017/1…html

1、前言

上篇文章機器學習實戰教程(四):樸素貝葉斯基礎篇之言論過濾器講解了樸素貝葉斯的基礎知識。本篇文章將在此基礎上進行擴展,你將看到如下內容:python

  • 拉普拉斯平滑
  • 垃圾郵件過濾(Python3)
  • 新浪新聞分類(sklearn)

2、樸素貝葉斯改進之拉普拉斯平滑

上篇文章提到過,算法存在必定的問題,須要進行改進。那麼須要改進的地方在哪裏呢?利用貝葉斯分類器對文檔進行分類時,要計算多個機率的乘積以得到文檔屬於某個類別的機率,即計算p(w0|1)p(w1|1)p(w2|1)。若是其中有一個機率值爲0,那麼最後的成績也爲0。咱們拿出上一篇文章的截圖。git

從上圖能夠看出,在計算的時候已經出現了機率爲0的狀況。若是新實例文本,包含這種機率爲0的分詞,那麼最終的文本屬於某個類別的機率也就是0了。顯然,這樣是不合理的,爲了下降這種影響,能夠將全部詞的出現數初始化爲1,並將分母初始化爲2。這種作法就叫作拉普拉斯平滑(Laplace Smoothing)又被稱爲加1平滑,是比較經常使用的平滑方法,它就是爲了解決0機率問題。github

除此以外,另一個遇到的問題就是下溢出,這是因爲太多很小的數相乘形成的。學過數學的人都知道,兩個小數相乘,越乘越小,這樣就形成了下溢出。在程序中,在相應小數位置進行四捨五入,計算結果可能就變成0了。爲了解決這個問題,對乘積結果取天然對數。經過求對數能夠避免下溢出或者浮點數舍入致使的錯誤。同時,採用天然對數進行處理不會有任何損失。下圖給出函數f(x)和ln(f(x))的曲線。算法

檢查這兩條曲線,就會發現它們在相同區域內同時增長或者減小,而且在相同點上取到極值。它們的取值雖然不一樣,但不影響最終結果。所以咱們能夠對上篇文章的trainNB0(trainMatrix, trainCategory)函數進行更改,修改以下:數組

""" 函數說明:樸素貝葉斯分類器訓練函數 Parameters: trainMatrix - 訓練文檔矩陣,即setOfWords2Vec返回的returnVec構成的矩陣 trainCategory - 訓練類別標籤向量,即loadDataSet返回的classVec Returns: p0Vect - 非侮辱類的條件機率數組 p1Vect - 侮辱類的條件機率數組 pAbusive - 文檔屬於侮辱類的機率 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-12 """
def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)                            #計算訓練的文檔數目
    numWords = len(trainMatrix[0])                            #計算每篇文檔的詞條數
    pAbusive = sum(trainCategory)/float(numTrainDocs)        #文檔屬於侮辱類的機率
    p0Num = np.ones(numWords); p1Num = np.ones(numWords)    #建立numpy.ones數組,詞條出現數初始化爲1,拉普拉斯平滑
    p0Denom = 2.0; p1Denom = 2.0                            #分母初始化爲2,拉普拉斯平滑
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:                            #統計屬於侮辱類的條件機率所需的數據,即P(w0|1),P(w1|1),P(w2|1)···
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:                                                #統計屬於非侮辱類的條件機率所需的數據,即P(w0|0),P(w1|0),P(w2|0)···
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = np.log(p1Num/p1Denom)                            #取對數,防止下溢出 
    p0Vect = np.log(p0Num/p0Denom)         
    return p0Vect,p1Vect,pAbusive                            #返回屬於侮辱類的條件機率數組,屬於非侮辱類的條件機率數組,文檔屬於侮辱類的機率
複製代碼

運行代碼,就能夠獲得以下結果:bash

瞧,這樣咱們獲得的結果就沒有問題了,不存在0機率。固然除此以外,咱們還須要對代碼進行修改classifyNB(vec2Classify, p0Vec, p1Vec, pClass1)函數,修改以下:app

""" 函數說明:樸素貝葉斯分類器分類函數 Parameters: vec2Classify - 待分類的詞條數組 p0Vec - 非侮辱類的條件機率數組 p1Vec -侮辱類的條件機率數組 pClass1 - 文檔屬於侮辱類的機率 Returns: 0 - 屬於非侮辱類 1 - 屬於侮辱類 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-12 """
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)        #對應元素相乘。logA * B = logA + logB,因此這裏加上log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0
複製代碼

爲啥這麼改?由於取天然對數了。logab = loga + logb。dom

這樣,咱們的樸素貝葉斯分類器就改進完畢了。機器學習

3、樸素貝葉斯之過濾垃圾郵件

在上篇文章那個簡單的例子中,咱們引入了字符串列表。使用樸素貝葉斯解決一些現實生活中的問題時,須要先從文本內容獲得字符串列表,而後生成詞向量。下面這個例子中,咱們將瞭解樸素貝葉斯的一個最著名的應用:電子郵件垃圾過濾。首先看一下使用樸素貝葉斯對電子郵件進行分類的步驟:

  • 收集數據:提供文本文件。
  • 準備數據:將文本文件解析成詞條向量。
  • 分析數據:檢查詞條確保解析的正確性。
  • 訓練算法:使用咱們以前創建的trainNB0()函數。
  • 測試算法:使用classifyNB(),並構建一個新的測試函數來計算文檔集的錯誤率。
  • 使用算法:構建一個完整的程序對一組文檔進行分類,將錯分的文檔輸出到屏幕上。

一、收集數據

數據我已經爲你們準備好了,能夠在個人Github上下載: 數據集下載

有兩個文件夾ham和spam,spam文件下的txt文件爲垃圾郵件。

二、準備數據

對於英文文本,咱們能夠以非字母、非數字做爲符號進行切分,使用split函數便可。編寫代碼以下:

# -*- coding: UTF-8 -*-
import re
 
""" 函數說明:接收一個大字符串並將其解析爲字符串列表 Parameters: 無 Returns: 無 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-14 """
def textParse(bigString):                                                   #將字符串轉換爲字符列表
    listOfTokens = re.split(r'\W*', bigString)                              #將特殊符號做爲切分標誌進行字符串切分,即非字母、非數字
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]            #除了單個字母,例如大寫的I,其它單詞變成小寫
 
""" 函數說明:將切分的實驗樣本詞條整理成不重複的詞條列表,也就是詞彙表 Parameters: dataSet - 整理的樣本數據集 Returns: vocabSet - 返回不重複的詞條列表,也就是詞彙表 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def createVocabList(dataSet):
    vocabSet = set([])                      #建立一個空的不重複列表
    for document in dataSet:               
        vocabSet = vocabSet | set(document) #取並集
    return list(vocabSet)
 
if __name__ == '__main__':
    docList = []; classList = []
    for i in range(1, 26):                                                  #遍歷25個txt文件
        wordList = textParse(open('email/spam/%d.txt' % i, 'r').read())     #讀取每一個垃圾郵件,並字符串轉換成字符串列表
        docList.append(wordList)
        classList.append(1)                                                 #標記垃圾郵件,1表示垃圾文件
        wordList = textParse(open('email/ham/%d.txt' % i, 'r').read())      #讀取每一個非垃圾郵件,並字符串轉換成字符串列表
        docList.append(wordList)
        classList.append(0)                                                 #標記非垃圾郵件,1表示垃圾文件 
    vocabList = createVocabList(docList)                                    #建立詞彙表,不重複
    print(vocabList)
複製代碼

這樣咱們就獲得了詞彙表,結果以下圖所示:

根據詞彙表,咱們就能夠將每一個文本向量化。咱們將數據集分爲訓練集和測試集,使用交叉驗證的方式測試樸素貝葉斯分類器的準確性。編寫代碼以下:

# -*- coding: UTF-8 -*-
import numpy as np
import random
import re
 
""" 函數說明:將切分的實驗樣本詞條整理成不重複的詞條列表,也就是詞彙表 Parameters: dataSet - 整理的樣本數據集 Returns: vocabSet - 返回不重複的詞條列表,也就是詞彙表 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def createVocabList(dataSet):
    vocabSet = set([])                      #建立一個空的不重複列表
    for document in dataSet:               
        vocabSet = vocabSet | set(document) #取並集
    return list(vocabSet)
 
""" 函數說明:根據vocabList詞彙表,將inputSet向量化,向量的每一個元素爲1或0 Parameters: vocabList - createVocabList返回的列表 inputSet - 切分的詞條列表 Returns: returnVec - 文檔向量,詞集模型 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)                                    #建立一個其中所含元素都爲0的向量
    for word in inputSet:                                                #遍歷每一個詞條
        if word in vocabList:                                            #若是詞條存在於詞彙表中,則置1
            returnVec[vocabList.index(word)] = 1
        else: print("the word: %s is not in my Vocabulary!" % word)
    return returnVec                                                    #返回文檔向量
 
 
""" 函數說明:根據vocabList詞彙表,構建詞袋模型 Parameters: vocabList - createVocabList返回的列表 inputSet - 切分的詞條列表 Returns: returnVec - 文檔向量,詞袋模型 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-14 """
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0]*len(vocabList)                                        #建立一個其中所含元素都爲0的向量
複製代碼

運行結果以下:

函數spamTest()會輸出在10封隨機選擇的電子郵件上的分類錯誤機率。既然這些電子郵件是隨機選擇的,因此每次的輸出結果可能有些差異。若是發現錯誤的話,函數會輸出錯誤的文檔的此表,這樣就能夠了解究竟是哪篇文檔發生了錯誤。若是想要更好地估計錯誤率,那麼就應該將上述過程重複屢次,好比說10次,而後求平均值。相比之下,將垃圾郵件誤判爲正常郵件要比將正常郵件歸爲垃圾郵件好。爲了不錯誤,有多種方式能夠用來修正分類器,這些內容會在後續文章中進行討論。

這部分代碼獲取:代碼獲取

4、樸素貝葉斯之新浪新聞分類(Sklearn)

一、中文語句切分

考慮一個問題,英文的語句能夠經過非字母和非數字進行切分,可是漢語句子呢?就好比我打的這一堆字,該如何進行切分呢?咱們本身寫個規則?

幸運地是,這部分的工做不須要咱們本身作了,能夠直接使用第三方分詞組件,即jieba,沒錯就是"結巴"。

jieba已經兼容Python2和Python3,使用以下指令直接安裝便可:

pip3 install jieba
複製代碼

Python中文分詞組件使用簡單:

民間教程:www.oschina.net/p/jieba 官方教程:github.com/fxsjy/jieba 新聞分類數據集我也已經準備好,能夠到個人Github進行下載:數據集下載

數據集已經作好分類,分文件夾保存,分類結果以下:

數據集已經準備好,接下來,讓咱們直接進入正題。切分中文語句,編寫以下代碼:

# -*- coding: UTF-8 -*-
import os
import jieba
 
def TextProcessing(folder_path):
    folder_list = os.listdir(folder_path)                        #查看folder_path下的文件
    data_list = []                                                #訓練集
    class_list = []
 
    #遍歷每一個子文件夾
    for folder in folder_list:
        new_folder_path = os.path.join(folder_path, folder)        #根據子文件夾,生成新的路徑
        files = os.listdir(new_folder_path)                        #存放子文件夾下的txt文件的列表
 
        j = 1
        #遍歷每一個txt文件
        for file in files:
            if j > 100:                                            #每類txt樣本數最多100個
                break
            with open(os.path.join(new_folder_path, file), 'r', encoding = 'utf-8') as f:    #打開txt文件
                raw = f.read()
 
            word_cut = jieba.cut(raw, cut_all = False)            #精簡模式,返回一個可迭代的generator
            word_list = list(word_cut)                            #generator轉換爲list
 
            data_list.append(word_list)
            class_list.append(folder)
            j += 1
        print(data_list)
        print(class_list)
if __name__ == '__main__':
    #文本預處理
    folder_path = './SogouC/Sample'                #訓練集存放地址
    TextProcessing(folder_path)
複製代碼

代碼運行結果以下所示,能夠看到,咱們已經順利將每一個文本進行切分,並進行了類別標記。

二、文本特徵選擇

咱們將全部文本分紅訓練集和測試集,並對訓練集中的全部單詞進行詞頻統計,並按降序排序。也就是將出現次數多的詞語在前,出現次數少的詞語在後進行排序。編寫代碼以下:

# -*- coding: UTF-8 -*-
import os
import random
import jieba
 
""" 函數說明:中文文本處理 Parameters: folder_path - 文本存放的路徑 test_size - 測試集佔比,默認佔全部數據集的百分之20 Returns: all_words_list - 按詞頻降序排序的訓練集列表 train_data_list - 訓練集列表 test_data_list - 測試集列表 train_class_list - 訓練集標籤列表 test_class_list - 測試集標籤列表 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-22 """
def TextProcessing(folder_path, test_size = 0.2):
    folder_list = os.listdir(folder_path)                        #查看folder_path下的文件
    data_list = []                                                #數據集數據
    class_list = []                                                #數據集類別
 
    #遍歷每一個子文件夾
    for folder in folder_list:
        new_folder_path = os.path.join(folder_path, folder)        #根據子文件夾,生成新的路徑
        files = os.listdir(new_folder_path)                        #存放子文件夾下的txt文件的列表
 
        j = 1
        #遍歷每一個txt文件
        for file in files:
            if j > 100:                                            #每類txt樣本數最多100個
                break
            with open(os.path.join(new_folder_path, file), 'r', encoding = 'utf-8') as f:    #打開txt文件
                raw = f.read()
 
            word_cut = jieba.cut(raw, cut_all = False)            #精簡模式,返回一個可迭代的generator
            word_list = list(word_cut)                            #generator轉換爲list
 
            data_list.append(word_list)                            #添加數據集數據
            class_list.append(folder)                            #添加數據集類別
            j += 1
 
    data_class_list = list(zip(data_list, class_list))            #zip壓縮合並,將數據與標籤對應壓縮
    random.shuffle(data_class_list)                                #將data_class_list亂序
    index = int(len(data_class_list) * test_size) + 1            #訓練集和測試集切分的索引值
    train_list = data_class_list[index:]                        #訓練集
    test_list = data_class_list[:index]                            #測試集
    train_data_list, train_class_list = zip(*train_list)        #訓練集解壓縮
    test_data_list, test_class_list = zip(*test_list)            #測試集解壓縮
 
    all_words_dict = {}                                            #統計訓練集詞頻
    for word_list in train_data_list:
        for word in word_list:
            if word in all_words_dict.keys():
                all_words_dict[word] += 1
            else:
                all_words_dict[word] = 1
 
    #根據鍵的值倒序排序
    all_words_tuple_list = sorted(all_words_dict.items(), key = lambda f:f[1], reverse = True)
    all_words_list, all_words_nums = zip(*all_words_tuple_list)    #解壓縮
    all_words_list = list(all_words_list)                        #轉換成列表
    return all_words_list, train_data_list, test_data_list, train_class_list, test_class_list
 
if __name__ == '__main__':
    #文本預處理
    folder_path = './SogouC/Sample'                #訓練集存放地址
    all_words_list, train_data_list, test_data_list, train_class_list, test_class_list = TextProcessing(folder_path, test_size=0.2)
    print(all_words_list)
複製代碼

all_words_list就是將全部訓練集的切分結果經過詞頻降序排列構成的單詞合集。觀察一下打印結果,不難發現,這裏包含了不少標點符號,很顯然,這些標點符號是不能做爲新聞分類的特徵的。總不能說,應爲這個文章逗號多,因此它是xx類新聞吧?爲了下降這些高頻的符號對分類結果的影響,咱們應該怎麼作呢?答曰:拋棄他們! 除了這些,還有"在","了"這樣對新聞分類無關痛癢的詞。而且還有一些數字,數字顯然也不能做爲分類新聞的特徵。因此要消除它們對分類結果的影響,咱們能夠定製一個規則。

一個簡單的規則能夠這樣制定:首先去掉高頻詞,至於去掉多少個高頻詞,咱們能夠經過觀察去掉高頻詞個數和最終檢測準確率的關係來肯定。除此以外,去除數字,不把數字做爲分類特徵。同時,去除一些特定的詞語,好比:"的","一","在","不","固然","怎麼"這類的對新聞分類無影響的介詞、代詞、連詞。怎麼去除這些詞呢?可使用已經整理好的stopwords_cn.txt文本。下載地址:點我下載

這個文件是這個樣子的:

因此咱們能夠根據這個文檔,將這些單詞去除,不做爲分類的特徵。咱們先去除前100個高頻詞彙,而後編寫代碼以下:

# -*- coding: UTF-8 -*-
import os
import random
import jieba
 
""" 函數說明:中文文本處理 Parameters: folder_path - 文本存放的路徑 test_size - 測試集佔比,默認佔全部數據集的百分之20 Returns: all_words_list - 按詞頻降序排序的訓練集列表 train_data_list - 訓練集列表 test_data_list - 測試集列表 train_class_list - 訓練集標籤列表 test_class_list - 測試集標籤列表 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-22 """
def TextProcessing(folder_path, test_size = 0.2):
    folder_list = os.listdir(folder_path)                        #查看folder_path下的文件
    data_list = []                                                #數據集數據
    class_list = []                                                #數據集類別
 
    #遍歷每一個子文件夾
    for folder in folder_list:
        new_folder_path = os.path.join(folder_path, folder)        #根據子文件夾,生成新的路徑
        files = os.listdir(new_folder_path)                        #存放子文件夾下的txt文件的列表
 
        j = 1
        #遍歷每一個txt文件
        for file in files:
            if j > 100:                                            #每類txt樣本數最多100個
                break
            with open(os.path.join(new_folder_path, file), 'r', encoding = 'utf-8') as f:    #打開txt文件
                raw = f.read()
 
            word_cut = jieba.cut(raw, cut_all = False)            #精簡模式,返回一個可迭代的generator
            word_list = list(word_cut)                            #generator轉換爲list
 
            data_list.append(word_list)                            #添加數據集數據
            class_list.append(folder)                            #添加數據集類別
            j += 1
 
    data_class_list = list(zip(data_list, class_list))            #zip壓縮合並,將數據與標籤對應壓縮
    random.shuffle(data_class_list)                                #將data_class_list亂序
    index = int(len(data_class_list) * test_size) + 1            #訓練集和測試集切分的索引值
    train_list = data_class_list[index:]                        #訓練集
    test_list = data_class_list[:index]                            #測試集
    train_data_list, train_class_list = zip(*train_list)        #訓練集解壓縮
    test_data_list, test_class_list = zip(*test_list)            #測試集解壓縮
 
    all_words_dict = {}                                            #統計訓練集詞頻
    for word_list in train_data_list:
        for word in word_list:
            if word in all_words_dict.keys():
                all_words_dict[word] += 1
            else:
                all_words_dict[word] = 1
 
    #根據鍵的值倒序排序
    all_words_tuple_list = sorted(all_words_dict.items(), key = lambda f:f[1], reverse = True)
    all_words_list, all_words_nums = zip(*all_words_tuple_list)    #解壓縮
    all_words_list = list(all_words_list)                        #轉換成列表
    return all_words_list, train_data_list, test_data_list, train_class_list, test_class_list
 
""" 函數說明:讀取文件裏的內容,並去重 Parameters: words_file - 文件路徑 Returns: words_set - 讀取的內容的set集合 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-22 """
def MakeWordsSet(words_file):
    words_set = set()                                            #建立set集合
    with open(words_file, 'r', encoding = 'utf-8') as f:        #打開文件
        for line in f.readlines():                                #一行一行讀取
            word = line.strip()                                    #去回車
            if len(word) > 0:                                    #有文本,則添加到words_set中
                words_set.add(word)                               
    return words_set                                             #返回處理結果
 
""" 函數說明:文本特徵選取 Parameters: all_words_list - 訓練集全部文本列表 deleteN - 刪除詞頻最高的deleteN個詞 stopwords_set - 指定的結束語 Returns: feature_words - 特徵集 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-22 """
def words_dict(all_words_list, deleteN, stopwords_set = set()):
    feature_words = []                            #特徵列表
    n = 1
    for t in range(deleteN, len(all_words_list), 1):
        if n > 1000:                            #feature_words的維度爲1000
            break                               
        #若是這個詞不是數字,而且不是指定的結束語,而且單詞長度大於1小於5,那麼這個詞就能夠做爲特徵詞
        if not all_words_list[t].isdigit() and all_words_list[t] not in stopwords_set and 1 < len(all_words_list[t]) < 5:
            feature_words.append(all_words_list[t])
        n += 1   
    return feature_words
 
if __name__ == '__main__':
    #文本預處理
    folder_path = './SogouC/Sample'                #訓練集存放地址
    all_words_list, train_data_list, test_data_list, train_class_list, test_class_list = TextProcessing(folder_path, test_size=0.2)
 
    #生成stopwords_set
    stopwords_file = './stopwords_cn.txt'
    stopwords_set = MakeWordsSet(stopwords_file)
 
    feature_words = words_dict(all_words_list, 100, stopwords_set)
    print(feature_words)
複製代碼

運行結果以下:

能夠看到,咱們已經濾除了那些沒有用的詞組,這個feature_words就是咱們最終選出的用於新聞分類的特徵。隨後,咱們就能夠根據feature_words,將文本向量化,而後用於訓練樸素貝葉斯分類器。這個向量化的思想和第三章的思想一致,所以再也不累述。

三、使用Sklearn構建樸素貝葉斯分類器

數據已經處理好了,接下來就可使用sklearn構建樸素貝葉斯分類器了。

官方英文文檔地址:文檔地址

樸素貝葉斯是一類比較簡單的算法,scikit-learn中樸素貝葉斯類庫的使用也比較簡單。相對於決策樹,KNN之類的算法,樸素貝葉斯須要關注的參數是比較少的,這樣也比較容易掌握。在scikit-learn中,一共有3個樸素貝葉斯的分類算法類。分別是GaussianNB,MultinomialNB和BernoulliNB。其中GaussianNB就是先驗爲高斯分佈的樸素貝葉斯,MultinomialNB就是先驗爲多項式分佈的樸素貝葉斯,而BernoulliNB就是先驗爲伯努利分佈的樸素貝葉斯。上篇文章講解的先驗機率模型就是先驗機率爲多項式分佈的樸素貝葉斯。

對於新聞分類,屬於多分類問題。咱們可使用MultinamialNB()完成咱們的新聞分類問題。另外兩個函數的使用暫且再也不進行擴展,能夠自行學習。MultinomialNB假設特徵的先驗機率爲多項式分佈,即以下式:

其中, P(Xj = Xjl | Y = Ck)是第k個類別的第j維特徵的第l個取值條件機率。mk是訓練集中輸出爲第k類的樣本個數。λ爲一個大於0的常數,經常取值爲1,即拉普拉斯平滑,也能夠取其餘值。

接下來,咱們看下MultinamialNB這個函數,只有3個參數:

參數說明以下:

  • alpha:浮點型可選參數,默認爲1.0,其實就是添加拉普拉斯平滑,即爲上述公式中的λ ,若是這個參數設置爲0,就是不添加平滑;
  • fit_prior:布爾型可選參數,默認爲True。布爾參數fit_prior表示是否要考慮先驗機率,若是是false,則全部的樣本類別輸出都有相同的類別先驗機率。不然能夠本身用第三個參數class_prior輸入先驗機率,或者不輸入第三個參數class_prior讓MultinomialNB本身從訓練集樣原本計算先驗機率,此時的先驗機率爲P(Y=Ck)=mk/m。其中m爲訓練集樣本總數量,mk爲輸出爲第k類別的訓練集樣本數。
  • class_prior:可選參數,默認爲None。

總結以下:

除此以外,MultinamialNB也有一些方法供咱們使用:

MultinomialNB一個重要的功能是有partial_fit方法,這個方法的通常用在若是訓練集數據量很是大,一次不能所有載入內存的時候。這時咱們能夠把訓練集分紅若干等分,重複調用partial_fit來一步步的學習訓練集,很是方便。GaussianNB和BernoulliNB也有相似的功能。 在使用MultinomialNB的fit方法或者partial_fit方法擬合數據後,咱們能夠進行預測。此時預測有三種方法,包括predict,predict_log_proba和predict_proba。predict方法就是咱們最經常使用的預測方法,直接給出測試集的預測類別輸出。predict_proba則不一樣,它會給出測試集樣本在各個類別上預測的機率。容易理解,predict_proba預測出的各個類別機率裏的最大值對應的類別,也就是predict方法獲得類別。predict_log_proba和predict_proba相似,它會給出測試集樣本在各個類別上預測的機率的一個對數轉化。轉化後predict_log_proba預測出的各個類別對數機率裏的最大值對應的類別,也就是predict方法獲得類別。具體細節再也不講解,可參照官網手冊。

瞭解了這些,咱們就能夠編寫代碼,經過觀察取不一樣的去掉前deleteN個高頻詞的個數與最終檢測準確率的關係,肯定deleteN的取值:

# -*- coding: UTF-8 -*-
from sklearn.naive_bayes import MultinomialNB
import matplotlib.pyplot as plt
import os
import random
import jieba
 
""" 函數說明:中文文本處理 Parameters: folder_path - 文本存放的路徑 test_size - 測試集佔比,默認佔全部數據集的百分之20 Returns: all_words_list - 按詞頻降序排序的訓練集列表 train_data_list - 訓練集列表 test_data_list - 測試集列表 train_class_list - 訓練集標籤列表 test_class_list - 測試集標籤列表 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-22 """
def TextProcessing(folder_path, test_size = 0.2):
    folder_list = os.listdir(folder_path)                        #查看folder_path下的文件
    data_list = []                                                #數據集數據
    class_list = []                                                #數據集類別
 
    #遍歷每一個子文件夾
    for folder in folder_list:
        new_folder_path = os.path.join(folder_path, folder)        #根據子文件夾,生成新的路徑
        files = os.listdir(new_folder_path)                        #存放子文件夾下的txt文件的列表
 
        j = 1
        #遍歷每一個txt文件
        for file in files:
            if j > 100:                                            #每類txt樣本數最多100個
                break
            with open(os.path.join(new_folder_path, file), 'r', encoding = 'utf-8') as f:    #打開txt文件
                raw = f.read()
 
            word_cut = jieba.cut(raw, cut_all = False)            #精簡模式,返回一個可迭代的generator
            word_list = list(word_cut)                            #generator轉換爲list
 
            data_list.append(word_list)                            #添加數據集數據
            class_list.append(folder)                            #添加數據集類別
            j += 1
 
    data_class_list = list(zip(data_list, class_list))            #zip壓縮合並,將數據與標籤對應壓縮
    random.shuffle(data_class_list)                                #將data_class_list亂序
    index = int(len(data_class_list) * test_size) + 1            #訓練集和測試集切分的索引值
    train_list = data_class_list[index:]                        #訓練集
    test_list = data_class_list[:index]                            #測試集
    train_data_list, train_class_list = zip(*train_list)        #訓練集解壓縮
    test_data_list, test_class_list = zip(*test_list)            #測試集解壓縮
 
    all_words_dict = {}                                            #統計訓練集詞頻
    for word_list in train_data_list:
        for word in word_list:
            if word in all_words_dict.keys():
                all_words_dict[word] += 1
            else:
                all_words_dict[word] = 1
 
    #根據鍵的值倒序排序
    all_words_tuple_list = sorted(all_words_dict.items(), key = lambda f:f[1], reverse = True)
    all_words_list, all_words_nums = zip(*all_words_tuple_list)    #解壓縮
    all_words_list = list(all_words_list)                        #轉換成列表
    return all_words_list, train_data_list, test_data_list, train_class_list, test_class_list
 
""" 函數說明:讀取文件裏的內容,並去重 Parameters: words_file - 文件路徑 Returns: words_set - 讀取的內容的set集合 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-22 """
def MakeWordsSet(words_file):
    words_set = set()                                            #建立set集合
    with open(words_file, 'r', encoding = 'utf-8') as f:        #打開文件
        for line in f.readlines():                                #一行一行讀取
            word = line.strip()                                    #去回車
            if len(word) > 0:                                    #有文本,則添加到words_set中
                words_set.add(word)                               
    return words_set                                             #返回處理結果
 
""" 函數說明:根據feature_words將文本向量化 Parameters: train_data_list - 訓練集 test_data_list - 測試集 feature_words - 特徵集 Returns: train_feature_list - 訓練集向量化列表 test_feature_list - 測試集向量化列表 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-22 """
def TextFeatures(train_data_list, test_data_list, feature_words):
    def text_features(text, feature_words):                        #出如今特徵集中,則置1 
        text_words = set(text)
        features = [1 if word in text_words else 0 for word in feature_words]
        return features
    train_feature_list = [text_features(text, feature_words) for text in train_data_list]
    test_feature_list = [text_features(text, feature_words) for text in test_data_list]
    return train_feature_list, test_feature_list                #返回結果
 
 
""" 函數說明:文本特徵選取 Parameters: all_words_list - 訓練集全部文本列表 deleteN - 刪除詞頻最高的deleteN個詞 stopwords_set - 指定的結束語 Returns: feature_words - 特徵集 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-22 """
def words_dict(all_words_list, deleteN, stopwords_set = set()):
    feature_words = []                            #特徵列表
    n = 1
    for t in range(deleteN, len(all_words_list), 1):
        if n > 1000:                            #feature_words的維度爲1000
            break                               
        #若是這個詞不是數字,而且不是指定的結束語,而且單詞長度大於1小於5,那麼這個詞就能夠做爲特徵詞
        if not all_words_list[t].isdigit() and all_words_list[t] not in stopwords_set and 1 < len(all_words_list[t]) < 5:
            feature_words.append(all_words_list[t])
        n += 1
    return feature_words
 
""" 函數說明:新聞分類器 Parameters: train_feature_list - 訓練集向量化的特徵文本 test_feature_list - 測試集向量化的特徵文本 train_class_list - 訓練集分類標籤 test_class_list - 測試集分類標籤 Returns: test_accuracy - 分類器精度 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-22 """
def TextClassifier(train_feature_list, test_feature_list, train_class_list, test_class_list):
    classifier = MultinomialNB().fit(train_feature_list, train_class_list)
    test_accuracy = classifier.score(test_feature_list, test_class_list)
    return test_accuracy
 
if __name__ == '__main__':
    #文本預處理
    folder_path = './SogouC/Sample'                #訓練集存放地址
    all_words_list, train_data_list, test_data_list, train_class_list, test_class_list = TextProcessing(folder_path, test_size=0.2)
 
    # 生成stopwords_set
    stopwords_file = './stopwords_cn.txt'
    stopwords_set = MakeWordsSet(stopwords_file)
 
 
    test_accuracy_list = []
    deleteNs = range(0, 1000, 20)                #0 20 40 60 ... 980
    for deleteN in deleteNs:
        feature_words = words_dict(all_words_list, deleteN, stopwords_set)
        train_feature_list, test_feature_list = TextFeatures(train_data_list, test_data_list, feature_words)
        test_accuracy = TextClassifier(train_feature_list, test_feature_list, train_class_list, test_class_list)
        test_accuracy_list.append(test_accuracy)
 
    plt.figure()
    plt.plot(deleteNs, test_accuracy_list)
    plt.title('Relationship of deleteNs and test_accuracy')
    plt.xlabel('deleteNs')
    plt.ylabel('test_accuracy')
    plt.show()
複製代碼

運行結果以下:

咱們繪製出了deleteNs和test_accuracy的關係,這樣咱們就能夠大體肯定去掉前多少的高頻詞彙了。每次運行程序,繪製的圖形可能不盡相同,咱們能夠經過屢次測試,來決定這個deleteN的取值,而後肯定這個參數,這樣就能夠順利構建出用於新聞分類的樸素貝葉斯分類器了。我測試感受450還不錯,最差的分類準確率也能夠達到百分之50以上。將if name == 'main'下的代碼修改以下:

if __name__ == '__main__':
    #文本預處理
    folder_path = './SogouC/Sample'                #訓練集存放地址
    all_words_list, train_data_list, test_data_list, train_class_list, test_class_list = TextProcessing(folder_path, test_size=0.2)
 
    # 生成stopwords_set
    stopwords_file = './stopwords_cn.txt'
    stopwords_set = MakeWordsSet(stopwords_file)
 
 
    test_accuracy_list = []
    feature_words = words_dict(all_words_list, 450, stopwords_set)
    train_feature_list, test_feature_list = TextFeatures(train_data_list, test_data_list, feature_words)
    test_accuracy = TextClassifier(train_feature_list, test_feature_list, train_class_list, test_class_list)
    test_accuracy_list.append(test_accuracy)
    ave = lambda c: sum(c) / len(c)
 
    print(ave(test_accuracy_list))
複製代碼

運行結果:

5、總結

  • 在訓練樸素貝葉斯分類器以前,要處理好訓練集,文本的清洗仍是有不少須要學習的東西。
  • 根據提取的分類特徵將文本向量化,而後訓練樸素貝葉斯分類器。
  • 去高頻詞彙數量的不一樣,對結果也是有影響的的。
  • 拉普拉斯平滑對於改善樸素貝葉斯分類器的分類效果有着積極的做用。
  • 若有問題,請留言。若有錯誤,還望指正,謝謝!

PS: 若是以爲本篇本章對您有所幫助,歡迎關注、評論、贊!

本文出現的全部代碼和數據集,都可在個人github上下載,歡迎Follow、Star:github.com/Jack-Cheris…


相關文章和視頻推薦

圓方圓學院聚集 Python + AI 名師,打造精品的 Python + AI 技術課程。 在各大平臺都長期有優質免費公開課,歡迎報名收看。 公開課地址: ke.qq.com/course/3627…

加入python學習討論羣 78486745 ,獲取資料,和廣大羣友一塊兒學習。

圓方圓python技術討論羣
圓方圓python技術討論羣
相關文章
相關標籤/搜索