導讀:樸素貝葉斯模型是機器學習經常使用的模型算法之一,其在文本分類方面簡單易行,且取得不錯的分類效果。因此很受歡迎,對於樸素貝葉斯的學習,本文首先介紹理論知識即樸素貝葉斯相關概念和公式推導,爲了加深理解,採用一個維基百科上面性別分類例子進行形式化描述。而後經過編程實現樸素貝葉斯分類算法,並在屏蔽社區言論、垃圾郵件、我的廣告中獲取區域傾向等幾個方面進行應用,包括建立數據集、數據預處理、詞集模型和詞袋模型、樸素貝葉斯模型訓練和優化等。而後結合復旦大學新聞語料進行樸素貝葉斯的應用。最後,你們熟悉其原理和實現以後,採用機器學習sklearn包進行實現和優化。因爲篇幅較長,採用理論理解、案例實現、sklearn優化三個部分進行學習。(本文原創,轉載必須註明出處: 一步步教你輕鬆學樸素貝葉斯模型算法Sklearn深度篇3)php
本節介紹樸素貝葉斯分類算法模型在中文領域中的應用。咱們對新聞語料進行多文本分類操做,本文選擇藝術、文學、教育、哲學、歷史五個類別的訓練文本,而後採用新的測試語料進行分類預測。html
數據集是從復旦新聞語料庫中抽取出來的,考慮學習使用,樣本選擇並不大。主要抽選藝術、文學、教育、哲學、歷史五個類別各10篇文章。所有數據文檔50篇。具體狀況不一樣對收集數據要求不一樣,你也能夠選擇網絡爬取,數據庫導出等。這文檔讀取時候可能會遇到gbk,utf-8等格式共存的狀況,這裏建議採用BatUTF8Conv.exe(點擊下載)工具,進行utf-8格式批量轉化。git
建立數據集代碼以下:github
'''建立數據集和類標籤''' def loadDataSet(): docList = [];classList = [] # 文檔列表、類別列表 dirlist = ['C3-Art','C4-Literature','C5-Education','C6-Philosophy','C7-History'] for j in range(5): for i in range(1, 11): # 總共10個文檔 # 切分,解析數據,並歸類爲 1 類別 wordList = textParse(open('./fudan/%s/%d.txt' % (dirlist[j],i),encoding='UTF-8').read()) docList.append(wordList) classList.append(j) # print(i,'\t','./fudan/%s/%d.txt' % (dirlist[j],i),'\t',j) return docList,classList ''' 利用jieba對文本進行分詞,返回切詞後的list ''' def textParse(str_doc): # 正則過濾掉特殊符號、標點、英文、數字等。 import re r1 = '[a-zA-Z0-9’!"#$%&\'()*+,-./:;<=>?@,。?★、…【】《》?「」‘’![\\]^_`{|}~]+' str_doc=re.sub(r1, '', str_doc) # 建立停用詞列表 stwlist = set([line.strip() for line in open('./stopwords.txt', 'r', encoding='utf-8').readlines()]) sent_list = str_doc.split('\n') # word_2dlist = [rm_tokens(jieba.cut(part), stwlist) for part in sent_list] # 分詞並去停用詞 word_2dlist = [rm_tokens([word+"/"+flag+" " for word, flag in pseg.cut(part) if flag in ['n','v','a','ns','nr','nt']], stwlist) for part in sent_list] # 帶詞性分詞並去停用詞 word_list = list(itertools.chain(*word_2dlist)) # 合併列表 return word_list ''' 去掉一些停用詞、數字、特殊符號 ''' def rm_tokens(words, stwlist): words_list = list(words) for i in range(words_list.__len__())[::-1]: word = words_list[i] if word in stwlist: # 去除停用詞 words_list.pop(i) elif len(word) == 1: # 去除單個字符 words_list.pop(i) elif word == " ": # 去除空字符 words_list.pop(i) return words_list
代碼分析:loadDataSet()方法是遍歷讀取文件夾,並對每篇文檔進行處理,最後返回所有文檔集的列表和類標籤。textParse()方法是對每篇文檔字符串進行數據預處理,咱們首選使用正則方法保留文本數據,而後進行帶有詞性的中文分詞和詞性選擇,rm_tokens()是去掉一些停用詞、數字、特殊符號。最終返回相對乾淨的數據集和標籤集。算法
前面兩篇文章都介紹了,咱們須要把文檔進行向量化表示,首先構建所有文章的單詞集合,實現代碼以下:數據庫
'''獲取全部文檔單詞的集合''' def createVocabList(dataSet): vocabSet = set([]) for document in dataSet: vocabSet = vocabSet | set(document) # 操做符 | 用於求兩個集合的並集 # print(len(vocabSet),len(set(vocabSet))) return list(vocabSet)
基於文檔模型的基礎上,咱們將特徵向量轉化爲數據矩陣向量,這裏使用的詞袋模型,構造與實現方法以下:apache
'''文檔詞袋模型,建立矩陣數據''' def bagOfWords2VecMN(vocabList, inputSet): returnVec = [0] * len(vocabList) for word in inputSet: if word in vocabList: returnVec[vocabList.index(word)] += 1 return returnVec
對矩陣數據能夠採用可視化分析方法或者結合NLTK進行數據分析,檢查數據分佈狀況和特徵向量構成狀況及其特徵選擇做爲參考。編程
咱們在前面兩篇文章介紹了樸素貝葉斯模型訓練方法,咱們在該方法下稍微改動就獲得以下實現:緩存
'''樸素貝葉斯模型訓練數據優化''' def trainNB0(trainMatrix, trainCategory): numTrainDocs = len(trainMatrix) # 總文件數 numWords = len(trainMatrix[0]) # 總單詞數 p1Num=p2Num=p3Num=p4Num=p5Num = ones(numWords) # 各種爲1的矩陣 p1Denom=p2Denom=p3Denom=p4Denom=p5Denom = 2.0 # 各種特徵和 num1=num2=num3=num4=num5 = 0 # 各種文檔數目 pNumlist=[p1Num,p2Num,p3Num,p4Num,p5Num] pDenomlist =[p1Denom,p2Denom,p3Denom,p4Denom,p5Denom] Numlist = [num1,num2,num3,num4,num5] for i in range(numTrainDocs): # 遍歷每篇訓練文檔 for j in range(5): # 遍歷每一個類別 if trainCategory[i] == j: # 若是在類別下的文檔 pNumlist[j] += trainMatrix[i] # 增長詞條計數值 pDenomlist[j] += sum(trainMatrix[i]) # 增長該類下全部詞條計數值 Numlist[j] +=1 # 該類文檔數目加1 pVect,pi = [],[] for index in range(5): pVect.append(log(pNumlist[index] / pDenomlist[index])) pi.append(Numlist[index] / float(numTrainDocs)) return pVect, pi
構建分類函數,其優化後的代碼實現以下:網絡
'''樸素貝葉斯分類函數,將乘法轉換爲加法''' def classifyNB(vec2Classify, pVect,pi): # 計算公式 log(P(F1|C))+log(P(F2|C))+....+log(P(Fn|C))+log(P(C)) bnpi = [] # 文檔分類到各種的機率值列表 for x in range(5): bnpi.append(sum(vec2Classify * pVect[x]) + log(pi[x])) # print([bnp for bnp in bnpi]) # 分類集合 reslist = ['Art','Literature','Education','Philosophy','History'] # 根據最大機率,選擇索引值 index = [bnpi.index(res) for res in bnpi if res==max(bnpi)] return reslist[index[0]] # 返回分類值
咱們加載構建的數據集方法,而後建立單詞集合,集合詞袋模型進行特徵向量化,構建訓練模型和分類方法,最終咱們從復旦新聞語料中選擇一篇未加入訓練集的教育類文檔,進行開放測試,具體代碼以下:
'''樸素貝葉斯新聞分類應用''' def testingNB(): # 1. 加載數據集 dataSet,Classlabels = loadDataSet() # 2. 建立單詞集合 myVocabList = createVocabList(dataSet) # 3. 計算單詞是否出現並建立數據矩陣 trainMat = [] for postinDoc in dataSet: trainMat.append(bagOfWords2VecMN(myVocabList, postinDoc)) with open('./word-bag.txt','w') as f: for i in trainMat: f.write(str(i)+'\r\n') # 4. 訓練數據 pVect,pi= trainNB0(array(trainMat), array(Classlabels)) # 5. 測試數據 testEntry = textParse(open('./fudan/test/C5-1.txt',encoding='UTF-8').read()) thisDoc = array(bagOfWords2VecMN(myVocabList, testEntry)) print(testEntry[:10], '分類結果是: ', classifyNB(thisDoc, pVect,pi))
實現結果以下:
Building prefix dict from the default dictionary ... Loading model from cache C:\Users\ADMINI~1\AppData\Local\Temp\jieba.cache Loading model cost 0.892 seconds. Prefix dict has been built succesfully. ['全國/n ', '舉辦/v ', '電影/n ', '新華社/nt ', '北京/ns ', '國家教委/nt ', '廣播電影電視部/nt ', '文化部/n ', '聯合/v ', '決定/v '] 分類結果是: Literature 耗時:29.4882 s
結果分析:咱們運行分類器得出結果易知,預測結果是文化類,且運行時間爲29s。首先分析爲何預測錯誤,這裏面主要是訓練集樣本比較少和特徵選擇的緣由。運行時間是因爲將特徵矩陣存儲本地後,後面直接讀取文本,至關於加載緩存,大大縮短運行時間。可是這裏還有值得優化的地方,好比每次運行都會加載訓練模型,大大消耗時間,咱們能不能訓練模型加載一次,屢次調用呢?固然是能夠的,這個問題下文繼續優化。咱們重點關注下特徵選擇問題
作文本分類的時候,遇到特徵矩陣1.5w。在測試篇幅小的文章老是分類錯誤?這個時候如何作特徵選擇?是否是說去掉特徵集中頻率極高和極低的一部分,對結果有所提高?
答:你說的這個狀況是很廣泛的現象,篇幅小的文章,特徵小,因此模型更容易判斷出錯!去掉高頻和低頻一般是可使得訓練的模型泛化能力變強
好比:藝術,文化,歷史,教育。界限原本就不明顯,好比測試數據「我愛藝術,藝術是個人所有」。結果會分類爲文化。其實這個裏面還有就是不一樣特徵詞的權重問題,採用tf-idf優化下應該會好一些?
答:我我的以爲作文本特徵提取,仍是須要本身去分析文本自己內容的文字特色,你能夠把每一類的文本的實體提取出來,而後統計一下每一個詞在每一類上的數量,看看數量分佈,也許能夠發現一些數據特色
我就是按照這個思路作的,還有改進時候的停用詞,其實能夠分析特徵文本,針對不一樣業務,使用自定義的停用詞要比通用的好
還有提早各種見最具表徵性的詞彙加權,凸顯本類的權重是吧?
答:好比,藝術類文章中,哪些詞出現較多,哪些詞出現少,再觀察這些詞的詞性主要是哪些,這樣可能會對你制定提取特徵規則方式的時候提供必定的思路參考,我能夠告訴你的是,有些詞絕對會某一類文章出出現多,而後在其餘類文章出現不多,這一類的詞就是文章的特徵詞
加載文檔數據集和分類集
數據準備和數據預處理上文已經介紹了,本節增長了一個全局變量存儲詞彙表,目的是寫入到本地文本里,本地讀取詞聚集,避免每次都作特徵向量時加載訓練集,提升運行時間。
myVocabList = [] # 設置詞彙表的全局變量 '''建立數據集和類標籤''' def loadDataSet(): docList = [];classList = [] # 文檔列表、類別列表、文本特徵 dirlist = ['C3-Art','C4-Literature','C5-Education','C6-Philosophy','C7-History'] for j in range(5): for i in range(1, 11): # 總共10個文檔 # 切分,解析數據,並歸類爲 1 類別 wordList = textParse(open('./fudan/%s/%d.txt' % (dirlist[j],i),encoding='UTF-8').read()) docList.append(wordList) classList.append(j) # print(i,'\t','./fudan/%s/%d.txt' % (dirlist[j],i),'\t',j) # print(len(docList),len(classList),len(fullText)) global myVocabList myVocabList = createVocabList(docList) # 建立單詞集合 return docList,classList,myVocabList ''' 利用jieba對文本進行分詞,返回切詞後的list ''' def textParse(str_doc): #與上文方法一致 ''' 去掉一些停用詞、數字、特殊符號 ''' def rm_tokens(words, stwlist): #與上文方法一致
文檔數據集和分類集在本地讀寫操做
# 本地存儲數據集和標籤 def storedata(): # 3. 計算單詞是否出現並建立數據矩陣 # trainMat =[[0,1,2,3],[2,3,1,5],[0,1,4,2]] # 訓練集 # classList = [0,1,2] #類標籤 docList,classList,myVocabList = loadDataSet() # 計算單詞是否出現並建立數據矩陣 trainMat = [] for postinDoc in docList: trainMat.append(bagOfWords2VecMN(myVocabList, postinDoc)) res = "" for i in range(len(trainMat)): res +=' '.join([str(x) for x in trainMat[i]])+' '+str(classList[i])+'\n' # print(res[:-1]) # 刪除最後一個換行符 with open('./word-bag.txt','w') as fw: fw.write(res[:-1]) with open('./wordset.txt','w') as fw: fw.write(' '.join([str(v) for v in myVocabList])) # 讀取本地數據集和標籤 def grabdata(): f = open('./word-bag.txt') # 讀取本地文件 arrayLines = f.readlines() # 行向量 tzsize = len(arrayLines[0].split(' '))-1 # 列向量,特徵個數減1即數據集 returnMat = zeros((len(arrayLines),tzsize)) # 0矩陣數據集 classLabelVactor = [] # 標籤集,特徵最後一列 index = 0 for line in arrayLines: # 逐行讀取 listFromLine = line.strip().split(' ') # 分析數據,空格處理 # print(listFromLine) returnMat[index,:] = listFromLine[0:tzsize] # 數據集 classLabelVactor.append(int(listFromLine[-1])) # 類別標籤集 index +=1 # print(returnMat,classLabelVactor) myVocabList=writewordset() return returnMat,classLabelVactor,myVocabList def writewordset(): f1 = open('./wordset.txt') myVocabList =f1.readline().split(' ') for w in myVocabList: if w=='': myVocabList.remove(w) return myVocabList
獲取文檔集合和構建詞袋模型
'''獲取全部文檔單詞的集合''' def createVocabList(dataSet): vocabSet = set([]) for document in dataSet: vocabSet = vocabSet | set(document) # 操做符 | 用於求兩個集合的並集 # print(len(vocabSet),len(set(vocabSet))) return list(vocabSet) '''文檔詞袋模型,建立矩陣數據''' def bagOfWords2VecMN(vocabList, inputSet): returnVec = [0] * len(vocabList) for word in inputSet: if word in vocabList: returnVec[vocabList.index(word)] += 1 return returnVec
GaussianNB 實現了運用於分類的高斯樸素貝葉斯算法。特徵的可能性(即機率)假設爲高斯分佈:
高斯樸素貝葉斯實現方法代碼:
'''高斯樸素貝葉斯''' def MyGaussianNB(trainMat='',Classlabels='',testDoc=''): # -----sklearn GaussianNB------- # 訓練數據 X = np.array(trainMat) Y = np.array(Classlabels) # 高斯分佈 clf = GaussianNB() clf.fit(X, Y) # 測試預測結果 index = clf.predict(testDoc) # 返回索引 reslist = ['Art','Literature','Education','Philosophy','History'] print(reslist[index[0]])
MultinomialNB 實現了服從多項分佈數據的樸素貝葉斯算法,也是用於文本分類(這個領域中數據每每以詞向量表示,儘管在實踐中 tf-idf 向量在預測時表現良好)的兩大經典樸素貝葉斯算法之一。 分佈參數由每類 y 的 向量決定, 式中 n 是特徵的數量(對於文本分類,是詞彙量的大小) 是樣本中屬於類 y 中特徵 i 機率 。
式中 是 訓練集 T 中 特徵 i 在類 y 中出現的次數,
是類 y 中出現全部特徵的計數總和。
先驗平滑因子應用於在學習樣本中沒有出現的特徵,以防在未來的計算中出現0機率輸出。 把 被稱爲拉普拉斯平滑(Lapalce smoothing),而 被稱爲利德斯通(Lidstone smoothing)。
多項樸素貝葉斯實現方法代碼:
'''多項樸素貝葉斯''' def MyMultinomialNB(trainMat='',Classlabels='',testDoc=''): # -----sklearn MultinomialNB------- # 訓練數據 X = np.array(trainMat) Y = np.array(Classlabels) # 多項樸素貝葉斯 clf = MultinomialNB() clf.fit(X, Y) # 測試預測結果 index = clf.predict(testDoc) # 返回索引 reslist = ['Art','Literature','Education','Philosophy','History'] print(reslist[index[0]])
BernoulliNB 實現了用於多重伯努利分佈數據的樸素貝葉斯訓練和分類算法,即有多個特徵,但每一個特徵 都假設是一個二元 (Bernoulli, boolean) 變量。 所以,這類算法要求樣本以二元值特徵向量表示;若是樣本含有其餘類型的數據, 一個 BernoulliNB 實例會將其二值化(取決於 binarize 參數)。伯努利樸素貝葉斯的決策規則基於
與多項分佈樸素貝葉斯的規則不一樣 伯努利樸素貝葉斯明確地懲罰類 y 中沒有出現做爲預測因子的特徵 i ,而多項分佈分佈樸素貝葉斯只是簡單地忽略沒出現的特徵。
在文本分類的例子中,詞頻向量(word occurrence vectors)(而非詞數向量(word count vectors))可能用於訓練和用於這個分類器。 BernoulliNB 可能在一些數據集上可能表現得更好,特別是那些更短的文檔。 若是時間容許,建議對兩個模型都進行評估。
伯努利樸素貝葉斯代碼實現以下:
'''伯努利樸素貝葉斯''' def MyBernoulliNB(trainMat='',Classlabels='',testDoc=''): # -----sklearn BernoulliNB------- # 訓練數據 X = np.array(trainMat) Y = np.array(Classlabels) # 多項樸素貝葉斯 clf = BernoulliNB() clf.fit(X, Y) # 測試預測結果 index = clf.predict(testDoc) # 返回索引 reslist = ['Art','Literature','Education','Philosophy','History'] print(reslist[index[0]])
代碼實現以下:
def testingNB():
# 加載數據集和單詞集合 trainMat,Classlabels,myVocabList = grabdata() # 讀取訓練結果 # 測試數據 testEntry = textParse(open('./fudan/test/C6-2.txt',encoding='UTF-8').read()) testDoc = np.array(bagOfWords2VecMN(myVocabList, testEntry)) # 測試數據 # 測試預測結果 MyGaussianNB(trainMat,Classlabels,testDoc) MyMultinomialNB(trainMat,Classlabels,testDoc) MyBernoulliNB(trainMat,Classlabels,testDoc)
運行結果:
Building prefix dict from the default dictionary ... Loading model from cache C:\Users\ADMINI~1\AppData\Local\Temp\jieba.cache Loading model cost 1.014 seconds. Prefix dict has been built succesfully. 高斯樸素貝葉斯:Education 多項樸素貝葉斯分類結果:Art 伯努利樸素貝葉斯分類結果:Literature 耗時:2.3996 s
源碼請進【機器學習和天然語言QQ羣:436303759】文件下載:
本文版權歸做者全部,旨在技術交流使用。未經做者贊成禁止轉載,轉載後需在文章頁面明顯位置給出原文鏈接,不然相關責任自行承擔。