一 前言
上一篇文章介紹了樸素貝葉斯的基本原理, 現在就來實踐一下吧, 閱讀了部分<機器學習實戰>上的代碼, 自己也敲了一遍, 做了一下驗證, 現在就在這裏分享一下.
環境:
Ubuntu 16.04
Python 3.5.2
二 使用樸素貝葉斯進行文檔分類
2.1 準備數據: 從文本中構建詞向量
加載數據
'''
加載訓練數據, postingList是所有的訓練集, 每一個列表代表一條言論, 一共有8條言論
classVec代表每一條言論的類別, 0是正常, 1是有侮辱性
返回 言論和類別
'''
def loadDataSet():
postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'hime'],
['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]
return postingList, classVec
看一下運行結果:
統計文檔中的單詞, 生成詞彙表, 詞彙表中每一個單詞只出現一次, 沒有重複的. (就是把文檔中的所有單詞放在一塊, 然後去重.)
'''
創建詞彙表, 就是把這個文檔中所有的單詞不重複的放在一個列表裏面
'''
def createVocabList(dataSet):
vocabSet = set([]) # 新建一個set集合, 保證裏面的數據不重複
for document in dataSet: # 獲得每一個文檔
vocabSet = vocabSet | set(document) # 文檔去重之後和詞彙表求並集
return list(vocabSet) # 詞彙錶轉換爲列表
這樣我們就生成了一個詞彙表. 看一下詞彙表:
文檔轉換爲詞向量, 對於每一個文檔, 我們都要把他轉換爲詞向量, 也就是由數字組成的一個向量, 此處的轉換很簡單. 上一步我們已經創建了一個詞彙表, 對於一個文檔, 首先我們生成一個和該文檔長度一致的全0列表returnVec, 然後遍歷該文當中的每一個單詞, 如果這個單詞在詞彙表中出現過, 就在returnVec中相應位置變爲1, 如果沒出現過, 就仍然保持爲0. 最後返回這個列表returnVec.
'''
vocabList是由createVocabList產生的詞彙表
inputSet是輸入新的文檔
'''
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList) # 生成一個全0列表, 個數爲輸入文檔的長度
for word in inputSet: # 遍歷輸入文檔中的每一個單詞
if word in vocabList: # 如果這個單詞在詞彙表中
returnVec[vocabList.index(word)] = 1 # 置1
else: # 否則依然爲0
print("the word %s is not in my Vocabulary" % word)
return returnVec
看一下第一個文檔轉換爲詞向量後是什麼樣子:
詞向量就是由0和1組成的數組.
2.2 計算先驗概率
計算先驗概率, 接下來就是計算各種先驗概率了, 還記得甚麼是先驗概率嗎? 不記得的同學可以去前面文章裏面看看甚麼是先驗概率.
首先統計一共有多少個文檔, 然後統計詞向量的長度, 接着計算侮辱性文檔的先驗概率, 再初始化p0Num, p0Denom, p0Num就是一個array(numpy), 大小是詞向量的長度, 它用於記錄當前文檔的每一個單詞是否在詞向量中存在, 當然它是初始化爲全1, 也就是拉普拉斯平滑. 請看for循環, 遍歷每一個文檔, 首先判斷當前文檔的label是否爲侮辱性的, 是侮辱性的就執行p1, 不是就執行p0. 我們看
p1Num += trainMatrix[i] , 這句話是兩個array之間的相加, 也就是下圖這種情況:
p1Denom += 1這個就是統計當前文檔中有多少是屬於侮辱性的, <機器學習實戰>上寫的是p1Denom += sum(trainMatrix[i]), 但是按照計算先驗概率的公式, 我認爲是加一即可. 最後p1Vect = log(p1Num / p1Denom), 取log是爲了防止多個小數相乘出現下溢. 這樣就計算出了每一個單詞的先驗概率, 以及每一個類別的先驗概率.
'''
計算先驗概率
trainMatrix: 詞向量矩陣
trainCategory: 每一個詞向量的類別
返回每一個單詞屬於侮辱性和非侮辱性詞彙的先驗概率, 以及訓練集包含侮辱性文檔的概率
'''
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 由訓練集生成的詞向量矩陣
numWords = len(trainMatrix[0]) # 每一個詞向量的長度
pAbusive = sum(trainCategory) / float(numTrainDocs) # 計算侮辱性文檔的先驗概率
p0Num = ones(numWords) # 生成全1 array, 長度爲詞向量的長度, 用於統計每個單詞在整個矩陣中出現的次數(分子)
p1Num = ones(numWords)
p0Denom = 2.0 # 初始化爲2(分母), 拉普拉斯平滑
p1Denom = 2.0
for i in range(numTrainDocs): # 遍歷每一個詞向量
if trainCategory[i] == 1: # 如果該詞向量的類別爲1
p1Num += trainMatrix[i] # 計算P(x0)..P(xn)
p1Denom += 1 # 統計侮辱性文檔的個數
else:
p0Num += trainMatrix[i] # 計算P(x0)..P(xn)
p0Denom += 1 # 統計非侮辱性文檔個數
p0Vect = log(p0Num / p0Denom) # 計算P(x0|0)P(xn|0)
p1Vect = log(p1Num / p1Denom) # 計算P(x0|1) P(x1|1) P(xn|1) 取對數是防止多個小數相乘出現下溢
return p0Vect, p1Vect, pAbusive
我們看一下計算結果:
p0V, p1V, pAb = trainNB0(trainMat, listClasses)
p0V, p1V, pAb
接下來就是將訓練集裏面的文檔轉換爲詞向量了. 代碼很簡單:
'''
製作詞向量矩陣
將每一個文檔轉換爲詞向量, 然後放入矩陣中
'''
trainMat = []
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
2.3 製作分類器, 測試
接下來就是根據上面計算出來的每一個單詞的先驗概率, 來預測一個未知文檔是否具有侮辱性了. 代碼如下:
'''
製作貝葉斯分類器
vec2Classify: 測試樣本的詞向量
p0Vec: P(x0|Y=0) P(x1|Y=0) P(xn|Y=0)
p1Vec: P(x0|Y=1) P(x1|Y=1) P(xn|Y=1)
pClass1: P(y)
# log(P(x1|1)*P(x2|1)*P(x3|1)P(1))=log(P(x1|1))+log(P(x2|1))+log(P(1))
'''
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1)
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
註釋部分已經解釋了爲什麼要加上log(pClass1)了.
檢驗效果的時候到了, 讀入一個文檔, 根據我們計算的先驗概率, 分別計算他屬於侮辱性文檔的概率和屬於非侮辱性文檔的概率, 比較兩個概率的大小, 大的那一類就是該文檔所屬於的類.
'''
測試貝葉斯分類器
'''
def testingNB():
listOPosts, listClasses = loadDataSet() # 加載數據
myVocabList = createVocabList(listOPosts) # 詞彙表
trainMat = [] # 訓練集詞向量
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB0(trainMat, listClasses) # 計算先驗概率
testEntry = ['love', 'my', 'dalmation'] # 測試文檔1
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as :', classifyNB(thisDoc, p0V, p1V, pAb))
testEntry = ['stupid', 'garbage', 'stupid'] # 測試文檔2
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as : ', classifyNB(thisDoc, p0V, p1V, pAb))
看一下結果吧,
可以看出, 第一個屬於非侮辱性的文檔, 第二個屬於侮辱性的文檔. 因爲文檔中存在stupid這樣的單詞, 也就是罵人是傻子的意思.所以被判定爲侮辱性的.
文章主要參考<機器學習實戰>和<統計學習方法>這兩本書, 自己也是一個初學者, 文中有紕漏或者不當的地方, 歡迎各位朋友指出來, 咱們共同進步. 謝謝