​​​​​​​【机器学习】朴素贝叶斯(Naive Bayes)

1. 概述

贝叶斯分类算法是统计学的一种概率分类方法,朴素贝叶斯分类(Naive Bayes)是贝叶斯分类中最简单的一种。

分类原理:利用贝叶斯公式根据某特征的先验概率计算出其后验概率,然后选择具有最大后验概率的类作为该特征所属的类。

之所以称之为”朴素”,是因为贝叶斯分类只做最原始、最简单的假设:所有的特征之间是统计独立的。

相关概念:

(1)条件概率

条件概率(Condittional probability),就是指在事件B发生的情况下,事件A发生的概率,用P(A|B)来表示。

表达如下:

(2)全概率

如果事件 A1,A2,...,An 构成一个完备事件且都有正概率,那么对于任意一个事件B则有:

(3)贝叶斯推断

根据条件概率和全概率公式,可以得到贝叶斯公式如下:

P(A)称为"先验概率"(Prior probability),即在B事件发生之前,我们对A事件概率的一个判断。
P(A|B)称为"后验概率"(Posterior probability),即在B事件发生之后,我们对A事件概率的重新评估。
P(B|A)/P(B)称为"可能性函数"(Likely hood),这是一个调整因子,使得预估概率更接近真实概率。

因此,我们就是通过先验概率乘以调整因子来计算其后验概率。即:

条件概率可以理解为:后验概率 = 先验概率 * 调整因子

其中:

  • 调整因子>1,表示事件B发生时,事件A发生的可能性变大,先验概率被增强;
  • 调整因子=1,表示事件B无助于判断事件A的可能性;
  • 调整因子<1,表示事件B发生时,事件A发生的可能性变小,先验概率呗削弱。

根据贝叶斯公式:

转换成分类任务的表达式:

那我们结合这个公式,就可以在相同特征下对于不同类别的概率大小的判断

若有以下数据:

假如某男(帅,性格不好,不上进)向女生求婚,该女生嫁还是不嫁?

那实际上我们需要计算 P(嫁 | 帅 性格不好 不上进) P(不嫁 | 帅 性格不好 不上进),即:

通过全概率公式计算分母,公式如下:

对表中数据统计可得:

则对于类别“嫁”的贝叶斯分子为:

对于类别“不嫁”的贝叶斯分子为:

最终计算结果:

因此选择不嫁

2. 朴素贝叶斯算法种类

在scikit-learn中,一共有3个朴素贝叶斯的分类算法。

分别是GaussianNBMultinomialNBBernoulliNB

(1)GaussianNB

GaussianNB就是先验为高斯分布(正态分布)的朴素贝叶斯,假设每个标签的数据都服从简单的正态分布。

调用scikit-learn的包实现GaussianNB:

数据集为4中特征,3种类别的数据。

# 导入包
import pandas as pd
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn import datasets

# 导入数据集
iris = datasets.load_iris()

# 切分数据集,将数据的内容余目标切分成训练集和测试集
Xtrain, Xtest, ytrain, ytest = train_test_split(iris.data,
                                                iris.target,
                                                random_state=12)
# 建模
clf = GaussianNB()
clf.fit(Xtrain, ytrain)
# 在测试集上执行预测,proba导出的是每个样本属于某类的概率
pre_result=clf.predict(Xtest)   # 预测结果
pro_result=clf.predict_proba(Xtest) # 预测的每一类的百分比
# 测试准确率
accuracy_value=accuracy_score(ytest, pre_result)
# 打印输出结果
print(ytest)        # 测试集的正确值
print(pre_result)   # 测试集的预测值
print(pro_result)   # 测试集的计算结果 每一类的可能性百分比
print(accuracy_value)   # 最后预测准确率

准确率为0.9736842105263158

(2)MultinomialNB

MultinomialNB就是先验为多项式分布的朴素贝叶斯。它假设特征是由一个简单多项式分布生成的。

多项分布可以描述各种类型样本出现次数的概率,因此多项式朴素贝叶斯非常适合用于描述出现次数或者出现次数比例的特征。

该模型常用于文本分类,特征表示的是次数,例如某个词语的出现次数。

多项式分布公式如下:

(3)BernoulliNB

BernoulliNB就是先验为伯努利分布的朴素贝叶斯。

假设特征的先验概率为二元伯努利分布,即如下式:

此时 l 只有两种取值, x_{jl}只能取值0或者1。
在伯努利模型中,每个特征的取值是布尔型的,即true和false,或者1和0。

在文本分类中,就是一个特征有没有在一个文档中出现。

总结:

  • 如果样本特征的分布大部分是连续值,使用GaussianNB会比较好。
  • 如果样本特征的分布大部分是多元离散值,使用MultinomialNB比较合适(常用于文本分类)。
  • 如果样本特征是二元离散值或者很稀疏的多元离散值,应该使用BernoulliNB。

3. 使用朴素贝叶斯进行文档分类

朴素贝叶斯一个很重要的应用就是文本分类

目的:构建一个快速过滤器屏蔽掉侮辱性的言论

对此问题建立两个类型:侮辱类和非侮辱类,使用1和0分别表示

思路:我们把文本看成单词向量或者词条向量,也就是说将句子转换为向量。考虑出现所有文档中的单词,再决定将哪些单词纳入词汇表或者说所要的词汇集合,然后必须要将每一篇文档转换为词汇表上的向量。简单起见,我们先假设已经将本文切分完毕,存放到列表中,并对词汇向量进行分类标注。

此案例所有的函数:

  • loadDataSet:创建实验数据集
  • createVocabList:生成词汇表
  • setOfWords2Vec:生成词向量
  • get_trainMat:所有词条向量列表
  • trainNB:朴素贝叶斯分类器训练函数
  • classifyNB:朴素贝叶斯分类器分类函数
  • testingNB:朴素贝叶斯测试函数

(1)创建实验数据集 loadDataSet

"""
函数功能:创建实验数据集
参数说明:无参数
返回:
    postingList:切分好的样本词条
    classVec:类标签向量
"""


def loadDataSet():
    dataSet = [['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]  # 类别标签向量,1代表侮辱性词汇,0代表非侮辱性词汇
    return dataSet, classVec

(2)生成词汇表 createVocabList

"""
函数功能:生成词汇表:将切分的样本词条整理成词汇表(不重复)
参数说明:
    dataSet:切分好的样本词条
返回:
    vocabList:不重复的词汇表
"""


def createVocabList(dataSet):
    vocabSet = set()  # 创建一个空的集合
    for doc in dataSet:  # 遍历dataSet中的每一条言论
        vocabSet = vocabSet | set(doc)  # 取并集
        vocabList = list(vocabSet)
    return vocabList

(3)生成词向量 setOfWords2Vec

"""
函数功能:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
参数说明:
    vocabList:词汇表
    inputSet:切分好的词条列表中的一条
返回:
    returnVec:文档向量,词集模型
"""


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(f" {word} is not in my Vocabulary!")
    return returnVec  # 返回文档向量

(4)所有词条向量列表 get_trainMat

"""
函数功能:生成训练集向量列表
参数说明:
dataSet:切分好的样本词条
返回:
trainMat:所有的词条向量组成的列表
"""


def get_trainMat(dataSet):
    trainMat = []  # 初始化向量列表
    vocabList = createVocabList(dataSet)  # 生成词汇表
    for inputSet in dataSet:  # 遍历样本词条中的每一条样本
        returnVec = setOfWords2Vec(vocabList, inputSet)  # 将当前词条向量化
        trainMat.append(returnVec)  # 追加到向量列表中
    return trainMat

(5)朴素贝叶斯分类器训练函数 trainNB

"""
函数功能:朴素贝叶斯分类器训练函数
参数说明:
    trainMat:训练文档矩阵
    classVec:训练类别标签向量
返回:
    p0V:非侮辱类的条件概率数组
    p1V:侮辱类的条件概率数组
    pAb:文档属于侮辱类的概率
"""


def trainNB(trainMat, classVec):
    n = len(trainMat)  # 计算训练的文档数目
    m = len(trainMat[0])  # 计算每篇文档的词条数
    pAb = sum(classVec) / n  # 文档属于侮辱类的概率
    p0Num = np.zeros(m)  # 词条出现数初始化为0
    p1Num = np.zeros(m)  # 词条出现数初始化为0
    p0Denom = 0  # 分母初始化为0
    p1Denom = 0  # 分母初始化为0
    for i in range(n):  # 遍历每一个文档
        if classVec[i] == 1:  # 统计属于侮辱类的条件概率所需的数据
            p1Num += trainMat[i]
            p1Denom += sum(trainMat[i])
        else:  # 统计属于非侮辱类的条件概率所需的数据
            p0Num += trainMat[i]
            p0Denom += sum(trainMat[i])
    p1V = p1Num / p1Denom
    p0V = p0Num / p0Denom
    return p0V, p1V, pAb  # 返回属于非侮辱类,侮辱类和文档属于侮辱类的概率

(6)朴素贝叶斯分类器分类函数 classifyNB

"""
函数功能:朴素贝叶斯分类器分类函数
参数说明:
vec2Classify:待分类的词条数组
p0V:非侮辱类的条件概率数组
p1V:侮辱类的条件概率数组
pAb:文档属于侮辱类的概率
返回:
0:属于非侮辱类
1:属于侮辱类
"""


def classifyNB(vec2Classify, p0V, p1V, pAb):
    p1 = reduce(lambda x, y: x * y, vec2Classify * p1V) * pAb  # 对应元素相乘
    p0 = reduce(lambda x, y: x * y, vec2Classify * p0V) * (1 - pAb)
    print('p0:', p0)
    print('p1:', p1)
    if p1 > p0:
        return 1
    else:
        return 0

(7)朴素贝叶斯测试函数 testingNB

"""
函数功能:朴素贝叶斯测试函数
参数说明:
testVec:测试样本
返回:测试样本的类别
"""


def testingNB(testVec):
    dataSet, classVec = loadDataSet()  # 创建实验样本
    vocabList = createVocabList(dataSet)  # 创建词汇表
    trainMat = get_trainMat(dataSet)  # 将实验样本向量化
    p0V, p1V, pAb = trainNB(trainMat, classVec)  # 训练朴素贝叶斯分类器
    thisone = setOfWords2Vec(vocabList, testVec)  # 测试样本向量化
    if classifyNB(thisone, p0V, p1V, pAb):
        print(testVec, '属于侮辱类')  # 执行分类并打印分类结果
    else:
        print(testVec, '属于非侮辱类')  # 执行分类并打印分类结果

最后测试:

# 测试样本1
testVec1 = ['love', 'my', 'dalmation']
testingNB(testVec1)
# 测试样本2
testVec2 = ['stupid', 'garbage']
testingNB(testVec2)

结果:

这是出现问题,明显‘stupid’与‘garbage’属于侮辱词汇,而最后判断为非侮辱的,探究一下原因。

由于最后的概率是非侮辱和侮辱概率相乘,因此打印样本2的这两个的概率看看:

可以看到,即使这两个词汇侮辱类的概率值是存在的,而非侮辱值由于初始化设置为零因此相乘之后也为 零,所以判断为非侮辱类

利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算p(w0|1)p(w1|1)p(w2|1)。如果其中有一个概率值为0,那么最后的成绩也为0

处理该问题的方法:

可以将所有词的出现数初始化为1,并将分母初始化为2。这种做法就叫做拉普拉斯平滑(Laplace Smoothing)又被称为加1平滑,是比较常用的平滑方法,它就是为了解决0概率问题。

因此修改

  • trainNB:朴素贝叶斯分类器训练函数
  • testingNB:朴素贝叶斯测试函数
# 使用拉普拉斯平滑处理的
def trainNB(trainMat,classVec):
    n = len(trainMat) #计算训练的文档数目
    m = len(trainMat[0]) #计算每篇文档的词条数
    pAb = sum(classVec)/n #文档属于侮辱类的概率
    p0Num = np.ones(m) #词条出现数初始化为1
    p1Num = np.ones(m) #词条出现数初始化为1
    p0Denom = 2 #分母初始化为2
    p1Denom = 2 #分母初始化为2
    for i in range(n): #遍历每一个文档
        if classVec[i] == 1: #统计属于侮辱类的条件概率所需的数据
            p1Num += trainMat[i]
            p1Denom += sum(trainMat[i])
        else: #统计属于非侮辱类的条件概率所需的数据
            p0Num += trainMat[i]
            p0Denom += sum(trainMat[i])
    p1V = np.log(p1Num/p1Denom)
    p0V = np.log(p0Num/p0Denom)
    return p0V,p1V,pAb #返回属于非侮辱类,侮辱类和文档属于侮辱类的概率

# 使用拉普拉斯平滑处理的
def classifyNB(vec2Classify, p0V, p1V, pAb):
    p1 = sum(vec2Classify * p1V) + np.log(pAb)  # 对应元素相乘
    p0 = sum(vec2Classify * p0V) + np.log(1 - pAb)  # 对应元素相乘
    if p1 > p0:
        return 1
    else:
        return 0

计算概率不再是零:

运行结果正确:

 

学习来源:菊安酱的机器学习实战

  • 1
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝色蛋黄包

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值