KNN和分类决策树都是要求分类器能够给出实例明确属于哪一类?但很多时候我们没法判断准确是哪一类时候,这时候起码能够给出最优可能的那一类和有多大概率属于该类。朴素贝叶斯分类算法就可以干这事。
首先概述整个朴素贝叶斯分类算法:对于给定的训练数据集,首先基于“特征条件独立假设”学习输入输出的联合概率分布;然后基于此模型,对于给定的输入(实例),利用贝叶斯定理求出后验概率最大的输出(类别)。
所以,我们的目标是,根据样本,算出该样本属于每个类别的后验概率,比较得出最大值对应的那个就是最有可能的类别,由贝叶斯定理有公式如下:
由于条件概率难算:是一个d维向量,那么就有d次方的指数级别种可能的组合,远大于样本的数量,说明一些组合是没有的,因此用样本出现的频数来估计条件概率既难算又不合理。所以用到了“特征条件独立假设”,该假设是说,用来分类的特征,在类确定的条件下是条件独立的,就是说每个特征对分类的影响独立。这是一个较强的假设(因为特征之间可能有关联性),使得该分类方法变得简单,损失一定准确性,所以称之为"朴素"。
由“特征条件独立假设”有公式如下:
并且由于对于每一个类别都是一样的,所以朴素贝叶斯分类器的表达式为:
参数估计用极大似然估计,其实也就是用频数来代表概率。
算法流程如下:
直接使用极大似然估计法时,需要注意,若某个属性值在训练集中没有与某个类同时出现,则直接基于之前的公式进行概率估计,再进行判别将出现问题。不能因为没有出现该样本,而直接把该样本的出现概率设为0。为了避免其他属性携带的信息被训练集中未出现的属性值 “抹去”,在估计概率值时通常需要进行 “平滑” (smoothing),我们常用 “拉普拉斯修正”。公式具体看李航书P51。
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']]
#0-非侮辱性,1-侮辱性
classVec = [0,1,0,1,0,1]
return postingList,classVec
def createVocabList(dataset):#获取词汇表:把文本集装换成一个不含重复词的列表
vocabSet = set([])#创建一个空集,可以进行集合操作
for document in dataset:
vocabSet = vocabSet |set(document)#每次把文件中的不重复元素放入
return list(vocabSet)
def setOfWords2Vec(vocabList, inputSet):#转化成向量:参数是词汇表和某个文档
returnVec = [0]*len(vocabList)#构建vocabList长度的0向量
for word in inputSet: #把该文档中在词汇表中有该单词的位置标位1(one-hot向量)
if word in vocabList:
returnVec[vocabList.index(word)]=1
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec
def trainNB0(trainMatrix, trainGategory):
numTrainDocs = len(trainMatrix) #返回训练的文档数目
numWords = len(trainMatrix[0]) #返回每一篇文档的词条数
pAbusive = sum(trainGategory)/float(numTrainDocs) #文档属于侮辱类的概率
#用拉普拉斯修正”来平滑数据
p0Num = np.ones(numWords); p1Num = np.ones(numWords) #创建numpy.zeros数组,词条出现数初始化为0
p0Denom = 2.0; p1Denom = 2.0 #分母初始化为2
for i in range(numTrainDocs):
if trainGategory[i] == 1: #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else: #统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = np.log(p1Num/p1Denom)#log防止下溢出
p0Vect = np.log(p0Num/p0Denom)
return p0Vect,p1Vect,pAbusive #返回属于非侮辱类的条件概率数组,属于侮辱类的条件概率数组,文档属于侮辱类的概率
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify*p1Vec)+np.log(pClass1)# #对应元素相乘。logA * B = logA + logB,所以这里加上log(pClass1)
p0 = sum(vec2Classify*p0Vec)+np.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(setOfWords2Vec(myVocabList, postinDoc))
p0V,p1V,pAb = trainNB0(np.array(trainMat),np.array(listClasses))
testEntry = ['love','my','dalmation']
thisDoc = np.array(setOfWords2Vec(myVocabList,testEntry))
print(testEntry,'classified as:',classifyNB(thisDoc,p0V,p1V,pAb))
testEntry = ['stupid','garbage']
thisDoc = np.array(setOfWords2Vec(myVocabList,testEntry))
print(testEntry,'classified as:',classifyNB(thisDoc,p0V,p1V,pAb))
#词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
def textParse(bigString):
"""
输入很长的字符串,转换为向量
参数:
bigString -- 长字符串
返回:
去掉少于两个字符,转换为小写的字符串
"""
import re
listOfTokens = re.split(r'\W*', bigString)#正则表达式来去掉空格和标点符号
return [tok.lower() for tok in listOfTokens if len(tok) > 2] #全变成小写,去掉少于两个字符的字符串
def spamTest():
#新建三个列表
docList=[]; classList = []; fullText =[]
#遍历垃圾邮件和正常邮件,各25个
for i in range(1,26):
#读取垃圾邮件
wordList = textParse(open("email/spam/{}.txt".format(i), errors = 'ignore').read())
#添加到列表
docList.append(wordList)
fullText.extend(wordList)
#添加到类
classList.append(1)
#读取正常邮件
#ham中的23.txt总是报错有不能解读的字节,选择忽略该错误
wordList = textParse(open("email/ham/{}.txt".format(i), errors = 'ignore').read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
#创建词汇表
vocabList = createVocabList(docList)
#训练集和测试集序号集
trainingSet = list(range(50)); testSet=[]
#随机抽取训练集中的10个序号,放入测试集
for i in range(10):
#生成随机序号
randIndex = np.int(np.random.uniform(0,len(trainingSet)))
#序号对应的元素由训练集移动到测试集中
testSet.append(trainingSet[randIndex])
del(trainingSet[randIndex])
#新建训练矩阵和训练标签
trainMat=[]; trainClasses = []
#对于训练集中的元素
for docIndex in trainingSet:
#对应词袋添加到训练矩阵中
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
#类别添加到标签中
trainClasses.append(classList[docIndex])
#训练朴素贝叶斯分类器
p0V,p1V,pSpam = trainNB0(np.array(trainMat),np.array(trainClasses))
#错误计数器初始化为0
errorCount = 0
#对于测试集
for docIndex in testSet:
#得到词袋向量
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
#判断结果
if classifyNB(np.array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
#统计错误
errorCount += 1
#打印错误信息
print("错误序号为:{}".format(docList[docIndex]))
print("总准确率为:{}".format(1 - np.float(errorCount)/len(testSet)))
【1】https://zhuanlan.zhihu.com/p/36545000
【2】《统计学习方法》李航
【3】《机器学习实战》