一、思路框架
1.收集數據
2.準備數據
3.分析數據
4.訓練算法
5.測試算法
6.使用算法
二、具體實施
1.準備數據階段:因爲《機器學習實戰》這本書提供的有源數據,因此省去了數據收集和準備的階段,直接分析數據。這裏分享一下《機器學習實戰》裏面的源數據鏈接:
https://pan.baidu.com/s/1B7PCunfHF8J4gmbu22ljPQ
提取碼:3vpk
2.分析數據:用open(filename).read()讀取並打開文本,發現其中一段的文本如下圖所示,可以發現文中除了字母數字還有一些空格,非字符串符合需要去除。因此需要使用正則化公式進行處理。
正則化公式可以設定爲【\w.*?】,其中,【\w]匹配字母、數字或者下劃線;【.】匹配除換行符(\n,\r)以外的任何單個字符;【*】匹配前面的子表達式零次或者多次;【?】匹配前面的子表達式一次或者零次。
經正則化處理後的結果如圖所示,發現裏面還有空格也被讀取了,需要處理掉;並且一些首字母是大寫的也要變成小寫的。使用三目表達式,可以節省代碼空間,使代碼更加簡潔。
[tok.lower() for tok in listOfTokens if len(tok) > 2]
3.算法:直接放python代碼(完整版)
import re
import random
from math import log
import numpy as np
# 創建文本詞彙查詢表
def createVocabList(dataSet):
# 創建一個空集
vocabSet = set([])
# 將新詞集合添加到創建的集合中
for document in dataSet:
# 操作符 | 用於求兩個集合的並集
vocabSet = vocabSet | set(document)
# 返回一個包含所有文檔中出現的不重複詞的列表
return list(vocabSet)
# 將文本詞彙轉換成矩陣向量
def setOfWords2Vec(vocabList, inputSet):
# 創建一個所含元素都爲0的向量
returnVec = [0] * len(vocabList)
# 遍歷文檔中詞彙
for word in inputSet:
# 如果文檔中的單詞在詞彙表中,則相應向量位置置1
if word in vocabList:
returnVec[vocabList.index(word)] = 1
# 否則輸出打印信息
else:
print("%s 不在詞袋中" % word)
# 向量的每一個元素爲1或0,表示詞彙表中的單詞在文檔中是否出現
return returnVec
# 訓練函數
def trainNB0(trainMatrix, trainCategory):
# 獲得訓練集中文檔個數
numTrainDocs = len(trainMatrix)
# 獲得訓練集中單詞個數
numWords = len(trainMatrix[0])
# 計算文檔屬於侮辱性文檔的概率
pAbusive = sum(trainCategory) / float(numTrainDocs)
# 初始化概率的分子變量
p0Num = np.ones(numWords)
p1Num = np.ones(numWords)
# 初始化概率的分母變量
p0Denom = 2.0
p1Denom = 2.0
# 遍歷訓練集trainMatrix中所有文檔
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 = np.log(p1Num / p1Denom)
p0Vect = np.log(p0Num / p0Denom)
# 返回兩個類別概率向量和一個概率
return p0Vect, p1Vect, pAbusive
# 分類函數
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass):
p1 = sum(vec2Classify * p1Vec) + log(pClass)
p0 = sum(vec2Classify * p0Vec) + log(1 - pClass)
if p1 > p0:
return 1
else:
return 0
# 文本處理
def textParse(bigString):
listOfTokens = re.split(r'\W.*?', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
# 測試函數
def spamTest():
docList = [];classList = []; fullText = []
# 導入並解析文件
for i in range(1, 26):
wordList = textParse(open("E:/python/Github/python/Python/machinelearninginaction/Ch04/email/spam/%d.txt" % i, 'r',encoding='gb18030',errors='ignore').read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(1)
wordList = textParse(open("E:/python/Github/python/Python/machinelearninginaction/Ch04/email/ham/%d.txt" % i, 'r',encoding='gb18030',errors='ignore').read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
vocabList = createVocabList(docList)
# 構建訓練集
trainingSet = list(range(50)); testSet = []
for i in range(10):
randIndex = int(random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[randIndex])
del trainingSet[randIndex]
trainMat = []; trainClasses = []
for docIndex in trainingSet:
trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V, p1V, pSpam = trainNB0(np.array(trainMat), np.array(trainClasses))
errorCount = 0
print(testSet)
# 對測試集分類
for docIndex in testSet:
wordVector = setOfWords2Vec(vocabList, docList[docIndex])
if classifyNB(np.array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
errorCount += 1
print("the error rate is:",float(errorCount)/len(testSet))
# 運行
if __name__ == '__main__':
spamTest()
三、總結
代碼跑完,對於爲什麼使用樸素貝葉斯進行文本分類,還是存在着一些疑惑。具體代碼的思路是:第一、先獲得兩個表,一個是訓練集文本的詞彙彙總表,一個是訓練集分類的列表(如本例是分兩類:含侮辱性詞彙爲1,不含侮辱性的爲0)。第二、將訓練集文本和測試集文本轉換成向量矩陣便於操作。第三、根據分類列表分別計算訓練集的文本向量中含侮辱性詞彙和不含侮辱性詞彙出現的概率,還有含有侮辱性詞彙的文本出現的概率。第四、就是將測試集的文本向量分別與兩組概率相乘再加數相應類別出現的概率,比較計算結果,結果較大的類別就是該測試文本所屬的類別。
那麼問題來了,爲什麼根據判斷詞彙出現的概率,而不是根據詞彙的內容就可以判斷詞彙所屬文本的類別了呢?
書中有這麼一段解釋我覺得很好理解,就是假設一個特徵或者一個單詞出現的可能性與它和其他單詞相鄰與否沒有關係。也就是說bacon(燻肉)出現在unhealthy(不健康的)後面和出現delicious(美味的)的後面的概率是相同的。但是實際上這種假設並不正確,bacon更常出現在delicious附近,而很少出現在unhealthy附近。那麼這樣就可以通過計算bacon出現的概率來判斷文本中是含有delicious的概率高還是含有unhealthy的概率高。
同理,我們可以據此來過濾垃圾郵件。