机器学习实战三(朴素贝叶斯)

一、概述

1. 原理:

  • 工作机制:

2. 优缺点

  • 优点:在数据少的情况下有效,可以处理多类别问题
  • 缺点:对于输入数据的准备方式较为敏感
  • 适用数据范围:标称型数据

3.条件概论:

  • 在B的条件下A出现的概率。 p(A|B)=p(AB)/p(B)
  • 交换条件中的条件与结果: p(B|A)=p(A|B)*p(B)/p(A)

4.贝叶斯决策理论的核心思想:

  • 选择具有最高概论的决策

5.朴素贝叶斯算法的两个假设:

  • (1)每个特征之间都是独立的,这就使得公式: p((f1,f2,...fn)|c)=p(f1|c)p(f2|c)...p(fn|c)
  • (2)每个特征同等重要,我们拿文本分类做例子,把文档中的单词作为特征。这种假设使得我们在进行分类的过程中无需考虑单词出现的次数,只考虑单词出现与否。这也就贝叶斯算法的贝努利模型实现方式。
  • 注:贝叶斯的另一种实现方式为多项式模型,在这种模型中则需要考虑单词的出现次数。

二、算法流程

  1. 收集数据:可用任何方法
  2. 准备数据:需要数值型或者布尔型数据
  3. 分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果更好
  4. 训练算法:计算不同的独立特征的条件概率
  5. 测试算法:计算错误率
  6. 使用算法:一个常见的朴素贝叶斯应用是文档分类。可以在任意的分类场景中使用朴素贝叶斯分类器,不一定非要是文本。

三、算法实践

1.问题

  • 对是否属于侮辱性文章进行分类

2.准备数据:从文本中构建词向量

  • 准备数据
    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
    # 产生训练数据
    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):
    # 创建一个跟词汇表(vocabList)等长的向量,并将其元素都设为0
    returnVec = [0]*len(vocabList)
    # 遍历输入,将含有词汇表单词的文档向量设为1
    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
1
2
3
4
5
6
7
命令行
>>> import bayes
>>> listOPosts,listClasses = bayes.loadDataSet()
>>> myVocabList = bayes.createVocabList(listOPosts)
>>> myVocabList
>>> bayes.setOfWords2Vec(myVocabList,listOPosts[0])
>>> bayes.setOfWords2Vec(myVocabList,listOPosts[3])

3.训练算法:从词向量计算概率

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
# 朴素贝叶斯分类器训练函数
# 输入参数trainMatrix表示输入的文档矩阵,trainCategory表示每篇文档类别标签所构成的向量
def trainNB0(trainMatrix,trainCategory):
# 留言数目
numTrainDocs=len(trainMatrix)
# 变换矩阵的列数目,即词汇表数目
numWords=len(trainMatrix[0])
# 侮辱性留言的概率
pAbusive=sum(trainCategory)/float(numTrainDocs)
# 将所有词的出现数初始化为1,将分母初始化为2,从而降低计算多个概率的乘积结果为零的影响
p0Num=ones(numWords)
p1Num=ones(numWords)
p0Denom=2.0
p1Denom=2.0
for i in range(numTrainDocs):
# 统计每类单词的数目,注意我们这里讨论的是一个二分问题
# 所以可以直接用一个if...else...即可,如果分类较多,则需要更改代码
if trainCategory[i] == 1:
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
# 对每个类别除以该类中的总词数
# 防止下溢出
p1Vec = log(p1Num/p1Denom)
p0Vec = log(p0Num/p0Denom)
# 函数返回两个概率向量,及一个概率
return p0Vec, p1Vec, pAbusive
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> import bayes
>>> listOPosts,listClasses = bayes.loadDataSet()
# 该语句从预先加载值中调入数据
>>> myVocabList = bayes.creatVocabList(listOPosts)
>>> trainMat=[]
>>> for postinDoc in listOPosts:
... trainMat.append(bayes.setOfWords2Vec(myVocabList,postinDoc))
...
# 下面给出属于侮辱性文章的概论以及两个类别的概论向量
>>> p0V,p1V,pAb=bayes.trainNB0(trainMat,listClasses)
>>> pAb
>>> p0V
>>> p1V

4.测试算法: 朴素贝叶斯分类函数

1
2
3
4
5
6
7
8
# 朴素贝叶斯分类函数
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass):
p1 = sum(vec2Classify*p1Vec)+log(pClass)
p0 = sum(vec2Classify*p0Vec)+log(1-pClass)
if p1 > p0:
return 1
else:
return 0
  • 该函数是用来测试(封装了一些操作)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #内嵌测试函数
    def testingNB():
    listOPosts, listClasses=loadDataSet()
    myVocabList = createVocabList(listOPosts)
    trainMat = []
    for postinDoc in listOPosts:
    trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0V, p1V, p1 = trainNB0(trainMat, listClasses)
    testEntry = ['love', 'my', 'dalmation']
    thisDoc = setOfWords2Vec(myVocabList, testEntry)
    print(testEntry, "classified as:", classifyNB(thisDoc, p0V, p1V, p1))
    testEntry = ['garbage', 'stupid']
    thisDoc = setOfWords2Vec(myVocabList, testEntry)
    print(testEntry, "classified as:", classifyNB(thisDoc, p0V, p1V, p1))
1
2
3
4
5
命令行
import bayes
>>> bayes.testingNB()
['love', 'my', 'dalmation'] classified as: 0
['garbage', 'stupid'] classified as: 1

5.词袋模型的转换函数(准备数据中优化)

  • 之前的算法我们只考虑了单词出现与否,使用的是一种词集模型。
  • 贝叶斯有两种实现方式,另一种多项式模型,需要考虑每个单词出现的次数,就是所谓的词袋模型。
  • 为了适应这种词袋模型,我们需要对函数setOfWords2Vec作一下修改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #词袋模型的转换函数  
    def bagOfWords2VecMN(vocabList,inputSet):
    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

四、示例:使用朴素贝叶斯进行垃圾邮件过滤

1.准备数据,切分文本

1
2
3
4
5
# 该函数将每个句子都解析成单词,并忽略空格,标点符号以及长度小于3的单词
def textParse(bigString):
import re
listOfTokens = re.split(r'\W*', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 2]

2.分类器

错误信息合集(参考)

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
# 检测垃圾邮件
def spamTest():
# 存放输入数据
docList = []
#存放类别标签
classList = []
# 所有的文本
fullText = []
# 分别读取邮件内容
for i in range(1, 26):
wordList = textParse(open('email/spam/%d.txt' % i, "rb").read().decode('GBK','ignore') )
docList.append(wordList)
fullText.extend(wordList)
classList.append(1)
wordList = textParse(open('email/ham/%d.txt' % i, "rb").read().decode('GBK','ignore') )
docList.append(wordList)
fullText.extend(wordList)
classList.append(0)
vocabList = createVocabList(docList)
# range(50)表示从050,不包括50
trainingSet = list(range(50))
# 测试集
testSet = []
# 随机抽取是个作为测试集
for i in range(10):
# 从50个数据集中随机选取十个作为测试集,并把其从训练集中删除
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))

# 测试分类器的错误率
errorCount = 0
for docIndex in testSet:
wordVector = setOfWords2Vec(vocabList, docList[docIndex])
if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1
print("Classification error:")
print(docList[docIndex])
print(errorCount)
print("the error rate is:", float(errorCount)/len(testSet))

github代码