赞
踩
朴素贝叶斯(naive Bayes)是贝叶斯决策理论的一部分,是基于贝叶斯定理与特征条件独立假设的分类方法,它是一种简单但极为强大的预测建模算法。之所以称为朴素贝叶斯,是因为它假设每个输入变量是独立的。这个假设很硬,现实生活中根本不满足,但是这项技术对于绝大部分的复杂问题仍然非常有效。最为广泛的两种分类模型是决策树模型(Decision Tree Model)和朴素贝叶斯模型(Naive Bayesian Model,NBM)。
朴素贝叶斯分类(NBC)是以贝叶斯定理为基础并且假设特征条件之间相互独立的方法,先通过已给定的训练集,以特征词之间独立作为前提假设,学习从输入到输出的联合概率分布,再基于学习到的模型,输入X求出使得后验概率最大的输出Y。
计算原理:
我们使用P1(x,y)表示数据点(x,y)属于类别1(如下图橘色的圆点)的概率,使用P2(x,y)表示数据点(x,y)属于类别2(如下图蓝色的叉叉)的概率,那么,对于一个新的数据点(x1,y1),我们可以用以下规则来判断它的类别:
- 如果p1(x1,y1) > p2(x1,y1),那么这个点类别为1;
- 如果p1(x1,y1) < p2(x1,y1),那么这个点类别为2;
- 如果p1(x1,y1) = p2(x1,y1),那么这个点可设为1也可设为2。
- 垃圾邮件过滤
- 文本情感分析:电影评论
- 论坛真实账号监测
- 浏览器输入自动提示
- 先验概率(Prior probability) :指根据以往经验和分析得到的概率,如全概率公式,它往往作为"由因求果"问题中的"因"出现的概率。
- 后验概率(Posterior probability ):指在得到“结果”的信息后重新修正的概率,是“执果寻因”问题中的"果"。就是发生结果之后,推测原因的概率。
举个例子:
- 联合概率(joint probability)指的是包含多个条件且所有条件同时成立的概率,记作P(X=a,Y=b)或P(a,b),有的书上也习惯记作P(ab),但是这种记法个人不太习惯,所以下文采用以逗号分隔的记法。
- 边缘概率(marginal probability)是与联合概率对应的,P(X=a)或P(Y=b),这类仅与单个随机变量有关的概率称为边缘概率。
这个很好理解,举一个和之前差不多的例子:
条件概率是我们朴素贝叶斯算法的核心,是必须要理解的一个概率问题。
条件概率 (conditional probability) 是指在事件 B 发生的情况下,事件 A 发生的概率,通常记为 P(A | B)。推导如下:
P ( A ∣ B ) = P ( A ⋂ B ) P ( B ) P(A|B) = \frac{P(A \bigcap B)}{P(B)} P(A∣B)=P(B)P(A⋂B)因此: P ( A ⋂ B ) = P ( A ∣ B ) P ( B ) P(A \bigcap B)=P(A|B)P(B) P(A⋂B)=P(A∣B)P(B) P ( A ⋂ B ) = P ( B ∣ A ) P ( A ) P(A \bigcap B) = P(B|A)P(A) P(A⋂B)=P(B∣A)P(A)可得: P ( A ∣ B ) P ( B ) = P ( B ∣ A ) P ( A ) P(A|B)P(B)=P(B|A)P(A) P(A∣B)P(B)=P(B∣A)P(A)由此可以推出朴素贝叶斯公式: P ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ) P(A|B)= \frac{P(B|A)P(A)}{P(B)} P(A∣B)=P(B)P(B∣A)P(A)这也就是条件概率的计算公式。
这个公式指的是:
在B事件的条件之下,算A事件的概率,我们可以拆分为:已知的在A事件概率下B事件的概率乘以B事件的概率,再除以A事件的概率。
由前面我们知道了一个求条件概率的公式: P ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ) P(A|B)= \frac{P(B|A)P(A)}{P(B)} P(A∣B)=P(B)P(B∣A)P(A)在我们的朴素贝叶斯中,如何去运用这个公式呢?
根据我们的条件概率公式,我们对此进行一些推导,则可转化为: p ( y i ∣ x ) = p ( x ∣ y i ) p ( y i ) p ( x ) p(y_i|x)=\frac{p(x|y_i)p(y_i)}{p(x)} p(yi∣x)=p(x)p(x∣yi)p(yi)由此我们展开可得: p ( y i ∣ x ) = p ( x 1 ∣ y i ) p ( x 2 ∣ y i ) . . . p ( x n ∣ y i ) p ( y i ) p(y_i|x)=p(x_1|y_i)p(x_2|y_i)...p(x_n|y_i)p(y_i) p(yi∣x)=p(x1∣yi)p(x2∣yi)...p(xn∣yi)p(yi)注意:在这个地方,因为我们求的不是概率,而是概率大小,而同条件下,求不同事件的概率,在最后进行除法时,除的是同一个概率,所以仅仅比较大小的情况下不需要再进行一次除法,从而得到上面的公式。
接下来我由一个示例图举一个例子讲述一下怎么计算的:
编号 | X1 | X2 | Y |
---|---|---|---|
1 | 1 | S | -1 |
2 | 1 | M | -1 |
3 | 1 | M | 1 |
4 | 1 | S | 1 |
5 | 1 | S | -1 |
6 | 2 | S | -1 |
7 | 2 | M | -1 |
8 | 2 | M | 1 |
9 | 2 | L | 1 |
10 | 2 | L | 1 |
11 | 3 | L | 1 |
12 | 3 | M | 1 |
13 | 3 | M | 1 |
14 | 3 | L | 1 |
15 | 3 | L | -1 |
如表,可知,总共有15条数据,我们可知几个基础的概率:
P(Y=1) = 9/15, P(Y=-1) = 6/15
由此我们可以求一些相关的条件概率:
Y=1时X1各个值的概率 | Y=1时X2各个值的概率 | Y=-1时X1各个值的概率 | Y=-1时X2各个值的概率 |
---|---|---|---|
P(X1=1|Y=1)=2/9 | P(X2=S|Y=1)=1/9 | P(X1=1|Y=-1)=3/6 | P(X2=S|Y=-1)=3/6 |
P(X1=2|Y=1)=3/9 | P(X2=M|Y=1)=4/9 | P(X1=2|Y=-1)=2/6 | P(X2=M|Y=-1)=2/6 |
P(X1=3|Y=1)=4/9 | P(X2=L|Y=1)=4/9 | P(X1=3|Y=-1)=1/6 | P(X2=L|Y=-1)=1/6 |
所以,我们可以求出X1=2,X2=S时Y=1的概率大还是Y=-1的概率大:
P
(
Y
=
1
∣
X
1
=
2
,
X
2
=
S
)
=
P
(
X
1
=
2
∣
Y
=
1
)
∗
P
(
X
2
=
S
∣
Y
=
1
)
∗
P
(
Y
=
1
)
=
3
9
∗
1
9
∗
9
15
=
1
45
P(Y=1|X_1=2,X_2=S)=P(X_1=2|Y=1)*P(X_2=S|Y=1)*P(Y=1)=\frac{3}{9} * \frac{1}{9}* \frac{9}{15}=\frac{1}{45}
P(Y=1∣X1=2,X2=S)=P(X1=2∣Y=1)∗P(X2=S∣Y=1)∗P(Y=1)=93∗91∗159=451
P
(
Y
=
−
1
∣
X
1
=
2
,
X
2
=
S
)
=
P
(
X
1
=
2
∣
Y
=
−
1
)
∗
P
(
X
2
=
S
∣
Y
=
−
1
)
∗
P
(
Y
=
−
1
)
=
2
6
∗
3
6
∗
6
15
=
1
15
P(Y=-1|X_1=2,X_2=S)=P(X_1=2|Y=-1)*P(X_2=S|Y=-1)*P(Y=-1)=\frac{2}{6} * \frac{3}{6} * \frac{6}{15} =\frac{1}{15}
P(Y=−1∣X1=2,X2=S)=P(X1=2∣Y=−1)∗P(X2=S∣Y=−1)∗P(Y=−1)=62∗63∗156=151
明显可知,该条件下它的标记应该是Y=-1。这就是朴素贝叶斯在计算概率分类时的原理。
from numpy import * import re import feedparser # 词表到向量的转换函数 def loadDataSet(): # 创建一些实验样本 posttingList = [['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代表侮辱性文字, 0代表正常言论 return posttingList, classVec def createVocabList(dataSet): # 创建一个包含在所有文档中出现的不重复词的列表,为此使用Python的set数据类型 vocabSet = set([]) for document in dataSet: vocabSet = vocabSet | set(document) # 去重 return list(vocabSet) def setOfWords2Vec(vocabList, inputSet): # 该函数的输入参数为词汇表及某个文档,输出的是文档向量 returnVec = [0] * len(vocabList) # 创建一个其中所含元素都为0的向量 for word in inputSet: # 遍历每个词条 if word in vocabList: # 如果词条存在于词汇表中,则置1 returnVec[vocabList.index(word)] = 1 else: print('the word: ' + word + 'is not in my Vocabulary!') return returnVec
在这里,我们创建了一个6条数据,并对每条数据进行了分类,这个过程得到的数据如下:
listOPosts, listClasses = loadDataSet()
myVovatbList = createVocabList(listOPosts)
myVovatbList
>>> ['food', 'love', 'is', 'quit', 'stop', 'steak', 'posting', 'has', 'how', 'maybe', 'worthless', 'not', 'him', 'licks', 'ate', 'problems', 'please', 'dog', 'stupid', 'mr', 'my', 'park', 'dalmation', 'I', 'help', 'cute', 'flea', 'garbage', 'buying', 'take', 'so', 'to']
setOfWords2Vec(myVovatbList, listOPosts[3])
>>> [0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0]
# 朴素贝叶斯分类器训练函数 def trainNB0(trainMatrix, trainCategory): numTrainDocs = len(trainMatrix) # 计算训练的文档数目 numWords = len(trainMatrix[0]) # 计算每篇文档的词条数 pAbusive = sum(trainCategory) / float(numTrainDocs) # 文档属于侮辱类的概率 p0Num = ones(numWords) p1Num = ones(numWords) # 创建NumPy.ones数组,词条出现数初始化为1,拉普拉斯平滑 p0Denom = 2.0 p1Denom = 2.0 # 分母初始化为2,拉普拉斯平滑 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 = log(p1Num / p1Denom) # 取对数,防止下溢出 p0Vect = log(p0Num / p0Denom) return p0Vect, p1Vect, pAbusive # 返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率
# 朴素贝叶斯分类函数 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: # 6个向量 trainMat.append(setOfWords2Vec(myVocabList, postinDoc)) # 将一个一个样本,利用词汇表转换为词向量 p0V, p1V, pAb = trainNB0(array(trainMat), array(listClasses)) # 训练-计算条件概率 0-非侮辱类条件概率集 1-侮辱类条件概率集 testEntry = ['love', 'my', 'dalmation'] thisDoc = array(setOfWords2Vec(myVocabList, testEntry)) # 测试样本向量化 print(str(testEntry) + ' classified as: ' + str(classifyNb(thisDoc, p0V, p1V, pAb))) testEntry = ['stupid', 'garbage'] thisDoc = array(setOfWords2Vec(myVocabList, testEntry)) print(str(testEntry) + ' classified as: ' + str(classifyNb(thisDoc, p0V, p1V, pAb)))
在这里,我们输入了一些词进行测试,运行结果如下:
testingNB()
>>> ['love', 'my', 'dalmation'] classified as: 0
['stupid', 'garbage'] classified as: 1
在这里给大家演示一下书上的例子,垃圾邮件过滤,本示例的数据集来自书中所给,如果有兴趣的同学可以点击此处下载书中相关的数据集以及代码,同时里面也带有《机器学习实战》英文版和中文版PDF电子书(侵权请告知)。下面我演示一下这个示例,以及最后的结果。
# 文件解析及完整的垃圾邮件测试函数 def textParse(bigString): # 接收一个大写字符串并将其解析为字符串列表 regEx = re.compile('\\W*') listOfTokens = regEx.split(str(bigString)) return [tok.lower() for tok in listOfTokens if len(tok) > 2] def spamTest(): # 对贝叶斯垃圾邮件分类器进行自动化处理 docList = [] classList = [] fullText = [] for i in range(1, 26): # 访问25个txt文件 wordList = textParse(open('email/spam/'+ str(i) +'.txt', 'rb').read()) # 读取每个垃圾邮件,并将字符串转换成字符串列表 docList.append(wordList) fullText.extend(wordList) classList.append(1) # 标记垃圾邮件,1表示垃圾文件 wordList = textParse(open('email/ham/'+ str(i) +'.txt', 'rb').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个邮件中,随机抽选40封电子邮件作为训练集,10封作为测试集 randIndex = int(random.uniform(0, len(trainingSet))) # 随机选取索引值 testSet.append(trainingSet[randIndex]) # 添加测试集的索引值 del(trainingSet[randIndex]) # 在训练集列表中删除添加到测试集的索引值 trainMat = [] trainClasses = [] # 创建训练集矩阵和训练集类别标签系向量 for docIndex in trainingSet: # 遍历训练集 trainMat.append(setOfWords2Vec(vocabList, docList[docIndex])) # 将生成的词集模型添加到训练矩阵中 trainClasses.append(classList[docIndex]) # 将类别添加到训练集类别标签系向量中 p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses)) # 训练朴素贝叶斯模型 errorCpunt = 0 # 错误分类计数 for docIndex in testSet: # 遍历测试集 wordVector = setOfWords2Vec(vocabList, docList[docIndex]) # 测试集的词集模型 if classifyNb(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]: # 如果分类错误的情况发生 errorCpunt += 1 # 错误计数加1 print('the error rate is: ' + str(float(errorCpunt)/len(testSet)))
通过运行该模型,最终我们可以得到一个结果,但是,请注意: 因为每次在取数据集的时候是随机的,所有最终的结果会有一定的误差,这里我给出三次运行的结果:
spamTest()
>>> the error rate is: 0.1
spamTest()
>>> the error rate is: 0.2
spamTest()
>>> the error rate is: 0.0
优点:
- 朴素贝叶斯模型发源于古典数学理论,有稳定的分类效率。
- 对小规模的数据表现很好,能个处理多分类任务,适合增量式训练,尤其是数据量超出内存时,我们可以一批批的去增量训练。
- 对缺失数据不太敏感,算法也比较简单,常用于文本分类。
缺点:
- 理论上,朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为朴素贝叶斯模型给定输出类别的情况下,假设属性之间相互独立,这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好。而在属性相关性较小时,朴素贝叶斯性能最为良好。对于这一点,有半朴素贝叶斯之类的算法通过考虑部分关联性适度改进。
- 需要知道先验概率,且先验概率很多时候取决于假设,假设的模型可以有很多种,因此在某些时候会由于假设的先验模型的原因导致预测效果不佳。
- 由于我们是通过先验和数据来决定后验的概率从而决定分类,所以分类决策存在一定的错误率。
- 对输入数据的表达形式很敏感。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。