机器学习-Sklearn-14(朴素贝叶斯)

机器学习-Sklearn-14(朴素贝叶斯)

1 概述

1.1 真正的概率分类器

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

1.2 朴素贝叶斯是如何工作的

朴素贝叶斯被认为是最简单的分类算法之一。首先,我们需要了解一些概率论的基本理论。假设有两个随机变量X和Y,他们分别可以取值为x和y。有这两个随机变量,我们可以定义两种概率:

关键概念:联合概率与条件概率
在这里插入图片描述
举个例子,我们让X为"气温",Y为“七星瓢虫冬眠”,则X和Y可能的取值分为别x和y,其中x = {0,1},0表示没有下降到0度以下,1表示下降到了0度以下。y = {0,1},其中0表示否,1表示是。
两个事件分别发生的概率就为:
P(X=1)=50%,则是说明,气温下降到0度以下的可能性为50%,则P(X=0)=1-P(X=1) = 50%
P(Y=1)=70%,则是说明,七星瓢虫会冬眠的可能性为70%,则P(Y=0)=1-P(Y=1) = 30%
则这两个事件的联合概率为P(X=1,Y=1),这个概率代表了气温下降到0度以下和七星瓢虫去冬眠这两件事情同时,独立发生的概率。

而两个事件之间的条件概率为P(Y=1|X=1),这个概率代表了,当气温下降到0度以下这个条件被满足之后,七星瓢虫会去冬眠的概率。也就是说,气温下降到0度以下,一定程度上影响了七星瓢虫去冬眠这个事件。

在概率论中,我们可以证明,两个事件的联合概率等于这两个事件任意条件概率 * 这个条件事件本身的概率。
在这里插入图片描述
简单一些,则可以将上面的式子写成:
在这里插入图片描述
由上面的式子,我们可以得到贝叶斯理论等式:
在这里插入图片描述
而这个式子,就是我们一切贝叶斯算法的根源理论。我们可以把我们的特征X当成是我们的条件事件,而我们要求解的标签Y当成是我们被满足条件后会被影响的结果,而两者之间的概率关系就是P(Y|X),这个概率在机器学习中,被我们称之为是标签的后验概率(posterior probability),即是说我们先知道了条件,再去求解结果。而标签Y在没有任何条件限制下取值为某个值的概率,被我们写作P(Y),与后验概率相反,这是完全没有任何条件限制的,标签的先验概率(prior probability)。而我们的P(X|Y)被称为“类的条件概率”,表示当Y的取值固定的时候,X为某个值的概率。那现在,有趣的事情就出现了。

1.2.1 瓢虫冬眠:理解P(Y|X)

假设,我们依然让X是“气温”,这就是我们的特征,Y是“七星瓢虫冬眠”,就是我们的标签。现在,我们建模的目的是,预测七星瓢虫是否会冬眠。在许多教材和博客里,大家会非常自然地开始说,我们现在求的就是我们的P(Y|X),然后根据贝叶斯理论等式开始做各种计算和分析。现在请问大家,我写作P(Y|X)的这个概率,代表了什么呢?更具体一点,这个表达,可以代表多少种概率呢?
机器学习-Sklearn-14(朴素贝叶斯)_第1张图片
数学中的第一个步骤,也就是最重要的事情,就是定义清晰。其实在数学中,P(Y|X)还真的就代表了全部的可能性,而不是单一的概率本身。现在我们的Y有两种取值,而X也有两种取值,就让概率P(Y|X)的定义变得很模糊,排列组合之后竟然有4种可能。在机器学习当中,一个特征X下取值可能远远不止两种,标签也可能是多分类的,还会有多个特征,排列组合以下,到底求解的P(Y|X)是什么东西,是一个太让人感到混淆的点。同理,P(Y|X)随着标签中分类的个数,可以有不同的取值。P(Y|X)也是一样。在这里,我们来为大家澄清:

机器学习中的简写P(Y),通常表示标签取到少数类的概率,少数类往往使用正样本表示,也就是P(Y=1),本质就是所有样本中标签为1的样本所占的比例。如果没有样本不均衡问题,则必须在求解的时候明确,你的Y的取值是什么。

而P(Y|X)是对于任意一个样本而言,如果这个样本的特征X的取值为1,则表示求解(Y=1|X=1)。如果这个样本下的特征X取值为0,则表示求解P(Y=1|X=0)。也就是说,P(Y|X)是具体到每一个样本上的,究竟求什么概率,由样本本身的特征的取值决定。**每个样本的P(Y|X)如果大于阈值0.5,则认为样本是少数类(正样本,1),如果这个样本的P(Y|X)小于阈值0.5,则认为样本是多数类(负样本,0或者-1)。**如果没有具体的样本,只是说明例子,则必须明确P(Y|X)中X的取值。

在机器学习当中,对每一个样本,我们不可能只有一个特征 ,而是会存在着包含n个特征的取值的特征向量X。因此机器学习中的后验概率,被写作P(Y|X),其中X中包含样本在n个特征Xi上的分别的取值xi,由此可i以表示为X = {X1-x1, X2 = x2, …,Xn = xn}。
机器学习-Sklearn-14(朴素贝叶斯)_第2张图片
虽然写法不同,但其实都包含折同样的含义。以此为基础,机器学习中,对每一个样本我们有:
在这里插入图片描述
对于分子而言,P(Y = 1)就是少数类占总样本量的比例,P(X|Y=1)则需要稍微复杂一点的过程来求解。假设我们只有两个特征X1,X2,有联合概率公式,我们可以有如下证明:
机器学习-Sklearn-14(朴素贝叶斯)_第3张图片
是一种若推广到n个X上,则有:
在这里插入图片描述
这个式子证明,在Y=1的条件下,多个特征的取值被同时取到的概率,就等于Y=1的条件下,多个特征的取值被分别取到的概率相乘。

其中,假设X1与X2是有条件独立则可以让公式P(X1|X2, Y=1) = P(X1|Y =1),这是在假设X2是一个对X1在某个条件下的取值完全无影响的变量。
在这里插入图片描述
假设特征之间是有条件独立的,可以解决众多问题,也简化了很多计算过程,这是朴素贝叶斯被称为”朴素“的理由。
因此,**贝叶斯在特征之间有较多相关性的数据集上表现不佳,而现实中的数据多多少少都会有一些相关性,所以贝叶斯的分类效力在分类算法中不算特别强大。**同时,一些影响特征本身的相关性的降维算法,比如PCA和SVD,和贝叶斯连用效果也会不佳。但无论如何,有了这个式子,我们就可以求解出我们的分子了。

接下来,来看看我们贝叶斯理论等式的分母P(X)。我们可以使用全概率公式来求解P(X):
在这里插入图片描述
其中 代表标签的种类,也就是说,对于二分类而言我们有:
在这里插入图片描述
机器学习-Sklearn-14(朴素贝叶斯)_第4张图片
现在,我们希望预测零下的时候,年龄为20天的瓢虫,是否会冬眠。
机器学习-Sklearn-14(朴素贝叶斯)_第5张图片
设定阈值为0.5,假设大于0.5的就被认为是会冬眠,小于0.5的就被认为是不会冬眠。**根据我们的计算,我们认为一个在零下条件下,年龄为20天的瓢虫,是不会冬眠的。**这就完成了一次预测。但是这样,有趣的地方又来了。刚才的预测过程是没有问题的。但我们总是好奇,这个过程如何对应sklearn当中的fit和predict呢?这个决策过程中,我们的训练集和我的测试集分别在哪里?以及,算法建模建模,我的模型在哪里呢?

1.2.2 贝叶斯的性质与最大后验估计

**之前学习的分类算法总是有一个特点:这些算法先从训练集中学习,获取某种信息来建立模型,然后用模型去对测试集进行预测。**比如逻辑回归,我们要先从训练集中获取让损失函数最小的参数,然后用参数建立模型,再对测试集进行预测。再比如支持向量机,我们要先从训练集中获取让边际最大的决策边界,然后用决策边界对测试集进行预测。相同的流程在决策树,随机森林中也出现,我们在fit的时候必然已经构造好了能够让对测试集进行判断的模型。
而朴素贝叶斯,似乎没有这个过程。

我给了大家一张有标签的表,然后提出说,我要预测零下的时候,年龄为20天的瓢虫,会冬眠的概率,然后我们就顺理成章地算了出来。没有利用训练集求解某个模型的过程,也没有训练完毕了我们来做测试的过程,而是直接对有标签的数据提出要求,就可以得到预测结果了。
这说明,朴素贝叶斯是一个不建模的算法。

以往我们学的不建模算法,比如KMeans,比如PCA,都是无监督学习,而朴素贝叶斯是第一个有监督的,不建模的分类算法

在我们刚才举的例子中,有标签的表格就是我们的训练集,而我提出的要求“零下的时候,年龄为20天的瓢虫”就是没有标签的测试集。我们认为,训练集和测试集都来自于同一个不可获得的大样本下,并且这个大样本下的各种属性所表现出来的规律应当是一致的,因此训练集上计算出来的各种概率,可以直接放到测试集上来使用。即便不建模,也可以完成分类。

但实际中,贝叶斯的决策过程并没有我们给出的例子这么简单。
在这里插入图片描述
在这里插入图片描述
机器学习-Sklearn-14(朴素贝叶斯)_第6张图片
机器学习-Sklearn-14(朴素贝叶斯)_第7张图片
在现实中,要求解分子也会有各种各样的问题。比如说,测试集中出现的某种概率组合,是训练集中从未出现的状况,这种时候就会出现某一个概率为0的情况,**贝叶斯概率的分子就会为0。**还有,现实中的大多数标签还是连续型变量,要处理连续型变量的概率,就不是单纯的数样本个数的占比的问题了。接下来我们就来看看,如何对连续型特征求解概率。

1.2.3 汉堡称重:连续型变量的概率估计

要处理连续型变量,我们可以有两种方法。第一种是把连续型变量分成j个箱,把连续型强行变成分类型变量。
在这里插入图片描述
但其实,我们没有必要这样做,因为我们可以直接通过概率论中来计算连续型变量的概率分布。在分类型变量的情况中,比如掷骰子的情况,我们有且仅有六种可能的结果1~6,并且每种结果的可能性为1/6。此时每个基本的随机事件发生的概率都是相等的,所以我们可以使用1/N来表示有N个基本随机事件可以发生的情况。
机器学习-Sklearn-14(朴素贝叶斯)_第8张图片
在这里插入图片描述
即买到的汉堡刚好是100g概率为0。当一个特征下有无数种可能发生的事件时,这个特征的取值就是连续型的,比如我们现在的特征“汉堡的重量”。从上面的例子可以看得出,当特征为连续型时,随机取到某一个事件发生的概率就为0。
在这里插入图片描述
在这里插入图片描述
如果我们基于100个汉堡绘制直方图,并规定每4g为一个区间,横坐标为汉堡的重量的分布,纵坐标为这个区间上汉堡的个数。
在这里插入图片描述
那我们可以看到最左边的图。m就是中间的浅绿色区间中所对应的纵坐标轴。则对于我们的概率,我们可以变换为:
在这里插入图片描述
如果我们购买一万个汉堡并绘制直方图(如中间的图),将直方图上的区间缩小,P(98g 在这里插入图片描述
一条曲线下的面积,就是这条曲线所代表的函数的积分。如果我们定义曲线可以用函数f(x)来表示的话,我们整条曲线下的面积就是:
在这里插入图片描述
机器学习-Sklearn-14(朴素贝叶斯)_第9张图片
在这里插入图片描述
在现实中,我们往往假设我们的f(x)是满足某种统计学中的分布的,最常见的就是高斯分布(正太分布,像我们购买汉堡的例子),常用的还有伯努利分布,多项式分布。这些分布对应着不同的贝叶斯算法,其实他们的本质都是相同的,只不过他们计算之中的f(x)不同。每个f(x)都对应着一系列需要我们去估计的参数,因此在贝叶斯中,我们的fit过程其实是在估计对应分布的参数,predict过程是在该参数下的分布中去进行概率预测

1.3 sklearn中的朴素贝叶斯

Sklearn基于这些分布以及这些分布上的概率估计的改进,为我们提供了四个朴素贝叶斯的分类器。
机器学习-Sklearn-14(朴素贝叶斯)_第10张图片
虽然朴素贝叶斯使用了过于简化的假设,这个分类器在许多实际情况中都运行良好,著名的是文档分类和垃圾邮件过滤。而且由于贝叶斯是从概率角度进行估计,它所需要的样本量比较少,极端情况下甚至我们可以使用1%的数据作为训练集,依然可以得到很好的拟合效果。当然,如果样本量少于特征数目,贝叶斯的效果就会被削弱。

与SVM和随机森林相比,朴素贝叶斯运行速度更快,因为求解P(Xi|Y)本质是在每个特征上单独对概率进行计算,然后再求乘积,所以每个特征上的计算可以是独立并且并行的,因此贝叶斯的计算速度比较快。不过相对的,贝叶斯的运行效果不是那么好,所以贝叶斯的接口调用的predict_proba其实也不是总指向真正的分类结果,这一点需要注意。

2 不同分布下的贝叶斯

2.1 高斯朴素贝叶斯GaussianNB

2.1.1 认识高斯朴素贝叶斯

在这里插入图片描述
在这里插入图片描述
机器学习-Sklearn-14(朴素贝叶斯)_第11张图片
在这里插入图片描述
这个类包含两个参数:
机器学习-Sklearn-14(朴素贝叶斯)_第12张图片
但在实例化的时候,我们不需要对高斯朴素贝叶斯类输入任何的参数,调用的接口也全部sklearn中比较标准的一些搭配,可以说是一个非常轻量级的类,操作非常容易。但过于简单也意味着贝叶斯没有太多的参数可以调整,因此贝叶斯算法的成长空间并不是太大,如果贝叶斯算法的效果不是太理想,我们一般都会考虑换模型。

无论如何,先来进行一次预测试试看吧:

1、展示我所使用的设备以及各个库的版本
在这里我们来使用watermask这个便利的模块来帮助我们,这是一个能够帮助我们一行代码查看设备和库的版本的模块。如果没有watermask的你可能需要在cmd中运行pip来安装。也可以直接使用魔法命令%%cmd作为一个cell的开头来帮助我们在jupyter lab中安装你的watermark。

%%cmd
pip install watermark
#%%表示魔法命令,必须写在每个cell的第一行第一个,如果之前有注释也是不行的。
#在这里必须分开cell,魔法命令必须是一个cell的第一部分内容
%load_ext watermark
#注意load_ext这个命令只能够执行一次,再执行就会报错,要求用reload命令
#这个命令只能执行一次
%watermark -a "jzh" -d -v -m -p numpy,pandas,matplotlib,scipy,sklearn

机器学习-Sklearn-14(朴素贝叶斯)_第13张图片

#2. 导入需要的库和数据
import numpy as np
import matplotlib.pyplot as plt
from sklearn.naive_bayes import GaussianNB
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
digits = load_digits() #实例化手写数据集
X = digits.data #特征矩阵
y =  digits.target #标签

Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.3,random_state=420)
#3. 建模,探索建模结果
Xtrain.shape #64个特征

在这里插入图片描述

Xtest.shape

在这里插入图片描述

np.unique(Ytrain) #分类型标签,多分类问题

在这里插入图片描述

#实例化和训练训练集
gnb = GaussianNB().fit(Xtrain,Ytrain)
#查看分数
acc_score = gnb.score(Xtest,Ytest) #返回预测的精确性
acc_score

在这里插入图片描述

#查看预测结果
Y_pred = gnb.predict(Xtest) 
Y_pred #对应的是所有样本返回的类别
#这里是如何预测的呢?
#0-9下面所有概率中,概率最大的那个标签,就是样本的标签。
#即第0行,所有列对应的概率gnb.predict_proba(Xtest),最大的概率所对应的标签,就是该样本预测出来的标签类型。

机器学习-Sklearn-14(朴素贝叶斯)_第14张图片

Y_pred.shape

在这里插入图片描述

#查看预测的概率结果
prob = gnb.predict_proba(Xtest)
prob.shape #每一列对应一个标签类别下的概率

在这里插入图片描述

prob

机器学习-Sklearn-14(朴素贝叶斯)_第15张图片

prob[1,:] #第一行所有的列,第一个样本0-9标签下的概率

在这里插入图片描述

prob[1,:].sum() #每一行的和都是一

在这里插入图片描述

prob.sum(axis=1)

机器学习-Sklearn-14(朴素贝叶斯)_第16张图片

prob.sum(axis=1).shape

在这里插入图片描述

#4. 使用混淆矩阵来查看贝叶斯的分类结果
from sklearn.metrics import confusion_matrix as CM
CM(Ytest,Y_pred) #测试集的标签和预测出来的标签
#注意,ROC曲线是不能用于多分类的。多分类状况下最佳的模型评估指标是混淆矩阵和整体的准确度
#右偏对角线上的数表示标签全部预测正确的数量。
#几乎大部分都集中在这条对角线上,说明预测还是可以的。

机器学习-Sklearn-14(朴素贝叶斯)_第17张图片

2.1.2 探索贝叶斯:高斯朴素贝叶斯擅长的数据集

那高斯普斯贝叶斯擅长什么样的数据集呢?我们还是使用常用的三种数据分布:月亮型,环形数据以及二分型数据。注意这段代码曾经在决策树中详细讲解过,在SVM中也有非常类似的代码,核心就是构建分类器然后画决策边界,只不过更换了需要验证的模型而已,因此在这里就不对这段代码是如何实现的进行赘述了。需要了解的小伙伴可以去查看第一章决策树完整版:决策树在合成数据集上的表现,或者查看SVM第一期完整版中,SVC在不同数据集上的表现。

#那高斯普斯贝叶斯擅长什么样的数据集呢?我们还是使用常用的三种数据分布:月亮型,环形数据以及二分型数据。
#注意这段代码曾经在决策树中详细讲解过,在SVM中也有非常类似的代码,核心就是构建分类器然后画决策边界,只不过更换了需要验证的模型而已,因此在这里就不对这段代码是如何实现的进行赘述了。
#查看:https://blog.csdn.net/m0_37755995/article/details/123777601
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_moons, make_circles, make_classification
from sklearn.naive_bayes import GaussianNB,MultinomialNB,BernoulliNB,ComplementNB
h = .02
names = ["Multinomial","Gaussian","Bernoulli","Complement"]
classifiers = [MultinomialNB(),GaussianNB(),BernoulliNB(),ComplementNB()]
X, y = make_classification(n_features=2, n_redundant=0, n_informative=2,
                           random_state=1, n_clusters_per_class=1)
X.shape

在这里插入图片描述

X

机器学习-Sklearn-14(朴素贝叶斯)_第18张图片

y 

机器学习-Sklearn-14(朴素贝叶斯)_第19张图片

np.unique(y)#标签只有两类0和1

在这里插入图片描述

y.shape

在这里插入图片描述

plt.scatter(X[:,0], X[:,1],c=y,cmap="rainbow")
plt.show()

机器学习-Sklearn-14(朴素贝叶斯)_第20张图片

rng = np.random.RandomState(2)
rng

在这里插入图片描述

X += 2 * rng.uniform(size=X.shape)
X

在这里插入图片描述

plt.scatter(X[:,0], X[:,1],c=y,cmap="rainbow")
plt.show()

机器学习-Sklearn-14(朴素贝叶斯)_第21张图片

linearly_separable = (X, y) #新型离散分类点
datasets = [make_moons(noise=0.3, random_state=0), #月亮型离散分类点
            make_circles(noise=0.2, factor=0.5, random_state=1), #环形离散分类点
            linearly_separable
           ]
len(datasets)

在这里插入图片描述

[*enumerate(datasets)]

机器学习-Sklearn-14(朴素贝叶斯)_第22张图片

figure = plt.figure(figsize=(6, 9))
i = 1
for ds_index, ds in enumerate(datasets):
    X, y = ds
    X = StandardScaler().fit_transform(X) #数据标准化
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.4, random_state=42)
    x1_min, x1_max = X[:, 0].min() - .5, X[:, 0].max() + .5
    x2_min, x2_max = X[:, 1].min() - .5, X[:, 1].max() + .5
    #np.arange(),三个参数时,第一个参数为起点,第二个参数为终点,第三个参数为步长。
    array1,array2 = np.meshgrid(np.arange(x1_min, x1_max, 0.2),np.arange(x2_min, x2_max, 0.2)) 
    #cm = plt.cm.RdBu表示颜色图,就像后面定义的cm_bright = ListedColormap(..)一样
    cm = plt.cm.RdBu
    cm_bright = ListedColormap(['#FF0000', '#0000FF'])
    ax = plt.subplot(len(datasets), 2, i) #第一个参数是行数,第二个参数是列数,第三个参数表示图形的标号。
    if ds_index == 0:
        ax.set_title("Input data")
    ax.scatter(X_train[:, 0], X_train[:, 1], c=y_train, 
               cmap=cm_bright,edgecolors='k')
    ax.scatter(X_test[:, 0], X_test[:, 1], c=y_test, 
               cmap=cm_bright, alpha=0.6,edgecolors='k')
    ax.set_xlim(array1.min(), array1.max())
    ax.set_ylim(array2.min(), array2.max())
    ax.set_xticks(())
    ax.set_yticks(())
    i += 1
    ax = plt.subplot(len(datasets),2,i)
    #高斯朴素贝叶斯模型实例化并训练训练集
    clf = GaussianNB().fit(X_train, y_train)
    score = clf.score(X_test, y_test)
    #np.c_,类似于np.vstack的功能,即将拉伸后的XX和YY合并
    #np.c_,类似于np.vstack的功能,结合在一起,.ravel()表示降维拉平
    #np.c_[XX.ravel(), YY.ravel()]这个就是所有点形成的网格坐标了
    #所有网格点所对应分类标签的预测概率,因为标签只有两个类别。所以选取任何一列的概率作为Z值即可。
    Z = clf.predict_proba(np.c_[array1.ravel(),array2.ravel()])
    print(Z)
    Z = clf.predict_proba(np.c_[array1.ravel(),array2.ravel()])[:, 0]
    Z = Z.reshape(array1.shape) #.reshape(XX.shape)为了改变形状,为了放入contour这个函数中
    #.contourf绘制填充型等高线,与.contour不同,.contour只绘制线,不进行填充。
    ax.contourf(array1, array2, Z, cmap=cm, alpha=.8)
    ax.scatter(X_train[:, 0], X_train[:, 1], c=y_train, cmap=cm_bright,
               edgecolors='k')
    ax.scatter(X_test[:, 0], X_test[:, 1], c=y_test, cmap=cm_bright,
               edgecolors='k', alpha=0.6)
    
    ax.set_xlim(array1.min(), array1.max())
    ax.set_ylim(array2.min(), array2.max())
    ax.set_xticks(())
    ax.set_yticks(())
    if ds_index == 0:
        ax.set_title("Gaussian Bayes")
   
    ax.text(array1.max() - .3, array2.min() + .3, ('{:.1f}%'.format(score*100)),
            size=15, horizontalalignment='right')
    i += 1
plt.tight_layout()
plt.show()

机器学习-Sklearn-14(朴素贝叶斯)_第23张图片
机器学习-Sklearn-14(朴素贝叶斯)_第24张图片
机器学习-Sklearn-14(朴素贝叶斯)_第25张图片
从图上来看,高斯贝叶斯属于比较特殊的一类分类器,其分类效果在二分数据和月亮型数据上表现优秀,但是环形数据不太擅长。我们之前学过的模型中,许多线性模型比如逻辑回归,线性SVM等等,在线性数据集上会绘制直线决策边界,因此难以对月亮型和环形数据进行区分,但高斯朴素贝叶斯的决策边界是曲线,可以是环形也可以是弧线,所以尽管贝叶斯本身更加擅长线性可分的二分数据,但朴素贝叶斯在环形数据和月亮型数据上也可以有远远胜过其他线性模型的表现。

2.1.3 探索贝叶斯:高斯朴素贝叶斯的拟合效果与运算速度

我们已经了解高斯朴素贝叶斯属于分类效果不算顶尖的模型,但我们依然好奇,这个算法在拟合的时候还有哪些特性呢?比如说我们了解,决策树是天生过拟合的模型,而支持向量机是不调参数的情况下就非常接近极限的模型。我们希望通过绘制高斯朴素贝叶斯的学习曲线与分类树,随机森林和支持向量机的学习曲线的对比,来探索高斯朴素贝叶斯算法在拟合上的性质。过去绘制学习曲线都是以算法类的某个参数的取值为横坐标,今天我们来使用sklearn中自带的绘制学习曲线的类learning_curve,在这个类中执行交叉验证并从中获得不同样本量下的训练和测试的准确度。

#1. 首先导入需要的模块和库
import numpy as np
import matplotlib.pyplot as plt
from sklearn.naive_bayes import GaussianNB
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.tree import DecisionTreeClassifier as DTC
from sklearn.linear_model import LogisticRegression as LR
from sklearn.datasets import load_digits
from sklearn.model_selection import learning_curve
from sklearn.model_selection import ShuffleSplit
from time import time
import datetime
#======================================
#2. 定义绘制学习曲线的函数
#输入我的分类器,我们的数据,画图所需要的一系列参数,交叉验证的模式,以及其他可能的参数。
#一次性帮助我画出所有的学习曲线。

#函数第一步:找出每个图像所需要的横纵坐标——就是来执行learning_curve这个类。
#学习曲线的横坐标:不同样本数量下的,用于建模的样本数量,而训练样本一共进行了5次不同的训练样本的取值。
#学习曲线的纵坐标:横坐标对应每次交叉验证后的平均训练分数。
#函数第二步:设定好需要来绘制子图所在的画布plt.figure(),就开始绘图了。


def plot_learning_curve(  estimator#estimator为clf分类器,这个是必须要输入的。
                        , title #标题
                        , X #X特征矩阵
                        , y #y标签
                        , ax #选择子图
                        , ylim=None #纵坐标的取值范围为none,因为统一设置所有画布的y轴的取值范围用于对比。
                        , cv=None #交叉验证
                        , n_jobs=None #设定索要使用的线程
                       ):
    train_sizes, train_scores, test_scores = learning_curve(estimator, X, y
                                                           ,cv=cv,n_jobs=n_jobs)    
    ax.set_title(title) #设置标题
    
    #设置所有图像的y轴取值分为都一致,这样好对比。
    if ylim is not None:
        ax.set_ylim(*ylim)
        
    ax.set_xlabel("Training examples")#横坐标的标签名
    ax.set_ylabel("Score")#纵坐标的标签名
    ax.grid() #显示网格作为背景,不是必须
    
    #两个画线的命令:
    #绘制训练器曲线:横坐标就是train_sizes,纵坐标就是训练分数在横轴方向上的均值。
    #'o-'表示带点的曲线。
    ax.plot(train_sizes, np.mean(train_scores, axis=1), 'o-'
           , color="r",label="Training score")
    #绘制测试集曲线:横坐标就是train_sizes,纵坐标就是测试分数在横轴方向上的均值。
    ax.plot(train_sizes, np.mean(test_scores, axis=1), 'o-'
           , color="g",label="Test score")
    ax.legend(loc="best")
    return ax
#=============================================
#3. 导入数据,定义循环
digits = load_digits()
X, y = digits.data, digits.target
X.shape

在这里插入图片描述

X #是一个稀疏矩阵

机器学习-Sklearn-14(朴素贝叶斯)_第26张图片

y

在这里插入图片描述

np.unique(y)

在这里插入图片描述

y.shape

在这里插入图片描述

#标题,5个算法的名字
title = ["Naive Bayes","DecisionTree","SVM, RBF kernel","RandomForest","Logistic"] 
#5个算法,与标题相对应。
model = [GaussianNB(),DTC(),SVC(gamma=0.001)
         ,RFC(n_estimators=50),LR(C=.1,solver="lbfgs")]
#交叉验证的的模式,用于输入到交叉验证中的cv
cv = ShuffleSplit(n_splits=50 #参数1:把数据分为多少份
                  , test_size=0.2 #参数2:表示这50份中的20%作为测试集,50*20%。
                  , random_state=0 #分交叉验证的份数的时候进行的随机抽样的模式。
                 )
#============================================
#4. 进入循环,绘制学习曲线
fig, axes = plt.subplots(1,5,figsize=(30,6)) 
#创建画布,1行5列和画布的尺寸。
#返回两个对象一个是画布fig,另一个是子图对象。

机器学习-Sklearn-14(朴素贝叶斯)_第27张图片

[*zip(range(len(title)),title,model)]

在这里插入图片描述

fig, axes = plt.subplots(1,5,figsize=(30,6)) 
#创建画布,1行5列和画布的尺寸。
#返回两个对象一个是画布fig,另一个是子图对象。

#开始循环,循环三样东西:子图、标题和模型。
#用zip将三样组成一个元组:zip(range(len(title)),title,model)
#设置对应的三个量与zip元组相对于,并进行循环。
for ind, title_, estimator in zip(range(len(title)),title,model):
    times = time()
    plot_learning_curve( estimator
                        , title_
                        , X
                        , y
                        , ax=axes[ind] #子图按照对应的index,子图如何定义fig, axes = plt.subplots(1,5,figsize=(30,6))
                        , ylim = [0.7, 1.05] #纵坐标的取值
                        , n_jobs=4
                        , cv=cv)
    print("{}:{}".format(title_,datetime.datetime.fromtimestamp(time()-
times).strftime("%M:%S:%f")))
plt.show()

在这里插入图片描述

#=======================================
#5、来单独查看一下learning_curve
#即自己定义的函数plot_learning_curve的第一步具体是如何执行的:
clf = GaussianNB()
cv = ShuffleSplit(n_splits=50 #参数1:把数据分为多少份
                  , test_size=0.2 #参数2:表示这50份中的20%作为测试集,50*20%。
                  , random_state=0 #分交叉验证的份数的时候进行的随机抽样的模式。
                 )
#返回三组数据
#train_sizes每次分训练集和测试集建模后,训练集上的样本数量。即每次建模时使用的样本数量。
#train_scores训练集上的分数,模型对数据的拟合效果。返回了50个结果,因为模型进行了50次的交叉验证。
#test_scores测试分数,也是5个不同训练样本取值下,模型在50次交叉验证之后得到的分数。
train_sizes, train_scores, test_scores = learning_curve(clf #参数一:clf分类器,这个是必须要输入的。
                                                        , X #参数二:特征矩阵
                                                        , y #参数三:标签
                                                        ,cv=cv #参数四:可以输入cv=10表示10折交叉验证;还可以输入cv=cv表示交叉验证模式。
                                                        ,n_jobs=4 #参数五:每次运行的时候可以允许算法使用多少运算资源。可以输入任意的正整数,输入-1表示运用所有的资源。
                                                       )
train_sizes #每次建模时使用的样本数量

在这里插入图片描述

train_scores #训练集上的分数,即模型对数据本身的拟合效果。

机器学习-Sklearn-14(朴素贝叶斯)_第28张图片

train_scores.shape #需要对训练分数求平均才能来画图。
#横坐标只有5个点,即一个横坐标对应每次交叉验证后的平均训练分数,而训练样本一共进行了5次不同的训练样本的取值。

在这里插入图片描述
三个模型表现出的状态非常有意思。
我们首先返回的结果是各个算法的运行时间。可以看到,决策树和贝叶斯不相伯仲(如果你没有发现这个结果,那么可以多运行几次,你会发现贝叶斯和决策树的运行时间逐渐变得差不多)。决策树之所以能够运行非常快速是因为sklearn中的分类树在选择特征时有所“偷懒”,没有计算全部特征的信息熵而是随机选择了一部分特征来进行计算,因此速度快可以理解,但我们知道决策树的运算效率随着样本量逐渐增大会越来越慢,但朴素贝叶斯却可以在很少的样本上获得不错的结果,因此我们可以预料,随着样本量的逐渐增大贝叶斯会逐渐变得比决策树更快。朴素贝叶斯计算速度远远胜过SVM,随机森林这样复杂的模型,逻辑回归的运行受到最大迭代次数的强烈影响和输入数据的影响(逻辑回归一般在线性数据上运行都比较快,但在这里应该是受到了稀疏矩阵的影响)。因此在运算时间上,朴素贝叶斯还是十分有优势的。

紧接着,我们来看一下每个算法在训练集上的拟合。手写数字数据集是一个较为简单的数据集,决策树,森林,SVC和逻辑回归都成功拟合了100%的准确率,但贝叶斯的最高训练准确率都没有超过95%,这也应证了我们最开始说的,朴素贝叶斯的分类效果其实不如其他分类器,贝叶斯天生学习能力比较弱。并且我们注意到,随着训练样本量的逐渐增大,其他模型的训练拟合都保持在100%的水平,但贝叶斯的训练准确率却逐渐下降,这证明样本量越大,贝叶斯需要学习的东西越多,对训练集的拟合程度也越来越差。反而比较少量的样本可以让贝叶斯有较高的训练准确率。

再来看看过拟合问题。首先一眼看到,所有模型在样本量很少的时候都是出于过拟合状态的(训练集上表现好,测试集上表现糟糕),但随着样本的逐渐增多,过拟合问题都逐渐消失了,不过每个模型的处理手段不同。比较强大的分类器们,比如SVM,随机森林和逻辑回归,是依靠快速升高模型在测试集上的表现来减轻过拟合问题。相对的,决策树虽然也是通过提高模型在测试集上的表现来减轻过拟合,但随着训练样本的增加,模型在测试集上的表现善生却非常缓慢。朴素贝叶斯独树一帜,是依赖训练集上的准确率下降,测试集上的准确率上升来逐渐解决过拟合问题。

接下来,看看每个算法在测试集上的拟合结果,即泛化误差的大小。随着训练样本数量的上升,所有模型的测试表现都上升了,但贝叶斯和决策树在测试集上的表现远远不如SVM,随机森林和逻辑回归。SVM在训练数据量增大到1500个样本左右的时候,测试集上的表现已经非常接近100%,而随机森林和逻辑回归的表现也在95%以上,而决策树和朴素贝叶斯还徘徊在85%左右。但这两个模型所面临的情况十分不同:决策树虽然测试结果不高,但是却依然具有潜力,因为它的过拟合现象非常严重,我们可以通过减枝来让决策树的测试结果逼近训练结果。然而贝叶斯的过拟合现象在训练样本达到1500左右的时候已经几乎不存在了,训练集上的分数和测试集上的分数非常接近,只有在非常少的时候测试集上的分数才能够比训练集上的结果更高,所以我们基本可以判断,85%左右就是贝叶斯在这个数据集上的极限了。可以预测到,如果我们进行调参,那决策树最后应该可以达到90%左右的预测准确率,但贝叶斯却几乎没有潜力了。

在这个对比之下,我们可以看出:贝叶斯是速度很快,但分类效果一般,并且初次训练之后的结果就很接近算法极限的算法,几乎没有调参的余地。也就是说,如果我们追求对概率的预测,并且希望越准确越好,那我们应该先选择逻辑回归。如果数据十分复杂,或者是稀疏矩阵,那我们坚定地使用贝叶斯。如果我们分类的目标不是要追求对概率的预测,那我们完全可以先试试看高斯朴素贝叶斯的效果(反正它运算很快速,还不需要太多的样本),如果效果很不错,我们就很幸运地得到了一个表现优秀又快速的模型。如果我们没有得到比较好的结果,那我们完全可以选择再更换成更加复杂的模型。

2.2 概率类模型的评估指标

混淆矩阵和精确性可以帮助我们了解贝叶斯的分类结果。然而,我们选择贝叶斯进行分类,大多数时候都不是为了单单追求效果,而是希望看到预测的相关概率。这种概率给出预测的可信度,所以对于概率类模型,我们希望能够由其他的模型评估指标来帮助我们判断,模型在“概率预测”这项工作上,完成得如何。接下来,我们就来看看概率模型独有的评估指标。

2.2.1 布里尔分数Brier Score

概率预测的准确程度被称为“校准程度”,是衡量算法预测出的概率和真实结果的差异的一种方式。一种比较常用的指标叫做布里尔分数,它被计算为是概率预测相对于测试样本的均方误差,表示为:
在这里插入图片描述
其中N是样本数量,pi为朴素贝叶斯预测出的概率,oi是样本所对应的真实结果,只能取到0或者1,如果事件发生则为1,如果不发生则为0。这个指标衡量了我们的概率距离真实标签结果的差异,其实看起来非常像是均方误差。布里尔分数的范围是从0到1,分数越高则预测结果越差劲,校准程度越差,因此布里尔分数越接近0越好。由于它的本质也是在衡量一种损失,所以在sklearn当中,布里尔得分被命名为brier_score_loss。我们可以从模块metrics中导入这个分数来衡量我们的模型评估结果:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.naive_bayes import GaussianNB
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.tree import DecisionTreeClassifier as DTC
from sklearn.linear_model import LogisticRegression as LR
from sklearn.datasets import load_digits
from sklearn.model_selection import learning_curve
from sklearn.model_selection import ShuffleSplit
from sklearn.model_selection import train_test_split

from time import time
import datetime
import pandas as pd
from pandas import DataFrame as df
digits = load_digits() #实例化手写数据集
X = digits.data #特征矩阵
y =  digits.target #标签

Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.3,random_state=420)
#实例化和训练训练集
gnb = GaussianNB().fit(Xtrain,Ytrain)

#查看预测结果
Y_pred = gnb.predict(Xtest) 
#对应的是所有样本返回的类别
#这里是如何预测的呢?
#0-9下面所有概率中,概率最大的那个标签,就是样本的标签。
#即第0行,所有列对应的概率gnb.predict_proba(Xtest),最大的概率所对应的标签,就是该样本预测出来的标签类型。

#查看预测的概率结果
prob = gnb.predict_proba(Xtest)
#每一列对应一个标签类别下的概率
prob.shape 
#10种标签所对应的10个概率,10个概率都加起来等于1

在这里插入图片描述

Ytest_= Ytest.copy()
Ytest_ = pd.get_dummies(Ytest_)
Ytest_ 
#变成哑变量,因为布里尔分数只能计算二分类了。

机器学习-Sklearn-14(朴素贝叶斯)_第29张图片

#取出1标签所对应的概率
np.array(Ytest_.iloc[:,1]).reshape(-1,1)
#取出一列并升维

机器学习-Sklearn-14(朴素贝叶斯)_第30张图片

from sklearn.metrics import brier_score_loss

#查看1这个标签下布里尔分数是多少:
#注意,第一个参数是真实标签,第二个参数是预测出的概率值
#在二分类情况下,接口predict_proba会返回两列,但SVC的接口decision_function却只会返回一列
#要随时注意,使用了怎样的概率分类器,以辨别查找置信度的接口,以及这些接口的结构
brier_score_loss(np.array(Ytest_.iloc[:,1]).reshape(-1,1), prob[:,1], pos_label=1)
#我们的pos_label与prob中的索引一致,就可以查看这个类别下的布里尔分数是多少

#这里报错是因为:
#布里尔是对每一个标签的类别取值进行计算的。
#新版sklearn中布里尔分数只能计算二分类了!!!

在这里插入图片描述

#布里尔分数可以用于任何可以使用predict_proba接口调用概率的模型。
#我们来探索一下在我们的手写数字数据集上,逻辑回归,SVC和我们的高斯朴素贝叶斯的效果如何:
from sklearn.metrics import brier_score_loss
brier_score_loss(np.array(Ytest_.iloc[:,8]).reshape(-1,1),prob[:,8],pos_label=8)

在这里插入图片描述

from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression as LR
np.unique(Ytrain)

在这里插入图片描述

Xtrain.shape

在这里插入图片描述

#这些参数是为了让模型更加收敛。
logi = LR(C=1., solver='lbfgs',max_iter=3000,multi_class="auto").fit(Xtrain,Ytrain)
svc = SVC(kernel = "linear",gamma=1).fit(Xtrain,Ytrain)

#对于标签类别为1的来说逻辑回归的效果更好。
brier_score_loss(np.array(Ytest_.iloc[:,1]).reshape(-1,1),logi.predict_proba(Xtest)[:,1],pos_label=1)

在这里插入图片描述

#每个样本点到决策边界的距离(置信度),距离越远预测越有信心,距离约近预测越没有信心。
svc.decision_function(Xtest)

机器学习-Sklearn-14(朴素贝叶斯)_第31张图片

#由于SVC的置信度并不是概率,为了可比性,我们需要将SVC的置信度“距离”归一化,压缩到[0,1]之间
svc_prob = (svc.decision_function(Xtest) - svc.decision_function(Xtest).min())/(svc.decision_function(Xtest).max() - svc.decision_function(Xtest).min())
svc_prob #对这个距离归一化一下

机器学习-Sklearn-14(朴素贝叶斯)_第32张图片

#依然是切片切出类别1这样的概率
#pos_label=1 表示正样本为1
#对于标签类别为1的来说支持向量机效果是最差的。
brier_score_loss(np.array(Ytest_.iloc[:,1]).reshape(-1,1),svc_prob[:,1],pos_label=1)

在这里插入图片描述

#如果将每个分类器每个标签类别下的布里尔分数可视化:
import pandas as pd
name = ["Bayes","Logistic","SVC"]
df = pd.DataFrame(index=range(10),columns=name)
df

在这里插入图片描述

df.shape[1]

在这里插入图片描述

for i in range(10):
    df.loc[i,name[0]] = brier_score_loss(np.array(Ytest_.iloc[:,i]).reshape(-1,1),prob[:,i],pos_label=i) #标签为i的时候的贝叶斯下的布里尔分数。
    df.loc[i,name[1]] = brier_score_loss(np.array(Ytest_.iloc[:,i]).reshape(-1,1),logi.predict_proba(Xtest)[:,i],pos_label=i) #标签为i的逻辑回归下的布里尔分数。
    df.loc[i,name[2]] = brier_score_loss(np.array(Ytest_.iloc[:,i]).reshape(-1,1),svc_prob[:,i],pos_label=i) #SVC下的布里尔分数。
df

机器学习-Sklearn-14(朴素贝叶斯)_第33张图片

name = ["Bayes","Logistic","SVC"]
color = ["red","black","orange"]
for i in range(df.shape[1]):
    plt.plot(range(10),df.iloc[:,i],c=color[i],label=name[i])
plt.legend()
plt.show()

机器学习-Sklearn-14(朴素贝叶斯)_第34张图片
可以观察到,逻辑回归的布里尔分数有着压倒性优势,SVC的效果明显弱于贝叶斯和逻辑回归(如同我们之前在SVC的讲解中说明过的一样,SVC是强行利用sigmoid函数来压缩概率,因此SVC产出的概率结果并不那么可靠)。
贝叶斯位于逻辑回归和SVC之间,效果也不错,但比起逻辑回归,还是不够精确和稳定。

2.2.2 对数似然函数Log Loss

另一种常用的概率损失衡量是对数损失(log_loss),又叫做对数似然,逻辑损失或者交叉熵损失,它是多元逻辑回归以及一些拓展算法,**比如神经网络中使用的损失函数。**它被定义为,对于一个给定的概率分类器,在预测概率为条件的情况下,真实概率发生的可能性的负对数(如何得到这个损失函数的证明过程和推导过程在逻辑回归的章节中有完整得呈现)。**由于是损失,因此对数似然函数的取值越小,则证明概率估计越准确,模型越理想。**值得注意得是,对数损失只能用于评估分类型模型。
在这里插入图片描述
和我们逻辑回归的损失函数一模一样:
在这里插入图片描述
只不过在逻辑回归的损失函数中,我们的真实标签是由yi表示,预测值(概率估计)是由y0(xi)来表示,仅仅是表示方式的不同。注意,这里的log表示以e为底的自然对数。

在sklearn中,我们可以从metrics模块中导入我们的对数似然函数:

第一个参数是真实标签,第二个参数是我们预测的概率。如果我们使用shift tab来查看log_loss的参数,会发现第二个参数写着y_pred,这会让人误解为这是我们的预测标签。由于log_loss是专门用于产出概率的算法的,因此它假设我们预测出的y就是以概率形式呈现,但在sklearn当中,我们的y_pred往往是已经根据概率归类后的类别{0,1,2},真正的概率必须要以接口predict_proba来调用,千万避免混淆。

from sklearn.metrics import log_loss

机器学习-Sklearn-14(朴素贝叶斯)_第35张图片

#对数似然函数Log Loss是可以衡量多分类的,所以就没有必要进行切片了。
#log_loss()模型中的参数
#其中一个参数是y_pred,这个参数是要传入概率的,而不是分类的类型。

#贝叶斯模型的log_loss最高
log_loss(Ytest,prob)

在这里插入图片描述

#逻辑回归模型的log_loss最小
log_loss(Ytest,logi.predict_proba(Xtest))

在这里插入图片描述

#支持向量机模型的log_loss介于上面两个模型之间。
log_loss(Ytest,svc_prob)

在这里插入图片描述

注意到,**我们用log_loss得出的结论和我们使用布里尔分数得出的结论不一致:**当使用布里尔分数作为评判标准的时候,SVC的估计效果是最差的,逻辑回归和贝叶斯的结果相接近。而使用对数似然的时候,虽然依然是逻辑回归最强大,但贝叶斯却没有SVC的效果好。为什么会有这样的不同呢?

因为逻辑回归和SVC都是以最优化为目的来求解模型,然后进行分类的算法。而朴素贝叶斯中,却没有最优化的过程。对数似然函数直接指向模型最优化的方向,甚至就是逻辑回归的损失函数本身,因此在逻辑回归和SVC上表现得更好。

那什么时候使用对数似然,什么时候使用布里尔分数?
在现实应用中,对数似然函数是概率类模型评估的黄金指标,往往是我们评估概率类模型的优先选择。但是它也有一些缺点,首先它没有界,不像布里尔分数有上限,可以作为模型效果的参考。其次,它的解释性不如布里尔分数,很难与非技术人员去交流对数似然存在的可靠性和必要性。第三,它在以最优化为目标的模型上明显表现更好。而且,它还有一些数学上的问题,比如不能接受为0或1的概率,否则的话对数似然就会取到极限值(考虑以 为底的自然对数在取到0或1的时候的情况)。所以因此通常来说,我们有以下使用规则:
机器学习-Sklearn-14(朴素贝叶斯)_第36张图片
回到我们的贝叶斯来看,如果贝叶斯的模型效果不如其他模型,而我们又不想更换模型,那怎么办呢?如果以精确度为指标来调整参数,贝叶斯估计是无法拯救了——不同于SVC和逻辑回归,贝叶斯的原理简单,根本没有什么可用的参数。但是产出概率的算法有自己的调节方式,**就是调节概率的校准程度。**校准程度越高,模型对概率的预测越准确,算法在做判断时就越有自信,模型就会更稳定。如果我们追求模型在概率预测上必须尽量贴近真实概率,那我们就可以使用可靠性曲线来调节概率的校准程度。

2.2.3 可靠性曲线Reliability Curve

可靠性曲线(reliability curve),又叫做概率校准曲线(probability calibration curve),可靠性图(reliability diagrams),这是一条以预测概率为横坐标,真实标签为纵坐标的曲线。我们希望预测概率和真实值越接近越好,最好两者相等,因此一个模型/算法的概率校准曲线越靠近对角线越好。校准曲线因此也是我们的模型评估指标之一。和布里尔分数相似,概率校准曲线是对于标签的某一类来说的,因此一类标签就会有一条曲线,或者我们可以使用一个多类标签下的平均来表示一整个模型的概率校准曲线。但通常来说,曲线用于二分类的情况最多,大家如果感兴趣可以自行探索多分类的情况。

根据这个思路,我们来绘制一条曲线试试看。

#1. 导入需要的库和模块
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification as mc #自己创建数据集
from sklearn.naive_bayes import GaussianNB
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression as LR
from sklearn.metrics import brier_score_loss
from sklearn.model_selection import train_test_split
#2. 创建数据集
#自己创建数据集的目的是创建出符合要求的数据集。
#在显示中这样的数据可能非常难寻找。

#make_classification创建数据集的参数:
#参数n_samples表示样本量,这里有十万个。
#n_features=20表示有20个特征。
#n_classes=2表示标签为2分类。
#n_informative=2,表示20个特征中有两个特征是比较重要的。
#n_redundant=10,表示20个特征中有10个没有用的特征。
X, y = mc(n_samples=100000,n_features=20 #总共20个特征
         ,n_classes=2 #标签为2分类
         ,n_informative=2 #其中两个代表较多信息
         ,n_redundant=10 #10个都是冗余特征
         ,random_state=42)
X

机器学习-Sklearn-14(朴素贝叶斯)_第37张图片

X.shape

在这里插入图片描述

y.shape

在这里插入图片描述

np.unique(y) #二分类

在这里插入图片描述

#样本量足够大,因此使用1%的样本作为训练集,99%作为测试集。
#朴素贝叶斯模型哪怕是很少的训练集上也能表现很好。
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X, y
                                               ,test_size=0.99
                                               ,random_state=42)
Xtrain

机器学习-Sklearn-14(朴素贝叶斯)_第38张图片

Xtrain.shape

在这里插入图片描述

Ytrain

机器学习-Sklearn-14(朴素贝叶斯)_第39张图片

np.unique(Ytrain)

在这里插入图片描述

#3. 基于自己建立的数据集来建立模型,绘制图像
gnb = GaussianNB()
gnb.fit(Xtrain,Ytrain)
y_pred = gnb.predict(Xtest) #获得预测标签
prob_pos = gnb.predict_proba(Xtest)
prob_pos.shape
#我们的预测概率prob_pos - 横坐标
#Ytest - 我们的真实标签 - 纵坐标
#可靠性曲线中,横坐标是预测值,纵坐标是真实值。
#运行这些代码,那怕测试集有数据量很大,也能非常快的运行出来结果。

在这里插入图片描述

prob_pos

机器学习-Sklearn-14(朴素贝叶斯)_第40张图片

#因为是二分类,取出来一列即可。
prob_pos = gnb.predict_proba(Xtest)[:,1] 
prob_pos

在这里插入图片描述

#在我们的横纵表坐标上,概率是由顺序的(由小到大),因为横坐标是0到正无穷,为了让图形规整一些,我们要先对预测概率和真实标签按照预测概率进行一个排序,这一点我们通过DataFrame来实现
#现在我们的测试集有99000个样本,但是我们不绘制这么多的点。
#利用字典来创建DataFrame创建两列,一列真实值,一列预测概率值,且都取出前500个。DataFrame({"列的名称":[列的值]})
df = pd.DataFrame({"ytrue":Ytest[:500],"probability":prob_pos[:500]})
df

机器学习-Sklearn-14(朴素贝叶斯)_第41张图片

#排序
df = df.sort_values(by="probability")#by="probability"表示请按照probability这一列帮我排序
#恢复索引
df.index = range(df.shape[0])
df

机器学习-Sklearn-14(朴素贝叶斯)_第42张图片

#紧接着我们就可以画图了
fig = plt.figure() #画布
ax1 = plt.subplot() #创建一个子图
# clf_score = brier_score_loss(df["ytrue"], df["probability"], pos_label=1)
ax1.plot([0, 1], [0, 1], "k:", label="Perfectly calibrated") #得做一条对角线来对比呀
# ax1.plot(df["probability"],df["ytrue"],"s-",label="%s (%1.3f)" % ("Bayes", clf_score))
ax1.plot(df["probability"],df["ytrue"],"s-")
ax1.set_ylabel("True label")
ax1.set_xlabel("predcited probability")
ax1.set_ylim([-0.05, 1.05])
ax1.legend()
plt.show()

机器学习-Sklearn-14(朴素贝叶斯)_第43张图片

#这个图像看起来非常可怕,完全不止所云!
#为什么存在这么多上下穿梭的直线?
#反应快的小伙伴可能很快就发现了,我们是按照预测概率的顺序进行排序的,而预测概率从0开始到1的过程中,真实取值不断在0和1之间变化,而我们是绘制折线图,因此无数个纵坐标分布在0和1的被链接起来了,所以看起来如此混乱。

#那我们换成散点图来试试看呢?
fig = plt.figure()
ax1 = plt.subplot()
ax1.plot([0, 1], [0, 1], "k:", label="Perfectly calibrated")
ax1.scatter(df["probability"],df["ytrue"],s=10)
ax1.set_ylabel("True label")
ax1.set_xlabel("predcited probability")
ax1.set_ylim([-0.05, 1.05])
ax1.legend()
plt.show()

机器学习-Sklearn-14(朴素贝叶斯)_第44张图片
可以看到,由于真实标签是0和1,所以所有的点都在y=1和y=0这两条直线上分布,这完全不是我们希望看到的图像。
回想一下我们的可靠性曲线的横纵坐标:横坐标是预测概率,而纵坐标是真实值,我们希望预测概率很靠近真实值,那我们的真实取值必然也需要是一个概率才可以,如果使用真实标签,那我们绘制出来的图像完全是没有意义的。

但是,我们去哪里寻找真实值的概率呢?
这是不可能找到的——如果我们能够找到真实的概率,那我们何必还用算法来估计概率呢,直接去获取真实的概率不就好了么?
所以真实概率在现实中是不可获得的。
但是,我们可以获得类概率的指标来帮助我们进行校准。
一个简单的做法是,将数据进行分箱,然后规定每个箱子中真实的少数类所占的比例为这个箱上的真实概率trueproba,这个箱子中预测概率的均值为这个箱子的预测概率predproba,然后以trueproba为纵坐标,predproba为横坐标,来绘制我们的可靠性曲线。

举个例子,来看下面这张表,这是一组数据不分箱时表现出来的图像:
机器学习-Sklearn-14(朴素贝叶斯)_第45张图片
再来看看分箱之后的图像:
机器学习-Sklearn-14(朴素贝叶斯)_第46张图片
可见,分箱之后样本点的特征被聚合到了一起,曲线明显变得单调且平滑。这种分箱操作本质相当于是一种平滑,在sklearn中,这样的做法可以通过绘制可靠性曲线的类calibration_curve来实现。和ROC曲线类似,类calibration_curve可以帮助我们获取我们的横纵坐标,然后使用matplotlib来绘制图像。该类有如下参数:
机器学习-Sklearn-14(朴素贝叶斯)_第47张图片

#4. 使用可靠性曲线的类在贝叶斯上绘制一条校准曲线
#这里需要使用到calibration_curve
from sklearn.calibration import calibration_curve

#从类calibiration_curve中获取横坐标和纵坐标
#参数1:真实标签
#参数2:预测概率
#参数3:10个箱
trueproba, predproba = calibration_curve(Ytest, prob_pos
                                         ,n_bins=10 #输入希望分箱的个数
                                       )
trueproba.shape

在这里插入图片描述

predproba.shape

在这里插入图片描述

fig = plt.figure()
ax1 = plt.subplot()
ax1.plot([0, 1], [0, 1], "k:", label="Perfectly calibrated")
clf_score = brier_score_loss(df["ytrue"], df["probability"], pos_label=1)
ax1.plot(predproba, trueproba,"s-",label="%s (%1.3f)" % ("Bayes", clf_score))
ax1.set_ylabel("True probability for class 1")
ax1.set_xlabel("Mean predcited probability")
ax1.set_ylim([-0.05, 1.05])
ax1.legend()
plt.show()

机器学习-Sklearn-14(朴素贝叶斯)_第48张图片

#5. 不同的n_bins取值下曲线如何改变?
fig, axes = plt.subplots(1,3,figsize=(18,4))
for ind,i in enumerate([3,10,100]):
    ax = axes[ind]
    ax.plot([0, 1], [0, 1], "k:", label="Perfectly calibrated")
    trueproba, predproba = calibration_curve(Ytest, prob_pos,n_bins=i)
    ax.plot(predproba, trueproba,"s-",label="n_bins = {}".format(i))
    ax1.set_ylabel("True probability for class 1")
    ax1.set_xlabel("Mean predcited probability")
    ax1.set_ylim([-0.05, 1.05])
    ax.legend()
plt.show()

机器学习-Sklearn-14(朴素贝叶斯)_第49张图片

#很明显可以看出,n_bins越大,箱子越多,概率校准曲线就越精确,但是太过精确的曲线不够平滑,无法和我们希望的完美概率密度曲线相比较。
#n_bins越小,箱子越少,概率校准曲线就越粗糙,虽然靠近完美概率密度曲线,但是无法真实地展现模型概率预测地结果。
#因此我们需要取一个既不是太大,也不是太小的箱子个数,让概率校准曲线既不是太精确,也不是太粗糙,而是一条相对平滑,又可以反应出模型对概率预测的趋势的曲线。
#通常来说,建议先试试看箱子数等于10的情况。箱子的数目越大,所需要的样本量也越多,否则曲线就会太过精确。
#6. 建立更多模型
name = ["GaussianBayes","Logistic","SVC"]
gnb = GaussianNB()
logi = LR(C=1., solver='lbfgs',max_iter=3000,multi_class="auto")
svc = SVC(kernel = "linear",gamma=1) #返回置信度
#7. 建立循环,绘制多个模型的概率校准曲线
fig, ax1 = plt.subplots(figsize=(8,6))
ax1.plot([0, 1], [0, 1], "k:", label="Perfectly calibrated")

for clf, name_ in zip([gnb,logi,svc],name):
    clf.fit(Xtrain,Ytrain)
    y_pred = clf.predict(Xtest)
    
    #hasattr(obj,name):查看一个类obj中是否存在名字为name的接口,存在则返回True
    if hasattr(clf, "predict_proba"):
        prob_pos = clf.predict_proba(Xtest)[:,1]
    else:  # use decision function
        prob_pos = clf.decision_function(Xtest)
        prob_pos = (prob_pos - prob_pos.min()) / (prob_pos.max() - prob_pos.min())
    
    #返回布里尔分数
    clf_score = brier_score_loss(Ytest, prob_pos, pos_label=y.max())
    trueproba, predproba = calibration_curve(Ytest, prob_pos,n_bins=10)
    ax1.plot(predproba, trueproba,"s-",label="%s (%1.3f)" % (name_, clf_score))
    
ax1.set_ylabel("True probability for class 1")
ax1.set_xlabel("Mean predcited probability")
ax1.set_ylim([-0.05, 1.05])
ax1.legend()
ax1.set_title('Calibration plots (reliability curve)')
plt.show()

机器学习-Sklearn-14(朴素贝叶斯)_第50张图片
从图像的结果来看,我们可以明显看出,逻辑回归的概率估计是最接近完美的概率校准曲线,所以逻辑虎归的效果最完美。相对的,高斯朴素贝叶斯和支持向量机分类器的结果都比较糟糕。支持向量机呈现类似于sigmoid函数的形状,而高斯朴素贝叶斯呈现和Sigmoid函数相反的形状。

**对于贝叶斯,如果概率校准曲线呈现sigmoid函数的镜像的情况,则说明数据集中的特征不是相互条件独立的。**贝叶斯原理中的”朴素“原则:特征相互条件独立原则被违反了(这其实是我们自己的设定,我们设定了10个冗余特征,这些特征就是噪音,他们之间不可能完全独立),因此贝叶斯的表现不够好。

而支持向量机的概率校准曲线效果其实是典型的置信度不足的分类器(under-confident classifier)的表现:**大量的样本点集中在决策边界的附近,因此许多样本点的置信度靠近0.5左右,即便决策边界能够将样本点判断正确,模型本身对这个结果也不是非常确信的。**相对的,离决策边界很远的点的置信度就会很高,因为它很大可能性上不会被判断错误。支持向量机在面对混合度较高的数据的时候,有着天生的置信度不足的缺点。

你可能感兴趣的:(Sklearn,python,机器学习,sklearn)