机器学习——朴素贝叶斯

目录

一、什么是朴素贝叶斯?

1、贝叶斯定理

2、朴素贝叶斯

 3、朴素贝叶斯分类器

4、拉普拉斯修正

 5、防溢出策略

二、使用朴素贝叶斯过滤垃圾邮件

三、实验总结 

1、实验中发生错误

2、总结

朴素贝叶斯的优点和缺点


一、什么是朴素贝叶斯?

1、贝叶斯定理

已知某条件概率,如何得到两个事件交换后的概率,也就是在已知P(A|B)的情况下如何求得P(B|A)。这里先解释什么是条件概率:

      表示事件B已经发生的前提下,事件A发生的概率,叫做事件B发生下事件A的条件概率。其基本求解公式为:

                                                         

      贝叶斯定理之所以有用,是因为我们在生活中经常遇到这种情况:我们可以很容易直接得出P(A|B),P(B|A)则很难直接得出,但我们更关心P(B|A),贝叶斯定理就为我们打通从P(A|B)获得P(B|A)的道路。

      下面不加证明地直接给出贝叶斯定理:

                ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        

 

2、朴素贝叶斯

朴素贝叶斯是经典的机器学习算法之一,也是为数不多的基于概率论的分类算法。对于大多数的分类算法,在所有的机器学习分类算法中,朴素贝叶斯和其他绝大多数的分类算法都不同。比如决策树,KNN,逻辑回归,支持向量机等,他们都是判别方法,也就是直接学习出特征输出Y和特征X之间的关系,要么是决策函数,要么是条件分布。但是朴素贝叶斯却是生成方法,该算法原理简单,也易于实现。

    贝叶斯公式:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        

 

                                                       (X:特征向量, Y:类别)

  先验概率P(X) 、 P(Y):先验概率是指根据以往经验和分析得到的概率。

  后验概率P(Y|X):事情已经发生,要求这件事情发生的原因是由某个因素引起的可能性的大小,后验分布P(Y|X)表示事件X已经发生的前提下,事件Y发生的概率,叫做事件X发生下事件Y的条件概率。

  后验概率P(X|Y):通常它除以P(X)被叫做调整因子,可能性函数。在已知Y发生后X的条件概率,也由于知道Y的取值而被称为X的后验概率。

  朴素:朴素贝叶斯算法是假设各个特征之间相互独立,然而现实生活中这样的事件不存在,也是朴素这词的意思,那么贝叶斯公式中的P(X|Y)可写成:

                                        

 

  朴素贝叶斯公式:

                                        

 3、朴素贝叶斯分类器

朴素贝叶斯分类器(Naïve Bayes Classifier)采用了“属性条件独立性假设” ,即每个属性独立地对分类结果发生影响。为方便公式标记,不妨记P(C=c|X=x)为P(c|x),基于属性条件独立性假设,贝叶斯公式可重写为:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        

其中d为属性数目,x_i 为  x 在第 i 个属性上的取值。

由于对所有类别来说 P(x)相同,因此MAP判定准则可改为:

                                                

其中  P(c)  和  P(x_i|c) 为目标参数。

        朴素贝叶斯分类器的训练器的训练过程就是基于训练集D估计类先验概率 P(c) ,并为每个属性估计条件概率  P(x_i|c) 。

        令  D_c  表示训练集D中第c类样本组合的集合,则类先验概率:   

                                                                

4、拉普拉斯修正

 若某个属性值在训练集中没有与某个类同时出现过,则训练后的模型会出现 over-fitting 现象。比如训练集中没有该样例,因此连乘式计算的概率值为0,这显然不合理。因为样本中不存在(概率为0),不代该事件一定不可能发生。所以为了避免其他属性携带的信息,被训练集中未出现的属性值“ 抹去” ,在估计概率值时通常要进行“拉普拉斯修正”。

        ​​​​​​​        ​​​​​​​        P(c|x) = \frac{P(x|c)P(c)}{P(x)} = \frac{P(c)}{P(x)}\prod_{i=1}^{d}P(x_i|c),我们要修正  P(x_i|c)   的值。

令 N 表示训练集 D 中可能的类别数,N_i  表示第i个属性可能的取值数,则贝叶斯公式可修正为:

        ​​​​​​​                                        \hat{P}(x_i|c) =\frac{|D_c,_x_i|+1}{|D|+N_i}

 

 5、防溢出策略

条件概率乘法计算过程中,因子一般较小(均是小于1的实数)。当属性数量增多时候,会导致累乘结果下溢出的现象。

在代数中有  ln(a*b) = ln(a)+ln(b) ,因此可以把条件概率累乘转化成对数累加。分类结果仅需对比概率的对数累加法运算后的数值,以确定划分的类别。

 

 

二、使用朴素贝叶斯过滤垃圾邮件

从文本中构建词向量函数

from numpy import *
 
# 创建不重复词的列表 ———— 词汇表
def createVocabList(dataSet):
    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("单词 %s 不在词汇表中!" % word)
    return returnVec
 

构造分类器训练函数 

# 朴素贝叶斯分类器训练函数
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)                      # 获得训练的文档总数
    numWords = len(trainMatrix[0])                       # 获得每篇文档的词总数
    pAbusive = sum(trainCategory) / float(numTrainDocs)  # 计算文档是侮辱类的概率
    p0Num = ones(numWords)                               # 创建numpy.ones数组,初始化概率
    p1Num = ones(numWords)                               # 创建numpy.ones数组,初始化概率
    p0Denom = 2.0                                        # 初始化为2.0
    p1Denom = 2.0                                        # 初始化为2.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]                      # 向量相加,统计侮辱类的条件概率的数据,即P(w0|1),P(w1|1),P(w2|1)···
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]                      # 向量相加,统计非侮辱类的条件概率的数据,即P(w0|0),P(w1|0),P(w2|0)···
            p0Denom += sum(trainMatrix[i])
    p1Vect = log(p1Num / p1Denom)                        # 侮辱类,每个元素除以该类别中的总词数
    p0Vect = log(p0Num / p0Denom)                        # 非侮辱类,每个元素除以该类别中的总词数
    return p0Vect, p1Vect, pAbusive                      # 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 bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec

使用朴素贝叶斯进行交叉验证

# 文件解析
def textParse(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):                                           # 遍历垃圾邮件和非垃圾邮件各25个
        wordList = textParse(open('email/spam/%d.txt' % i,encoding='ISO-8859-1').read())   # 读取垃圾邮件,将大字符串并将其解析为字符串列表
        docList.append(wordList)                                     # 垃圾邮件加入文档列表
        fullText.extend(wordList)                                    # 把当前垃圾邮件加入文档内容集合
        classList.append(1)                                          # 1表示垃圾邮件,标记垃圾邮件
        wordList = textParse(open('email/ham/%d.txt' % i,encoding='ISO-8859-1').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:                                     # for循环使用词向量来填充trainMat列表t
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))  # 把词集模型添加到训练矩阵中
        trainClasses.append(classList[docIndex])                     # 把类别添加到训练集类别标签中
    p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses)) # 朴素贝叶斯分类器训练函数
 
    print('词表:\n', vocabList)
    print('p0V:\n', p0V)
    print('p1V:\n', p1V)
    print('pSpam:\n', pSpam)
 
    errorCount = 0                                                   # 用于计数错误分类
    for docIndex in testSet:                                         # 循环遍历训练集
        wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])  # 获得测试集的词集模型
        if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
            errorCount += 1                                          # 预测值和真值不一致,则错误分类计数加1
            print("分类错误集", docList[docIndex])
    print('错误率: ', float(errorCount) / len(testSet))
    # return vocabList,fullText

 构建邮件分类器

# 邮件分类器
def classifyEmail():
    docList = []                 # 文档列表
    classList = []               # 文档标签
    fullText = []                # 全部文档内容集合
    for i in range(1, 26):                                           # 遍历垃圾邮件和非垃圾邮件各25个
        wordList = textParse(open('email/spam/%d.txt' % i,encoding='ISO-8859-1').read())   # 读取垃圾邮件,将大字符串并将其解析为字符串列表
        docList.append(wordList)                                     # 垃圾邮件加入文档列表
        fullText.extend(wordList)                                    # 把当前垃圾邮件加入文档内容集合
        classList.append(1)                                          # 1表示垃圾邮件,标记垃圾邮件
        wordList = textParse(open('email/ham/%d.txt' % i,encoding='ISO-8859-1').read())    # 读非垃圾邮件,将大字符串并将其解析为字符串列表
        docList.append(wordList)                                     # 非垃圾邮件加入文档列表
        fullText.extend(wordList)                                    # 把当前非垃圾邮件加入文档内容集合
        classList.append(0)                                          # 0表示垃圾邮件,标记非垃圾邮件,
 
    vocabList = createVocabList(docList)                             # 创建不重复的词汇表
    trainingSet = list(range(50))                                    # 为训练集添加索引
    trainMat = []                                                    # 创建训练集矩阵训练集类别标签系向量
    trainClasses = []                                                # 训练集类别标签
    for docIndex in trainingSet:                                     # for循环使用词向量来填充trainMat列表
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))  # 把词集模型添加到训练矩阵中
        trainClasses.append(classList[docIndex])                     # 把类别添加到训练集类别标签中
    p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses)) # 朴素贝叶斯分类器训练函数
 
    testList = textParse(open('email/test/1.txt',encoding='ISO-8859-1').read())            # 读取邮件,将大字符串并将其解析为字符串列表
    testVector = bagOfWords2VecMN(vocabList, testList)               # 获得测试集的词集模型
    if classifyNB(array(testVector), p0V, p1V, pSpam):
        result = "垃圾邮件"
    else:
        result = "正常邮件"
    print("输入邮件内容为: ")
    print(' '.join(testList))
    print('该邮件被分类为: ', result)
if __name__ == '__main__':
    classifyEmail()

 测试结果:

 

机器学习——朴素贝叶斯_第1张图片

 

机器学习——朴素贝叶斯_第2张图片

三、实验总结 

1、实验中发生错误:

 

机器学习——朴素贝叶斯_第3张图片

按网上给的方法,在读文件后面加上encoding='utf-8',结果发现还是出现错误:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0x92 in position 884: invalid start byte

最后换成encoding='ISO-8859-1',成功解决。

2、总结

朴素贝叶斯的优点和缺点

优点

1)预测测试数据集很容易也很快,在多类预测中表现良好。

2)对缺失数据不太敏感,算法简单,常用于文本分类。

3)朴素贝叶斯模型有稳定的分类效率。

4)适合增量式训练,尤其是数据量超出内存时,可以一批批的去增量训练。

缺点

1)如果分类变量具有在训练数据集中未观察到的类别,则模型将指定0(零)概率并且将无法进行预测。

2)理论上,朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为朴素贝叶斯模型给定输出类别的情况下,假设属性之间相互独立,这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好。而在属性相关性较小的时,朴素贝叶斯性能最为良好。对于这一点,有半朴素贝叶斯之类的算法通过考虑部分关联性适度改进。

3)需要知道先验概率,且先验概率很多时候取决于假设。

4)通过先验和数据来决定后验的概率从而决定分类,所以分类决策存在一定的错误率。

5)对输入数据的表达形式很敏感。


实验代码及数据集:

链接:https://pan.baidu.com/s/1WuRenQnb7vAzHBZrk52PsQ 
提取码:x3wo

 

你可能感兴趣的:(人工智能)