貝葉斯定理
w
是由待測數據的所有屬性組成的向量。p(c|x)
表示,在數據爲x時,屬於c類的概率。
如果數據的目標變量最後有兩個結果,則需要分別計算p(c1|x)
和p(c2|x)
取最大的值爲分類的結果
算法的目的就在於找到使p最大的 。由於只需要比較兩個概率的大小,則分母p(w)
可以不用算,並不影響結果。那 又如何計算呢?一條數據w其實包含很多屬性w=w1,w2,w3,...,wn
.以爲例:
p(c0)
表示分類結果爲c0
的概率:
而 == 。樸素貝葉斯分類假設所有屬性之間是獨立的,互不影響。那麼就滿足如下關係:
至此,已經計算出了足夠數據來計算出,用這些概率可以給新的數據分類。如果此時有數據 w=w1,w3,w5
,那需要分別算出兩個概率:
比較大小,找到最大的概率,最大概率的就是分類的結果
算法實現
收集數據
1、從文本文件中讀取數據,並分割成每個單詞,放到一個list中。每個文件一個list,最後是一個二維的list:
[[‘my’, ‘dog’, ‘has’, ‘flea’, ‘problems’, ‘help’, ‘please’],
[‘maybe’, ‘not’, ‘take’, ‘him’, ‘to’, ‘dog’, ‘park’, ‘stupid’]]
def loadDataSet():
"""
創建數據集
:return: 文檔列表 docList, 所屬類別classVec
"""
docList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
['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 is abusive, 0 not
return docList, classVec
2、遍歷上例的二維表,找到所有出現的單詞,使用set()去重。這個集合就作爲數據集的屬性.上例的詞彙表爲:
[‘steak’, ‘dog’, ‘problems’, ‘so’, ‘buying’, ‘my’, ‘how’, ‘licks’, ‘dalmation’, ‘take’, ‘food’, ‘maybe’, ‘stop’, ‘posting’, ‘him’, ‘garbage’, ‘has’, ‘stupid’, ‘park’, ‘ate’, ‘mr’, ‘not’, ‘love’, ‘help’, ‘worthless’, ‘flea’, ‘please’, ‘quit’, ‘to’, ‘is’, ‘I’, ‘cute’]
def createVocabList(docList):
"""構造詞彙表,統計所有文本中的所有單詞
:return list 去重的詞彙表
"""
vocalSet = set([])
for line in docList:
vocalSet = vocalSet | set(line) # 集合求並集操作
return list(vocalSet)
3、將每個文件的單詞列表轉換爲向量。遍歷文件中的每個單詞,如果出現在詞彙表中則爲1,否則爲0
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1]
def setOfWords2Vec(vocabList, inputSet):
"""將輸入數據轉換爲向量.存在這個單詞記爲1,不存在則記爲0"""
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
return returnVec
訓練算法
訓練算法就是計算一系列概率的過程。要預測一個文本,需要計算以下概率:
- 和
- 和
下面的代碼計算出了這些概率,其中p0是一個列表,其中記錄了每一個的值。p1同理。pc1表示, 可以用 1-pc1
得到
p0: [[0.04166667 0.04166667 0.04166667 0.04166667 …]]
p1: [[0. 0.10526316 0. 0. …]]
pc: 0.5
def trainNB0(trainMatrix, trainCategory):
"""分類器訓練函數"""
numberOfAttr = len(trainMatrix[0])
numbrOfDoc = len(trainMatrix)
p0 = np.zeros((1, numberOfAttr)) # p(wi|c0)
p1 = np.zeros((1, numberOfAttr)) # p(wi|c1)
p0Total = 0.0
p1Total = 0.0
pc1 = float(sum(trainCategory)) / numbrOfDoc # p(c1)
for i in range(numbrOfDoc):
if trainCategory[i] == 0:
p0 += trainMatrix[i] # 統計先驗概率c0下,每個單詞出現的次數
p0Total += sum(trainMatrix[i])
else:
p1 += trainMatrix[i] # 統計先驗概率c1下,每個單詞出現的次數
p1Total += sum(trainMatrix[i])
p0 = p0 / p0Total # 用c0下每個單詞出現的次數,分別除以c0下的總數==> p(wi|c0)
p1 = p1 / p1Total # p(wi|c1)
return p0, p1, pc1
分類
根據之前的理論。如果數據有三個單詞 w=w1,w3,w5
,那需要分別算出兩個概率:
優化代碼
1、書上說,對於 如果其中任何一個概率爲0,則總概率爲零,所以把所有單詞出現的次數初始化爲1。(其實不改也行,但是代碼中inputData * p0
已經過濾出了所有非零元素)
2、 概率都是很小的數,如果直接以小數運算會帶來很大的誤差。書上採用了對數替代直接的小數運算。本來的概率是這樣算的
現在使用對數,In(fx)
並不會影響f(x)
的單調性,所以計算的結果可以直接比較大小,不會影響分類結果。計算方式如下:
優化後的訓練代碼如下:
def trainNB1(trainMatrix, trainCategory):
"""分類器訓練函數"""
numberOfAttr = len(trainMatrix[0])
numbrOfDoc = len(trainMatrix)
p0 = np.ones((1, numberOfAttr)) # p(wi|c0)
p1 = np.ones((1, numberOfAttr)) # p(wi|c1)
p0Total = 2.0 # 不唯一
p1Total = 2.0
pc1 = float(sum(trainCategory)) / numbrOfDoc # p(c1)
for i in range(numbrOfDoc):
if trainCategory[i] == 0:
p0 += trainMatrix[i]
p0Total += sum(trainMatrix[i])
else:
p1 += trainMatrix[i]
p1Total += sum(trainMatrix[i])
p0 = np.log(p0 / p0Total)
p1 = np.log(p1 / p1Total)
return p0, p1, pc1
分類的代碼如下:
def classifyNB(inputData, p0, p1, pc1):
"""使用計算得到的概率分類"""
prob0 = np.sum(inputData * p0) + np.log(1-pc1)
prob1 = np.sum(inputData * p1) + np.log(pc1)
if prob0 > prob1:
return 0
else:
return 1
測試代碼:
def testNB():
"""測試函數"""
docList, classVec = loadDataSet()
vocabList = createVocabList(docList)
trainMat = [] # 由0/1組成的數據集 : [[0,1,0,0,1,....],[0,0,0,0,0,1,...]]
for doc in docList:
trainMat.append(setOfWords2Vec(vocabList, doc))
p0, p1, pc1 = trainNB0(trainMat, classVec)
testData = ['love', 'my', 'dalmation']
thisDoc = setOfWords2Vec(vocabList, testData)
print("分類結果是:", classifyNB(thisDoc, p0, p1, pc1))
testData = ['stupid', 'garbage']
thisDoc = setOfWords2Vec(vocabList, testData)
print("分類結果是:", classifyNB(thisDoc, p0, p1, pc1))
詞袋模型
上面的代碼中,文件中出現的單詞,記爲1,否則爲0。這種方式叫詞集模型(set-of-words model)。得到的是如下的向量:
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1]
但是同一個單詞在文檔中可能多次出現,在向量中記錄單詞出現的次數的方式叫做詞袋模型(bag-of-words model)。得到的向量可能是這樣的:
[0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 7, 0, 0, 2]
要實現詞袋模型只需要改動很少量的代碼:
def bagOfWords2Vec(vocabList, inputSet):
"""[詞袋模型]將輸入數據轉換爲向量.存在這個單詞記爲1,不存在則記爲0"""
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
案例:過濾垃圾郵件
def textParse(bigString):
"""將字符串返回成單詞列表
1. 以空白字符作爲分隔符
2. 排除長度小於2的單詞,他可能沒有實際意義
3. 所有單詞轉換爲小寫
"""
import re
listOfWords = re.split(r'\W*', bigString)
return [word.lower() for word in listOfWords if len(word) > 2]
def spamTest():
"""測試算法。使用交叉驗證"""
docList, classList, fullText = [], [], []
# 1. 解析文本文件。一個文件解析成一個list,所有文件保存爲一個二維list
for i in range(1, 26):
spam = open("dataset/email/spam/%d.txt" % i)
wordList = textParse(spam.read())
docList.append(wordList)
classList.append(1)
ham = open("dataset/email/ham/%d.txt" % i)
wordList = textParse(ham.read())
docList.append(wordList)
classList.append(0)
# 2.格式化
vocabList = createVocabList(docList) # 創建詞彙表
# 3. 隨機挑選10個測試數據(可能沒有10個)
trainSet = list(range(50)) # 記錄了所有用於訓練的數據集的下標
testSet = [] # 記錄了所有用於測試的數據集的下標
for i in range(10):
randIndex = int(random.uniform(0, len(trainSet)))
testSet.append(trainSet[randIndex])
del trainSet[randIndex]
# 4. 訓練
trainMatrix, trainCategory = [], []
for i in range(len(trainSet)):
trainMatrix.append(bagOfWords2Vec(vocabList, docList[trainSet[i]]))
trainCategory.append(classList[trainSet[i]])
p0, p1, pc1 = trainNB1(trainMatrix, trainCategory)
# 5. 測試
testMatrix, testCategory, error = [], [], 0
for i in range(len(testSet)):
line = bagOfWords2Vec(vocabList, docList[testSet[i]])
result = classifyNB(line, p0, p1, pc1)
if result != classList[testSet[i]]:
error += 1
print("錯誤率爲:", (float(error)/len(testSet)))