樸素貝葉斯
樸素貝葉斯是一種基于貝葉斯決策理論的分類方法。
我們用p1(x,y)表示資料點(x,y)屬于類别1的機率,用p2(x,y)表示資料點(x,y)屬于類别2的機率,那麼對于一個新資料點(x,y),可以用下面的規則來判斷它的類别:
- 如果 p1(x,y) > p2(x,y),那麼類别為1。
- 如果 p2(x,y) > p1(x,y),那麼類别為2。
我們會選擇高機率對應的類别。這就是貝葉斯決策理論的核心思想,即選擇具有最高機率的決策。
貝葉斯推論
條件機率:
p ( A ∣ B ) = p ( A B ) p ( B ) p(A|B) = \frac{p(AB)}{p(B)} p(A∣B)=p(B)p(AB)
對條件機率公式和全機率公式進行變形:
p ( A ∣ B ) = p ( A ) p ( B ∣ A ) p ( B ) p(A|B) = p(A)\frac{p(B|A)}{p(B)} p(A∣B)=p(A)p(B)p(B∣A)
我們把P(A)稱為“先驗機率”(Prior probability),即在B事件發生之前,我們對A事件機率的一個判斷。
P(A|B)稱為“後驗機率”(Posterior probability),即在B事件發生之後,我們對A事件機率的重新評估。
P(B|A)/P(B)稱為“可能性函數”(Likelyhood),這是一個調整因子,使得預估機率更接近真實機率。
後驗機率 = 先驗機率 x 調整因子
這就是貝葉斯推斷的含義。我們先預估一個“先驗機率”,然後加入實驗結果,看這個實驗到底是增強還是削弱了“先驗機率”,由此得到更接近事實的“後驗機率”。
在這裡,如果“可能性函數”P(B|A)/P(B)>1,意味着“先驗機率”被增強,事件A的發生的可能性變大;如果“可能性函數”=1,意味着B事件無助于判斷事件A的可能性;如果“可能性函數”<1,意味着”先驗機率”被削弱,事件A的可能性變小。
樸素貝葉斯的特點
優點:在資料較少的情況下仍然有效,可以處理多類别問題。
缺點:對于輸入資料的準備方式較為敏感。
适用資料類型:标稱型資料。
樸素貝葉斯的一般流程
- 收集資料:可以使用任何方法。後面的執行個體使用RSS源。
- 準備資料:需要數值型或者布爾型資料。
- 分析資料:有大量特征時,使用直方圖效果更好。
- 訓練算法:計算不同的獨立特征的條件機率。
- 測試算法:計算錯誤率。
- 使用算法:一個常見的樸素貝葉斯應用是文檔分類。可以在任意的分類場景中使用樸素貝葉斯分類器,不一定非要是文本。
使用樸素貝葉斯進行文檔分類
以線上社群留言闆為例:屏蔽侮辱性的言論,建構一個分類器,過濾侮辱性的言論。
1.準備資料:從文本中建構詞向量
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):
vocabSet = set() #建立一個空集
for document in dataSet:
vocabSet = vocabSet | set(document) #建立兩個集合的并集
return list(vocabSet)
将詞表轉化為向量
#周遊檢視該單詞是否出現,出現該單詞則将該單詞置1
def setOfWordsVec(vocabList, inputSet):
returnVec = [0] * len(vocabList) #建立一個其中所含元素都為0的向量
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1 #索引位置
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec
2.訓練算法:通過詞向量計算機率
利用貝葉斯準則計算機率,僞代碼如下所示:
計算每個類别中的文檔數目
對每篇訓練文檔:
對每個類别:
如果詞條出現在文檔中→ 增加該詞條的計數值
增加所有詞條的計數值
對每個類别:
對每個詞條:
将該詞條的數目除以總詞條數目得到條件機率
傳回每個類别的條件機率
python實作的樸素貝葉斯訓練函數如下:
#樸素貝葉斯分類器訓練函數
from numpy import *
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0]) #計算每篇文檔的詞條數
#侮辱性檔案出現的機率,這個例子隻有兩個分類,非侮辱性機率 = 1- 侮辱性的機率
#侮辱性檔案的個數除以檔案總數 = 侮辱性檔案出現的機率
pAbusive = sum(trainCategory) / float(numTrainDocs)
#單詞出現的次數
p0Num = zeros(numWords)
p1Num = zeros(numWords)
#整個資料集中單詞出現的次數
p0Denom = 0.0
p1Denom = 0.0
#周遊所有的檔案
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
p0Vect = p0Num/p0Denom
return p0Vect, p1Vect, pAbusive
使用拉普拉斯平滑優化後的訓練函數:使用拉普拉斯平滑,為了解決0機率的問題。
#優化版訓練函數
def trainNB1(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pAbusive = sum(trainCategory) / float(numTrainDocs)
p0Num = ones(numWords)
p1Num = ones(numWords)
#整個資料集單詞出現總數,2.0(主要是為了避免分母為0的情況)
p0Denom = 2.0 #拉普拉斯平滑
p1Denom = 2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
#累加侮辱詞的頻次
p1Num += trainMatrix[i]
#對每篇文章的侮辱詞的頻次進行統計
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
#類别1,侮辱性文檔的清單[log(P(F1|C1)...]
p1Vect = log(p1Num / p1Denom)
#類别0,正常文檔的清單
p0Vect = log(p0Num / p0Denom)
return p0Vect, p1Vect, pAbusive
3.測試算法:建構分類函數
#樸素貝葉斯分類函數
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1) # 對應元素相乘,log中變為相加
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 postinDoc in listOposts:
trainMat.append(setOfWordsVec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB1(array(trainMat),array(listClasses))
testEntry = ['love', 'my', 'dalmation']
thisDoc = array(setOfWordsVec(myVocabList, testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
testEntry = ['stupid', 'garbage']
thisDoc = array(setOfWordsVec(myVocabList, testEntry))
print(testEntry, 'classified as: ',classifyNB(thisDoc, p0V, p1V, pAb))
測試結果如下圖所示:
使用使用樸素貝葉斯過濾垃圾郵件
1.準備資料:切分文本
使用正規表達式來切分句子,其中分隔符是除單詞、數字外的任意字元串。
#文本解析,分詞,解析為一共字元串清單
def textParse(bigString):
import re
#r表示raw String,自動将反斜杠轉義
listOfTokens = re.split(r'\W+', bigString) #比對除單詞、數字外的任意字元串
# 将字元串轉化為小寫,并傳回長度大于2的字元串
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
2.測試算法:使用樸素貝葉斯進行交叉驗證
留存交叉驗證(hold-out cross validation):随機選擇一部分資料作為訓練集,而剩餘的資料用于測試集。
#垃圾郵件測試函數
import random
def spamTest():
docList =[]
classList = []
fullText = []
for i in range(1,26):
#切分解析文本資料
try:
wordList = textParse(open('H:/python/email/spam/{}.txt'.format(i)).read())
except:
wordList = textParse(open('H:/python/email/spam/{}.txt'.format(i),encoding = 'Windows 1252').read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(1)
try:
wordList = textParse(open('H:/python/email/ham/{}.txt'.format(i)).read())
except:
wordList = textParse(open('H:/python/email/ham/{}.txt'.format(i), encoding='Windows 1252').read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
# 建立詞彙表
vocabList = createVocabList(docList)
# 訓練資料集,總共50封郵件
trainingSet = list(range(50)) #range傳回的是range對象,不傳回數組對象,不用list後面删除會出錯
testSet = []
#随機取10封郵件用來進行測試
for i in range(10):
# random.uniform(x, y) 随機生成一個範圍為 x - y 的實數
randIndex = int(random.uniform(0,len(trainingSet)))
#留存交叉驗證
testSet.append(trainingSet[randIndex]) #添加到測試集
del(trainingSet[randIndex]) #在訓練集删除添加到測試集中的資料
trainMat = []
trainClasses = []
#用訓練集訓練
for docIndex in trainingSet:
trainMat.append(setOfWordsVec(vocabList,docList[docIndex])) #建構詞向量
trainClasses.append(classList[docIndex])
p0V, p1V, pSpam = trainNB1(array(trainMat), array(trainClasses))
errorCount = 0 #錯誤個數
#進行測試
for docIndex in testSet:
wordVector = setOfWordsVec(vocabList, docList[docIndex])
if classifyNB(array(wordVector), p0V,p1V,pSpam) != classList[docIndex]:
errorCount += 1 #分類錯誤數+1
print('the errorCount is: ', errorCount)
print('the testSet length is: ', len(testSet))
print('the error rate is:', float(errorCount)/len(testSet))
測試結果如下:
使用樸素貝葉斯分類器從個人廣告中擷取區域傾向
1.收集資料:導入 RSS 源
RSS源分類器及高頻詞去除函數
# 高頻詞去除函數
def calcMostFreq(vocabList, fullText):
#周遊詞彙表中的每個詞并統計它在文本中出現的次數
freqDict = {}
for token in vocabList:
freqDict[token] = fullText.count(token) #統計每個詞在文本中出現的次數
#根據出現次數從高到低對詞典進行排序,最後傳回排序最高的30個單詞
sortedFreq = sorted(freqDict.items(), key = operator.itemgetter(1), reverse = True) #True表示降序排列
return sortedFreq[0:30]
# RSS源分類器
def localWords(feed1, feed0):
docList = []
classList = []
fullText = []
minLen = min(len(feed1['entries']), len(feed0['entries']))
for i in range(minLen):
wordList = textParse(feed1['entries'][i]['summary']) #每次通路一條RSS源
docList.append(wordList)
fullText.extend(wordList)
classList.append(1)
wordList = textParse(feed0['entries'][i]['summary'])
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
vocabList = createVocabList(docList)
top30Words = calcMostFreq(vocabList, fullText)
for pairW in top30Words:
if pairW[0] in vocabList:
vocabList.remove(pairW[0]) #去除出現次數最高的那些詞
trainingSet = list(range(2 * minLen))
testSet = []
import random
for i in range(20):
rangeIndex = int(random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[rangeIndex])
del(trainingSet[rangeIndex])
#testSet = [int(num) for num in random.sample(range(2 * minLen),20)]
#trainingSet = list(set(range(2 * minLen)) - set(testSet))
trainMat = []
trainClasses = []
for docIndex in trainingSet:
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V, p1V, pSpam = trainNB1(array(trainMat), array(trainClasses))
errorCount = 0
for docIndex in testSet:
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1
print('the error rate is: ', float(errorCount) / len(testSet))
return vocabList, p0V, p1V
詞袋模型:它與函數setOfWords2Vec()幾乎完全相同,唯一不同的是每當遇到一個單詞時,它會增加詞向量中的對應值,而不隻是将對應的數值設為1。
#樸素貝葉斯詞袋模型
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0] * len(vocabList) #建立一個其中所含元素都為0的向量
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
這個案例由于RSS源資料進行了更新,測試的資料得不到我們想要的結果。
小結
對于分類而言,使用機率有時要比使用硬規則更為有效。貝葉斯機率及貝葉斯準則提供了一種利用已知值來估計未知機率的有效方法。
參考文章
https://blog.csdn.net/c406495762/article/details/77500679
https://blog.csdn.net/c406495762/article/details/77341116
https://github.com/apachecn/MachineLearning/blob/master/docs/4.樸素貝葉斯.md