朴素贝叶斯(Naive Bayes)
算法思想
朴素贝叶斯(Naive Bayes)是基于贝叶斯定理与特征条件假设的分类方法。给定一个未知的样本XXX,分类法将预测XXX属于具有最高后验概率的类,即,未知的样本分配给yjy_jyj,当且仅当
P(yj∣X)>P(yj∣X),1≤i≤k,i≠jP(y_j|X)>P(y_j|X),1≤i≤k,i \neq jP(yj∣X)>P(yj∣X),1≤i≤k,i̸=j
贝叶斯定理
介绍贝叶斯定理前,先理解条件概率和全概率公式。
先验概率
P(X=玩lol)=0.6;P(X=不玩lol)=0.4P(X=玩lol)=0.6;P(X=不玩lol)=0.4P(X=玩lol)=0.6;P(X=不玩lol)=0.4,这个概率是统计得到的,即X的概率分布已知,我们称其为先验概率(prior probability);
后验概率
P(Y=男性|X=玩lol)=0.8,P(Y=小姐姐|X=玩lol)=0.2
P(Y=男性|X=不玩lol)=0.2,P(Y=小姐姐|X=不玩lol)=0.8
那么我想问在已知玩家为男性的情况下,他是lol玩家的概率是多少,最后算出的P(X=玩lol|Y=男性)称之为X的后验概率,它获得是在观察到事件Y发生后得到的。
条件概率
P(A∣B)P(A|B)P(A∣B)表示事件BBB已经发生的前提下,事件AAA发生的概率,叫做事件BBB发生下事件AAA的条件概率。其基本求解公式为
P(A∣B)=P(AB)P(B)P(A|B)=\frac{P(AB)}{P(B)}P(A∣B)=P(B)P(AB)
全概率公式
若时间B1,B2,...BnB_1,B_2,...B_nB1,B2,...Bn构成完备事件组,且P(Bi)>0(i=1,2,3,...,n)P(B_i)>0(i=1,2,3,...,n)P(Bi)>0(i=1,2,3,...,n),则有对于任何事件A,有
P(A)=∑i=1nP(Bi)P(A∣Bi)P(A)=\sum_{i=1}^{n}P(B_i)P(A|B_i)P(A)=i=1∑nP(Bi)P(A∣Bi)
贝叶斯定理
若事件B1,B2,...BnB_1,B_2,...B_nB1,B2,...Bn构成完备事件组,且P(Bi)>0(i=1,2,3,...,n)P(B_i)>0(i=1,2,3,...,n)P(Bi)>0(i=1,2,3,...,n),则有对于任何概率大于零事件A,有
P(Bi∣A)=P(ABi)P(A)=P(Bi)P(A∣Bi)P(A)P(B_i|A)=\frac{P(AB_i)}{P(A)}=\frac{P(B_i)P(A|B_i)}{P(A)}P(Bi∣A)=P(A)P(ABi)=P(A)P(Bi)P(A∣Bi)
再根据全概率公式,有
P(Bi∣A)=P(Bi)P(A∣Bi)∑i=1nP(Bi)P(A∣Bi)P(B_i|A)=\frac{P(B_i)P(A|B_i)}{\sum_{i=1}^{n}P(B_i)P(A|B_i)}P(Bi∣A)=∑i=1nP(Bi)P(A∣Bi)P(Bi)P(A∣Bi)
模型推导
给定训练数据集(X,Y)(X,Y)(X,Y),其中每个样本XXX都包括nnn维特征,即x=(x1,x2,⋅⋅⋅,xn)x=(x_1,x_2,⋅⋅⋅,x_n)x=(x1,x2,⋅⋅⋅,xn),类标记集合含有K种类别,即y=(y1,y2,⋅⋅⋅,yk)y=(y_1,y_2,⋅⋅⋅,y_k)y=(y1,y2,⋅⋅⋅,yk),如果现在来了一个新样本x我们要怎么判断它的类别?从概率的角度来看,这个问题就是给定x,它属于哪个类别的概率更大。那么问题就转化为求P(y1∣x)P(y_1|x)P(y1∣x),P(y2∣x)P(y_2|x)P(y2∣x),P(yk∣x)P(y_k|x)P(yk∣x)中最大的那个,那P(yk∣x)P(y_k|x)P(yk∣x)怎么求解?答案就是贝叶斯定理:
P(yi∣X)=P(yi)P(X∣yi)P(X)P(y_i|X)=\frac{P(y_i)P(X|y_i)}{P(X)}P(yi∣X)=P(X)P(yi)P(X∣yi)
目标函数:
maxP(yi∣X)max P(y_i|X)maxP(yi∣X)
朴素贝叶斯分类器所需要的信息
-
计算每个类的先验概率P(yi)P(y_i)P(yi)
P(yi)=njnP(y_i)=\frac{n_j}{n}P(yi)=nnj
其中,njn_jnj是yiy_iyi类的训练样本数,而nnn是训练样本总数 -
计算P(X∣yi)P(X|y_i)P(X∣yi)
计算P(X∣yi)P(X|y_i)P(X∣yi)需要得到联合改了分布:P(X1∣,X2,X3,...,X4∣Y)P(X_1|,X_2,X_3,...,X_4|Y)P(X1∣,X2,X3,...,X4∣Y),但是它的参数规模是指数数量级别的,假设第iii维特征xix_ixi可取值的个数有SiS_iSi个,类别取值个数为k个,那么参数个数为k∏nj=1Sjk∏_n^{j=1}S_jk∏nj=1Sj
这显然是不可行的。针对这个问题,朴素贝叶斯算法对条件概率分布做了独立性的假设,通俗地讲就是说假设各个维度的特征x1,x2,⋅⋅⋅,xnx_1,x_2,⋅⋅⋅,x_nx1,x2,⋅⋅⋅,xn互相独立,由于这是一个较强的假设,朴素贝叶斯算法也因此得名。在这个假设的前提上,条件概率可以转化为:
P(X∣yi)=P((x1,x2,x3,...,xn∣yi)=P(x1∣yj)P(x2∣yj)...P(xn∣yj)P(X|y_i)=P((x_1,x_2,x_3,...,x_n|y_i)=P(x_1|y_j)P(x_2|y_j)...P(x_n|y_j)P(X∣yi)=P((x1,x2,x3,...,xn∣yi)=P(x1∣yj)P(x2∣yj)...P(xn∣yj) -
计算P(xi∣yi)P(x_i|y_i)P(xi∣yi)
1.若第iii个属性AiA_iAi是离散值属性,则
P(xi∣yi)=nijnjP(x_i|y_i)=\frac{n_{ij}}{n_j}P(xi∣yi)=njnij其中nijn_ijnij是属性AiA_iAi上具有值xix_ixi的yjy_jyj类的训练样本数,而njn_jnj是yjy_jyj类的训练样本数
2.若第iii个属性AiA_iAi是连续值属性
- 将AjA_jAj离散化
- 假定AjA_jAj服从高斯分布
其中, μijμ_{ij}μij,σijσ_{ij}σij分别为给定yj类的训练样本在属性Ai上的均值和标准差
实例演算
解答过程:
Laplace估计
在实际应用中,会出现一个问题如果诸条件概率P(Xi=xi∣Y=yj)P(X_i=x_i |Y=y_j)P(Xi=xi∣Y=yj) 中的一个为0,则它们的乘积(计算P(X |Y=yj)的表达式)为0,很可能每个P(X |Y=y_j)都为0。
为此朴素贝叶斯模型计算P(X∣yi)P(X|y_i)P(X∣yi)时引入Laplace估计,以避免这种情况的出现。
原估计:P(Xi=xi∣Y=yj)=nijnj原估计: P(X_i=x_i |Y=y_j) = \frac{n_{ij}}{n_j}原估计:P(Xi=xi∣Y=yj)=njnij
Laplace估计:P(Xi=xi∣Y=yj)=nij+1nj+kLaplace估计: P(X_i=x_i |Y=y_j) = \frac{n_{ij}+1}{n_j+k}Laplace估计:P(Xi=xi∣Y=yj)=nj+knij+1
算法特点
- 对孤立的噪声点的鲁棒性
个别点对概率估计的影响很小 - 容易处理缺失值
在估计概率时忽略缺失值的训练实例 - 对不相关属性的鲁棒性
各类在不相关属性上具有类似分布 - 类条件独立假设可能不成立
使用其他技术,如贝叶斯信念网络( Bayesian Belief Networks,BBN)
python实现
from numpy import *
class Naive_Bayes:
def __init__(self):
self._creteria = "NB"
#创建不重复词集
def _creatVocabList(self,dataSet):
vocabSet = set([]) # 创建一个空的SET
for document in dataSet:
vocabSet = vocabSet | set(document) # 并集
return list(vocabSet) # 返回不重复词表(SET的特性)
#文档词集向量模型
def _setOfWordToVec(self,vocabList, inputSet):
"""
功能:给定一行词向量inputSet,将其映射至词库向量vocabList,出现则标记为1,否则标记为0.
"""
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
return returnVec
#文档词袋模型
def _bagOfsetOfWordToVec(self,vocabList, inputSet):
"""
功能:对每行词使用第二种统计策略,统计单个词的个数,然后映射到此库中
输出:一个n维向量,n为词库的长度,每个取值为单词出现的次数
"""
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1 #更新此处代码
return returnVec
def _trainNB0(self,trainMatrix, trainCategory):
"""
输入:训练词矩阵trainMatrix与类别标签trainCategory,格式为Numpy矩阵格式
功能:计算条件概率p0Vect、p1Vect和类标签概率pAbusive
"""
numTrainDocs = len(trainMatrix)#样本个数
numWords = len(trainMatrix[0])#特征个数,此处为词库长度
pAbusive = sum(trainCategory) / float(numTrainDocs)#计算负样本出现概率(先验概率)
p0Num = ones(numWords)#初始词的出现次数为1,以防条件概率为0,影响结果
p1Num = ones(numWords)#同上
p0Denom = 2.0#类标记为2,使用拉普拉斯平滑法,
p1Denom = 2.0
#按类标记进行聚合各个词向量
for i in range(numTrainDocs):
if trainCategory[i] == 0:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
else:
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
p1Vect = log(p1Num / p1Denom)#计算给定类标记下,词库中出现某个单词的概率
p0Vect = log(p0Num / p0Denom)#取log对数,防止条件概率乘积过小而发生下溢
return p0Vect, p1Vect, pAbusive
def _classifyNB(self,vec2Classify, p0Vec, p1Vec, pClass1):
"""
该算法包含四个输入:
vec2Classify表示待分类的样本在词库中的映射集合,
p0Vec表示条件概率P(wi|c=0)P(wi|c=0),
p1Vec表示条件概率P(wi|c=1)P(wi|c=1),
pClass1表示类标签为1时的概率P(c=1)P(c=1)。
p1=ln[p(w1|c=1)p(w2|c=1)…p(wn|c=1)p(c=1)]
p0=ln[p(w1|c=0)p(w2|c=0)…p(wn|c=0)p(c=0)]
log取对数为防止向下溢出
功能:使用朴素贝叶斯进行分类,返回结果为0/1
"""
p1 = sum(vec2Classify * p1Vec) + log(pClass1)
p0 = sum(vec2Classify * p0Vec) + log(1 - pClass1)
if p1 > p0:
return 1
else:
return 0
#test
def testingNB(self,testSample):
"step1:加载数据集与类标号"
listOPosts, listClasses = loadDataSet()
"step2:创建词库"
vocabList = self._creatVocabList(listOPosts)
"step3:计算每个样本在词库中出现的情况"
trainMat = []
for postinDoc in listOPosts:
trainMat.append(self._bagOfsetOfWordToVec(vocabList, postinDoc))
p0V, p1V, pAb = self._trainNB0(trainMat, listClasses)
"step4:测试"
thisDoc = array(self._bagOfsetOfWordToVec(vocabList, testSample))
result=self._classifyNB(thisDoc, p0V, p1V, pAb)
print (testSample, 'classified as:', result)
# return result
###
# 加载数据集
def loadDataSet():
postingList = [['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 postingList, classVec
#测试
if __name__=="__main__":
clf = Naive_Bayes()
testEntry = [['love', 'my', 'girl', 'friend'],
['stupid', 'garbage'],
['Haha', 'I', 'really', "Love", "You"],
['This', 'is', "my", "dog"],
['maybe','stupid','worthless']]
for item in testEntry:
clf.testingNB(item)
程序结果:
使用朴素贝叶斯分类器对垃圾邮件进行过滤:
import re
from numpy import *
def textParse(bigString):
import re
listOfTokens=re.split(r'\w*',bigString)
return [tok.lower() for tok in listOfTokens if len(tok)>2]
def spamTest():
clf = Naive_Bayes()
docList=[]
classList=[]
fullText=[]
for i in range(1,26):
wordList=textParse(open('email/spam/%d.txt'%i,encoding='ISO-8859-1').read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(1)
wordList=textParse(open('email/ham/%i.txt'%i,encoding='ISO-8859-1').read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
print(docList)
vocabList=clf._creatVocabList(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])
trainMatix=[];trainClasses=[]
for docIndex in trainingSet:
trainMatix.append(clf._bagOfsetOfWordToVec(vocabList,docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V,p1V,pSpam=clf._trainNB0(array(trainMatix),array(trainClasses))
errorCount = 0
for docIndex in testSet:
wordVector = clf._bagOfsetOfWordToVec(vocabList,docList[docIndex])
if clf._classifyNB(array(wordVector), p0V, p1V, pSpam)!=classList[docIndex]:
errorCount+=1
print ("classification error",docList[docIndex],classList[docIndex])
print ('the error rate is :',float(errorCount)/len(testSet))
if __name__=="__main__":
spamTest()