龙空技术网

高斯朴素贝叶斯教程与Python实现

人工智能遇见磐创 517

前言:

现时小伙伴们对“pythonnaivebayes”都比较着重,朋友们都想要剖析一些“pythonnaivebayes”的相关内容。那么小编在网上收集了一些有关“pythonnaivebayes””的相关文章,希望我们能喜欢,你们一起来了解一下吧!

这是每个数据科学职业生涯开始时的经典:朴素贝叶斯分类器。

或者更应该说是朴素贝叶斯分类器家族,因为它们有很多种风格例如,有多项式朴素贝叶斯,伯努利朴素贝叶斯,还有高斯朴素贝叶斯分类器,每个分类器在一个小细节上都不同,我们会发现:朴素贝叶斯算法在设计上非常简单,但在许多复杂的实际情况下被证明是有用的。

在本文中,你可以学习:

朴素贝叶斯分类器的工作原理,为什么用他们是有意义的如何使用NumPy在Python中实现它们。

你可以在我的Github上找到代码: 。

我们将开始探索朴素贝叶斯分类的理论,然后转向实现。

理论

分类时我们真正感兴趣的是什么?我们到底在做什么,输入和输出是什么?答案很简单:

给定一个数据点x,x属于某类c的概率是多少?

这就是本文要回答的。你可以直接将此语句建模为条件概率:p(c|x)。

例如,如果有:

3个类:c₁、c₃、c₃x由2个特征x₁,x₁组成

分类器的结果可能类似于p(c₁|x₁, x₂)=0.3,p(c₂|x₁, x₂)=0.5和p(c₃|x₁, x₂)=0.2。如果我们想要一个标签作为输出,我们会选择一个概率最高的标签,即这里概率为50%的c2。

朴素贝叶斯分类器试图直接计算这些概率。

朴素贝叶斯

给定一个数据点x,我们要计算所有类c的p(c|x),然后以最大概率输出c。在公式中,你经常把它看作:

注意:max p(c|x)返回最大概率,而argmax p(c|x) 返回具有此最大概率的c。

但在优化p(c|x)之前,我们必须先计算它。为此,我们使用贝叶斯定理:

这是朴素贝叶斯的贝叶斯部分。但是现在,我们有以下问题:什么是 p(x|c)和p(c)?

这就是训练朴素贝叶斯分类器的意义所在。

训练

为了说明一切,让我们使用一个玩具数据集,其中包含两个真正的特征x₁,x₁,以及下面的三个类c₁,c₃,c₃。

你可以通过以下代码创建:

from sklearn.datasets import make_blobsX, y = make_blobs(n_samples=20, centers=[(0,0), (5,5), (-5, 5)], random_state=0)

让我们从类概率p(c)开始,即在标记的数据集中观察到某些类c的概率。估计这一点的最简单方法就是计算类的频率,并将它们用作概率。我们可以使用我们的数据集来了解这到底意味着什么。

在数据集中,20个点中有7个标记为c₁类(蓝色),因此我们称 p(c₁)=7/20。类(红色)也有7个点,因此我们将p(c₂)=7/20。最后一类c₃(黄色)只有6个点,因此p(c₃)=6/20。

这类概率的简单计算类似于最大似然法。但是,如果愿意,也可以使用另一个先验分布。例如,如果你知道此数据集不代表真实总体,因为类c₃应该出现在50%的情况下,那么你可以设置p(c₁)=0.25,p(c₂)=0.25和p(c₃)=0.5。或者任何有助于提高测试集性能的东西。

现在我们来讨论可能性p(x|c)=p(x₁, x₂|c)。计算这种可能性的一种方法是过滤数据集中带有标签c的样本,然后尝试找到一个能够捕获特征x₁,x₁的分布(例如二维高斯分布)。

可惜的是,通常情况下,每个类没有足够的样本来正确估计可能性。

为了能够建立一个更健壮的模型,我们朴素地假设,给定c,特征x₁,x2是随机独立的。

这就是朴素贝叶斯的朴素部分的来源,因为这个等式一般不成立。不过,即便如此,朴素的贝叶斯在实践中也会产生好的、有时甚至是出色的结果。特别是对于具有词袋特征的NLP问题,多项式朴素贝叶斯方法更为有效。

上面给出的参数对于你可以找到的任何朴素贝叶斯分类器都是相同的。现在它只取决于如何建模 p(x₁|c₁), p(x₂|c₁), p(x₁|c₂), p(x₂|c₂), p(x₁|c₃) 和p(x₂|c₃)。

如果特征仅为0和1,则可以使用伯努利分布。如果它们是整数,则为多项式分布。然而,我们有真实的特征值,并决定为高斯分布,因此得名高斯朴素贝叶斯。我们采用以下形式:

其中μᵢ,ⱼ是平均值,σ₂,₁是我们必须根据数据估计的标准差。这意味着我们得到一个平均值,每个特征i加上一个类cⱼ,在我们的例子中2*3=6平均值。标准差也是如此。这就需要一个例子。

我们试着估算μ₂,₁和σ₂,₁。因为j=1,我们只对c₁类感兴趣,所以只保留有这个标签的样品。保留以下样本:

# label = c_1的样本array([[ 0.14404357,  1.45427351],       [ 0.97873798,  2.2408932 ],       [ 1.86755799, -0.97727788],       [ 1.76405235,  0.40015721],       [ 0.76103773,  0.12167502],       [-0.10321885,  0.4105985 ],       [ 0.95008842, -0.15135721]])

现在,因为i=2,我们只需要考虑第二列。 μ₂,₁是此列的平均值,σ₂,₁是标准差,即μ₂,₁=0.49985176和σ₂,₁=0.9789976。

如果你再从上面看散点图,这些数字是有意义的。从图中可以看出,c₁类样品的特征 x₂约为0.5。

我们现在为其他五个组合计算这个就完成了!

在Python中,可以这样做:

from sklearn.datasets import make_blobsimport numpy as np# 创建数据。这些类是c_1=0, c_2=1 and c_3=2.X, y = make_blobs(n_samples=20, centers=[(0,0), (5,5), (-5, 5)], random_state=0)# 类概率.# np.bin统计每个标签的出现次数.prior = np.bincount(y) / len(y)# np.where(y==i) 返回所有索引,其中y==i.# 这是过滤步骤.means = np.array([X[np.where(y==i)].mean(axis=0) for i in range(3)])stds = np.array([X[np.where(y==i)].std(axis=0) for i in range(3)])

我们得到:

# 先验array([0.35, 0.35, 0.3 ])# 均值 array([[ 0.90889988,  0.49985176],       [ 5.4111385 ,  4.6491892 ],       [-4.7841679 ,  5.15385848]])       # 标准差array([[0.6853714 , 0.9789976 ],       [1.40218915, 0.67078568],       [0.88192625, 1.12879666]])

这是高斯朴素贝叶斯分类器训练的结果。

预测

完整的预测公式是:

假设有一个新的数据点x*=(-2,5)。

为了确定它属于哪个类,我们计算所有类的p(c|x*)。从图片上看,它应该属于c₃=2类,但我们暂时忽略分母p(x),使用下面的循环计算结果。

x_new = np.array([-2, 5])for j in range(3):    print(f'Probability for class {j}: {(1/np.sqrt(2*np.pi*stds[j]**2)*np.exp(-0.5*((x_new-means[j])/stds[j])**2)).prod()*p[j]:.12f}')

我们收到:

Probability for class 0: 0.000000000263Probability for class 1: 0.000000044359Probability for class 2: 0.000325643718

当然,这些概率(我们不应该这样称呼它们)加起来不是一,因为我们忽略了分母。然而这不是问题,因为我们可以把这些非标准化的概率除以它们的和,那么它们加起来就是一。所以,把这三个值除以它们的和,大约0.00032569,我们得到:

是准确的。现在我们实现它!

实现

这种实现方式迄今为止效率不高,在数量上也不稳定,它只服务于教育目的。我们已经讨论了大部分事情,所以现在应该很容易理解了。

你可以忽略所有的检查函数,或者阅读我的文章构建你自己的定制scikit-learn。

请注意,我首先实现了一个predict_proba方法来计算概率。predict方法只调用这个方法,并使用argmax函数返回概率最大的索引。

import numpy as npfrom sklearn.base import BaseEstimator, ClassifierMixinfrom sklearn.utils.validation import check_X_y, check_array, check_is_fittedclass GaussianNaiveBayesClassifier(BaseEstimator, ClassifierMixin):    def fit(self, X, y):        X, y = check_X_y(X, y)        self.priors_ = np.bincount(y) / len(y)        self.n_classes_ = np.max(y) + 1                self.means_ = np.array([X[np.where(y==i)].mean(axis=0) for i in range(self.n_classes_)])        self.stds_ = np.array([X[np.where(y==i)].std(axis=0) for i in range(self.n_classes_)])                        return self        def predict_proba(self, X):        check_is_fitted(self)        X = check_array(X)                res = []        for i in range(len(X)):            probas = []            for j in range(self.n_classes_):                            probas.append((1/np.sqrt(2*np.pi*self.stds_[j]**2)*np.exp(-0.5*((X[i]-self.means_[j])/self.stds_[j])**2)).prod()*self.priors_[j])            probas = np.array(probas)            res.append(probas / probas.sum())                            return np.array(res)        def predict(self, X):        check_is_fitted(self)        X = check_array(X)                res = self.predict_proba(X)                return res.argmax(axis=1)
测试实现

代码有点长,无法完全确定我们没有犯任何错误。所以,我们比较一下它与scikit-learn GaussianNB分类器的差异。

my_gauss = GaussianNaiveBayesClassifier()my_gauss.fit(X, y)my_gauss.predict_proba([[-2, 5], [0,0], [6, -0.3]])

输出:

array([[8.06313823e-07, 1.36201957e-04, 9.99862992e-01],       [1.00000000e+00, 4.23258691e-14, 1.92051255e-11],       [4.30879705e-01, 5.69120295e-01, 9.66618838e-27]])

使用预测方法进行的预测如下:

# my_gauss.predict([[-2, 5], [0,0], [6, -0.3]])array([2, 0, 1])

现在,让我们使用scikit-learn。输入一些代码:

from sklearn.naive_bayes import GaussianNBgnb = GaussianNB()gnb.fit(X, y)gnb.predict_proba([[-2, 5], [0,0], [6, -0.3]])

输出:

array([[8.06314158e-07, 1.36201959e-04, 9.99862992e-01],       [1.00000000e+00, 4.23259111e-14, 1.92051343e-11],       [4.30879698e-01, 5.69120302e-01, 9.66619630e-27]])

这些数字看起来有点像我们的分类器,但是在最后几个显示的数字中它们有点不一致。我们做错什么了吗?没有。scikit-learn仅仅使用了另一个超参数var_smoothing=1e-09。如果我们把这个设为零,我们就能得到准确的数字。很完美!

看看分类器的决策区域。我标记了用于测试的三个点。这一点接近边界只有56.9%的机会属于红色类,你可以看到predict_proba输出。另外两点分类的可信度要高得多。

结论

在这篇文章中,我们学习了高斯朴素贝叶斯分类器是如何工作的,并给出了它为什么被设计成这样,它是一种直接的建模概率的方法。将其与Logistic回归进行比较:在Logistic回归,概率是使用线性函数建模的,并在其上应用sigmoid函数。它仍然是一个简单的模型,但感觉不像朴素的贝叶斯分类器那么自然。

我们继续计算了一些例子,并收集了一些有用的代码。最后,我们已经实现了一个完整的高斯朴素贝叶斯分类器的方式,以及与sciket-learn版本。

最后,我们通过导入sciket-learns自己的Gaussian-naivebayes分类器并测试我们的分类器和sciket-learns的分类器是否产生相同的结果,进行了一个小的健全性检查。这次测试成功了。

感谢阅读!

标签: #pythonnaivebayes