机器学习 基于概率论的分类方法:朴素贝叶斯
分类器可能产生错误分类,要求分类器给出一个最优的类别猜测结果并给出这个猜测的概率估计值。
朴素贝叶斯的特点:
优点:在数据较少的情况下依然有效,可以处理多类别问题;
缺点:对于输入数据的准备方式较为敏感;
适用数据类型:标称型数据
条件概率:在A条件下发生B结果的概率:
P(B|A) = P(A&B)/P(A) 在A条件下发生B结果的概率等于A和B同时发生的概率除以A发生的概率
P(A&B) = P(A)*P(B|A) A和B同时发生的概率等于A发生的概率乘以A条件下B发生的概率
P(A&B) = P(A)*P(B|A)=P(B)*P(A|B)
贝叶斯公式:
P(B|A)=P(B)*P(A|B)/P(A)
P(c1|x,y) 给定某个由(x,y)表示的数据点,那么该数据点来自类别c1的概率
P(c2|x,y) 给定某个由(x,y)表示的数据点,那么该数据点来自类别c2的概率
计算公式:
P(ci|x,y)=p(x,y|ci)*P(ci)/P(x,y)
判定原则:
如果P(c1|x,y)>P(c2|x,y),那么类别属于c1
如果P(c1|x,y)<P(c2|x,y),那么类别属于c2
朴素贝叶斯:整个形式化过程只做最原始最简单的假设。
使用朴素贝叶斯进行文档分类:
假设:
1、特征之间相互独立,即一个特征或者单词出现的可能与它和其他单词相邻没有关系
2、每个特征同等重要
要从文本中获取特征,首先要拆分文本。文本的特征是词条,即为单词,1表示词条出现在文档中,0表示词条未出现。分类结果为侮辱性和非侮辱性,用1和0表示。
准备数据:从文本中构建词向量,将一组单词转化为一组数字。
from numpy import * 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] #此标签列表对应上面的文档 return postingList,classVec def createVocabList(dataset): #创建一个包含在所有文档中出现的不重复词的列表,词向量列表,输入的是处理过的文本,例如postingList vacabSet=set([]) for document in dataset: vacabSet=vacabSet|set(document) #并集 return list(vacabSet) def setOfWordsToVec(vocabList,inputSet): #输入词汇表和某个文档,输出文档向量,向量的每一元素为0或1,表示词汇表中的单词在输入文档中是否出现 returnVec=[0]*len(vocabList) 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
用以上函数得到的数字计算概率的方法:将之前的(x,y)替换成w,w表示向量,有多个数值组成。在这个例子中,数值个数与词汇表中的词个数相等。
p(ci|w)=p(w|ci)*p(ci)/p(w)
ci表示不同的类别(侮辱性和非侮辱性),
p(ci)为ci类别文档的概率,等于ci类别的文档个数除以所有文档的个数
因为假设所有特征相互独立,所以
p(w|ci)=p(w0|ci)*p(w1|ci)*p(w2|ci)...*p(wn|ci)
极大简化了p(w|ci)的计算过程。
朴素贝叶斯分类器训练函数:
def trainNB0(trainMatrix,trainCategory): #输入文档矩阵、每篇文档类别标签构成的向量 #trainMatrix是由原文档列表转化为多维文档向量,即每个元素均为1或者0,每行元素个数都相同,等于词汇列表的长度,行数即为文本个数 numTrainDocs = len(trainMatrix) #文档的行数 numWords = len(trainMatrix[0]) #第一行文本的长度,每行文本的长度相同 pAbusive = sum(trainCategory)/float(numTrainDocs) #侮辱性文本概率P=侮辱性文本个数/文本总数 p0Num = zeros(numWords) #zeros()是numpy的方法,产生的是数组 p1Num = zeros(numWords) p0Denom = 0.0 p1Denom = 0.0 for i in range(numTrainDocs): #遍历文档内的每条文本 if trainCategory[i] == 1: #第i行文本的类别是侮辱性 p1Num += trainMatrix[i] #数组对应元素相加 p1Denom += sum(trainMatrix[i]) #p1Denom表示侮辱性文本包含的总词数,如果不同文本有同样的词汇则重复计数 else: p0Num += trainMatrix[i] p0Denom += sum(trainMatrix[i]) p1Vect = p1Num/p1Denom #p1Num是向量,p1Denom是侮辱性文本的总词数,p1Vect表示在侮辱性文本的条件下出现某些词汇的概率 p0Vect = p0Num/p0Denom #p0Vect表示在非侮辱性文本的条件下出现某些词汇的概率 return p0Vect,p1Vect,pAbusive
目前,已经建立了贝叶斯训练函数,可以求取给定文档列表的侮辱性文本概率、在侮辱性文本的条件下出现某些词汇的条件概率和在非侮辱性文本的条件下出现某些词汇的概率,测试并输出:
listOPosts,listClasses=loadDataset() #首先获取文档列表、文档内的文本分类标签列表 myVocabList=createVocabList(listOPosts) #从文档列表获取词汇列表,无重复词汇 trainMat=[] #trainMat是用来表示文档的向量 for postinDoc in listOPosts: trainMat.append(setOfWordsToVec(myVocabList,postinDoc)) #对比词汇列表和文本获得文本的向量表示,再获得文档的向量表示,trainMat是多维数组 p0v,p1v,pAb=trainNB0(trainMat,listClasses) print('侮辱性文本概率:',pAb) print('在非侮辱性文本的条件下出现某些词汇的概率:',p0v) print('在侮辱性文本的条件下出现某些词汇的概率',p1v)
输出:
侮辱性文本概率: 0.5 在非侮辱性文本的条件下出现某些词汇的概率: [0.04166667 0.04166667 0.125 0.04166667 0. 0. 0.04166667 0. 0.04166667 0.04166667 0.04166667 0.04166667 0. 0.04166667 0. 0.04166667 0. 0.04166667 0. 0. 0. 0.04166667 0.04166667 0. 0.04166667 0.04166667 0.04166667 0. 0.04166667 0.04166667 0.04166667 0.08333333] 在侮辱性文本的条件下出现某些词汇的概率 [0. 0. 0. 0. 0.05263158 0.15789474 0. 0.05263158 0. 0. 0. 0. 0.05263158 0. 0.05263158 0.05263158 0.05263158 0. 0.05263158 0.05263158 0.05263158 0.05263158 0. 0.05263158 0.10526316 0. 0. 0.10526316 0. 0. 0. 0.05263158]
目前计算出了p(wi|c0)、p(wi|c1)、p(ci)。
测试算法:
问题1:计算p(w|ci)=p(w0|ci)*p(w1|ci)*p(w2|ci)...*p(wn|ci)时,任何p(wj|ci)为0就导致p(w|ci)为0,为了降低此影响,将所有词的出现次数初始化为1,并将分母初始化为2.
将:
修改为:
问题2:p(w|ci)=p(w0|ci)*p(w1|ci)*p(w2|ci)...*p(wn|ci)中各项都很小,计算过程中可能会出现下溢出(多个很小的数相乘结果为0),一种解决办法是对乘积取自然对数。
ln(a*b)=ln(a)+ln(b)
代码做如下修改:
将:
p1Vect = p1Num/p1Denom p0Vect = p0Num/p0Denom
修改为:
p1Vect = log(p1Num/p1Denom) p0Vect = log(p0Num/p0Denom)
numpy模块的log(a)即为对a取自然对数。
使用分类器:
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): 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 postinDoc in listOPosts: trainMat.append(setOfWordsToVec(myVocabList, postinDoc)) p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses)) testEntry = ['love', 'my', 'dalmation'] thisDoc = array(setOfWordsToVec(myVocabList, testEntry)) print(testEntry,'classified to be: ',classifyNB(thisDoc,p0V,p1V,pAb)) testEntry = ['stupid', 'garbage'] thisDoc = array(setOfWordsToVec(myVocabList, testEntry)) print(testEntry,'classified to be: ',classifyNB(thisDoc,p0V,p1V,pAb))
现在,执行testingNB得到如下结果:
['love', 'my', 'dalmation'] classified to be: 0 ['stupid', 'garbage'] classified to be: 1
目前为止,我们将每个词的出现与否作为一个特征,这可以被描述为“词集模型”,无论待测文本里某个词汇出现多少次,其对应的数值均为1,在概率计算中会引入误差,解决方法:词袋模型
词袋中每个词汇可以出现多次,修改setOfWordsToVec为bagOfWordsToVec:
setOfWordsToVec:
def setOfWordsToVec(vocabList,inputSet): #输入词汇表和某个文档,输出文档向量,向量的每一元素为0或1,表示词汇表中的单词在输入文档中是否出现 returnVec=[0]*len(vocabList) #词集模型,只能表征某个词汇是否出现,不能表征其出现的次数 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
bagOfWordsToVec:
def bagOfWordsToVec(vocabList,inputSet): #词袋模型,能表征某个词汇出现的次数,不出现即为0 returnVec=[0]*len(vocabList) 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
所有代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from numpy import * 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] #此标签列表对应上面的文档 return postingList,classVec def createVocabList(dataset): #创建一个包含在所有文档中出现的不重复词的列表,词向量列表,输入的是处理过的文本,例如postingList vacabSet=set([]) for document in dataset: vacabSet=vacabSet|set(document) #并集 return list(vacabSet) def setOfWordsToVec(vocabList,inputSet): #输入词汇表和某个文档,输出文档向量,向量的每一元素为0或1,表示词汇表中的单词在输入文档中是否出现 returnVec=[0]*len(vocabList) #词集模型,只能表征某个词汇是否出现,不能表征其出现的次数 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 def bagOfWordsToVec(vocabList,inputSet): #词袋模型,能表征某个词汇出现的次数,不出现即为0 returnVec=[0]*len(vocabList) 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 # listOPosts,listClasses=loadDataset() # myVocabList=createVocabList(listOPosts) # print(myVocabList) # result=setOfWordsToVec(myVocabList,listOPosts[0]) # print(result) def trainNB0(trainMatrix,trainCategory): #输入文档矩阵、每篇文档类别标签构成的向量 #trainMatrix是由原文档列表转化为多维文档向量,即每个元素均为1或者0,每行元素个数都相同,等于词汇列表的长度,行数即为文本个数 numTrainDocs = len(trainMatrix) #文档的行数 numWords = len(trainMatrix[0]) #第一行文本的长度,每行文本的长度相同 pAbusive = sum(trainCategory)/float(numTrainDocs) #侮辱性文本概率P=侮辱性文本个数/文本总数 p0Num = ones(numWords) #zeros()是numpy的方法,产生的是数组 p1Num = ones(numWords) p0Denom = 2.0 p1Denom = 2.0 for i in range(numTrainDocs): #遍历文档内的每条文本 if trainCategory[i] == 1: #第i行文本的类别是侮辱性 p1Num += trainMatrix[i] #数组对应元素相加 p1Denom += sum(trainMatrix[i]) #p1Denom表示侮辱性文本包含的总词数,如果不同文本有同样的词汇则重复计数 else: p0Num += trainMatrix[i] p0Denom += sum(trainMatrix[i]) p1Vect = log(p1Num/p1Denom) #p1Num是向量,p1Denom是侮辱性文本的总词数,p1Vect表示在侮辱性文本的条件下出现某些词汇的概率 p0Vect = log(p0Num/p0Denom) #p0Vect表示在非侮辱性文本的条件下出现某些词汇的概率 return p0Vect,p1Vect,pAbusive # listOPosts,listClasses=loadDataset() #首先获取文档列表、文档内的文本分类标签列表 # myVocabList=createVocabList(listOPosts) #从文档列表获取词汇列表,无重复词汇 # trainMat=[] #trainMat是用来表示文档的向量 # for postinDoc in listOPosts: # trainMat.append(setOfWordsToVec(myVocabList,postinDoc)) #对比词汇列表和文本获得文本的向量表示,再获得文档的向量表示,trainMat是多维数组 # p0v,p1v,pAb=trainNB0(trainMat,listClasses) # print('侮辱性文本概率:',pAb) # print('在非侮辱性文本的条件下出现某些词汇的概率:',p0v) # print('在侮辱性文本的条件下出现某些词汇的概率',p1v) def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): 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 postinDoc in listOPosts: trainMat.append(bagOfWordsToVec(myVocabList, postinDoc)) p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses)) testEntry = ['love', 'my', 'dalmation','fucking'] thisDoc = array(bagOfWordsToVec(myVocabList, testEntry)) # print(thisDoc) # print(p0V) # print(p1V) # print(pAb) print(testEntry,'classified to be: ',classifyNB(thisDoc,p0V,p1V,pAb)) # testEntry = ['stupid', 'garbage'] # thisDoc = array(setOfWordsToVec(myVocabList, testEntry)) # print(testEntry,'classified to be: ',classifyNB(thisDoc,p0V,p1V,pAb)) testingNB()
使用朴素贝叶斯过滤垃圾邮件
切分文本:
import re mySent='This book is the best book on python or M.L. I have ever laid my eyes upon.' # print(mySent.split()) regEx=re.compile('\\W+') # listOfTokens=regEx.split(mySent) # listOfTokens=[word.lower() for word in listOfTokens if len(word)>0] # print(listOfTokens) emailText=open('email/spam/6.txt').read() listOfTokens=regEx.split(emailText) listOfTokens=[word.lower() for word in listOfTokens if len(word)>3] print(listOfTokens)
其中,regEx=re.compile('\\W+')表示正则表达式的匹配规则,即为非数字非字母。
测试算法:使用朴素贝叶斯进行交叉验证
def textParse(bigString): import re listOfTokens = re.split(r'\W+', bigString) return [word.lower() for word in listOfTokens if len(word) > 2] def spamTest(): docList = [] #文档列表 classList = [] fullText = [] for i in range(1, 26): wordList = textParse(open('email/spam/%d.txt'%i).read()) docList.append(wordList) #构建文档列表,多维列表 fullText.extend(wordList) #构建词列表,一维列表 classList.append(1) #前25个文件都是1类型的 wordList = textParse(open('email/ham/%d.txt'%i).read()) docList.append(wordList) fullText.extend(wordList) classList.append(0) #0类型的文件 vocabList = createVocabList(docList) trainingSet = list(range(50)) testSet = [] for i in range(10): #随机选取50个文件中的10个当作测试文件,剩余的40个作为训练文件 randIndex = int(random.uniform(0, len(trainingSet))) testSet.append(trainingSet[randIndex]) del trainingSet[randIndex] trainMat = [] trainClasses = [] for docIndex in trainingSet: trainMat.append(bagOfWordsToVec(vocabList, docList[docIndex])) trainClasses.append(classList[docIndex]) p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses)) errorCount = 0 for docIndex in testSet: wordVector = bagOfWordsToVec(vocabList, docList[docIndex]) if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]: errorCount += 1 print("classification error:", docList[docIndex]) print('the error rate is: ', float(errorCount) / len(testSet))
执行spamTest函数,输出分类的错误率。因为总共邮件有50封,随机选择10封作为测试邮件,另外40封作为训练邮件,所以每次执行结果不同,可以取多次执行结果的平均值作为分类器的分类错误率。