樸素貝葉斯(naive bayes)模型主要用於文本分類,比如要將郵件分類爲正常郵件和帶侮辱性詞彙郵件
對於一封郵件來說其特徵可以表示爲該郵件中單詞出現的情況。
比如我們有一個5000個詞的詞典表,那麼郵件的特徵可表示成一個特徵向量,特徵向量的維數等於詞典表的單詞個數,特徵向量每一維的取值空間爲0或1(即這個單詞是否出現)
對於p(x|y),在某一組樣本中:
p(x1x2...x5000|y)=p(x1|y)p(x2|y,x1).....p(x5000|y,x1,x2...x4999)
這個問題是很複雜的,因此我們需要做樸素貝葉斯假設(這就是爲什麼這個算法樸素的原因):
我們假設樣本中的特徵(即每個詞出現的情況)相互獨立
該假設不符合常理,因爲很多詞的出現概率之間是有聯繫的。例如,郵件中如果出現了‘Obama’,那麼郵件中出現‘USA’的概率將會大大提高。
但是我們做了這個假設後,能夠簡化我們的問題,且分類器的效果還不錯
因此上式可改爲:
p(x1x2...x5000|y)=p(x1|y)p(x2|y,x1).....p(x5000|y,x1,x2...x4999)
=p(x1|y)p(x2|y)..p(x5000|y)
之後求解極大似然估計啥的就不寫了,最終通過訓練樣本我們需要得到的是:y=0時第j個詞出現的概率,y=1時第j個詞出現的概率,以及樣本中y=1的概率
因此,對於一組輸入,我們要確定它屬於0類還是1類,我們只需要比較:
y=1時輸入中所有出現的詞在樣本中出現的概率之積*樣本中y=1的概率 與
y=0時輸入中所有出現的詞在樣本中出現的概率之積*樣本中y=0的概率 哪個大
說的不是很清楚,因爲東西太多了,表達能力有限,公式也沒寫全。具體的細節大家可以看看吳恩達的機器學習課程來學習。
下面上python代碼,主要參考了《機器學習實戰》。裏邊包括了一些細節,比如log優化,拉普拉斯平滑,大家就看看代碼的註釋吧,註釋寫的清楚。
naiveBayes.py
# coding=UTF-8
from numpy import *
import matplotlib.pyplot as plt
import time
import math
import re
def loadTrainDataSet(): #讀取訓練集
fileIn = open('testSet.txt')
postingList=[] #郵件表,二維數組
classVec=[]
i=0
for line in fileIn.readlines():
lineArr = line.strip().split()
temp=[]
for i in range(len(lineArr)):
if i==0:
classVec.append(int(lineArr[i]))
else:
temp.append(lineArr[i])
postingList.append(temp)
i=i+1
return postingList,classVec
def createVocabList(dataSet): #創建詞典
vocabSet = set([]) #定義list型的集合
for document in dataSet:
vocabSet = vocabSet | set(document)
return list(vocabSet)
def setOfWords2Vec(vocabList,inputSet): #對於每一個訓練樣本,得到其特徵向量
returnVec= [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else:
pass
#print("\'%s\' 不存在於詞典中"%word)
return returnVec
def createTrainMatrix(vocabList,postingList): #生成訓練矩陣,即每個樣本的特徵向量
trainMatrix=[] #訓練矩陣
for i in range(len(postingList)):
curVec=setOfWords2Vec(vocabList,postingList[i])
trainMatrix.append(curVec)
return trainMatrix
def trainNB0(trainMatrix,trainCategory):
numTrainDocs = len(trainMatrix) #樣本數量
numWords = len(trainMatrix[0]) #樣本特徵數
pAbusive = sum(trainCategory)/float(numTrainDocs) #p(y=1)
#分子賦值爲1,分母賦值爲2(拉普拉斯平滑)
p0Num=ones(numWords); #初始化向量,代表所有0類樣本中詞j出現次數
p1Num=ones(numWords); #初始化向量,代表所有1類樣本中詞j出現次數
p0Denom=p1Denom=2.0 #代表0類1類樣本的總詞數
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 #概率向量(p(x0=1|y=1),p(x1=1|y=1),...p(xn=1|y=1))
p0Vect = p0Num/p0Denom #概率向量(p(x0=1|y=0),p(x1=1|y=0),...p(xn=1|y=0))
#取對數,之後的乘法就可以改爲加法,防止數值下溢損失精度
p1Vect=log(p1Vect)
p0Vect=log(p0Vect)
return p0Vect,p1Vect,pAbusive
def classifyNB(vocabList,testEntry,p0Vec,p1Vec,pClass1): #樸素貝葉斯分類
#先將輸入文本處理成特徵向量
regEx = re.compile('\\W*') #正則匹配分割,以字母數字的任何字符爲分隔符
testArr=regEx.split(testEntry)
testVec=array(setOfWords2Vec(vocabList,testArr))
#此處的乘法並非矩陣乘法,而是矩陣相同位置的2個數分別相乘
#矩陣乘法應當 dot(A,B) 或者 A.dot(B)
#下式子是原式子取對數,因此原本的連乘變爲連加
p1=sum(testVec*p1Vec)+log(pClass1)
p0=sum(testVec*p0Vec)+log(1.0-pClass1)
#比較大小即可
if p1>p0:
return 1
else:
return 0
#測試方法
def testingNB():
postingList,classVec=loadTrainDataSet()
vocabList=createVocabList(postingList)
trainMatrix=createTrainMatrix(vocabList,postingList)
p0V,p1V,pAb=trainNB0(trainMatrix,classVec)
#輸入測試文本,單詞必須用空格分開
testEntry='welcome to my blog!'
#testEntry='fuck you bitch!!!'
print('測試文本爲: '+testEntry)
if classifyNB(vocabList,testEntry,p0V,p1V,pAb):
print("--------侮辱性郵件--------")
else:
print("--------正常郵件--------")
testingNB()
testSet.txt //訓練集,開頭的01代表郵件是否是侮辱性郵件
0 i want you
1 fuck you
0 i want to go shopping
1 you are sillyb
0 i eat a lot of food
0 my university is UPC and ECNU
0 my professor is gaoming
0 i am learning data mining and machine learning
0 i think i need to buy some pen
1 you are shit fucking
0 haha haha haha
0 will you merry me:
1 fuck u dog sillyb
1 you are stupid
1 fuck stupid dog
1 fuck mother
1 fuck father dick
1 shit fuck bitch penis dick
分類結果:
輸入爲 'welcome to my blog!' 分類爲普通郵件
輸入爲‘fuck you bitch!!’ 分類爲侮辱性郵件