機器學習實戰讀書筆記(3)--樸素貝葉斯
基於貝葉斯決策理論的分類方法
優點:在資料較少的情況下仍然有效,可以處理多類別問題
缺點:對輸入資料的準備方式比較敏感,需要標稱資料.確定貝葉斯最優假設的計算代價較大
樸素貝葉斯是貝葉斯決策理論的一部分.貝葉斯決策理論的核心思想:一個數據集包括2類(或兩類以上)資料,這些資料有一些維度,如果已知一個數據的特徵,由該特徵得到其屬於第一類的可能性
和其屬於第二類的可能性
為了計算上文中提到的概率,需要引入條件概率和貝葉斯變換.
條件概率:
貝葉斯變換:
通過貝葉斯變換,可以將求P(c1|x,y)轉化為求P(x,y|c1)P(c1),進而通過訓練資料集的帶有標籤的特徵訓練,在c先驗概率下求x,y的概率,求得P(c1|x,y),對未知標籤的(x,y)進行判斷.
樸素貝葉斯的假設:
- 一個特徵或單詞的出現的可能性與其他鄰近單詞沒有關係.(不準確但是有效,樸素的含義)
- 每個特徵同等重要
使用貝葉斯進行文字分類
從文字獲取特徵,即需要拆分文字,特徵來自詞條(token),一個詞條是字元的任意組合,一個文字可以表示為一個詞向量,其中為1表示這個詞條存在,為0表示未出現.
from numpy import *
import feedparser
# 準備資料
def loadDataSet():
postingList = [['my', 'dog', 'has', 'flea', \
'problem', '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代表侮辱性文字, 0代表正常言論
return postingList, classVec
首先做準備工作包括對文字提取詞,並製作詞表,所謂詞表就是出現過的所有有效詞的集合,有了詞表之後,將文字按照詞表轉換成詞向量.
def createVocabList(dataSet):
'''
從資料集提取所有出現過的詞作為詞表
dataSet可以是一個二維陣列
'''
vocabSet = set([]) # 建立一個空集
for document in dataSet:
vocabSet = vocabSet | set(document) # 建立兩個集合的並集
return list(vocabSet)
def setOfWords2Vec(vocabList, inputSet):
'''詞集模型, 將資料集中的詞彙列表轉換為詞向量'''
returnVec = [0]*len(vocabList) # 建立一個和vocabList等長的向量
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1 # 如果word在vocabList中,其索引對應位置的特徵維度修改為1
else:
print("The word:%s is not in my Vocabulary" %word)
return returnVec
def bagOfWords2Vec(vocabList, inputSet):
'''詞袋模型'''
returnVec = [0]*len(vocabList) # 建立一個和vocabList等長的向量
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1 # 如果word在vocabList中,其索引對應位置的特徵維度加1
else:
print("The word:%s is not in my Vocabulary" %word)
return returnVec
接下來是真正的訓練部分.我們知道要計算
,就需要計算
,其中w為一個文字,文字獨一無二,用來計算概率沒有意義,所以其實是計算構建文字的詞條的概率,那麼詞條和文字之間的關係是什麼呢,根據樸素貝葉斯的第一個假設,各詞條之間獨立,則有
訓練部分做的就是計算每個詞條的 ,累計某個分類中的文字內各詞條出現的次數,去除以該類文字的詞條總量即是所求的概率.這個概率去乘以P(c),即該類中文字數量在文字總數中的出現的頻數,就是我們需要的 .對於一個未知的 ,求其 即是求上式.
def trainNB0(trainMatrix, trainCategory):
'''
計算詞表/詞袋中的詞在有辱罵性句子/沒有辱罵性句子的前提下出現的條件概率
trainMatrix--訓練矩陣,注意!!!是ndarray格式
trainCategory--訓練向量的標籤列表 注意!!!也是ndarray格式
'''
numofDoc = len(trainMatrix) # 用於訓練的句子數
numofWord = len(trainMatrix[0]) # 用於分類的詞彙數量
pAbusive = sum(trainCategory)/numofDoc # p(c1)為有辱罵性的句子的總數除以句子總數
# p1Num = zeros(numofWord); p0Num = zeros(numofWord) # 初始化各單詞的計數
# p1Cal = 0.0; p0Cal = 0.0
p1Num = ones(numofWord); p0Num = ones(numofWord) # 初始化各單詞的計數
p1Cal = 2.0; p0Cal = 2.0
# 這裡的修改解決了一個問題,在第一版情況下,有大量的詞的條件概率為0,
# 在利用貝葉斯變換相乘時會導致結果的錯誤.所以把每個詞的初始頻率為
# 0.5,即在初始情況下,一個詞出現在兩邊的概率相等.
for i in range(numofDoc):
if trainCategory[i] == 1:
p1Num += trainMatrix[i] # p1Num是一個向量,對有辱罵性句子中的各詞出現次數累加
p1Cal += sum(trainMatrix[i]) # p0Num同樣是向量~
else:
p0Num += trainMatrix[i] # 計算有辱罵性句子中詞彙總數
p0Cal += sum(trainMatrix[i]) #計算無辱罵性~
# p1 = p1Num/p1Cal # 計算各詞的p(w_i | c1)
# p0 = p0Num/p0Cal # 計算各詞的p(w_i | c0)
p1 = log(p1Num / p1Cal)
p0 = log(p0Num/p0Cal)
# 第二版解決了另一個問題,即使每個詞的初始概率為0.5,大量詞的概率相乘後,得到的數非常小,可能
# 導致下溢位,即變成0,將小數相乘轉化為對數相加,能夠有效避免下溢位並且結果的數值雖然發生變化,
# 但趨勢不變
return p0, p1, pAbusive
def classifyNB(vec2Classify,p0Vec, p1Vec, pClass1):
'''根據詞表中提供的條件概率,及侮辱性句子的在資料集中出現的概率,計算一個測試句子是/不是侮辱性句子的概率
由於vec2classify和p兩個向量需要做元素相乘,
故vec2classify也需要是ndarray
但是list和一個array相乘時,numpy會把list轉為array然後做相乘,這是一個trick
但是最好還是在使用函式的時候加上array()
'''
p1 = sum(vec2Classify * p1Vec) + log(pClass1)
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
else: return 0
def testingNB():
'''便利函式,將功能的測試封裝到一個函式'''
listOPosts, listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
trainMat = []
for line in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, line))
p0V, p1V, pAb = trainNB0(trainMat, listClasses)
testEntry = ['love', 'my', 'dalmation']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry)) # 注意轉化為ndarray陣列
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))
testingNB()
['love', 'my', 'dalmation'] classified as: 0
['stupid', 'garbage'] classified as: 1
利用樸素貝葉斯過濾垃圾郵件
過濾垃圾郵件同樣是對垃圾郵件和非垃圾郵件的文字進行詞表製作,然後計算詞表中的各詞的概率.
談一談這裡面測試集和訓練集的問題,從資料集中隨機選取一部分作為訓練集,而剩餘部分作為測試集的過程稱為留存交叉驗證,分類器所需要的概率計算只能從訓練集中的文件來完成.
def splitParse(bigString):
'''split the phrase to words longer than 2 characters'''
import re
regEx = re.compile('\\W*')
listOfToken = regEx.split(bigString)
tokenList = [tok.lower() for tok in listOfToken if len(tok) > 2]
return tokenList
def spamTest():
docList = [] # 文件列表,每個元素是該文件的劃分詞後的列表
fullText = [] # 包含所有文件的詞彙,用於製作詞表 這個是幹什麼的呢?
classList = [] # 即上文的label,用於標記資料集的分類標籤
for i in range(1,26):
wordList = splitParse(open('MLIA_self/ch_04/email/spam/%d.txt' % i,encoding='ISO-8859-1').read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(1) #類別標籤為1,即是垃圾郵件
wordList = splitParse(open('MLIA_self/ch_04/email/ham/%d.txt' % i, encoding='ISO-8859-1').read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(0) # 類別標籤為1,即是垃圾郵件
vocabList = createVocabList(docList)
trainSet = list(range(50)); testSet = [] # 初始化訓練集和測試集的index
for i in range(10):
randIndex = int(random.uniform(0,len(trainSet)))
testSet.append(trainSet[randIndex])
del(trainSet[randIndex]) # 這樣刪除不會導致刪了一個之後index就變了麼? 上一行要改
trainMat = []; trainClasses = []
for i in trainSet: # 把訓練集和訓練集的標籤匯入
trainMat.append(setOfWords2Vec(vocabList,docList[i]))
trainClasses.append(classList[i])
p0V, p1V, pSpam = trainNB0(array(trainMat),array(trainClasses))
errorCount = 0.0
for i in testSet:
result = classifyNB(array(setOfWords2Vec(vocabList, docList[i])),p0V,p1V,pSpam)
if result != classList[i]:
errorCount += 1
print('The error rate is: %.3f' % float(errorCount / len(testSet)))
spamTest()
The error rate is: 0.000
/home/caid/anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py:5: FutureWarning: split() requires a non-empty pattern match.
"""
在本章的利用貝葉斯處理文字的例子中,詞條切分器和停用詞的設定都值得深入研究,如果遇到文字處理問題時,這一塊還要繼續深入.