朴素贝叶斯 - GaussianNB(二)

687 阅读17分钟

根据菜菜的课程进行整理,方便记忆理解

代码位置如下:

sklearn中的朴素贝叶斯

Sklearn基于这些分布以及这些分布上的概率估计的改进,为我们提供了四个朴素贝叶斯的分类器

含义
naive_bayes.BernoulliNB伯努利分布下的朴素贝叶斯
naive_bayes.GaussianNB高斯分布下的朴素贝叶斯
naive_bayes.MultinomialNB多项式分布下的朴素贝叶斯
naive_bayes.ComplementNB补集朴素贝叶斯
linear_model.BayesianRidge贝叶斯岭回归,在参数估计过程中使用贝叶斯回归技术来包括正则化参数

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

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

不同分布下的贝叶斯

高斯朴素贝叶斯GaussianNB

认识高斯朴素贝叶斯

class sklearn.naive_bayes.GaussianNB (priors=None, var_smoothing=1e-09)

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

image.png

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

这个类包含两个参数:

参数含义
prior可输入任何类数组结构,形状为(n_classes,)
表示类的先验概率。如果指定,则不根据数据调整先验,如果不指定,则自行根据数据计
算先验概率。
var_smoothing浮点数,可不填(默认值= 1e-9)
在估计方差时,为了追求估计的稳定性,将所有特征的方差中最大的方差以某个比例添加
到估计的方差中。这个比例,由var_smoothing参数控制。

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

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

  • 展示我所使用的设备以及各个库的版本
%load_ext watermark
# 用watermask这个便利的模块能够帮助我们一行代码查看设备和库的版本的模块

%watermark -a "kaikeba" -d -v -m -p numpy,pandas,matplotlib,scipy,sklearn
# 我们可以看到报告的名称是"tf_cpu20",可以看到我们的库的版本和我们的电脑的配置

"""
kaikeba 2022-04-25 

CPython 3.6.8
IPython 7.16.3

numpy 1.19.5
pandas 1.1.5
matplotlib 3.3.4
scipy 1.5.4
sklearn 0.0

compiler   : MSC v.1916 64 bit (AMD64)
system     : Windows
release    : 10
machine    : AMD64
processor  : Intel64 Family 6 Model 165 Stepping 5, GenuineIntel
CPU cores  : 16
interpreter: 64bit
"""
  • 导入需要的库和数据
# 导入所需要的库
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,y = digits.data,digits.target
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.3,random_state=420)
  • 建模,探索建模结果
# 构建模型,训练数据,预测数据
gnb = GaussianNB()
gnb = gnb.fit(Xtrain,Ytrain)
gnb.score(Xtest,Ytest)
# 0.8592592592592593

# 查看预测的分类
Y_pred = gnb.predict(Xtest)

Y_pred
"""
array([6, 1, 3, 0, 4, 5, 0, 8, 3, 8, 6, 8, 7, 8, 8, 8, 5, 9, 5, 6, 5, 4,
       7, 4, 8, 2, 7, 2, 8, 9, 2, 8, 3, 6, 0, 3, 8, 8, 1, 5, 2, 8, 8, 9,
       2, 2, 0, 7, 3, 6, 7, 2, 8, 0, 5, 4, 1, 9, 4, 0, 5, 8, 9, 1, 7, 8,
       7, 5, 8, 2, 4, 4, 8, 2, 6, 1, 2, 1, 7, 8, 8, 5, 9, 4, 3, 6, 9, 7,
       4, 2, 4, 8, 0, 5, 7, 7, 7, 4, 7, 8, 8, 7, 0, 7, 2, 1, 9, 9, 8, 7,
       1, 5, 1, 8, 0, 4, 8, 9, 5, 6, 4, 8, 3, 8, 0, 6, 8, 6, 7, 6, 1, 8,
       5, 0, 8, 2, 1, 8, 8, 6, 6, 0, 2, 4, 7, 8, 9, 5, 9, 4, 7, 8, 8, 6,
       7, 0, 8, 4, 7, 2, 2, 6, 4, 4, 1, 0, 3, 4, 3, 8, 7, 0, 6, 9, 7, 5,
       5, 3, 6, 1, 6, 6, 2, 3, 8, 2, 7, 3, 1, 1, 6, 8, 8, 8, 7, 7, 2, 5,
       0, 0, 8, 6, 6, 7, 6, 0, 7, 5, 5, 8, 4, 6, 5, 1, 5, 1, 9, 6, 8, 8,
       8, 2, 4, 8, 6, 5, 9, 9, 3, 1, 9, 1, 3, 3, 5, 5, 7, 7, 4, 0, 9, 0,
       9, 9, 6, 4, 3, 4, 8, 1, 0, 2, 9, 7, 6, 8, 8, 0, 6, 0, 1, 7, 1, 9,
       5, 4, 6, 8, 1, 5, 7, 7, 5, 1, 0, 0, 9, 3, 9, 1, 6, 3, 7, 2, 7, 1,
       9, 9, 8, 3, 3, 5, 7, 7, 7, 3, 9, 5, 0, 7, 5, 5, 1, 4, 9, 2, 0, 6,
       3, 0, 8, 7, 2, 8, 1, 6, 4, 1, 2, 5, 7, 1, 4, 9, 5, 4, 2, 3, 5, 9,
       8, 0, 0, 0, 0, 4, 2, 0, 6, 6, 8, 7, 1, 1, 8, 1, 1, 7, 8, 7, 8, 3,
       1, 4, 6, 1, 8, 1, 6, 6, 7, 2, 8, 5, 3, 2, 1, 8, 7, 8, 5, 1, 7, 2,
       1, 1, 7, 8, 9, 5, 0, 4, 7, 8, 8, 9, 5, 5, 8, 5, 5, 8, 1, 0, 4, 3,
       8, 2, 8, 5, 7, 6, 9, 9, 5, 8, 9, 9, 1, 8, 6, 4, 3, 3, 3, 3, 0, 8,
       0, 7, 7, 6, 0, 8, 9, 8, 3, 6, 6, 8, 7, 5, 8, 4, 5, 8, 6, 7, 6, 7,
       7, 8, 0, 8, 2, 2, 0, 5, 7, 3, 0, 2, 8, 2, 0, 2, 3, 6, 8, 1, 7, 5,
       7, 1, 7, 7, 2, 7, 5, 2, 6, 5, 8, 0, 0, 8, 1, 3, 7, 6, 1, 5, 6, 2,
       0, 1, 5, 7, 8, 0, 3, 5, 0, 7, 5, 4, 4, 1, 5, 9, 5, 3, 7, 1, 7, 3,
       5, 8, 5, 8, 5, 6, 1, 6, 7, 4, 3, 7, 0, 5, 4, 9, 3, 3, 6, 3, 5, 2,
       9, 8, 9, 3, 9, 7, 3, 4, 9, 4, 3, 1])
"""

# 查看预测分类的概率
prob = gnb.predict_proba(Xtest)
prob
"""
array([[0.00000000e+000, 4.69391744e-052, 1.74871280e-098, ...,
        0.00000000e+000, 4.19588993e-033, 1.51751459e-119],
       [0.00000000e+000, 1.00000000e+000, 9.26742456e-013, ...,
        0.00000000e+000, 0.00000000e+000, 0.00000000e+000],
       [0.00000000e+000, 0.00000000e+000, 3.73608152e-026, ...,
        0.00000000e+000, 1.29541754e-039, 5.54684869e-077],
       ...,
       [0.00000000e+000, 2.43314963e-047, 4.82483668e-305, ...,
        2.31612692e-008, 1.23891596e-126, 2.87896140e-257],
       [0.00000000e+000, 8.26462929e-129, 4.99150558e-012, ...,
        0.00000000e+000, 4.01802372e-003, 6.19000712e-013],
       [0.00000000e+000, 9.99929965e-001, 1.45462767e-013, ...,
        5.05856094e-005, 1.94498169e-005, 3.42317317e-042]])
"""
# 是个分类,540个样本,对应每个分类都有一个概率
prob.shape
# (540, 10)

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

prob.sum(axis=1)
"""
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
"""
  • 使用混淆矩阵来查看贝叶斯的分类结果
# 采用混淆矩阵对结果进行评判
from sklearn.metrics import confusion_matrix as CM
CM(Ytest,Y_pred)

"""
array([[47,  0,  0,  0,  0,  0,  0,  1,  0,  0],
       [ 0, 46,  2,  0,  0,  0,  0,  3,  6,  2],
       [ 0,  2, 35,  0,  0,  0,  1,  0, 16,  0],
       [ 0,  0,  1, 40,  0,  1,  0,  3,  4,  0],
       [ 0,  0,  1,  0, 39,  0,  1,  4,  0,  0],
       [ 0,  0,  0,  2,  0, 58,  1,  1,  1,  0],
       [ 0,  0,  1,  0,  0,  1, 49,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0, 54,  0,  0],
       [ 0,  3,  0,  1,  0,  0,  0,  2, 55,  0],
       [ 1,  1,  0,  1,  2,  0,  0,  3,  7, 41]], dtype=int64)
"""
探索贝叶斯:高斯朴素贝叶斯擅长的数据集

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

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn.naive_bayes import GaussianNB,MultinomialNB,ComplementNB,BernoulliNB
from sklearn.datasets import make_blobs, make_circles, make_classification,make_moons
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

h = 0.02
names = ["Multinomial", "Gaussion", "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)

rng = np.random.RandomState(2)
X += 2 * rng.uniform(size=X.shape)
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]

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=0.4,random_state=42)
    x1_min,x1_max = X[:,0].min()-0.5,X[:,0].max() + 0.5
    x2_min,x2_max = X[:,1].min()-0.5,X[:,1].max() + 0.5
    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(['#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)
    Z = clf.predict_proba(np.c_[array1.ravel(), array2.ravel()])[:, 1]
    Z = Z.reshape(array1.shape)
    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()

image.png

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

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

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

  • 首先导入需要的模块和库
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
  • 定义绘制学习曲线的函数
# 定义绘制学习曲线的函数
def plot_learning_curve(estimator,title, X, y, 
                        ax, #选择子图
                        ylim=None, #设置纵坐标的取值范围
                        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)
    if ylim is not None:
        ax.set_ylim(*ylim)
    ax.set_xlabel("Training examples")
    ax.set_ylabel("Score")
    ax.grid() #显示网格作为背景,不是必须
    ax.plot(train_sizes, np.mean(train_scores, axis=1), 'o-' , color="r",label="Training score")
    ax.plot(train_sizes, np.mean(test_scores, axis=1), 'o-', color="g",label="Test score")
    ax.legend(loc="best")
    return ax
  • 导入数据
digits = load_digits()
X, y = digits.data, digits.target
X.shape
# (1797, 64)

X #是一个稀疏矩阵
"""
array([[ 0.,  0.,  5., ...,  0.,  0.,  0.],
       [ 0.,  0.,  0., ..., 10.,  0.,  0.],
       [ 0.,  0.,  0., ..., 16.,  9.,  0.],
       ...,
       [ 0.,  0.,  1., ...,  6.,  0.,  0.],
       [ 0.,  0.,  2., ..., 12.,  0.,  0.],
       [ 0.,  0., 10., ..., 12.,  1.,  0.]])
"""
  • 定义循环
title = ["Naive Bayes","DecisionTree","SVM, RBF kernel","RandomForest","Logistic"] 
# 循环定义五个模型
model = [GaussianNB(),DTC(),SVC(gamma=0.001) ,RFC(n_estimators=50),LR(C=.1,solver="lbfgs")]
cv = ShuffleSplit(n_splits=50, test_size=0.2, random_state=0)
  • 进入循环,绘制学习曲线
fig, axes = plt.subplots(1,5,figsize=(30,6))
for ind,title_,estimator in zip(range(len(title)),title,model):
    times = time()
    plot_learning_curve(estimator, title_, X, y,ax=axes[ind], ylim = [0.7, 1.05],n_jobs=4, cv=cv)
    print("{}:{}".format(title_,datetime.datetime.fromtimestamp(time()-times).strftime("%M:%S:%f")))
plt.show()
"""
Naive Bayes:00:04:372710
DecisionTree:00:00:941039
SVM, RBF kernel:00:05:149549
RandomForest:00:05:906385
Logistic:00:23:170769
"""

image.png

三个模型表现出的状态非常有意思。

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

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

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

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

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