机器学习之朴素贝叶斯

分类算法有前面说的knn和决策树,这两个算法中,决策树是knn的优化版,因为knn算法虽然是实现分类的万金油算法,但是它的计算量太庞大;而决策树又不容易分类成功,泛化能力弱以及容易过拟合。因此,今天学习了利用概率来实现分类的入门算法——朴素贝叶斯。

朴素贝叶斯最重要的就是条件概率,那么什么是条件概率?

条件概率

p(A|B) = p(A & B) / p(B),意思是指在B条件下发生A事件的概率;举一个例子,假设有十个小球:四个白的,六个黑的。如果都放到一个黑箱子中,取到白球的概率是2/5,取到黑球的概率是3/5;如果现在有两个箱子A.B,A箱子中有五个球:三个白球两个黑球;B箱子中有五个球:一个白球四个黑球;那么,我取到B箱子中黑球的概率是多少?这就是一个典型的条件概率事件。我们可以这样算:用B箱子中的黑球个数除以总的球数得到pa,然后在用B箱子中的总球数除以总球数得到pb,然后只需要用pa/pb就可以得出最后我们要求的概率

1
pa = 1 / 10; pb = 5 / 10; p(黑球|B箱子) = pa / pb = 1 / 5;

那么朴素贝叶斯是怎么计算条件概率的呢?这里有一个贝叶斯准则

贝叶斯准则

如果要求(c|x),已知p(x|c),那么可以这样求得p(c|x)

1
p(x|c) = (p(x|c) * p(c)) / p(x)

如果我们把 c 看作是类别,而 x 是待分类的,最终确定x的分类是通过比较p(x|c1), p(x|c2), p(x|c3) … p(x|cn)的条件概率的大小,取概率最大值对应的分类为x的最终分类结果

现在,通过留言处理是否是侮辱性留言来深刻理解下朴素贝叶斯算法

首先我们先人为创建一个留言样本

我们规定 classVec[i]的值为0的是侮辱性的留言;

1
2
3
4
5
6
7
8
9
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

接着,我们需要将这些留言中的单词提取出来放到一张词汇表中

1
2
3
4
5
6
7
8
9
10
def createVocabList(dataSet):
"""
创建单词字典
:param dataSet:
:return:
"""
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document)
return list(vocabSet)

然后,我们有两条留言(假设我们的词汇表已经很大,留言的词汇都在我们的词汇表中)

1
2
testEntry_1 = ['love', 'my', 'dalmation']
testEntry_2 = ['stupid', 'garbage']

这个时候,我们需要做一件事:将留言的语句转为向量表示;或许会有疑问,这个怎么转为向量表示?这个时候我们就需要用到我们之前前面创建的词汇表了,我们为testEntry_1和testEntry_2创建长度和词汇表等长的list并初始化全为0,然后去和词汇表对比词汇,如果某个位置上的单词在留言testEntry_1或者testEntry_2中存在,那么就将testEntry_1或testEntry_2对应的list相应位置上的值赋值为1,这样,就成功的将testEntry_1和testEntry_2用向量进行了表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def setOfWords2Vec(vocabList, inputSet):
"""
将字符串文本转为向量
:param vocabList:
:param inputSet:
:return:
"""
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 Vocabluary!" % word
return returnVec

接下来,我们该如何利用朴素贝叶斯来实现对留言的分类呢?首先我们需要先训练样本得出一些数据——样本留言中侮辱性留言的条件概率和非侮辱性的条件概率以及任取一条留言是侮辱性留言的概率。那么是怎么得出的呢?

  • 先分别计算在非侮辱性(侮辱性)留言下每个词汇出现的次数
  • 再计算非侮辱性(侮辱性)留言中所有词汇的个数
  • 计算非侮辱性(侮辱性)词汇出现的概率pa(pb)
  • 计算样本中任取一则留言为侮辱性留言的概率pc
  • 将未知的留言文本向量分别乘上pa(pb)后在加上1-pc(pc)
  • 比较两个结果的值,取概率大的

由于我们要求的目标概率是p(待分类留言|侮辱性留言)和p(待分类留言|非侮辱性留言),也就是p(c|x);那么我们就反过来计算p(x|c),就是计算一则留言中侮辱性词汇出现的概率是多少,非侮辱性的词汇出现的概率是多少。

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
def trainNBO(trainMatrix, trainCategory):
"""
:param trainMatrix: 文档矩阵,二维矩阵,第一个维度表示文档的数目,第二个维度表示文档的内容向量
:param trainCategory: 文档类别标签组成的向量
:return: 返回每个单词属于侮辱性单词的概率
"""
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pAbusive = sum(trainCategory) / float(numTrainDocs)
p0Num = ones(numWords)
p1Num = ones(numWords)
p0Denom = 2.0
p1Denom = 2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
# 为了防止出现概率因为精度问题出现0的情况,采用对乘积取对数的操作
print p1Num
p1Vect = log(p1Num / p1Denom)
p0Vect = log(p0Num / p0Denom)
return p0Vect, p1Vect, pAbusive

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
"""
朴素贝叶斯分类器
:param vec2Classify: 要分类的向量
:param p0Vec: 标准一
:param p1Vec: 标准二
:param pClass1: 概率(侮辱性文本占所有文本的概率)
:return:
"""
p1 = sum(vec2Classify * p1Vec) + log(pClass1)
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
return 0

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def testingNB():
listOPosts, listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
trainMat = []
# trainMat[] 将所有文本转为向量形式
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNBO(array(trainMat), array(listClasses))
testEntry = ['love', 'my', 'dalmation']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb)
testEntry = ['stupid', 'garbage']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb)


testingNB()

结果

machine-learning-3.1