美文网首页
41、如何在Python中从头开始实现朴素贝叶斯

41、如何在Python中从头开始实现朴素贝叶斯

作者: 攻城狮笔记 | 来源:发表于2019-03-05 21:18 被阅读0次

朴素贝叶斯算法简单有效,应该是您尝试分类问题的第一种方法之一。

在本教程中,您将学习Naive Bayes算法,包括它的工作原理以及如何在Python中从头开始实现它。

关于朴素贝叶斯

朴素贝叶斯算法是一种直观的方法,它使用属于每个类的每个属性的概率来进行预测。如果您想要概率性地建模预测建模问题,那么您将提出监督学习方法。

朴素贝叶斯通过假设属于给定类值的每个属性的概率独立于所有其他属性来简化概率的计算。这是一个强有力的假设,但会产生一种快速有效的方法。

给定属性值的类值的概率称为条件概率。通过将条件概率乘以给定类值的每个属性,我们得到属于该类的数据实例的概率。

为了进行预测,我们可以计算属于每个类的实例的概率,并选择具有最高概率的类值。

天真碱基通常使用分类数据来描述,因为它易于使用比率进行描述和计算。用于我们目的的更有用的算法版本支持数字属性并假设每个数字属性的值是正态分布的(落在钟形曲线上的某处)。同样,这是一个强有力的假设,但仍然提供了可靠的结果。

预测糖尿病的发病

我们将在本教程中使用的测试问题是Pima Indians Diabetes问题

这个问题包括对Pima印第安人专利的医疗细节的768次观察。记录描述了从患者身上获取的瞬时测量值,例如他们的年龄,怀孕次数和血液检查次数。所有患者均为21岁或以上的女性。所有属性都是数字,其单位因属性而异。

每个记录具有类别值,该类别值指示患者在进行测量(1)或不进行测量(0)的5年内是否患有糖尿病。

这是一个标准的数据集,已在机器学习文献中进行了大量研究。良好的预测准确率为70%-76%。

下面是pima-indians.data.csv文件中的一个示例,用于了解我们将要使用的数据(更新:从此处下载)。

6,148,72,35,0,33.6,0.627,50,1
1,85,66,29,0,26.6,0.351,31,0
8,183,64,0,0,23.3,0.672,32,1
1,89,66,23,94,28.1,0.167,21,0
0,137,40,35,168,43.1,2.288,33,1

朴素贝叶斯算法教程
本教程分为以下几个步骤:

  • 处理数据:从CSV文件加载数据并将其拆分为训练和测试数据集。
  • 汇总数据:总结训练数据集中的属性,以便我们可以计算概率并进行预测。
  • 进行预测:使用数据集的摘要生成单个预测。
  • 制作预测:根据测试数据集和汇总的训练数据集生成预测。
  • 评估准确度:评估为测试数据集做出的预测的准确性,作为所有预测中的正确百分比。
  • 将代码合并:使用所有代码元素来呈现Naive Bayes算法的完整且独立的实现。

1.处理数据

我们需要做的第一件事是加载我们的数据文件。数据为CSV格式,没有标题行或任何引号。我们可以使用open函数打开文件,并使用csv模块中的reader函数读取数据行。

我们还需要将作为字符串加载的属性转换为可以使用它们的数字。下面是loadCsv()函数,用于加载Pima indians数据集。

import csv
def loadCsv(filename):
    lines = csv.reader(open(filename, "rb"))
    dataset = list(lines)
    for i in range(len(dataset)):
        dataset[i] = [float(x) for x in dataset[i]]
    return dataset

我们可以通过加载pima indians数据集并打印已加载的数据实例的数量来测试此函数。

filename = 'pima-indians-diabetes.data.csv'
dataset = loadCsv(filename)
print('Loaded data file {0} with {1} rows').format(filename, len(dataset))

运行此测试,您应该看到类似的内容:

Loaded data file pima-indians-diabetes.data.csv rows

接下来,我们需要将数据拆分为Naive Bayes可用于进行预测的训练数据集和我们可用于评估模型准确性的测试数据集。我们需要将数据集随机分成列车和数据集,比率为67%列车和33%测试(这是在数据集上测试算法的常用比率)。

下面是splitDataset()函数,它将给定数据集拆分为给定的拆分比率。

import random
def splitDataset(dataset, splitRatio):
    trainSize = int(len(dataset) * splitRatio)
    trainSet = []
    copy = list(dataset)
    while len(trainSet) < trainSize:
        index = random.randrange(len(copy))
        trainSet.append(copy.pop(index))
    return [trainSet, copy]

我们可以通过定义一个包含5个实例的模拟数据集来测试它,将其拆分为训练和测试数据集并打印出来以查看哪些数据实例最终到达哪里。

dataset = [[1], [2], [3], [4], [5]]
splitRatio = 0.67
train, test = splitDataset(dataset, splitRatio)
print('Split {0} rows into train with {1} and test with {2}').format(len(dataset), train, test)

运行此测试,您应该看到类似的内容:

Split 5 rows into train with [[4], [3], [5]] and test with [[1], [2]]

2.总结数据

朴素贝叶斯模型由训练数据集中的数据摘要组成。然后在进行预测时使用此摘要。

收集的训练数据摘要涉及每个属性的平均值和标准偏差,按类别值。例如,如果有两个类值和7个数值属性,那么我们需要每个属性(7)和类值(2)组合的均值和标准差,即14个属性摘要。

在进行预测以计算属于每个类值的特定属性值的概率时,这些是必需的。

我们可以将此摘要数据的准备工作分解为以下子任务:

  • 按类别分开数据
  • 计算平均值
  • 计算标准差
  • 总结数据集
  • 按类别汇总属性

按类别分开数据

第一个任务是按类值分隔训练数据集实例,以便我们可以计算每个类的统计数据。我们可以通过创建每个类值的映射到属于该类的实例列表并将实例的整个数据集排序到适当的列表中来实现。

下面的separateByClass()函数就是这样做的。

def separateByClass(dataset):
    separated = {}
    for i in range(len(dataset)):
        vector = dataset[i]
        if (vector[-1] not in separated):
            separated[vector[-1]] = []
        separated[vector[-1]].append(vector)
    return separated

您可以看到该函数假定最后一个属性(-1)是类值。该函数将类值映射返回到数据实例列表。

我们可以使用一些示例数据测试此函数,如下所示:

dataset = [[1,20,1], [2,21,0], [3,22,1]]
separated = separateByClass(dataset)
print('Separated instances: {0}').format(separated)

运行此测试,您应该看到类似的内容:

Separated instances: {0: [[2, 21, 0]], 1: [[1, 20, 1], [3, 22, 1]]}

计算平均值

我们需要计算类值的每个属性的平均值。均值是数据的中心中心或中心趋势,我们将在计算概率时将其用作高斯分布的中间。

我们还需要计算类值的每个属性的标准偏差。标准偏差描述了数据传播的变化,我们将用它来表征计算概率时高斯分布中每个属性的预期传播。

标准偏差计算为方差的平方根。方差计算为每个属性值与平均值的平方差的平均值。注意我们使用的是N-1方法,它在计算方差时从属性值的数量中减去1。

import math
def mean(numbers):
    return sum(numbers)/float(len(numbers))

def stdev(numbers):
    avg = mean(numbers)
    variance = sum([pow(x-avg,2) for x in numbers])/float(len(numbers)-1)
    return math.sqrt(variance)

我们可以通过取1到5的数字的平均值来测试这个。

numbers = [1,2,3,4,5]
print('Summary of {0}: mean={1}, stdev={2}').format(numbers, mean(numbers), stdev(numbers))

运行此测试,您应该看到类似的内容:

Summary of [1, 2, 3, 4, 5]: mean=3.0, stdev=1.58113883008

总结数据集

现在我们有了汇总数据集的工具。对于给定的实例列表(对于类值),我们可以计算每个属性的均值和标准差。

zip函数将数据实例中每个属性的值分组到它们自己的列表中,以便我们可以计算属性的均值和标准差值。

def summarize(dataset):
    summaries = [(mean(attribute), stdev(attribute)) for attribute in zip(*dataset)]
    del summaries[-1]
    return summaries

我们可以 使用一些测试数据来测试这个summarize()函数,这些测试数据显示第一和第二数据属性的平均值和标准偏差值明显不同。

dataset = [[1,20,0], [2,21,1], [3,22,0]]
summary = summarize(dataset)
print('Attribute summaries: {0}').format(summary)

运行此测试,您应该看到类似的内容:

Attribute summaries: [(2.0, 1.0), (21.0, 1.0)]

按类别汇总属性

我们可以通过首先将训练数据集分成按类分组的实例来将它们整合在一起。然后计算每个属性的摘要。

def summarizeByClass(dataset):
    separated = separateByClass(dataset)
    summaries = {}
    for classValue, instances in separated.iteritems():
        summaries[classValue] = summarize(instances)
    return summaries

我们可以使用一个小的测试数据集测试这个summaryCyClass()函数。

dataset = [[1,20,1], [2,21,0], [3,22,1], [4,22,0]]
summary = summarizeByClass(dataset)
print('Summary by class value: {0}').format(summary)

运行此测试,您应该看到类似的内容:

Summary by class value: 
{0: [(3.0, 1.4142135623730951), (21.5, 0.7071067811865476)], 
1: [(2.0, 1.4142135623730951), (21.0, 1.4142135623730951)]}

3.进行预测

我们现在准备使用从我们的培训数据准备的摘要进行预测。进行预测涉及计算给定数据实例属于每个类的概率,然后选择具有最大概率的类作为预测。

我们可以将这部分分为以下任务:

计算高斯概率密度函数

  • 计算类概率
  • 做一个预测
  • 估计准确度
  • 计算高斯概率密度函数
    在给定从训练数据估计的属性的已知平均值和标准偏差的情况下,我们可以使用高斯函数来估计给定属性值的概率。

假定为每个属性和类值准备的属性汇总,结果是给定类值的给定属性值的条件概率。

有关高斯概率密度函数的详细信息,请参阅参考资料。总之,我们将已知细节插入高斯(属性值,平均值和标准偏差)并读取属性值属于类的可能性。

在calculateProbability()函数中,我们首先计算指数,然后计算主要的除法。这让我们可以在两条线上很好地拟合方程。

import math
def calculateProbability(x, mean, stdev):
    exponent = math.exp(-(math.pow(x-mean,2)/(2*math.pow(stdev,2))))
    return (1 / (math.sqrt(2*math.pi) * stdev)) * exponent

我们可以使用一些示例数据对此进行测试,如下所示。

x = 71.5
mean = 73
stdev = 6.2
probability = calculateProbability(x, mean, stdev)
print('Probability of belonging to this class: {0}').format(probability)

运行此测试,您应该看到类似的内容:

Probability of belonging to this class: 0.0624896575937

计算类概率

现在我们可以计算出属于某个类的属性的概率,我们可以组合数据实例的所有属性值的概率,并得出整个数据实例属于该类的概率。

我们将概率乘以它们,将概率结合在一起。在下面的calculateClassProbabilities()中,通过将每个类的属性概率相乘来计算给定数据实例的概率。结果是类值与概率的映射。

def calculateClassProbabilities(summaries, inputVector):
   probabilities = {}
   for classValue, classSummaries in summaries.iteritems():
       probabilities[classValue] = 1
       for i in range(len(classSummaries)):
           mean, stdev = classSummaries[i]
           x = inputVector[i]
           probabilities[classValue] *= calculateProbability(x, mean, stdev)
   return probabilities

我们可以测试calculateClassProbabilities()函数。

summaries = {0:[(1, 0.5)], 1:[(20, 5.0)]}
inputVector = [1.1, '?']
probabilities = calculateClassProbabilities(summaries, inputVector)
print('Probabilities for each class: {0}').format(probabilities)

运行此测试,您应该看到类似的内容:

Probabilities for each class: {0: 0.7820853879509118, 1: 6.298736258150442e-05}

做一个预测

现在,可以计算属于每个类值的数据实例的概率,我们可以查找最大概率并返回关联类。

该预测()函数属于做到了这一点。

def predict(summaries, inputVector):
    probabilities = calculateClassProbabilities(summaries, inputVector)
    bestLabel, bestProb = None, -1
    for classValue, probability in probabilities.iteritems():
        if bestLabel is None or probability > bestProb:
            bestProb = probability
            bestLabel = classValue
    return bestLabel

我们可以测试predict()函数如下:

summaries = {'A':[(1, 0.5)], 'B':[(20, 5.0)]}
inputVector = [1.1, '?']
result = predict(summaries, inputVector)
print('Prediction: {0}').format(result)

运行此测试,您应该看到类似的内容:

Prediction: A

4.做出预测

最后,我们可以通过对测试数据集中的每个数据实例进行预测来估计模型的准确性。该getPredictions()将做到这一点,并返回预测为每个测试实例的列表。

def getPredictions(summaries, testSet):
    predictions = []
    for i in range(len(testSet)):
        result = predict(summaries, testSet[i])
        predictions.append(result)
    return predictions

我们可以测试getPredictions()函数。

summaries = {'A':[(1, 0.5)], 'B':[(20, 5.0)]}
testSet = [[1.1, '?'], [19.1, '?']]
predictions = getPredictions(summaries, testSet)
print('Predictions: {0}').format(predictions)

运行此测试,您应该看到类似的内容:

Predictions: ['A', 'B']

5.获得准确性

可以将预测与测试数据集中的类值进行比较,并且可以将分类精度计算为0和100%之间的准确度。所述getAccuracy()将计算该精度比。

def getAccuracy(testSet, predictions):
    correct = 0
    for x in range(len(testSet)):
        if testSet[x][-1] == predictions[x]:
            correct += 1
    return (correct/float(len(testSet))) * 100.0

我们可以使用下面的示例代码测试getAccuracy()函数。

testSet = [[1,1,1,'a'], [2,2,2,'a'], [3,3,3,'b']]
predictions = ['a', 'a', 'a']
accuracy = getAccuracy(testSet, predictions)
print('Accuracy: {0}').format(accuracy)

运行此测试,您应该看到类似的内容:

Accuracy: 66.6666666667

6.将代码合并

最后,将代码合并。

下面提供了从头开始在Python中实现的Naive Bayes的完整代码清单

# Example of Naive Bayes implemented from Scratch in Python
import csv
import random
import math

def loadCsv(filename):
    lines = csv.reader(open(filename, "rb"))
    dataset = list(lines)
    for i in range(len(dataset)):
        dataset[i] = [float(x) for x in dataset[i]]
    return dataset

def splitDataset(dataset, splitRatio):
    trainSize = int(len(dataset) * splitRatio)
    trainSet = []
    copy = list(dataset)
    while len(trainSet) < trainSize:
        index = random.randrange(len(copy))
        trainSet.append(copy.pop(index))
    return [trainSet, copy]

def separateByClass(dataset):
    separated = {}
    for i in range(len(dataset)):
        vector = dataset[i]
        if (vector[-1] not in separated):
            separated[vector[-1]] = []
        separated[vector[-1]].append(vector)
    return separated

def mean(numbers):
    return sum(numbers)/float(len(numbers))

def stdev(numbers):
    avg = mean(numbers)
    variance = sum([pow(x-avg,2) for x in numbers])/float(len(numbers)-1)
    return math.sqrt(variance)

def summarize(dataset):
    summaries = [(mean(attribute), stdev(attribute)) for attribute in zip(*dataset)]
    del summaries[-1]
    return summaries

def summarizeByClass(dataset):
    separated = separateByClass(dataset)
    summaries = {}
    for classValue, instances in separated.iteritems():
        summaries[classValue] = summarize(instances)
    return summaries

def calculateProbability(x, mean, stdev):
    exponent = math.exp(-(math.pow(x-mean,2)/(2*math.pow(stdev,2))))
    return (1 / (math.sqrt(2*math.pi) * stdev)) * exponent

def calculateClassProbabilities(summaries, inputVector):
    probabilities = {}
    for classValue, classSummaries in summaries.iteritems():
        probabilities[classValue] = 1
        for i in range(len(classSummaries)):
            mean, stdev = classSummaries[i]
            x = inputVector[i]
            probabilities[classValue] *= calculateProbability(x, mean, stdev)
    return probabilities
            
def predict(summaries, inputVector):
    probabilities = calculateClassProbabilities(summaries, inputVector)
    bestLabel, bestProb = None, -1
    for classValue, probability in probabilities.iteritems():
        if bestLabel is None or probability > bestProb:
            bestProb = probability
            bestLabel = classValue
    return bestLabel

def getPredictions(summaries, testSet):
    predictions = []
    for i in range(len(testSet)):
        result = predict(summaries, testSet[i])
        predictions.append(result)
    return predictions

def getAccuracy(testSet, predictions):
    correct = 0
    for i in range(len(testSet)):
        if testSet[i][-1] == predictions[i]:
            correct += 1
    return (correct/float(len(testSet))) * 100.0

def main():
    filename = 'pima-indians-diabetes.data.csv'
    splitRatio = 0.67
    dataset = loadCsv(filename)
    trainingSet, testSet = splitDataset(dataset, splitRatio)
    print('Split {0} rows into train={1} and test={2} rows').format(len(dataset), len(trainingSet), len(testSet))
    # prepare model
    summaries = summarizeByClass(trainingSet)
    # test model
    predictions = getPredictions(summaries, testSet)
    accuracy = getAccuracy(testSet, predictions)
    print('Accuracy: {0}%').format(accuracy)

main()

运行该示例提供如下输出:

Split 768 rows into train=514 and test=254 rows
Accuracy: 76.3779527559%

扩展

本节为您提供了可以应用的扩展的概念,并使用您在本教程中实现的Python代码进行调查。

您已经从头开始在python中实现了自己的Gaussian Naive Bayes版本。

您可以进一步扩展。

  • 计算类概率:更新示例以将属于每个类的数据实例的概率作为比率汇总。这可以被计算为属于一个类的数据实例的概率除以属于每个类的数据实例的概率之和。例如,A类的概率为0.02,B类的概率为0.001,属于A类的实例的可能性为(0.02 /(0.02 + 0.001))* 100,约为95.23%。
  • 对数概率:给定属性值的每个类的条件概率很小。当它们相乘时会产生非常小的值,这可能导致浮点下溢(数字太小而无法在Python中表示)。对此的常见修复是将概率的对数组合在一起。研究并实施这一改进。
  • 标称属性:更新实现以支持名义属性。这非常相似,您可以为每个属性收集的摘要信息是每个类的类别值的比率。深入了解参考资料以获取更多信息。
  • 不同的密度函数(bernoulli或多项式):我们已经看过高斯朴素贝叶斯,但你也可以看看其他分布。实现不同的分布,例如多项式,bernoulli或内核朴素贝叶斯,它们对属性值的分布和/或它们与类值的关系做出不同的假设。

相关文章

网友评论

      本文标题:41、如何在Python中从头开始实现朴素贝叶斯

      本文链接:https://www.haomeiwen.com/subject/azlauqtx.html