高斯朴素贝叶斯(Gaussian Naive Bayes),高斯朴素贝叶斯算法假设所有特征都具有高斯分布(正态/钟形曲线)。这适用于连续数据,例如日温度,高度。
高斯分布中68%的数据在均值的一个标准差范围内,96%在均值的两个标准差范围内。非正态分布的数据在高斯朴素贝叶斯分类器中使用精度较低,可以使用分布不同的朴素贝叶斯分类器。
让我们用Python实现一个高斯朴素贝叶斯分类器。
我终于找到时间做一些机器学习。这是我一直想要开始实践的东西,因为很明显,这是复杂问题解决的未来。事实上,对于某些任务,我们没有一个可以编写和执行的算法,所以我们从数据中进行弥补。
机器学习使用统计理论来建立数学模型。- Ethem Alpaydin
ML试图解决的一个典型问题是分类。它可以表示为,在给定一些输入数据的情况下,为一个示例分配一个“类标签”的能力。
为了让事情更清楚,让我们举个例子。假设我们对对象的样本进行分析并收集它们的规格。现在,根据这些信息,我们想知道该对象是否是窗户玻璃(来自车辆或建筑)或不是窗户玻璃(容器、餐具或大灯)。不幸的是,我们没有一个公式,给出这些值,就能给出答案。
一个戴过眼镜的人可能仅仅通过看或摸它就能知道那是不是玻璃。这是因为他通过观察许多不同种类眼镜的例子获得了经验。这正是机器学习所发生的。我们说我们“训练”算法从已知的例子中学习。
我们提供了一个“训练集”,其中我们指定了类及其类别的输入规范。该算法通过实例,学习窗口玻璃的特征,从而推断出给定未分类实例的类别。
我们将使用一个名为“玻璃鉴定数据库”的数据集,由德国中央研究机构内政部法医科学服务中心的B. German创建。原始数据集将玻璃分为7类:4类窗口玻璃类和3类非窗口玻璃类。我们的版本将所有4种类型的窗口玻璃类都视为一个类,而所有3种类型的非窗口玻璃类也都视为一个类。
属性和类信息:
每一行都是一个例子,包含11个属性,如下所示。
1.Example number
2.RI: refractive index
3.Na: Sodium (unit measurement: weight percent in corresponding oxide, as are attributes 4-10)
4.Mg: Magnesium
5.Al: Aluminum
6.Si: Silicon
7.K: Potassium
8.Ca: Calcium
9.Ba: Barium
10.Fe: Iron
11.Type of glass: (class) – 1 window glass (from vehicle or building) – 2 not window glass (containers, tableware, or headlamps)
下面是数据集的摘录:
1,1.51824,12.87,3.48,1.29,72.95,0.6,8.43,0,0,1
2,1.51832,13.33,3.34,1.54,72.14,0.56,8.99,0,0,1
3,1.51747,12.84,3.5,1.14,73.27,0.56,8.55,0,0,1
...
196,1.52315,13.44,3.34,1.23,72.38,0.6,8.83,0,0,2
197,1.51848,13.64,3.87,1.27,71.96,0.54,8.32,0,0.32,1
198,1.523,13.31,3.58,0.82,71.99,0.12,10.17,0,0.03,1
199,1.51905,13.6,3.62,1.11,72.64,0.14,8.76,0,0,1
200,1.52213,14.21,3.82,0.47,71.77,0.11,9.57,0,0,1
朴素贝叶斯分类器
解决分类问题最简单但有效的算法之一是朴素贝叶斯算法。它是一种基于贝叶斯定理的概率方法,具有输入属性之间的朴素独立性假设。
我们将 C 定义为要分析的类,将 x 定义为输入数据或观测值。以下方程(贝叶斯定理)是在给定观测值 x时 C 类的概率。这等于 C 类概率(不考虑输入值)乘以给定 C 类的概率比该观测值的概率。
P(C)也被称为“先验概率”,因为它是我们在观察可观测的x之前就知道C的值的知识。我们还知道P(C = 0) + P(C = 1) = 1。
P(x | C)称为类似然,即属于C的事件具有相关观测值x的概率。在统计推理中,我们利用样本提供的信息进行决策。在本例中,我们假设样本来自某个服从已知模型的分布,例如高斯分布。这个任务的一部分是生成描述数据的高斯函数,因此我们可以使用概率密度函数来计算给定属性的概率。如前所述,每个属性都将被视为独立于其他属性的。
最后,P(x),也称为证据,是观察到x的概率,不管例子中的C类是什么。
P(C|x) = \frac{P(C)⋅P(x|C)}{P(x)}
上式为“后验概率”,即C类在观察到x后的概率。
在这一点上,给定几个类的后验概率,我们就可以决定哪一个是最有可能的。有趣的是,所有类的分母都是相同的,所以我们可以通过比较贝叶斯定理的分子来简化计算。
读取数据
首先,我们要读取数据集,以便能够对其执行分析。它是一个CSV文件,所以我们可以使用CSV Python库,但我个人更喜欢使用一些更强大的东西,比如panda。
panda是一个开源库,为Python编程语言提供高性能、易于使用的数据结构和数据分析工具。
panda.read_csv将把CVS文件读入一个DataFrame,这是一个带有标记轴的二维表格数据结构。这样,我们的数据集将非常容易操作。我还决定给我的专栏加上标签,这样一切都会更清晰。
ATTR_NAMES = ["RI", "Na", "Mg", "Al", "Si", "K", "Ca", "Ba", "Fe"]
FIELD_NAMES = ["Num"] + ATTR_NAMES + ["Class"]
data = pandas.read_csv(args.filename, names=FIELD_NAMES)
现在我们在内存中有了数据集,我们想把它分成两部分:训练集和测试集。前者将用于训练ML模型,而后者用于检查模型的准确性。
以下代码将拆分数据将数据划分为块(基于blocks_num 的数量),并选择作为测试集的位置 test_block 块,该块也将从训练集中删除。如果除了数据集之外没有提供任何内容,则函数将只对训练和测试集使用相同的数据。
def split_data(data, blocks_num=1, test_block=0):
blocks = numpy.array_split(data, blocks_num)
test_set = blocks[test_block]
if blocks_num > 1:
del blocks[test_block]
training_set = pandas.concat(blocks)
return training_set, test_set
Prior
估计给定训练样本的P(C)相当简单。先验概率基于以前的经验,在本例中,是类在数据集中所占的百分比。
我们要计算每个类的频率,并通过除以例子的数量得到比率。这样做的代码非常简洁,也因为panda库使得频率的计算变得微不足道。
def __prior(self):
counts = self.__training_set["Class"].value_counts().to_dict()
self.__priors = {(k, v / self.__n) for k, v in counts.items()}
均值和方差
要计算“pdf”(概率密度函数),我们需要知道描述数据的分布情况。为此,我们需要计算每个类的每个属性的均值和方差(或最终的标准差)。由于我们的数据集中有9个属性和2个类,我们最终将有18对均值-方差对。
同样,对于这个任务,我们可以使用panda提供的helper函数,在这里我们选择感兴趣的列并调用它们的mean()和std()方法。
def __calculate_mean_variance(self):
self.__mean_variance = {}
for c in self.__training_set["Class"].unique():
filtered_set = self.__training_set[
(self.__training_set['Class'] == c)]
m_v = {}
for attr_name in ATTR_NAMES:
m_v[attr_name] = []
m_v[attr_name].append(filtered_set[attr_name].mean())
m_v[attr_name].append(
math.pow(filtered_set[attr_name].std(), 2))
self.__mean_variance[c] = m_v
高斯概率密度函数
计算“pdf”的函数只是一个静态方法,它将属性值和高斯分布(均值和方差)的描述作为输入,并根据“pdf”方程返回一个概率。
@staticmethod
def __calculate_probability(x, mean, variance):
exponent = math.exp(-(math.pow(x - mean, 2) / (2 * variance)))
return (1 / (math.sqrt(2 * math.pi * variance))) * exponent
预测
现在我们已经准备好了一切,是时候预测我们的课程了。
基本上,下面要做的是,遍历测试集,对每个样本使用贝叶斯定理计算每个类的概率。这里唯一的不同之处在于,我们使用log概率,因为每个给定属性值的类的概率都很小,它们可能会下溢。
所以它变成:\log[p(x|C)*p(C)] = \log p(C) + \sum_{n=1}^9 \log p(x_i|C)
def predict(self):
predictions = {}
for _, row in self.__test_set.iterrows():
results = {}
for k, v in self.__priors:
p = 0
for attr_name in ATTR_NAMES:
prob = self.__calculate_probability(row[attr_name], self.__mean_variance[
k][attr_name][0], self.__mean_variance[k][attr_name][1])
if prob > 0:
p += math.log(prob)
results[k] = math.log(v) + p
predictions[int(row["Num"])] = max([key for key in results.keys() if results[
key] == results[max(results, key=results.get)]])
return predictions
因此,我们需要将概率最高的类作为预测。如果两个或两个以上的类最终具有相同的概率,那么我们决定选择较早出现的类(按字母顺序倒序排列),但是对于给定的数据集,实际上并不需要这样做。
精度
一旦我们获得了预测,我们就可以将它们与测试数据集中的类值进行比较,这样我们就可以计算出正确的预测值与预测总数的比值。这种度量也称为精确度,它允许估计所使用的ML模型的质量。
def calculate_accuracy(test_set, predictions):
correct = 0
for _, t in test_set.iterrows():
if t["Class"] == predictions[t["Num"]]:
correct += 1
return (correct / len(test_set)) * 100.0
在我们的测试中,我们使用相同的数据集进行训练和测试,获得了90%的准确率。
交叉验证
现在我们知道了如何执行预测,让我们再次查看数据。训练算法然后在相同的数据上进行测试真的有意义吗?可能不会。我们想要两个不同的集合,但是当你没有足够的数据时,这并不总是可能的。
我们的示例数据集包含200条记录,理想情况下,我们希望尽可能地压缩它,并对所有200个样本执行测试,但是这样我们就没有任何东西可用来训练模型了。
ML人员这样做的方法称为交叉验证。数据集被划分为块(如前所示),例如5,模型针对5个块中的4个进行训练,另一个块用于测试。此操作重复的次数与块的数量相同,以便对每个块执行测试。最后,对每次重复所收集的精度值求平均值。
同样,即使使用5倍交叉验证,我们也获得了相同的精度,等于90%。
Zero-R分类器
Zero-R分类器简单地预测大多数类(训练集中最频繁的类)。有时候,一个不太智能的学习算法可以在一个特定的学习任务上达到很高的准确性,仅仅是因为这个任务很简单。例如,如果数据集非常不平衡,它可以在一个2-class问题中达到很高的精度。
在数据集上运行Zero-R分类器,与朴素贝叶斯算法进行比较,准确率达到74.5%。
下面是一个简单的实现:
class zero_r_classifier(object):
def __init__(self, training_set, test_set):
self.__test_set = test_set
classes = training_set["Class"].value_counts().to_dict()
self.__most_freq_class = max(classes, key=classes.get)
def predict(self):
predictions = {}
for _, row in self.__test_set.iterrows():
predictions[int(row["Num"])] = self.__most_freq_class
return predictions
受欢迎的实现
Python中最流行的库之一是scikit-learn,它实现了多种ML算法,如分类、回归和集群。该库也有一个高斯朴素贝叶斯分类器实现,其API相当容易使用。您可以在这里找到文档和一些示例:http://scikit-learn.org/……
这个实现肯定还没有准备好生产,即使它获得了与scikit-learn相同的预测,因为实际上在底层发生的是相同的。另一方面,它并没有经过太多的设计,因为它的范围只是用来玩弄天真的贝叶斯。无论如何,在大多数情况下,查看一个简单的实现可能更容易和更有效。您可以在这里找到完整的源代码和使用的数据集:https://github.com/amallia/GaussianNB