前言
前篇基礎理論知識:機器學習:貝葉斯分類器詳解(一)-貝葉斯決策理論與樸素貝葉斯
這篇主要使用代碼實作貝葉斯分類,
一、準備資料
創建一個bayes.py程式,從文本中構建詞向量,實作詞表向向量轉換函式,
from numpy import *
def loadDataSet():
postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], # 分詞可用wordcloud
['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']]
classVec = [0,1,0,1,0,1]#1代表侮辱性文字,0代表正常言論
return postingList,classVec #回傳的第二個變數為人工標注用于區別侮辱性和非侮辱性的標簽,
#創建一個空集
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document) #創建兩個集合的并集 劃掉重復出現的單詞
return list(vocabSet)
#處理樣本輸出為向量形式
def setOfWords2Vec(vocaList , inputSet):
returnVec = [0]*len(vocaList)#創建一個其中所含元素全為0的向量代替文本
for word in inputSet:
if word in vocaList:
returnVec[vocaList.index(word)] = 1
else:
print("the word:%s is not in my Vocabulary!"" % word")
return returnVec
第一個函式創建了一些實驗樣本,第二個函式創建一個包含在所有檔案中出現的不重復的串列,第三個函式輸入引數為詞匯表及某個檔案,輸出的是檔案向量,向量的每一個元素為1或0,分別表示詞匯表中的單詞在輸入檔案中是否出現,
可檢驗函式是否正常作業:
listOPosts,listClasses=loadDataSet()
myVocabList=createVocabList(listOPosts)
print(myVocabList)
print(setOfWords2Vec(myVocabList,listOPosts[0]))
print(setOfWords2Vec(myVocabList,listOPosts[3]))

二、訓練演算法:從詞向量計算概率
該函式偽代碼如下:
根據前篇基礎理論先求得P(w|ci),再計算P(ci),
樸素貝葉斯分類器訓練函式:
def trainNB0(trainMatrix,trainCategory):#樸素貝葉斯分類器訓練函式,引數:1:向量化檔案2:詞條向量
numTrainDocs = len(trainMatrix)#文本矩陣
numWords = len(trainMatrix[0])
pAbusive = sum(trainCategory)/float(numWords)
p0Num = zeros(numWords);p1Num = zeros(numWords)#創建兩個長度為詞條向量等長的串列,平滑處理:初始值設為1
p0Denom = 0.000001;p1Denom = 0.000001
for i in range (numTrainDocs):
if trainCategory[i] ==1:
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = p1Num/p1Denom # 利用Numpy陣列計算p(wi/c1),即類1條件下各詞條出現的概率
p0Vect = p0Num/p0Denom # 利用Numpy陣列計算p(wi/c0),為避免下溢,后面會改為log()
return p0Vect, p1Vect, pAbusive # 回傳
由于當p0Num時會報RuntimeWarning: invalid value encountered in true_divide,這是由0/0導致,因此在設定p0Denom時不能設定為0.
首先,計算檔案屬于侮辱性檔案(class=1)的概率,即P(1),P(0)可由1-P(1)得到,
檢驗:
print(p1v)
print(p0v)
print(pAb)
結果:

利用貝葉斯分類器對檔案進行分類時,要進行多個概率的乘積可獲得檔案屬于某個類別額的概率,即計算p(w0|1)p(w1|1)p(w2|1),其中一個概率值為0,那么最后的乘積也為0.我們可以將所有出現的詞初始值初始化為1,并將分母初始化為2.
修改:
p0Num = ones(numWords); p1Num = ones(numWords)#創建兩個長度為詞條向量等長的串列,平滑處理:初始值設為1
p0Denom = 2.0;p1Denom = 2.0#平滑處理,初始值設為2
另一個問題為下溢位,這是由于太多很小的數相乘造成的,當計算乘積 p(w0|ci) * p(w1|ci) * p(w2|ci)... p(wn|ci) 時,由于大部分因子都非常小,所以程式會下溢位或者得到不正確的答案,(用 Python 嘗試相乘許多很小的數,最后四舍五入后會得到 0),一種解決辦法是對乘積取自然對數,在代數中有 ln(a * b) = ln(a) + ln(b), 于是通過求對數可以避免下溢位或者浮點數舍入導致的錯誤,同時,采用自然對數進行處理不會有任何損失,
修改:
p1Vect = log(p1Num/p1Denom)#利用Numpy陣列計算p(wi/c1),即類1條件下各詞條出現的概率
p0Vect = log(p0Num/p0Denom)#利用Numpy陣列計算p(wi/c0),為避免下溢,后面會改為log()

三、分類函式
樸素貝葉斯分類函式:
#樸素貝葉斯分類函式
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):#注意引數2,3均已log化
p1 = sum(vec2Classify * p1Vec) + log(pClass1)# P(w|c1) * P(c1) ,即貝葉斯準則的分子
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1) # P(w|c0) * P(c0) ,即貝葉斯準則的分子
if p1 > p0:
return 1
else:
return 0
"""
使用演算法:
# 將乘法轉換為加法
乘法: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 pClass1: 類別1,侮辱性檔案的出現概率
:return: 類別1 or 0
"""
# 計算公式 log(P(F1|C))+log(P(F2|C))+....+log(P(Fn|C))+log(P(C))
測驗:
def testingNB():
"""
測驗樸素貝葉斯演算法
"""
# 1. 加載資料集
listPosts,listClasses =loadDataSet()
#2. 創建單詞集合
myVocabList = createVocabList(listPosts)
#3.計算單詞是否出現并創建資料矩陣
trainMat = []
for postinDoc in listPosts:
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
#4.訓練資料
p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses))
#5.測驗資料
testEntry = ['love','my','dalmation']
thisDoc = array(setOfWords2Vec(myVocabList,testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
testEntry = ['stupid', 'garbage']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))

四、檔案詞袋模型
由于我們將每個詞的出現作為一個特征,這可以被描述為詞集模型,但單詞往往有多義性,意味著一個單詞在檔案出現可能代表有不同的含義,這種方法被稱為詞袋模型,
在詞袋中,每個單詞可以出現多次,而在詞集中,每個詞只能出現一次,為適應詞袋模型,需要對函式setOfWords2Vec稍加修改:
def bagOfWords2VecMN(vocaList , inputSet):
returnVec = [0]*len(vocaList)#創建一個其中所含元素全為0的向量代替文本
for word in inputSet:
if word in vocaList:
returnVec[vocaList.index(word)] += 1 #每遇到一個單詞,相應加一
else:
print("the word:%s is not in my Vocabulary!"" % word")
return returnVec
五、使用樸素貝葉斯過濾垃圾郵件

1.收集資料
使用樸素貝葉斯過濾垃圾郵件資料集
資料集說明: 資料集下包含兩個檔案夾,其中spam檔案夾下為垃圾郵件,ham檔案夾下為非垃圾郵件,
資料集格式: txt檔案
2.準備資料(處理資料)
英文由于單詞之間有空格,方便切分,中文有jieba庫,有興趣的可以了解一下,
myStr = 'This book is the best book on Python.'
myStr.split()
['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python.']
但是最后一個詞有標點符號,這個我們通過正則運算式解決,正則運算式在文本分類中是有很大作用的,
import re
regEx = re.compile('\\W*')
listOfTokens = regEx.split(myStr)
listOfTokens
['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python', '']

這里會有空字串產生,我們可以計算字串的長度,只回傳字串長度大于0的字串,
[tok for tok in listOfTokens if len(tok)>0]
['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python']
另外我們考慮構建詞庫,并不用考慮單詞的大小寫,全部改為小寫
[tok.lower() for tok in listOfTokens if len(tok)>0]
['this', 'book', 'is', 'the', 'best', 'book', 'on', 'python']
這么一來我們就完成了簡單文本的切分,當然一些文本也有非常復雜的處理方法,具體看文本的內容和性質,
3.測驗演算法:使用樸素貝葉斯進行交叉驗證
直接貼上代碼
def textParse(bigString):
import re
listOfTokens = re.split(r'\W*', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
def spamTest():
docList = [] # 檔案(郵件)矩陣
classList = [] # 類標簽串列
for i in range(1, 26):
wordlist = textParse(open('trashclass/spam/{}.txt'.format(str(i))).read())
docList.append(wordlist)
classList.append(1)
wordlist = textParse(open('trashclass/ham/{}.txt'.format(str(i))).read())
docList.append(wordlist)
classList.append(0)
vocabList = bayes.createVocabList(docList) # 所有郵件內容的詞匯表
import pickle
file=open('trashclass/vocabList.txt',mode='wb') #存盤詞匯表 二進制方式寫入
pickle.dump(vocabList,file)
file.close()
# 對需要測驗的郵件,根據其詞表fileWordList構造向量
# 隨機構建40訓練集與10測驗集
trainingSet = list(range(50))
testSet = []
for i in range(10):
randIndex = int(np.random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[randIndex])
del (trainingSet[randIndex])
trainMat = [] # 訓練集
trainClasses = [] # 訓練集中向量的類標簽串列
for docIndex in trainingSet:
# 使用詞袋模式構造的向量組成訓練集
trainMat.append(bayes.setOfWords2Vec(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0v,p1v,pAb=bayes.trainNB0(trainMat,trainClasses)
file=open('trashclass/threeRate.txt',mode='wb') #用以存盤分類器的三個概率 二進制方式寫入
pickle.dump([p0v,p1v,pAb],file)
file.close()
errorCount=0
for docIndex in testSet:
wordVector=bayes.setOfWords2Vec(vocabList,docList[docIndex])
if bayes.classifyNB(wordVector,p0v,p1v,pAb)!=classList[docIndex]:
errorCount+=1
return float(errorCount)/len(testSet)
加入序列化永久性保存物件,保存物件的位元組序列到本地檔案中,本例中共有50封電子郵件,其中10封電子郵件被隨機選擇為測驗集合,選擇出的數字所對應的檔案被添加到測驗集,同時也將其從訓練集中剔除,這種隨機選擇資料的一部分作為訓練集,而剩余部分作為測驗集的程序稱為留存交叉驗證,現在我們只作出一次迭代,為了更精確的估計分類器的錯誤率,我們應該多次迭代后求出平均錯誤率,
當然你也可以用:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.2,
random_state=0)
方法很多很簡單,這里不重復敘述,
(對了上面的代碼setOfWords2Vec其實是bagOfWords2VecMN,我只是沒有換名字而已,內容是bagOfWords2VecMN)
開始構造分類器:
import bayes
import numpy as np
import tkinter as tk
from tkinter import filedialog
def fileClassify(filepath):
import pickle
fileWordList=textParse(open(filepath,mode='r').read())
file=open('trashclass/vocabList.txt',mode='rb')
vocabList=pickle.load(file)
vocabList=vocabList
fileWordVec=bayes.setOfWords2Vec(vocabList,fileWordList) #被判斷檔案的向量
file=open('trashclass/threeRate.txt',mode='rb')
rate=pickle.load(file)
p0v=rate[0];p1v=rate[1];pAb=rate[2]
return bayes.classifyNB(fileWordVec,p0v,p1v,pAb)
if __name__=='__main__':
print('樸素貝葉斯分類的錯誤率為:{}'.format(spamTest())) #測驗演算法的錯誤率
# filepath=input('輸入需判斷的郵件路徑')
root = tk.Tk()
root.withdraw()
Filepath = filedialog.askopenfilename() # 獲得選擇好的檔案
print(Filepath)
#判斷某一路徑下的郵件是否為垃圾郵件
if fileClassify(Filepath)==1:
print('垃圾郵件')
else:
print('非垃圾郵件')
這里我直接用Tk直接選路徑懶得打了QWQ
完成!~~~~感覺不錯就點個贊吧~
總結
實踐才是硬道理,
參閱:
https://www.cnblogs.com/carlber/archive/2004/01/13/11798127.html
https://blog.csdn.net/qq_45393426/article/details/106149701
https://www.cnblogs.com/fengbing/p/3517962.html
https://blog.csdn.net/qq_38233659/article/details/100991466
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/232451.html
標籤:AI

