朴素贝叶斯 - 多项式朴素贝叶斯以及其变化(四)

490 阅读19分钟

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

代码位置如下:

多项式朴素贝叶斯以及其变化

多项式朴素贝叶斯MultinomialNB

多项式贝叶斯可能是除了高斯之外,最为人所知的贝叶斯算法了。它也是基于原始的贝叶斯理论,但假设概率分布是服从一个简单多项式分布。多项式分布来源于统计学中的多项式实验,这种实验可以具体解释为:实验包括n次重复试验,每项试验都有不同的可能结果。在任何给定的试验中,特定结果发生的概率是不变的。

举个例子,比如说一个特征矩阵X\pmb{X}表示投掷硬币的结果,则得到正面的概率为P(X=正面Y)P(X_{ = 正面}|Y) = 0.5,得到反面的概率为P(X=反面Y)P(X_{ = 反面}|Y) = 0.5,只有这两种可能并且两种结果互不干涉,并且两个随机事件的概率加和为1,这就是一个二项分布。这种情况下,适合于多项式朴素贝叶斯的特征矩阵应该长这样:

测试编号X1X_1:出现正面X2X_2:出现反面
001
110
210
301

假设另一个特征表示投掷骰子的结果,则就可以在[1,2,3,4,5,6]中取值,六种结果互不干涉,且只要样本量足够大,概率都为16\frac{1}{6},这就是一个多项分布。多项分布的特征矩阵应该长这样:

测试编号出现1出现2出现3出现4出现5出现6
0100000
1000001
2001000
……
m000001

可以看出:

  1. 多项式分布擅长的是分类型变量,在其原理假设中, P(xiY)P(x_i|Y)的概率是离散的,并且不同xix_i下的P(xiY)P(x_i|Y)相互独立,互不影响。虽然sklearn中的多项式分布也可以处理连续型变量,但现实中,如果我们真的想要处理连续型变量,我们应当使用高斯朴素贝叶斯。
  2. 多项式实验中的实验结果都很具体,它所涉及的特征往往是次数,频率,计数,出现与否这样的概念,这些概念都是离散的正整数,因此sklearn中的多项式朴素贝叶斯不接受负值的输入

由于这样的特性,多项式朴素贝叶斯的特征矩阵经常是稀疏矩阵(不一定总是稀疏矩阵),并且它经常被用于文本分类。我们可以使用著名的TF-IDF向量技术,也可以使用常见并且简单的单词计数向量手段与贝叶斯配合使用。这两种手段都属于常见的文本特征提取的方法,可以很简单地通过sklearn来实现,在案例中我们会来详细讲到。

从数学的角度来看,在一种标签类别Y=cY = c下,我们有一组分别对应特征的参数向量θc=(θc1,θc2,,θcn)\theta_c = (\theta_{c1},\theta_{c2},……,\theta_{cn}),其中n表示特征的总数。一个θci\theta_{ci}表示这个标签类别下的第i个特征所对应的参数。这个参数被我们定义为

image.png

记作P(XiY=c)P(\pmb{X}_i|Y = c),表示当Y = c这个条件固定的时候,一组样本在XiX_i这个特征上的取值被取到的概率。注意,我们在高斯朴素贝叶斯中求解的概率P(xiY)P(x_i|Y)是对于一个样本来说,而我们现在求解的P(XiY=c)P(\pmb{X}_i|Y = c)是对于一个特征Xi X_i来说的概率。

对于一个在标签类别Y=cY = c下,结构为(m, n)的特征矩阵来说,我们有:

image.png

其中每个xjix_{ji}都是特征XiX_i发生的次数。基于这些理解,我们通过平滑后的最大似然估计来求解参数θy\theta_y

image.png

对于每个特征,yj=cxji\sum_{y_j = c}x_{ji} 是特征XiX_i下所有标签为c的样本的特征取值之和,其实就是特征矩阵中每一列的和。i=1nyj=cxji\sum_{i = 1}^n\sum_{y_j = c}x_{ji}是所有标签类别为c的样本上,所有特征的取值之和,其实就是特征矩阵Xy\pmb{X_y}中所有元素的和。α\alpha被称为平滑系数,我们令α>0\alpha > 0来防止训练数据中出现过的一些词汇没有出现在测试集中导致的0概率,以避免让参数θ \theta为0的情况。如果我们将α\alpha设置为1,则这个平滑叫做拉普拉斯平滑,如果α\alpha小于1,则我们把它叫做利德斯通平滑。两种平滑都属于自然语言处理中比较常用的用来平滑分类数据的统计手段。

在sklearn中,用来执行多项式朴素贝叶斯的类MultinomialNB包含如下的参数和属性:

class sklearn.naive_bayes.MultinomialNB (alpha=1.0, fit_prior=True, class_prior=None)

参数含义
alpha浮点数, 可不填 (默认为1.0)
拉普拉斯或利德斯通平滑的参数α\alpha,如果设置为0则表示完全没有平滑选项。但是需要注意的是,平滑相当于人
为给概率加上一些噪音,因此α\alpha设置得越大,多项式朴素贝叶斯的精确性会越低(虽然影响不是非常大),布里
尔分数也会逐渐升高。
fit_prior布尔值, 可不填 (默认为True)
是否学习先验概率P(Y=c)P(Y = c)。如果设置为false,则不使用先验概率,而使用统一先验概率(uniform
prior),即认为每个标签类出现的概率是1nclasses\frac{1}{n_classes}
class_prior形似数组的结构,结构为(n_classes, ),可不填(默认为None)
类的先验概率P(Y=c)P(Y = c)。如果没有给出具体的先验概率则自动根据数据来进行计算

通常我们在实例化多项式朴素贝叶斯的时候,我们会让所有的参数保持默认。先来简单建一个多项式朴素贝叶斯的例子试试看:

  • 导入需要的模块和库
from sklearn.preprocessing import MinMaxScaler
from sklearn.naive_bayes import MultinomialNB
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_blobs
from sklearn.metrics import brier_score_loss
import numpy as np
  • 建立数据集
class_1 = 500 
class_2 = 500 #两个类别分别设定500个样本
centers = [[0.0, 0.0], [2.0, 2.0]] #设定两个类别的中心
clusters_std = [0.5, 0.5] #设定两个类别的方差
X, y = make_blobs(n_samples=[class_1, class_2],
                  centers=centers,
                  cluster_std=clusters_std,
                  random_state=0, shuffle=False)

X.shape
# (1000, 2)

np.unique(y)
# array([0, 1])

Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y
                                                ,test_size=0.3
                                                ,random_state=420)
  • 归一化,确保输入的矩阵不带有负数
mms = MinMaxScaler().fit(Xtrain) #训练集上来实例化和训练我们的模型
Xtrain_ = mms.transform(Xtrain)
Xtest_ = mms.transform(Xtest)
  • 建立一个多项式朴素贝叶斯分类器吧
mnb = MultinomialNB().fit(Xtrain_, Ytrain)

#重要属性:调用根据数据获取的,每个标签类的对数先验概率log(P(Y))
#由于概率永远是在[0,1]之间,因此对数先验概率返回的永远是负值
mnb.class_log_prior_
# array([-0.69029411, -0.69600841])

Ytrain == 1 #计数:所有标签类别=1的样本量
"""
array([ True, False, False, False,  True,  True, False,  True,  True,
        True,  True,  True, False,  True,  True,  True,  True,  True,
        True,  True,  True, False,  True,  True, False, False, False,
        True, False,  True, False,  True, False,  True, False,  True,
       False, False, False, False,  True,  True,  True, False, False,
        True,  True, False,  True, False, False, False, False, False,
        True,  True, False, False, False, False, False, False, False,
        True,  True, False, False,  True,  True,  True,  True, False])
"""

(Ytrain == 0).sum()/Ytrain.shape[0]
# 0.5014285714285714

mnb.class_log_prior_.shape #永远等于标签中所带的类别数量
# (2,)

#可以使用np.exp来查看真正的概率值
np.exp(mnb.class_log_prior_)
# array([0.50142857, 0.49857143])

#重要属性:返回一个固定标签类别下的每个特征的对数概率log(P(Xi|y))
mnb.feature_log_prob_
# array([[-0.76164788, -0.62903951],
#       [-0.72500918, -0.6622691 ]])

mnb.feature_log_prob_.shape #2个特征,2个标签
# (2, 2)

#重要属性:在fit时每个标签类别下包含的样本数
#当fit接口中的sample_weight被设置时,该接口返回的值也会受到加权的影响
mnb.class_count_
# array([351., 349.])

mnb.class_count_.shape #返回和我们的标签类别一样的结构
# (2,)
  • 那分类器的效果如何
#一些传统的接口
mnb.predict(Xtest_)
"""
array([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0,
       1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
       0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1,
       0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
       0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0,
       1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
"""

mnb.predict_proba(Xtest_) #每个样本在每个标签取值下的概率
"""
array([[0.49847128, 0.50152872],
       [0.50065987, 0.49934013],
       [0.50122363, 0.49877637],
       [0.50183745, 0.49816255],
       [0.50146433, 0.49853567],
       [0.50153147, 0.49846853],
       [0.50204549, 0.49795451],
       [0.50033124, 0.49966876],
       [0.50105254, 0.49894746],
       [0.50182815, 0.49817185]])
"""

mnb.score(Xtest_,Ytest)
# 0.5433333333333333

brier_score_loss(Ytest,mnb.predict_proba(Xtest_)[:,1],pos_label=1)
# 0.24977828412546027
  • 效果不太理想,思考一下多项式贝叶斯的性质,我们能够做点什么呢
#来试试看把Xtiain转换成分类型数据吧
#注意我们的Xtrain没有经过归一化,因为做哑变量之后自然所有的数据就不会又负数了
from sklearn.preprocessing import KBinsDiscretizer #对连续性变量进行分箱
kbs = KBinsDiscretizer(n_bins=10, encode='onehot').fit(Xtrain)

Xtrain_ = kbs.transform(Xtrain)
Xtest_ = kbs.transform(Xtest)
Xtrain_.shape #2个特征中,每个特征分了10个箱所分出来的哑变量

mnb = MultinomialNB().fit(Xtrain_, Ytrain)

mnb.score(Xtest_,Ytest)
# 0.9966666666666667

brier_score_loss(Ytest,mnb.predict_proba(Xtest_)[:,1],pos_label=1)
# 0.0014593932778211862

可以看出,多项式朴素贝叶斯的基本操作和代码都非常简单。同样的数据,如果采用哑变量方式的分箱处理,多项式贝叶斯的效果会突飞猛进。作为在文本分类中大放异彩的算法,我们将会在案例中来详细讲解多项式贝叶斯的使用,并为大家介绍文本分类的更多细节

伯努利朴素贝叶斯BernoulliNB

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

伯努利贝叶斯类BernoulliN假设数据服从多元伯努利分布,并在此基础上应用朴素贝叶斯的训练和分类过程。多元伯努利分布简单来说,就是数据集中可以存在多个特征,但每个特征都是二分类的,可以以布尔变量表示,也可以表示为{0,1}或者{-1,1}等任意二分类组合。因此,这个类要求将样本转换为二分类特征向量,如果数据本身不是二分类的,那可以使用类中专门用来二值化的参数binarize来改变数据

伯努利朴素贝叶斯与多项式朴素贝叶斯非常相似,都常用于处理文本分类数据。但由于伯努利朴素贝叶斯是处理二项分布,所以它更加在意的是“存在与否”,而不是“出现多少次”这样的次数或频率,这是伯努利贝叶斯与多项式贝叶斯的根本性不同。在文本分类的情况下,伯努利朴素贝叶斯可以使用单词出现向量(而不是单词计数向量)来训练分类器。文档较短的数据集上,伯努利朴素贝叶斯的效果会更加好。如果时间允许,建议两种模型都试试看。

来看看伯努利朴素贝叶斯类的参数:

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

参数含义
alpha浮点数, 可不填 (默认为1.0)
拉普拉斯或利德斯通平滑的参数α\alpha,如果设置为0则表示完全没有平滑选项。但是需要注意的是,平滑相当于人
为给概率加上一些噪音,因此α\alpha设置得越大,多项式朴素贝叶斯的精确性会越低(虽然影响不是非常大),布里
尔分数也会逐渐升高
binarize浮点数或None,可不填,默认为0
将特征二值化的阈值,如果设定为None,则会假定说特征已经被二值化完毕
fit_prior布尔值, 可不填 (默认为True)
是否学习先验概率P(Y=c)P(Y = c)。如果设置为false,则不使用先验概率,而使用统一先验概率(uniform
prior),即认为每个标签类出现的概率是1nclasses\frac{1}{n_classes}
class_prior形似数组的结构,结构为(n_classes, ),可不填(默认为None)
类的先验概率P(Y=c)P(Y = c)。如果没有给出具体的先验概率则自动根据数据来进行计算

在sklearn中,伯努利朴素贝叶斯的实现也非常简单

from sklearn.naive_bayes import BernoulliNB

#普通来说我们应该使用二值化的类sklearn.preprocessing.Binarizer来将特征一个个二值化
#然而这样效率过低,因此我们选择归一化之后直接设置一个阈值

mms = MinMaxScaler().fit(Xtrain)
Xtrain_ = mms.transform(Xtrain)
Xtest_ = mms.transform(Xtest)#不设置二值化


#不设置二值化
bnl_ = BernoulliNB().fit(Xtrain_, Ytrain)
bnl_.score(Xtest_,Ytest)
# 0.49666666666666665

brier_score_loss(Ytest,bnl_.predict_proba(Xtest_)[:,1],pos_label=1)
# 0.25000009482193225

#设置二值化阈值为0.5
bnl = BernoulliNB(binarize=0.5).fit(Xtrain_, Ytrain)
bnl.score(Xtest_,Ytest)
# 0.9833333333333333

brier_score_loss(Ytest,bnl.predict_proba(Xtest_)[:,1],pos_label=1)
# 0.010405875827339534

和多项式贝叶斯一样,伯努利贝叶斯的结果也受到数据结构非常大的影响。因此,根据数据的模样选择贝叶斯,是贝叶斯模型选择中十分重要的一点

探索贝叶斯:贝叶斯的样本不均衡问题

接下来,我们来探讨一个分类算法永远都逃不过的核心问题:样本不平衡。贝叶斯由于分类效力不算太好,因此对样本不平衡极为敏感,我们接下来就来看一看样本不平衡如何影响了贝叶斯

  • 导入需要的模块,建立样本不平衡的数据集
from sklearn.naive_bayes import MultinomialNB, GaussianNB, BernoulliNB
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_blobs 
from sklearn.preprocessing import KBinsDiscretizer
from sklearn.metrics import brier_score_loss as BS,recall_score,roc_auc_score as AUC
import numpy as np
  • 查看所有贝叶斯在样本不平衡数据集上的表现
class_1 = 50000 #多数类为50000个样本
class_2 = 500 #少数类为500个样本 1%
centers = [[0.0, 0.0], [5.0, 5.0]] #设定两个类别的中心
clusters_std = [3, 1] #设定两个类别的方差
X, y = make_blobs(n_samples=[class_1, class_2],
                  centers=centers,
                  cluster_std=clusters_std,
                  random_state=0, shuffle=False)

X.shape
# (50500, 2)

np.unique(y)
# array([0, 1])
  • 查看表现
name = ["Multinomial","Gaussian","Bernoulli"]
models = [MultinomialNB(),GaussianNB(),BernoulliNB()]

for name,clf in zip(name,models):
    #分测试集和训练集
    Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y
                                                ,test_size=0.3
                                                ,random_state=420)
    #预处理
    if name != "Gaussian":
        kbs = KBinsDiscretizer(n_bins=10, encode='onehot').fit(Xtrain)
        Xtrain = kbs.transform(Xtrain)
        Xtest = kbs.transform(Xtest)
    
    #拟合&结果
    clf.fit(Xtrain,Ytrain)
    y_pred = clf.predict(Xtest)
    proba = clf.predict_proba(Xtest)[:,1]
    score = clf.score(Xtest,Ytest) #准确率
    print(name)
    print("\tBrier:{:.3f}".format(BS(Ytest,proba,pos_label=1)))
    print("\tAccuracy:{:.3f}".format(score))
    print("\tRecall:{:.3f}".format(recall_score(Ytest,y_pred)))
    print("\tAUC:{:.3f}".format(AUC(Ytest,proba)))
    
"""
Multinomial
	Brier:0.007
	Accuracy:0.990
	Recall:0.000
	AUC:0.991
Gaussian
	Brier:0.006
	Accuracy:0.990
	Recall:0.438
	AUC:0.993
Bernoulli
	Brier:0.009
	Accuracy:0.987
	Recall:0.771
	AUC:0.987
"""

从结果上来看,多项式朴素贝叶斯判断出了所有的多数类样本,但放弃了全部的少数类样本,受到样本不均衡问题影响最严重。高斯比多项式在少数类的判断上更加成功一些,至少得到了43.8%的recall。伯努利贝叶斯虽然整体的准确度和布里尔分数不如多项式和高斯朴素贝叶斯和,但至少成功捕捉出了77.1%的少数类。可见,伯努利贝叶斯最能够忍受样本不均衡问题。

可是,伯努利贝叶斯只能用于处理二项分布数据,在现实中,强行将所有的数据都二值化不会永远得到好结果,在我们有多个特征的时候,我们更需要一个个去判断究竟二值化的阈值该取多少才能够让算法的效果优秀。这样做无疑是非常低效的。那如果我们的目标是捕捉少数类,我们应该怎么办呢?高斯朴素贝叶斯的效果虽然比多项式好,但是也没有好到可以用来帮助我们捕捉少数类的程度——43.8%,还不如抛硬币的结果。因此,孜孜不倦的统计学家们改进了朴素贝叶斯算法,修正了包括无法处理样本不平衡在内的传统朴素贝叶斯的众多缺点,得到了新兴贝叶斯算法:补集朴素贝叶斯

改进多项式朴素贝叶斯:补集朴素贝叶斯ComplementNB

补集朴素贝叶斯(complement naive Bayes,CNB)算法是标准多项式朴素贝叶斯算法的改进。CNB的发明小组创造出CNB的初衷是为了解决贝叶斯中的“朴素”假设带来的各种问题,他们希望能够创造出数学方法以逃避朴素贝叶斯中的朴素假设,让算法能够不去关心所有特征之间是否是条件独立的。以此为基础,他们创造出了能够解决样本不平衡问题,并且能够一定程度上忽略朴素假设的补集朴素贝叶斯。在实验中,CNB的参数估计已经被证明比普通多项式朴素贝叶斯更稳定,并且它特别适合于样本不平衡的数据集。有时候,CNB在文本分类任务上的表现有时能够优于多项式朴素贝叶斯,因此现在补集朴素贝叶斯也开始逐渐流行。

简单来说,CNB使用来自每个标签类别的补集的概率,并以此来计算每个特征的权重

image.png

其中j表示每个样本, xijx_{ij}表示在样本j上对于特征i的下的取值,在文本分类中通常是计数的值或者是TF-IDF值。α\alpha是像标准多项式朴素贝叶斯中一样的平滑系数。可以看出,这个看似复杂的公式其实很简单,yjcxij\sum_{y_j \neq c}x_{ij} 其实指的就是,一个特征i下,所有标签类别不等于c值的样本的特征取值之和。而i,yci=1nxij\sum_{i,y \neq c}\sum_{i = 1}^nx_{ij}其实就是,所有特征下,素有标签类别不等于c值得样本的特征取值之和。其实就是多项式分布的逆向思路。

image.png

对于这个概率,我们对它取对数后得到权重。我们还可以选择除以它的L2范式,以解决了在多项式分布中,特征取值比较多的样本(比如说比较长的文档)支配参数估计的情况。很多时候我们的特征矩阵是稀疏矩阵,但也不排除在有一些随机事件中,可以一次在两个特征中取值的情况。如果一个样本下的很多个随机事件可以同时发生,并且互不干涉,那么这个样本上可能有很多个特征下都有取值——文本分类中就有很多这样的情况。比如说我们可以有如下特征矩阵:

索引X1X2
011
101

这种状况下,索引为0的样本就会在参数估计中占更多的权重。

更甚,如果一个样本下的很多个随机事件同时发生,还在一次实验中发生了多次,那这个样本在参数估计中也会占有更大的权重。

索引X1X2
051
101

基于这个权重,补充朴素贝叶斯中一个样本的预测规则为:

image.png

即我们求解出的最小补集概率所对应的标签就是样本的标签,因为YcY \neq c的概率越小,则意味着Y=cY = c的概率越大,所以样本属于标签类别c。

在sklearn中,补集朴素贝叶斯由类ComplementNB完成,它包含的参数和多项式贝叶斯也非常相似:

class sklearn.naive_bayes.ComplementNB (alpha=1.0, fit_prior=True, class_prior=None, norm=False)

参数含义
alpha浮点数, 可不填 (默认为1.0)
拉普拉斯或利德斯通平滑的参数α\alpha,如果设置为0则表示完全没有平滑选项。但是需要注意的是,平滑相当于人
为给概率加上一些噪音,因此α\alpha设置得越大,多项式朴素贝叶斯的精确性会越低(虽然影响不是非常大),布里
尔分数也会逐渐升高
norm布尔值,可不填,默认False
在计算权重的时候是否适用L2范式来规范权重的大小。默认不进行规范,即不跟从补集朴素贝叶斯算法的全部
内容,如果希望进行规范,请设置为True
fit_prior布尔值, 可不填 (默认为True)
是否学习先验概率P(Y=c)P(Y = c)。如果设置为false,则不使用先验概率,而使用统一先验概率(uniform
prior),即认为每个标签类出现的概率是1nclasses\frac{1}{n_classes}
class_prior形似数组的结构,结构为(n_classes, ),可不填(默认为None)
类的先验概率P(Y=c)P(Y = c)。如果没有给出具体的先验概率则自动根据数据来进行计算

那来看看,补集朴素贝叶斯在不平衡样本上的表现吧,同时我们来计算一下每种贝叶斯的计算速度:

from sklearn.naive_bayes import ComplementNB
from time import time
import datetime

name = ["Multinomial","Gaussian","Bernoulli","Complement"]
models = [MultinomialNB(),GaussianNB(),BernoulliNB(),ComplementNB()]

for name,clf in zip(name,models):
    times = time()
    Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y
                                                ,test_size=0.3
                                                ,random_state=420)
    #预处理
    if name != "Gaussian":
        kbs = KBinsDiscretizer(n_bins=10, encode='onehot').fit(Xtrain)
        Xtrain = kbs.transform(Xtrain)
        Xtest = kbs.transform(Xtest)
    
    clf.fit(Xtrain,Ytrain)
    y_pred = clf.predict(Xtest)
    proba = clf.predict_proba(Xtest)[:,1]
    score = clf.score(Xtest,Ytest)
    print(name)
    print("\tBrier:{:.3f}".format(BS(Ytest,proba,pos_label=1)))
    print("\tAccuracy:{:.3f}".format(score))
    print("\tRecall:{:.3f}".format(recall_score(Ytest,y_pred)))
    print("\tAUC:{:.3f}".format(AUC(Ytest,proba)))
    print(datetime.datetime.fromtimestamp(time()-times).strftime("%M:%S:%f"))
    
"""
Multinomial
	Brier:0.007
	Accuracy:0.990
	Recall:0.000
	AUC:0.991
00:00:028000
Gaussian
	Brier:0.006
	Accuracy:0.990
	Recall:0.438
	AUC:0.993
00:00:015001
Bernoulli
	Brier:0.009
	Accuracy:0.987
	Recall:0.771
	AUC:0.987
00:00:028998
Complement
	Brier:0.038
	Accuracy:0.953
	Recall:0.987
	AUC:0.991
00:00:028002
"""

可以发现,补集朴素贝叶斯牺牲了部分整体的精确度和布里尔指数,但是得到了十分高的召回率Recall,捕捉出了98.7%的少数类,并且在此基础上维持了和原本的多项式朴素贝叶斯一致的AUC分数。和其他的贝叶斯算法比起来,我们的补集朴素贝叶斯的运行速度也十分优秀。如果我们的目标是捕捉少数类,那我们毫无疑问会希望选择补集朴素贝叶斯作为我们的算法