《机器学习实战》学习笔记(四):朴素贝叶斯

目录

  • 引言
  • 一、贝叶斯理论
    • 1.1 准备知识:条件概率
    • 1.2 “朴素”的含义
    • 1.3 朴素贝叶斯分类器
    • 1.4 源码实现
  • 二、算法实例——垃圾邮件过滤
    • 2.1 收集数据
    • 2.2 准备数据
    • 2.3 分析数据
    • 2.4 训练算法
    • 2.5 测试算法
    • 2.6 使用算法
  • 三、实验总结
    • 3.1 贝叶斯原理、贝叶斯分类和朴素贝叶斯的关系
    • 3.2 贝叶斯的优缺点

引言

在之前讲述KNN以及决策树时。曾要求分类器做出决策给出“该实例属于哪一类”这类问题的明确答案。不过分类器有时候会产生错误的结果,这个时候要求分类器给出一个最优的类别猜测结果,同时给出这个猜测的概率估计值。之前决策树章节里信息熵的计算就涉及到了一些概率知识,接下来便在这个基础上深入讨论。

一、贝叶斯理论

1.1 准备知识:条件概率

因为朴素贝叶斯是贝叶斯决策理论的一部分,所以先了解一下贝叶斯决策理论。
假设有两类数据组成的数据集如下:

《机器学习实战》学习笔记(四):朴素贝叶斯_第1张图片
其中,假设两个概率分布的参数已知,并用p1(x,y)表示当前数据点(x,y)属于类别一的概率;用p2(x,y)表示当前数据点(x,y)属于类别二的概率。

贝叶斯决策理论的核心思想是:选择高概率所对应的类别,选择具有最高概率的决策。有时也被总结成“多数占优”的原则。

具体到实例,对于一个数据点(x,y),可以用如下规则判定它的类别:

若p1(x,y)>p2(x,y),那么点(x,y)被判定为类别一。
若p1(x,y)

在实际情况中,单单依靠以上的判定无法解决所有的问题,因为以上准则还不是贝叶斯决策理论的所有内容,使用p1(x,y)和p2(x,y) 只是为了简化描述。更多的,我们使用p(ci|x,y) 来确定给定坐标的点(x,y),该数据点来自类别ci的概率是多少。具体地,应用贝叶斯准则可得到,该准则可以通过已知的三个概率值来计算未知的概率值:
《机器学习实战》学习笔记(四):朴素贝叶斯_第2张图片
利用上面的公式,我们可以计算出在给定实例点的情况下,分类计算其属于各个类别的概率,然后比较概率值,选择具有最大概率的那么类作为点(x,y)的预测分类结果。

以上我们知道了通过贝叶斯准则来计算属于各个分类的概率值,那么具体而言,就是计算贝叶斯公式中的三个概率,只要得到了这三个概率值,显然我们就能通过贝叶斯算法预测分类的结果了。至此,我们就知道了朴树贝叶斯算法的核心所在。

1.2 “朴素”的含义

这个朴素的贝叶斯分类器有什么“朴素”的呢?

在推导5个特征的数据集方程时,我们只需乘以各个特征的所有条件概率,比如P(X1 | Y)*P(X2 | Y)….*P(X5 | Y)。当我们假设特征相互独立时,我们只能把总的条件概率写成特征的个别条件概率的乘积。这是我们在这里做的“天真”假设,是为了让贝叶斯定理对我们有用。

但是,在现实生活中,当特性彼此独立时,几乎从来没有这种情况。功能中总是有某种依赖关系。例如,如果一个特征是一个人的年龄,而另一个特征是年薪,那么在大多数情况下都有明显的依赖关系。

然而,我们仍然继续把这个定理应用于分类问题,甚至文本分类,它的效果会出奇的好。

1.3 朴素贝叶斯分类器

朴素贝叶斯分类器就是利用贝叶斯原理实现的,在分类问题中,类别C相当于事件A,特征X相当于影响因素B,那么贝叶斯公式可以改写为如下形式,它表示的含义是当特征为X时,X属于类别C的概率:
在这里插入图片描述
设类别C={undefinedy_{1},y_{2}…y_{m}},特征向量X={undefinedx_{1},x_{2}…x_{n}},贝叶斯分类器的工作原理如下:
在这里插入图片描述
1)求解在X条件下每个类别的概率即求得
2)中概率最大的项对应的类别即为X的预测类别

贝叶斯分类器的工作原理:寻找中的最大值,最大值对应的类别即为预测类别。然而在实际情况中往往无法直接求得,所以根据贝叶斯公式将求解转化为求解。P(X)与类别无关且为固定值,不会影响中的最大值的求解。
所以总结来说:贝叶斯分类器就是最大后验概率估计

1.4 源码实现

def NaiveBayes(traindata, trainlabel):
    '''
    通过训练集计算先验概率分布p(c)和条件概率分布p(x|c)
    :param traindata: 训练数据集   (m,n)
    :param trainLabel: 训练标记集  (m,1)
    :return: p(c)和p(x|c)
    '''
 
    classes = 10  # 类别数
    features = 784  # 样本的维度
 
    sampleNum = trainlabel.shape[0]
 
    # 计算p(c)
    Pc = np.zeros((classes, 1))
    for c in range(classes):
        c_i = (trainlabel == c)  # 统计标签中类别为c的样本数量
        c_i_num = np.sum(c_i)
        Pc[c] = (c_i_num+1)/sampleNum  # Laplace校准
    Pc = np.log(Pc)
 
    # 计算p(x|c)
    y_num = 2  # 每个特征可能的取值的个数
    c_f_y_count = np.zeros((classes, features, y_num))  # 统计每个类别每个特征的每种可能取值出现的次数
    for k in range(sampleNum):
        c = trainlabel[k]
        data = traindata[k]
        for f in range(features):
            y = data[f]
            c_f_y_count[c][f][y] += 1
 
    Px_c = np.zeros((classes, features, y_num))   # 统计每个类别每个特征的每种可能取值的概率
    for c in range(classes):
        for f in range(features):
            c_f_y_num = np.sum(c_f_y_count[c][f])
            for y in range(y_num):
                Px_c[c][f][y] = np.log((c_f_y_count[c][f][y]+1)/c_f_y_num)  # Laplace校准
 
    return Pc, Px_c
 
 
def predict(Pc, Px_c, x):
    '''
    根据先验概率分布p(c)和条件概率分布p(x|c)对新样本进行预测
    '''
    classes = 10
    features = 784
 
    Pc_x = [0]*classes  # 记录每个类别的后验概率
    for c in range(classes):
        Px_c_sum = 0
        for f in range(features):
            Px_c_sum += Px_c[c][f][x[f]]  # 对概率值取log 连乘变成了求和运算
 
        Pc_x[c] = Px_c_sum + Pc[c]
 
    pre_c = Pc_x.index(max(Pc_x))  # 找到每个类别的后验概率中的最大值对应的类别
 
    return pre_c
 
 
def test(Pc, Px_c, testdata, testlabel):
    sampleNum = testlabel.shape[0]
 
    count = 0.0
    for i in range(sampleNum):
        data = testdata[i]
        label = testlabel[i]
        pre_label = predict(Pc, Px_c, data)
        if(pre_label == label):
            count += 1
 
    acc = count / sampleNum
 
    return acc
 
 
if __name__ == '__main__':
    #加载训练集和验证集
    traindata, trainlabel = loadData('../Mnist/mnist_train.csv')
    evaldata, evallabel = loadData('../Mnist/mnist_test.csv')
 
    Pc, Px_c = NaiveBayes(traindata, trainlabel)
 
    accuracy = test(Pc, Px_c, evaldata, evallabel)
    print('accuracy rate is:', accuracy)

二、算法实例——垃圾邮件过滤

2.1 收集数据

提供文本文件,下载地址:http://download.csdn.net/detail/liyuefeilong/9106481,放在工程目录下并解压即可;

2.2 准备数据

将文本文件解析成词条向量;从文本中构建词向量,这里考虑出现在所有文档中的所有单词,并将每一篇文档转化为词汇表上的向量。下面的代码实现了功能,其中:

函数loadDataSet() 创建了一些实验样本postingList和对应的标签listClass,有一些样本被标签为带有侮辱性文字;
函数createNonRepeatedList() 统计并保存一个列表vocList,该列表包含所有文档中出现的词(不重复),这里使用了Python的set函数;
函数detectInput(vocList, inputStream)使用了词列表vocList, inputStream为待检测的word串,输出文档向量,向量的每一元素为1或0,分别表示词汇表中的单词在输入文档中是否出现。

# -*- coding: utf-8 -*-
"""
Created on Tue Sep 08 16:12:55 2015

@author: Administrator
"""

from numpy import *

# 创建实验样本,可能需要对真实样本做一些处理,如去除标点符号
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']]
    listClass = [0, 1, 0, 1, 0, 1] # 1代表存在侮辱性的文字,0代表不存在
    return postingList, listClass

# 将所有文档所有词都存到一个列表中,用set()函数去除重复出现的词
def createNonRepeatedList(data):
    vocList = set([])
    for doc in data:
        vocList = vocList | set(doc) # 两集合的并集
    return list(vocList)

def detectInput(vocList, inputStream):
    returnVec = [0]*len(vocList) # 创建和vocabList一样长度的全0列表
    for word in inputStream:
        if word in vocList: # 针对某段words进行处理
            returnVec[vocList.index(word)] = 1 # ?
        else:
            print "The word :%s is not in the vocabulary!" % word
    return returnVec

2.3 分析数据

检查词条确保解析的正确性;

2.4 训练算法

使用我们之前建立的trainNaiveBayes(trainMatrix, classLabel)函数;

  • 计算每个类别中的文档数目
    对每篇训练文档:
    对每个类别:
    如果词条出现在文档中 -> 增加该词条的计数值
    增加所有词条的计数值
    对每个类别:
    将该词条的数目除以总词条数目得到条件概率
    返回每个类别的条件概率

    贝叶斯分类器训练函数代码如下:

def trainNaiveBayes(trainMatrix, classLabel):
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pBase = sum(classLabel) / float(numTrainDocs)
    # The following Settings aim at avoiding the probability of 0
    p0Num = ones(numWords)
    p1Num = ones(numWords)
    p0Denom = 2.0
    p1Denom = 2.0
    for i in range(numTrainDocs):
        if classLabel[i] == 1:
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])            
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])       
    p0 = log(p0Num / p0Denom)
    p1 = log(p1Num / p1Denom)
    return p0, p1, pBase

2.5 测试算法

使用函数naiveBayesClassify(vec2Classify, p0, p1, pBase),并且构建一个新的测试函数来计算文档集的错误率;测试分类器的效果:

def trainNaiveBayes(trainMatrix, classLabel):
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pBase = sum(classLabel) / float(numTrainDocs)
    # The following Settings aim at avoiding the probability of 0
    p0Num = ones(numWords)
    p1Num = ones(numWords)
    p0Denom = 2.0
    p1Denom = 2.0
    for i in range(numTrainDocs):
        if classLabel[i] == 1:
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])            
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])       
    p0 = log(p0Num / p0Denom)
    p1 = log(p1Num / p1Denom)
    return p0, p1, pBase

trainMat = []
for doc in loadData:
    trainMat.append(detectInput(vocList, doc))

p0,p1,pBase = trainNaiveBayes(trainMat, dataLabel)
#print "trainMat : "
#print trainMat

# test the algorithm
def naiveBayesClassify(vec2Classify, p0, p1, pBase):
    p0res = sum(vec2Classify * p0) + log(1 - pBase)
    p1res = sum(vec2Classify * p1) + log(pBase)
    if p1res > p0res:
        return 1
    else:
        return 0

def testNaiveBayes():
    loadData, classLabel = loadDataSet()
    vocList = createNonRepeatedList(loadData)
    trainMat = []
    for doc in loadData:
         trainMat.append(detectInput(vocList, doc))
    p0, p1, pBase = trainNaiveBayes(array(trainMat), array(classLabel))
    testInput = ['love', 'my', 'dalmation']
    thisDoc = array(detectInput(vocList, testInput))
    print testInput, 'the classified as: ', naiveBayesClassify(thisDoc, p0, p1, pBase)
    testInput = ['stupid', 'garbage']
    thisDoc = array(detectInput(vocList, testInput))
    print testInput, 'the classified as: ', naiveBayesClassify(thisDoc, p0, p1, pBase)

testNaiveBayes()

2.6 使用算法

构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。
在这里插入图片描述
最后,对两组word串进行检测,第一段被判定为非侮辱性用语,而第二段则被判定为侮辱性用语,分类正确。

三、实验总结

3.1 贝叶斯原理、贝叶斯分类和朴素贝叶斯的关系

贝叶斯原理、贝叶斯分类和朴素贝叶斯这三者之间是有区别的。
贝叶斯原理是最大的概念,它解决了概率论中“逆向概率”的问题,在这个理论基础上,人们设计出了贝叶斯分类器,朴素贝叶斯分类是贝叶斯分类器中的一种,也是最简单,最常用的分类器。朴素贝叶斯之所以朴素是因为它假设属性是相互独立的,因此对实际情况有所约束,如果属性之间存在关联,分类准确率会降低。 不过好在对于大部分情况下,朴素贝叶斯的分类效果都不错。

《机器学习实战》学习笔记(四):朴素贝叶斯_第3张图片

3.2 贝叶斯的优缺点

优点:

  • 朴素贝叶斯模型发源于古典数学理论,有稳定的分类效率。
  • 对小规模的数据表现很好,能个处理多分类任务,适合增量式训练,尤其是数据量超出内存时,我们可以一批批的去增量训练。
  • 对缺失数据不太敏感,算法也比较简单,常用于文本分类。

缺点:

  • 理论上,朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为朴素贝叶斯模型给定输出类别的情况下,假设属性之间相互独立,这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好。而在属性相关性较小时,朴素贝叶斯性能最为良好。对于这一点,有半朴素贝叶斯之类的算法通过考虑部分关联性适度改进。
  • 需要知道先验概率,且先验概率很多时候取决于假设,假设的模型可以有很多种,因此在某些时候会由于假设的先验模型的原因导致预测效果不佳。
  • 由于我们是通过先验和数据来决定后验的概率从而决定分类,所以分类决策存在一定的错误率。
  • 对输入数据的表达形式很敏感。

你可能感兴趣的:(机器学习,机器学习,朴素贝叶斯算法,人工智能)