机器学习-贝叶斯分类器

一. 概率知识

  • 先验概率:先验概率(prior probability)是指根据以往经验和分析得到的概率

  • 后验概率:后验概率,事情已经发生,要求这件事情发生的原因是由某个因素引起的可能性的大小
    后验概率就是条件概率

      p(c|x) = p(x|c)p(c)/p(x)  
    
      贝叶斯概率引入先验知识和逻辑推理来处理不确定的命题
    
  • 事情还没有发生,要求这件事情发生的可能性的大小,是先验概率。事情已经发生,要求这件事情发生的原因是由某个因素引起的可能性的大小,是后验概率。

  • 先验概率的计算比较简单,没有使用贝叶斯公式;而后验概率的计算,要使用贝叶斯公式,而且在利用样本资料计算逻辑概率时,还要使用理论概率分布,需要更多的数理统计知识

二. 贝叶斯分类器基本原理

1. 贝叶斯判定准则

假设训练集为 T = {(x1,y1),(x2,y2),....,(xN,yN)}, 有 K 中标记类型,标记集 y = {c1,c1,...,cK}

定义损失函数 L(Y=ck,f(x))为将样本 x 分类错误所产生的损失
机器学习-贝叶斯分类器

  • 基于后验概率可以得到将样本 x 分类为 ci 产生的期望损失,即在样本x上的‘条件风险

    机器学习-贝叶斯分类器

    注:期望损失也称为风险

  • 对于分类准则f(x),总体风险为:

    机器学习-贝叶斯分类器

我们的任务就是找到一个分类准则使总体风险最小化。如果对于每个样本 x ,若f(x)能最小化条件风险 R(f(x)|x) ,则总体风险 R(f) 也将最小化。

  • 贝叶斯判定准则: 为了最小化总体风险,只需在每个样本上选择那个能使条件风险R(c|x)最小的类别标记

    机器学习-贝叶斯分类器

上式为最小分类错误的贝叶斯最优分类器,即对每个样本x,选择能使后验概率 P(c | x)最大的类别标记,所以期望风险最小化准则就是后验概率最大化准则

2. 朴素贝叶斯

2.1 朴素贝叶斯的原理

经过上面的分析我们得到要想设计最优的贝叶斯判定准则来最小化决策风险,首先要获得的就是后验概率 P(c| x)。

由贝叶斯定理可以得到后验概率的计算公式:

机器学习-贝叶斯分类器

P( c ) 是先验该概率,P(x | c)是样本 x 相对于类标记 c 的条件概率,或称为似然概率

  • 先验概率p©表示样本空间中各类样本所占的比例
  • 类条件概率p(x | c)是样本x所有属性的联合概率,难以从有限的训练样本中直接估计得到,为了避开这个障碍简化问题,朴素贝叶斯法做了**‘ 属性条件独立的 ’**的假设,即对已知类别,假设所有属性相互独立

这就是朴素贝叶斯的由来,朴素贝叶斯分类器中的**’ 朴素 ‘**的含义是: 所有属性特征相互独立同等重要。如果属性之间不相互独立就是贝叶斯网,一种经典的概率图模型。

有朴素贝叶斯分类器的的属性相互独立的假设条件可以将后验概率公式变为如下:

机器学习-贝叶斯分类器

由于对于所有类别来说 P(x) 相同,因此可以得到贝叶斯判定准则如下,xi 为样本 x 第 i 个属性的值:

机器学习-贝叶斯分类器

这就是朴素贝叶斯分类器的表达式

所以朴素贝叶斯分类器学习的关键是如何求解 P© 和 P(x | c)

2.2 极大似然估计法

初学,目前还没有搞懂,先占个坑吧!

在朴素贝叶斯算法中,分类器的训练学习意味着估计 P© 和 P(xi | c) ,可以使用极大似然估计法估计响应的概率,先验概率和条件概率的极大似然估计如下:

机器学习-贝叶斯分类器

2.3 朴素贝叶斯分类算法

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

机器学习-贝叶斯分类器

算法实例

机器学习-贝叶斯分类器

机器学习-贝叶斯分类器

注:算法和实例图来自于《统计学方法》

2.5 贝叶斯估计

实现算法中可能出现的问题

  • 下溢出问题

    训练朴素贝叶斯分类器之后,当有新样本时,会计算样本属于某个类别的概率

    P(i) = P(ci) P(w0 | ci) P(w1 | ci) ... P(wk | ci)

    在计算概率是由于太多很小的数相乘,程序会下溢出或者得不到正确答案(比如python程序在乘法中得到非常小的输时会四舍五入)

    一种解决方法是对上面的乘法计算公式取对数将乘法转化为加法,ln(a*b) = ln(a) + ln(b),通过对数避免下溢出或四舍五入,采用对数进行处理不会有任何损失,不会影响最终结果(因为算法最后是通过比较各个类别概率大小决定样本的类别,而不是返回概率本身)

  • 训练集中特征不存在,概率为零

    在计算条件概率时,如果某个属性在训练集中没有和某个类同事出现,那么这个条件概率就为0,这样在最后计算样本属于该类的概率时不管其他属性如何,这个概率都为零,为了避免其他属性携带的信息被训练集中未出现的属性抹去,通常要进行**“平滑”**,常用拉普拉斯平滑。

贝叶斯估计

用极大似然估计会出现所要计算的概率为0的情况,会影响到后验概率的计算进而影响最终的判定结果,使分类产生偏差。解决这一问题的方法是使用贝叶斯估计代替极大似然估计,下面公式是先验概率和条件概率的贝叶斯估计:

机器学习-贝叶斯分类器

λ ≧ 0 ,当 λ = 0 时,就是最大似然估计;当 λ = 1 时, 就是拉普拉斯平滑

2.5 朴素贝叶斯分类器的实现方式

  • 基于高斯分布模型

    GuassianNB:高斯朴素贝叶斯算法是基于假设特征服从高斯分布

  • 基于贝努利模型

    MultinomialNB:用于实现服从多项式分布数据的朴素贝叶斯算法,常用于文本分类(文本数据经常用词向量来表示)

  • 基于多项式模型

    BernoulliNB:用于实现服从多变量贝努利分布数据的朴素贝叶斯算法,这种算法要求样本表示成二进制特征向量

三. 算法编程实现

1. python 实现朴素贝叶斯算法

#程序是基于电子邮件垃圾过滤的实例对朴素贝叶斯的实现
import re
import numpy as np

def spamTest():
    docList = []    #存储文本分割成的字符串列表
    classList = []  #存储每个邮件的类别
    #一共有50封邮件,25封垃圾邮件,25封正常邮件,将每封邮件转化成单次向量,添加到docList中,并将邮件类型添加到classList中
    for i in range(1,26):
        #file1 = "E:\courseware\machine leaning\code\MachineLearningInAction\Ch04\email\spam\%d.txt" % i
        stringOfText = open("E:\courseware\machine leaning\code\MachineLearningInAction\Ch04\email\spam\%d.txt" % i, 'r', encoding='gbk', errors='ignore').read()
        wordList = textParse(stringOfText)
        docList.append(wordList)
        classList.append(1)
        #file2 = "E:\courseware\machine leaning\code\MachineLearningInAction\Ch04\email\ham\%d.txt" % i
        stringOfText = open("E:\courseware\machine leaning\code\MachineLearningInAction\Ch04\email\ham\%d.txt" % i, 'r', encoding='gbk', errors='ignore' ).read()
        wordList = textParse(stringOfText)
        docList.append(wordList)
        classList.append(0)
    #利用docList获得词汇表向量
    vocabList = createVocabList(docList)
    #分割训练集和测试集,一共有50个样本,从中选出10个样本作为测试集
    trainIndex = list(range(50))
    testIndex = []
    for i in range(10):
        index = int(np.random.uniform(0, len(trainIndex))) #从0到len的均匀分布中随机选取一个整数
        testIndex.append(trainIndex[index])
        trainIndex.pop(index)
    trainSet = []
    trainClass = []
    #将训练索引中对应的文档向量添加到训练集
    for docIndex in trainIndex:
        trainSet.append(setOfWords2Vec(vocabList, docList[docIndex]))
        trainClass.append(classList[docIndex])
    #用训练集训练分类器
    p0Vect, p1Vect, p1 = fit(np.array(trainSet), np.array(trainClass))
    #用测试集才评估训练的分类器
    errorCount = 0
    for docIndex in testIndex:
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])
        if predict(wordVector, p0Vect,p1Vect,p1) != classList[docIndex]:
            errorCount += 1
    errorRate = float(errorCount) / len(testIndex)
    print("The error rate is:%f" % errorRate)

#将整个文本组成的字符串按word分割成字符串列表
def textParse(testString):
    stringListOfText = re.split(r'\W*', testString)
    return [s.lower() for s in stringListOfText if len(s) > 2]

#创建词汇表向量,里面包含出现在所有文本中的word
def createVocabList(dataSet):
    vocabList = set([])
    for text in dataSet:
        vocabList = vocabList | set(text)  # | 的作用是集合求并集或者按位求或(or)的操作
    return list(vocabList)

#将一个文本的词向量转化成文档向量,向量每个元素为0或1,表示文本中的单词是否在词汇表中出现,eg[0,1,0,1,1,1]
def setOfWords2Vec(vocabList, wordSet):
    resultVec = [0]*len(vocabList)
    for word in wordSet:
        if word in vocabList:
            resultVec[vocabList.index(word)] = 1
    return resultVec

#朴素贝叶斯分类器的训练函数,以二分类为例
def fit(trainSet,trainClass):
    numDocs = len(trainClass)
    numWords = len(trainSet[0])
    p1 = (sum(trainClass)+1) / (float(numDocs)+2)
    p0Num = np.ones(numWords)
    p1Num = np.ones(numWords)
    p0Denom = 2.0
    p1Denom = 2.0
    for i in range(numDocs):
        if trainClass[i] == 1:
            p1Num += trainSet[i]
            #p0Denom += sum(trainSet[i])
            p0Denom += 1
        else:
            p0Num += trainSet[i]
            #p1Denom +=sum(trainSet[i])
            p1Denom += 1
    p0Vec = np.log(p0Num / p0Denom)
    p1Vec = np.log(p1Num / p1Denom)
    return p0Vec, p1Vec, p1

def predict(testVec, p0Vec, p1Vec, pClass1):
    p1 = sum(testVec * p1Vec) + np.log(pClass1)
    p0 = sum(testVec * p0Vec) + np.log(1.0 - pClass1)
    if p0 > p1:
        return 0
    else:
        return 1

if __name__ == '__main__':
    spamTest()

程序内容参考 《机器学习实战》

2. 朴素贝叶斯在 scikit-learn中的实现


import re
from sklearn import naive_bayes
from sklearn.model_selection import train_test_split

def naive_bayesian():
    #加载数据并划分训练集和测试集
    docList = []    #存储文本分割成的字符串列表
    classList = []  #存储每个邮件的类别
    #一共有50封邮件,25封垃圾邮件,25封正常邮件,将每封邮件转化成单次向量,添加到docList中,并将邮件类型添加到classList中
    for i in range(1,26):
        #file1 = "E:\courseware\machine leaning\code\MachineLearningInAction\Ch04\email\spam\%d.txt" % i
        stringOfText = open("E:\courseware\machine leaning\code\MachineLearningInAction\Ch04\email\spam\%d.txt" % i,encoding='gbk', errors='ignore').read()
        wordList = textParse(stringOfText)
        docList.append(wordList)
        classList.append(1)
        #file2 = "E:\courseware\machine leaning\code\MachineLearningInAction\Ch04\email\ham\%d.txt" % i
        stringOfText = open("E:\courseware\machine leaning\code\MachineLearningInAction\Ch04\email\ham\%d.txt" % i, encoding='gbk', errors='ignore' ).read()
        wordList = textParse(stringOfText)
        docList.append(wordList)
        classList.append(0)
    #利用docList获得词汇表向量
    vocabList = createVocabList(docList)
    dataList = []
    for docIndex in range(50):
        dataList.append(setOfWords2Vec(vocabList, docList[docIndex]))
    #划分训练集和测试集
    x_train, x_test, y_train, y_test = train_test_split(dataList, classList)
    #创建模型
    model = naive_bayes.GaussianNB()
    #训练模型
    model.fit(x_train, y_train)
    #用测试集测试模型
    accuracy = model.score(x_test,y_test)
    print("精确度为:%f%%" % (accuracy*100))

#将整个文本组成的字符串按word分割成字符串列表
def textParse(testString):
    stringListOfText = re.split(r'\W*', testString)
    return [s.lower() for s in stringListOfText if len(s) > 2]

#创建词汇表向量,里面包含出现在所有文本中的word
def createVocabList(dataSet):
    vocabList = set([])
    for text in dataSet:
        vocabList = vocabList | set(text)  # | 的作用是集合求并集或者按位求或(or)的操作
    return list(vocabList)

#将一个文本的词向量转化成文档向量,向量每个元素为0或1,表示文本中的单词是否在词汇表中出现,eg[0,1,0,1,1,1]
def setOfWords2Vec(vocabList, wordSet):
    resultVec = [0]*len(vocabList)
    for word in wordSet:
        if word in vocabList:
            resultVec[vocabList.index(word)] = 1
    return resultVec

if __name__ == '__main__':
    naive_bayesian()

程序使用的邮件数据在我的 github 上可以下载 朴素贝叶斯-邮件数据集和程序