朴素贝叶斯算法

  朴素贝叶斯(naive bayes)算法是基于贝叶斯定理与特征条件独立假设的分类方法。对于给定的训练数据集,首先基于特征条件独立假设学习输入、输出的联合概率分布,然后基于此模型,对给定的输入x,利用贝叶斯定理求出后验概率最大的输出y。朴素贝叶斯法实现简单,学习与预测的效率都很高,是一种常用方法。

数学原理

  设输入空间XRnn维向量的集合,输出空间为类标记集合Y=c1,c2,,cK。输入为特征向量xX,输出为类标记yYX是定义在输入空间X上的随机变量,Y是定义在输出空间Y上的随机变量。P(X,Y)XY的联合概率分布。训练数据集
T=(x1,y1),(x2,y2),,(xN,yN)


P(X,Y)独立同分布产生。

  朴素贝叶斯法通过训练数据集学习联合概率分布P(X,Y)。具体地,学习以下先验概率分布及条件概率分布。先验概率分布,

P(Y=cK),k=1,2,,K

条件概率分布

P(X=x|Y=ck)=P(X(1)=x(1),,X(n)=x(n)),k=1,2,,K

于是学习到联合概率分布P(X,Y)

  条件概率分布P(X=x|Y=ck)有指数量级的参数,其估计实际是不可行的。事实上,假设x(j)可取值有Sj个,j=1,2,,nY可取值有K个,那么参数个数为Knj=1Sj

  朴素贝叶斯算法对条件概率分布作了条件独立的假设,即用于分类的特征在类别确定的条件下都是条件独立的。
P(X=x|Y=ck)=P(X(1)=x(1),,X(n)=x(n)|Y=ck)=nj=1P(X(j)=x(j)|Y=ck)


这一假设使朴素贝叶斯算法变得简单,但是有时会牺牲一定的分类准确率。

  朴素贝叶斯法分类时,对给定的输入x,通过学习到的模型计算后验概率分布P(Y=ck|X=x),将后验概率最大的类作为x的类输出。后验概率计算根据贝叶斯定理进行:

P(Y=ck|X=x)=P(X=x|Y=ck)P(Y=ck)kP(X=x|Y=ck)P(Y=ck)


把1式代入2式,有

P(Y=ck|X=x)=P(Y=ck)nj=1P(X(j)=x(j)|Y=ck)kP(Y=ck)nj=1P(X(j)=x(j)|Y=ck)

所以输出为
y=f(x)=argmaxckP(Y=ck)nj=1P(X(j)=x(j)|Y=ck)kP(Y=ck)nj=1P(X(j)=x(j)|Y=ck)

由于3式中分母对所有ck都相同,所以
y=f(x)=argmaxckP(Y=ck)nj=1P(X(j)=x(j)|Y=ck)

参数估计

极大似然估计

  在朴素贝叶斯算法中,学习意味着估计P(Y=ck)P(X(j)=x(j)|Y=ck)。可以使用极大似然估计法估计相应的概率。先验概率P(Y=ck)的极大似然估计是
P(Y=ck)=Ni=1I(yi=ck)N,k=1,2,,K

设第j个特征x(j)可能取值的集合为{aj1,a,,ajSj},条件概率P(X(j)=ajl|Y=ck)的极大似然估计是
P(X(j)=ajl|Y=ck)=Ni=1I(x(j)i=ajl,yi=ck)Ni=1I(yi=ck)


式中,x(j)i是第i个样本的第j个特征;ajl是第j个特征可能取的第l个值;I为指示函数。

贝叶斯估计

  用极大似然估计可能出现所要估计的值为0的情况,这时会影响到后验概率的计算结果,使分类产生偏差。解决这一问题的方法是采用贝叶斯估计,条件概率的贝叶斯估计是
Pλ(X(j)=ajl|Y=ck)=Ni=1I(x(j)i=ajl,yi=ck)+λNi=1I(yi=ck)+Sjλ


式中λ0。等价于在随机变量各个取值的频数上赋予一个正数λ>0。当λ=0时就是极大似然估计。常取λ=1,这时称为拉普拉斯平滑(Laplace smoothing)。显然对于任何l=1,2,,Sj,k=1,2,,K,有

Pλ(X(j)=ajl|Y=ck)>0Sjli=1P(X(j)=ajl|Y=ck)=1


同样,先验概率的贝叶斯估计是
Pλ(Y=ck)=Ni=1I(yi=ck)+λN+Kλ

示例

  训练数据如下表,学习一个朴素贝叶斯分类器并确定x=(2,S)T的类标记y,表中X(1)X(2)为特征,取值的集合分别为A1=1,2,3,A2=S,M,LY为类标记,YC={1,1}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
X(1) 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3
X(2) S M M S S S M M L L L M M L L
Y -1 -1 1 1 -1 -1 -1 1 1 1 1 1 1 1 -1

使用贝叶斯估计进行建模

P(Y=1)=9+115+2=1017,P(Y=1)=6+115+2=717


P(X(1)=1|Y=1)=2+19+3=312,P(X(1)=2|Y=1)=3+19+3=412,P(X(1)=3|Y=1)=4+19+3=512

P(X(1)=1|Y=1)=3+16+3=49,P(X(1)=2|Y=1)=2+16+3=39,P(X(1)=3|Y=1)=1+16+3=29

P(X(2)=S|Y=1)=1+19+3=212,P(X(2)=M|Y=1)=4+19+3=512,P(X(2)=L|Y=1)=4+19+3=512

P(X(2)=S|Y=1)=3+16+3=49,P(X(2)=M|Y=1)=2+16+3=39,P(X(2)=L|Y=1)=1+16+3=29

对于给定的x=(2,S)T计算,
P(Y=1)P(X(1)=2|Y=1)P(X(2)=S|Y=1)=1017×412×212=0.0327


P(Y=1)P(X(1)=2|Y=1)P(X(2)=S|Y=1)=717×39×49=0.0610

由于P(Y=1)P(X(1)=2|Y=1)P(X(2)=S|Y=1)最大,所以y=1

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# coding=utf-8
from numpy import *
import os


def createVocabList(dataSet):
vocabSet = set()
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:
print 'the word {} is not in my Vocabulary!'.format(word)
return returnVec


def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
# 包含敏感词的此条出现的概率
pAbusive = sum(trainCategory) / float(numTrainDocs)
# 构造单词出现次数列表 [1,1,...,1],这里使用了拉普拉斯平滑的思想
p0Num = ones(numWords)
p1Num = ones(numWords)
p0Denom = 2.0
p1Denom = 2.0
for i in range(numTrainDocs):
# 是否是侮辱性文件
if trainCategory[i] == 1:
# 对侮辱性文件的向量进行加和,[0,1,1,....] + [0,1,1,....]->[0,2,2,...]
p1Num += trainMatrix[i]
# 计算所有侮辱性文件中出现的单词总数
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
# 在1类别下,每个单词出现的概率,[1,2,3,5]/90->[1/90,...]
p1Vect = log(p1Num / p1Denom)
# 在0类别下,每个单词出现的概率
p0Vect = log(p0Num / p0Denom)
return p0Vect, p1Vect, pAbusive


def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
# p1Vec是每个词在类别1里出现的概率,vec2Classify表示这个词在该文章中是否出现过,乘起来表示文章中每个词出现的概率
# 这里进行了log变换,所以写成了加法
p1 = sum(vec2Classify * p1Vec) + log(pClass1)
p0 = sum(vec2Classify * p0Vec) + log(1 - pClass1)
if p1 > p0:
return 1
else:
return 0


def testParse(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 = []
for i in range(1, 26):
# 读取非垃圾邮件文本
wordList = testParse(open(os.getcwd() + '/email/spam/{}.txt'.format(i)).read())
docList.append(wordList)
fullText.append(wordList)
classList.append(1)
# 读取垃圾邮件文本
wordList = testParse(open(os.getcwd() + '/email/ham/{}.txt'.format(i)).read())
docList.append(wordList)
fullText.append(wordList)
classList.append(0)
# 建立词集
vocabList = createVocabList(docList)
# 训练集语料的下标
trainingSet = range(50)
testSet = []
# 选10个语料作为测试集
for i in range(10):
randIndex = int(random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[randIndex])
# 删除后训练集变成40个
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))
errorCount = 0
# 进行测试
for docIndex in testSet:
# 构建词向量
wordVector = setOfWords2Vec(vocabList, docList[docIndex])
# 进行分类
if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1
print 'the error rate is {}'.format(float(errorCount) / len(testSet))


def main():
spamTest()


if __name__ == '__main__':
main()

代码详见朴素贝叶斯代码实现

参考

  1. 机器学习实战,人民邮电出版社
  2. 统计学习方法,清华大学出版社
  3. AI Learning
-------------本文结束感谢您的阅读-------------

本文标题:朴素贝叶斯算法

文章作者:小建儿

发布时间:2018年12月24日 - 10:12

最后更新:2018年12月27日 - 11:12

原始链接:http://yajian.github.io/朴素贝叶斯算法/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。