0%

朴素贝叶斯算法

朴素贝叶斯概述

在许多分类算法应用中,特征和标签之间的关系并非是决定性的。比如说,我们想预测一个人究竟是否会在泰坦尼克号海难中生存下来,那我们可以建立某个分类模型来学习我们的训练集。

在训练中,其中一个人的特征为 30 岁,男,普 通舱,他最后在泰坦尼克号海难中去世了。当我们测试的时候,我们发现有另一个人的特征也为 30 岁,男,普通 舱。

基于在训练集中的学习,我们的模型必然会给这个人打上标签:去世。然而这个人的真实情况一定是去世了吗?并非如此。也许这个人是心脏病患者,得到了上救生艇的优先权。又有可能,这个人就是挤上了救生艇,活了下来。

对分类算法 来说,基于训练的经验,这个人“很有可能”是没有活下来,但算法永远也无法确定”这个人一定没有活下来“。即便这 个人最后真的没有活下来,算法也无法确定基于训练数据给出的判断,是否真的解释了这个人没有存活下来的真实情况。

这就是说,算法得出的结论,永远不是 100% 确定的,更多的是判断出了一种“样本的标签更可能是某类的可能性”,而非一种“确定”。

我们通过模型算法的某些规定,来强行让算法为我们返回一个固定的分类结果。但许多时候,我们也希望能够理解算法判断出结果的可能性概率。

无论如何,我们都希望使用真正的概率来衡量可能性,因此就有了真正的概率算法:朴素贝叶斯。

朴素贝叶斯是一种直接衡量标签和特征之间的概率关系的有监督学习算法,是一种专注分类的算法。朴素贝叶斯的算法根源就是基于概率论和数理统计的贝叶斯理论,因此它是根正苗红的概率模型。

接下来,我们就来认识一下这个简单快速的概率算法。

现在我们对邮箱的邮件和文章进行分类:

上图所示,是不是说,那个类别可能性占的比例较大,则将邮件或者文章归为哪一类呢?这里的占比指的就是分类的概率。

概率基础

所谓概率,也就是一件事情发生的可能性。比如,扔一个硬币,正面朝上的可能性,或者某天是阴天的可能性。

接下来,我们以一个例子,简单研究一下概率的含义和计算方式。

我们有如下样本,根据职业可提醒的不同,被喜欢的程度也是不一样的。需要注意的是,这里只是举一个例子,只有七个样本方便计算。在现实中,这么少的样本出现的频率是不可以认作为概率的。这里需要假设一个前提,就是样本中出现的频率就等同于该事件发生的概率。同时,还要假设体型和职业这两个事件相互独立。

样本编号 职业 体型 女神是否喜欢
1 程序员 超重 不喜欢
2 产品 匀称 喜欢
程序员 匀称 喜欢
程序员 超重 喜欢
美工 匀称 不喜欢
美工 超重 不喜欢
产品 匀称 喜欢

计算如下几个概率:

  1. 女神喜欢一个人的概率。

    总共有 7 个人,女神喜欢 4 个,所以女神喜欢一个人的概率是 4/7

  2. 职业是程序员并且体型匀称的概率。

    职业是程序员的概率为 3/7;体型匀称的概率为 4/7。那么职业是程序员且体型匀称的概率为 3/7 * 4/7 = 12/49

    这里应用到一个独立事件同时发生的计算公式,我们稍后会提到,P(程序员, 匀称) = P(程序员) * P(匀称)

  3. 在女神喜欢的条件下,职业是程序员的概率。

    女神喜欢的情况有 4 种,其中有两个情况是程序员,概率为 2/4 = 1/2

    这里涉及到条件概率,可以这样表示:P(程序员|女神喜欢) = 1/2

  4. 在女神喜欢的条件下,职业是产品,体重超重的概率。

    这个是综合使用条件概率和乘法公式的案例,其计算方法为:P(产品, 超重|女神喜欢) = P(产品|女神喜欢) * P(超重|女神喜欢)

    于是这道题的概率为:1/2 * 1/4 = 1/8

通过以上例子我们了解到两个概率计算准则:联合概率和条件概率。

联合概率指的是多个条件同时成立的概率,记作:P(A, B)

独立事件的联合概率的计算公式为:P(A, B) = P(A) * P(B)

条件概率指的是在事件 A 发生的条件下,事件 B 发生的概率,记作:P(B|A)

条件概率的计算公式为:P(B|A) = P(AB)/P(B)

注意:

  • 上述的求概率公式只适用于各个特征之间是条件独立(每个特征之间没有必然关系)的。条件不独立指的是特征之间有关联的。比如,体重和是否喜欢吃零食这两个条件之间就有关联。
  • 朴素贝叶斯只适用于特征之间是条件独立的情况下。否则分类效果不好。这里的朴素指的就是条件独立。
  • 朴素贝叶斯主要被广泛地应用于文章分类中。

朴素贝叶斯的分类

在 sklearn 中提供了三种不同类型的贝叶斯模型算法:

  • 高斯模型
  • 多项式模型
  • 伯努利模型

高斯分布(正态分布)

高斯分布,也就是正态分布,是一种连续型变量的概率分布。简单来说,高斯分布就是当频率直方图的区间变得特别小时的拟合曲线,像座小山峰,其中两端的特别小,越往中间越高。

所谓正态分布,就是正常形态的分布,它是自然界的一种规律。

现实生活中有很多现象均服从高斯分布,比如收入,身高,体重等,大部分都处于中等水平,特别少和特别多的比例都会比较低。

下面我们通过一个例子,更好地了解高斯分布。

一起玩一个游戏抛硬币,游戏规则为:

定义每个抛出硬币正面记 +1 分,反面记 -1 分

此时我们可以得知抛一次硬币为正面的概率和为反面的概率各为 50%,那也就是会有一半概率 +1 分,一半概率 -1 分。

如果我们连续抛10次硬币,则得到的记分统计为:

画个图感受一下:

如果抛 100 次,甚至是无穷多次呢?平均分数分布情况大概是什么样呢?画个图感受一下:

曲线呈现为正太分布

为什么在我们的现实生活中正太分布如此常见呢?

通常情况下一个事物的影响因素往往有多个,比如身高的影响有:

  • 家庭的饮食习惯
  • 家庭长辈的身高
  • 运动情况
  • ……

其中的每一个因素,都会对身高产生一定的影响,要么是正向的影像,要么是反向的影响。所有因素最终让整体身高接近于正态分布。

通过假设 P(xi|Y) 是服从高斯分布(也就是正态分布),来估计每个特征下分到每个类别 Y 上的条件概率。对于每个特征下的取值,高斯朴素贝叶斯有如下公式:

  • exp 函数为高等数学里以自然常数 e 为底的指数函数

高斯分布模型的作用:

  • 在贝叶斯分类中,高斯模型就是用来处理连续型特征变量的,当使用此模型时,我们会假定特征属于高斯分布,然后基于训练样本计算特征所属标签的均值(𝜇)和标准差(𝛔),这样就可以估计某个特征属于某个类别的概率。

    比如:判断一个人帅还是丑,则帅&丑就是分类的标签,一个人的特征假设有身高、体重,三围,则高斯模型会计算带有身高的特征分到帅的条件概率和丑的条件概率,在计算带有体重的特征分到帅和丑的条件概率,以此类推。

  • 对于任意一个 Y 的取值,贝叶斯都以求解最大化的P为目标,这样我们才能够比较在不同标签下我们的样本究 竟更靠近哪一个取值。以最大化 P 为目标,高斯朴素贝叶斯会为我们求解公式中的参数 𝜇y𝛔y。求解出参数后,带入一个 xi 的值,就能够得到一个 P 的概率取值。

高斯模型 API

  • from sklearn.naive_bayes import GaussianNB

实例化模型对象的时候,我们不需要对高斯朴素贝叶斯类输入任何的参数,可以说是一个非常轻量级的类,操作非常容易。但过于简单也意味着贝叶斯没有太多的参数可以调整,因此贝叶斯算法的成长空间并不是太大,如果贝叶斯算法的效果不是太理想,我们一般都会考虑换模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
# 获取数据
digits = load_digits()
feature = digits.data
target = digits.target
# 拆分测试集和训练集
x_train, x_test, y_train, y_test = train_test_split(feature, target, random_state=420, test_size=0.2)
# 实例化模型对象,使用训练集训练模型
gnb = GaussianNB()
gnb.fit(x_train, y_train)
# 查看测试集的分数
gnb.score(x_test, y_test) # 0.8472222222222222

预测准确率还是可以的。

从上面的操作我们看到,高斯分布的原理虽然复杂,但是已经将算法封装好,我们只需要简单调用即可。现阶段单纯使用不必深究原理,日后需要更进一步实现高端操作,可以研究算法模型。

我们可以使用 predict 方法来预测指定特征的分类。predict 返回是分类结果,就是分类概率最大的那一个类别。比如我们使用测试集的第一个样本进行测试(要注意 predict 需要的参数是二维数据,x_test 直接取索引获得的是一维数据,需要将其转成二维数据才能作为参数传递给 predict):

1
gnb.predict(x_test[0].reshape(1, -1))

预测的结果为:array([6])

我们看一下测试集目标数据的第一个元素:

1
y_test[0]

果然是 6,预测成功。

除了预测分类结果,我们还可以使用 predict_proba 方法查看每一个测试集样本属于每个类别的概率,最大的就是分类结果:

1
gnb.predict_proba(x_test[0].reshape(1, -1))    # 参数需要是二维数据

预测到的每个分类的可能性为:

1
2
3
4
array([[0.00000000e+000, 9.69688733e-054, 1.08613431e-106,
3.57594751e-096, 0.00000000e+000, 4.60851822e-046,
1.00000000e+000, 0.00000000e+000, 4.83837016e-036,
1.55718269e-123]])

除了第七个之外,所有的概率都非常小(不要看数值大小,要看 e 后面的量级大小)。而第七个类别的概率是 1。因为数据是从 0 开始排列,第七个刚好就是 6,也就是预测的结果了。

上面的预测结果中,数据量级差异特别巨大。这时,我们可以使用它们的对数来比较大小。对数函数是一个增函数,所以对数值越大的数,原来的数值也越大。所以我们只需找到对数最大的,即可找到概率最大的。

predict_log_proba 方法就是 predict_proba 的对数转化,最大的就是分类结果:

1
gnb.predict_log_proba(x_test[0].reshape(1, -1))

得到了每个分类可能性的对数值:

1
2
3
4
array([[-1.75243114e+03, -1.22067790e+02, -2.43991395e+02,
-2.19773939e+02, -1.85849980e+08, -1.04391008e+02,
0.00000000e+00, -1.85847933e+09, -8.13164854e+01,
-2.82775088e+02]])

对数最大的显然还是第七个,预测结果没有变。

经验:贝叶斯本身更加擅长线性可分的二分数据,但朴素贝叶斯在环形数据和月亮型数据上也可以有远远胜过其他线 性模型的表现。

多项式模型

与高斯分布相反,多项式模型主要适用于离散特征的概率计算,且 sklearn 的多项式模型不接受输入负值。虽然 sk-learn 中的多项式模型也可以被用作在连续性特征概率计算中,但是我们如果想要处理连续性变量则最好选择使用高斯模型。

注意:因为多项式不接受负值的输入,所以如果样本数据的特征为数值型数据的话,务必要进行归一化处理保证特征数据中无负值出现。

原理:计算出一篇文章为某些类别的概率(文章是固定的,也就是说在该违章为前提下求出所属类别的概率,因此文章就是概率论中条件概率的条件),最大概率的类型就是该文章的类别。

P(类别|文章):类别可以为军事,财经,体育等等,文章其实就是一个又一个的词语。比如有一篇文章

  • P(体育|词1,词2,词3......)==1/6
  • P(财经|词1,词2,词3......)==1/3

则该文章属于财经类别,那么 P(财经|词1,词2,词3......) 如何计算值呢?如何计算多个条件下一个结果的概率呢?

朴素贝叶斯算法公式:

细节解释:

  • w 为给定文档的特征,也就是文章中拆分出来的不同词语
  • c 为文档的类别(财经,体育,军事……)

那么,一篇文档为财经和军事的概率计算如下:

  • P(财经|词1, 词2, 词3) = P(词1, 词2, 词3|财经) * P(财经)/P(W)
  • P(军事|词1, 词2, 词3) = P(词1, 词2, 词3|军事) * P(军事)/P(W)

上述两个公式中都有想用的 P(W),可以抵消,则公式简化为:

  • P(词1, 词2, 词3|财经)*P(财经) ==> P(W|C)*P(C)
  • P(词1, 词2, 词3|军事)*P(军事) ==> P(W|C)*P(C)

这样的公式我们是可以进行计算的,这就是条件概率

  • P(C):每个文档类别的概率(某个文档类别文章的数量/总文档数量)
  • P(W|C):给定类别下特征的概率,此处的特征就是预测文档中出现的词语
  • P(W|C) 的计算方法:P(F1|C) = Ni/N
    • F1:预测文档中的某一个词,C 为指定的类别
    • Ni:F1 这个词在 C 类别所有文档中出现的次数
    • N:所属类别 C下 的文档所有词出现的次数和

实例推导,请看题:

解释:上图的表格为训练集的统计结果,训练集中一共有 30 篇科技文章,60 篇娱乐文章,共计 90 篇文章。这些文章中根据 tf-idf 提取出重要的词语分别有(商场,影院,支付宝和云计算),然后这些词分别在不同训练集中不同类别文章中出现的次数也统计出来了。

现在有一个将要被预测的文章,该文章中出现重要的次为影院,支付宝和云计算,则计算该文章属于科技、娱乐的概率分别是多少?

思考:属于某个类别的概率为 0,合适吗?

似乎不太合适。虽然被预测文章中没有出现云计算这个词语,但是出现娱乐类别中的其他词,所以概率为 0 不合适!那么如何处理呢?

朴素贝叶斯 API

  • from sklearn.naive_bayes import MultinomialNB
  • MultinomialNB(alpha=1.0, fit_prior=True, class_prior=None)
    • alpha:拉普拉斯平滑系数

实战:数据使用- 实战:数据使用 fetch_20newsgroups 中的数据,包含了 20 个主题的 18000 个新闻组的帖子

流程:

  • 加载 20 类新闻数据,并进行样本分割
  • 生成文章特征词
  • 使用模型进行文章分类

sklearn 文本特征提取——TfidfVectorizer(求出 Ni/N 的值)

什么是 TF-IDF:

  • Ni/N:可以表示一个词对于整片文章的重要性。
  • TF:词频。TF(w)=(词w在文档中出现的次数)/(文档的总词数) == P(F1|C)=Ni/N
  • IDF:逆向文章频率。有些词可能在文本中频繁出现,但并不重要,也即信息量小,如 is、of、that 这些单词。这些单词在语料库中出现的频率也非常大,我们就可以利用这点,降低其权重。IDF(w)=log_e(语料库的总文档数)/(语料库中词w《is, of...》出现的文档数)。e 是自然对数的底数,是一个无限不循环小数,其值是2.71828…
  • TF-IDF:将上面的 TF-IDF 相乘就得到了综合参数:TF-IDF = TF * IDF

示例:新闻分类

下面,我们用新闻内容分类的示例演示一下。

首先,获取数据,将其拆分成测试集和训练集:

1
2
3
4
5
6
from sklearn import datasets
from sklearn.model_selection import train_test_split
news = datasets.fetch_20newsgroups(subset='all')
feature = news.data # 返回的是列表,列表中为一篇篇的文章
target = news.target # 返回的ndarray,存储的是每一篇文章的类别
x_train, x_test, y_train, y_test = train_test_split(feature, target, random_state=20, test_size=0.25)

使用 TF-IDF 对数据集进行特征抽取:

1
2
3
4
5
from sklearn.feature_extraction.text import TfidfVectorizer
tf = TfidfVectorizer()
x_train = tf.fit_transform(x_train) # 返回训练集所有文章中每个词的重要性
x_test = tf.transform(x_test) # 返回测试集所有文章中每个词的重要性
# print(tf.get_feature_names()) # 所有文章中出现的词语

训练模型,测试模型:

1
2
3
4
5
6
7
from sklearn.naive_bayes import MultinomialNB
mnb = MultinomialNB(alpha=1)
mnb.fit(x_train, y_train)
y_pred = mnb.predict(x_test)
print('预测文章类别为:', y_pred)
print('真实文章类别为:', y_test)
print('准确率为:', mnb.score(x_test, y_test))

输出的结果为:

1
2
3
预测文章类别为: [18 16 11 ...  9  2 16]
真实文章类别为: [18 16 11 ... 9 2 6]
准确率为: 0.8550509337860781

注意:

  • fit_transform 干了两件事:fit 找到数据转换规则,并将数据标准化
  • transform:是将数据进行转换,比如数据的归一化和标准化,将测试数据按照训练数据同样的模型进行转换,得到特征向量。可以直接把转换规则拿来用,所以并不需要 fit_transform,否则,两次标准化后的数据格式(或者说数据参数)就不一样了。

示例:手写数字识别

再来尝试对 sklearn 提供的手写数字数据进行识别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB

digits = load_digits()
feature = digits.data
target = digits.target

x_train, x_test, y_train, y_test = train_test_split(feature, target, random_state=420, test_size=0.2)

mnb = MultinomialNB(alpha=1)
mnb.fit(x_train, y_train)

mnb.score(x_test, y_test) # 0.9138888888888889

原理很难,但是代码写起来却一气呵成。识别的准确率也是蛮高的。

伯努利模型

多项式朴素贝叶斯可同时处理二项分布(抛硬币)和多项分布(掷骰子),其中二项分布又叫做伯努利分布,它是一种现实中常见,并且拥有很多优越数学性质的分布。因此,既然有着多项式朴素贝叶斯,我们自然也就又专门用来处理二项分布的朴素贝叶斯:伯努利朴素贝叶斯。

简单来说,就是数据集中可以存在多个特征,但每个特征都是二分类的,可以以布尔变量表示,也可以表示为 {0,1} 或者 {-1,1} 等任意二分类组合。因此,这个类要求将样本转换为二分类特征向量,如果数据本身不是二分类的,那可以使用类中专门用来二值化的参数 binarize 来改变数据。

伯努利朴素贝叶斯与多项式朴素贝叶斯非常相似,都常用于处理文本分类数据。但由于伯努利朴素贝叶斯是处理二项分布,所以它更加在意的是“是与否”。判定一篇文章是否属于体育资讯,而不是说属于体育类还是娱乐类。

伯努利模型 API:

  • class sklearn.naive_bayes.BernoulliNB (alpha=1.0, binarize=0.0, fit_prior=True, class_prior=None)

参数介绍:

  • alpha:拉普拉斯平滑系数
  • binarize:可以是数值或者不输入。如果不输入,则 BernoulliNB 认为每个数据特征都已经是二元(二值化)的。否则的话,小于 binarize 的会归为一类,大于 binarize 的会归为另外一类

由于伯努利分布只能处理二分类的数据,所以在训练模型前,我们需要把数据转换成两个分类的形式,也就是进行二值化操作。二值化操作示例:

1
2
3
4
5
6
7
import numpy as np
from sklearn.preprocessing import Binarizer
x = np.array([[1, -2, 2, 3, 1, 10],
[1, 2, 3, 33, 4, -90],
[11, 29, 90, -80, 0, 4]])
binarizer = Binarizer(threshold=5) # threshold阈值,默认0.0
binarizer.fit_transform(x)

设定阈值为 5 的二值化结果为:

1
2
3
array([[0, 0, 0, 0, 0, 1],
[0, 0, 0, 1, 0, 0],
[1, 1, 1, 0, 0, 0]])

我们使用伯努利模型来对手写数字进行识别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import BernoulliNB

digits = load_digits()
feature = digits.data
target = digits.target

x_train, x_test, y_train, y_test = train_test_split(feature, target, random_state=420, test_size=0.2)

bnb = BernoulliNB() # 默认进行二值化处理,binarize=0.0
bnb.fit(x_train, y_train)

bnb.score(x_test, y_test) # 0.8722222222222222

还可以对新闻分类进行类似的预测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from sklearn.datasets import fetch_20newsgroups
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import BernoulliNB

news = fetch_20newsgroups(subset='all')
feature = news.data
target = news.target

x_train, x_test, y_train, y_test = train_test_split(feature, target, random_state=20, test_size=0.25)

tf = TfidfVectorizer()
x_train = tf.fit_transform(x_train)
x_test = tf.transform(x_test)

bnb = BernoulliNB()
bnb.fit(x_train, y_train)

y_pred = bnb.predict(x_train)
print('预测文章类别为:', y_pred)
print('真实文章类别为:', y_test)
print('准确率为:', bnb.score(x_test, y_test))

输出的结果为:

1
2
3
预测文章类别为: [17  6  6 ...  6  4  0]
真实文章类别为: [18 16 11 ... 9 2 6]
准确率为: 0.6973684210526315

可见,对于非二分类的数据来说,使用伯努利模型效果并不很好。

朴素贝叶斯分类优缺点

优点:

  • 朴素贝叶斯模型发源于古典数学理论,有稳定的分类效率
  • 对缺失数据不太敏感,算法也比较简单,常用于文本分类
  • 分类准确度高,速度快

缺点:

  • 由于使用了样本属性独立的假设,所以如果样本属性有关联的时候,分类效果不好