专栏首页海天一树Python从0实现朴素贝叶斯分类器

Python从0实现朴素贝叶斯分类器

一、 朴素贝叶斯

朴素贝叶斯算法是一个直观的方法,使用每个属性归属于某个类的概率来做预测。你可以使用这种监督性学习方法,对一个预测性建模问题进行概率建模。 给定一个类,朴素贝叶斯假设每个属性归属于此类的概率独立于其余所有属性,从而简化了概率的计算。这种强假定产生了一个快速、有效的方法。 给定一个属性值,其属于某个类的概率叫做条件概率。对于一个给定的类值,将每个属性的条件概率相乘,便得到一个数据样本属于某个类的概率。 我们可以通过计算样本归属于每个类的概率,然后选择具有最高概率的类来做预测。 通常,我们使用分类数据来描述朴素贝叶斯,因为这样容易通过比率来描述、计算。一个符合我们目的、比较有用的算法需要支持数值属性,同时假设每一个数值属性服从正态分布(分布在一个钟形曲线上),这又是一个强假设,但是依然能够给出一个健壮的结果。

二、 数据集

本文中的数据集使用的是“皮马印第安人糖尿病数据集”。该数据集由美国国立糖尿病、消化和肾脏疾病研究所(United States National Institute of Diabetes and Digestive and Kidney Diseases,简称NIDDK)提供。这里的“皮马”指的是位于美国亚利桑那州南部的一个县。

令人吃惊的是,有超过30%的皮马人患有糖尿病。与此形成对照的是,美国糖尿病的患病率为8.3%,中国为4.2%。

该数据集可从 https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv 下载,具体方法是打开此链接后,会看到数据展现在网页中,右击save as,保存类型选为“Microsoft Excel Comman Separated Values File“,即CSV格式,文件名按默认为pima-indians-diabetes.data.csv。

当然,也可以通过百度搜索,从csdn或新浪微盘下载。

数据里包行了768行 X 9列数据。每一行表示一个超过21岁的皮马女性糖尿病患者的信息。 前8列表示属性特征,

1.怀孕次数。
2.2小时口服葡萄糖耐量测试中得到的血糖浓度。
3.舒张期血压(mm Hg)。
4.三头肌皮脂厚度(mm)。
5.2小时血清胰岛素(mu U/ml)。
6.身体质量指数(体重kg/(身高in m)^2)。
7.糖尿病家族遗传作用值。
8.年龄。

第9列表示分类结果,这个类指明以测量时间为止,患者是否是在5年之内感染的糖尿病。如果是,则为1,否则为0。

以数据集中的第一行数据为例, 6,148,72,35,0,33.6,0.627,50,1 这些数据所代表的患者怀孕过6次,服糖2小时后的血糖为148,舒张期血压为72,三头肌皮脂厚度为35,服糖2小时后的血清胰岛素为0,身体质量指数为33.6,糖尿病家族遗传作用值为0.627,50岁。她的糖尿病是在测量时的5年内患的。

三、 算法实现

开发环境:Win 10,Python 3.6 算法的实现过程分为如下几步: (一)处理数据:从CSV文件中载入数据,然后划分为训练集和测试集。 (二)提取数据特征:提取训练数据集的属性特征,以便我们计算概率并做出预测。 (三)单一预测:使用数据集的特征生成单个预测。 (四)多重预测:基于给定测试数据集和一个已提取特征的训练数据集生成预测。 (五)评估精度:评估对于测试数据集的预测精度作为预测正确率。 (六)合并代码:使用所有代码呈现一个完整的、独立的朴素贝叶斯算法的实现。

(一) 处理数据

先加载数据文件

import csv
def loadCsv(filename):
    lines = csv.reader(open(filename, "r"))
    dataset = list(lines)
    for i in range(len(dataset)):
        dataset[i] = [float(x) for x in dataset[i]] #为何要把每个数都转为浮点数
    return dataset

#测试
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 with 768 rows

下一步,我们将数据分为用于朴素贝叶斯预测的训练数据集,以及用来评估模型精度的测试数据集。我们需要将数据集随机分为包含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]

#测试
dataset = [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15]]
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 15 rows into train with [[4], [3], [12], [8], [5], [14], [7], [6], [2], [13]] and test with [[1], [9], [10], [11], [15]]

(二) 提取数据特征

朴素贝叶斯模型包含训练数据集中数据的特征,然后使用这个数据特征来做预测。 所收集的训练数据的特征,包含相对于每个类的每个属性的均值和标准差。举例来说,如果如果有2个类和7个数值属性,然后我们需要每一个属性(7)和类(2)的组合的均值和标准差,也就是14个属性特征。 在对特定的属性归属于每个类的概率做计算、预测时,将用到这些特征。

我们将数据特征的获取划分为以下的子任务: 1 按类别划分数据 2 计算均值和标准差 3 提取数据集特征 4 按类别提取属性特征

1 按类别划分数据

首先将训练数据集中的样本按照类别进行划分,然后计算出每个类的统计数据。我们可以创建一个类别到属于此类别的样本列表的的映射,并将整个数据集中的样本分类到相应的列表。 下面的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

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

运行结果:

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

2 计算均值和标准差

我们需要计算在每个类中每个属性的均值。均值是数据的中点或者集中趋势,在计算概率时,我们用它作为高斯分布的中值。 我们也需要计算每个类中每个属性的标准差。标准差描述了数据散布的偏差,在计算概率时,我们用它来刻画高斯分布中,每个属性所期望的散布。 标准差是方差的平方根。方差是每个属性值与均值的离差平方的平均数。注意分母我们使用N-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)

#测试
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.5811388300841898

3 提取数据集的特征

现在我们可以提取数据集特征。对于一个给定的样本列表(对应于某个类),我们可以计算每个属性的均值和标准差。 zip函数将数据样本按照属性分组为一个个列表,然后可以对每个属性计算均值和标准差。

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

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)]

4 按类别提取属性特征

合并代码,我们首先将训练数据集按照类别进行划分,然后计算每个属性的摘要。

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

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: {1: [(2.0, 1.4142135623730951), (21.0, 1.4142135623730951)], 0: [(3.0, 1.4142135623730951), (21.5, 0.7071067811865476)]}

(三) 预测

我们现在可以使用从训练数据中得到的摘要来做预测。做预测涉及到对于给定的数据样本,计算其归属于每个类的概率,然后选择具有最大概率的类作为预测结果。

我们可以将这部分划分成以下任务: 1 计算高斯分布的概率密度函数 2 计算对应类的概率 3 单一预测 4 多重预测

1 计算高斯分布(正态分布)的概率密度函数

给定来自训练数据中已知属性的均值和标准差,我们可以使用高斯函数来评估一个给定的属性值的概率。

已知每个属性和类值的属性特征,在给定类值的条件下,可以得到给定属性值的条件概率。 关于高斯概率密度函数,可以查看参考文献。总之,我们要把已知的细节融入到高斯函数(属性值,均值,标准差),并得到属性值归属于某个类的似然(译者注:即可能性)。 在calculateProbability()函数中,我们首先计算指数部分,然后计算等式的主干。这样可以将其很好地组织成2行。

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

2 计算所属类的概率

既然我们可以计算一个属性属于某个类的概率,那么合并一个数据样本中所有属性的概率,最后便得到整个数据样本属于某个类的概率。 使用乘法合并概率,在下面的calculClassProbilities()函数中,给定一个数据样本,它所属每个类别的概率,可以通过将其属性概率相乘得到。结果是一个类值到概率的映射。

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

#测试
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}

3 单一预测

既然可以计算一个数据样本属于每个类的概率,那么我们可以找到最大的概率值,并返回关联的类。 下面的predict()函数可以完成以上任务。

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

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

运行结果:

PredictionP : A

4 多重预测

测试数据集中多个数据样本的预测

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

#测试
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']

(四) 评估精度

预测值和测试数据集中的类别值进行比较,可以计算得到一个介于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

#测试
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.66666666666666

(五)合并代码

import csv
import random
import math

def loadCsv(filename):
    lines = csv.reader(open(filename, "r"))
    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.items():
        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.items():
        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.items():
        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: 68.11023622047244%

四、 后续扩展

到此,你已经使用Python一步步完成了高斯版本的朴素贝叶斯。

你可以进一步扩展算法实现:

1 计算所属类的概率:将一个数据样本归属于每个类的概率更新为一个比率。计算上就是将一个样本数据归属于某个类的概率,比上其归属于每一个类的概率的和。举例来说,一个样本属于类A的概率时0.02,属于类B的概率时0.001,那么样本属于类A的可能性是(0.02/(0.02+0.001))*100 大约为95.23%。

2 对数概率:对于一个给定的属性值,每个类的条件概率很小。当将其相乘时结果会更小,那么存在浮点溢出的可能(数值太小,以至于在Python中不能表示)。一个常用的修复方案是,合并其概率的对数值。可以研究实现下这个改进。

3 名词属性:改进算法实现,使其支持名词属性。这是十分相似的,你所收集的每个属性的摘要信息是对于每个类的类别值的比率。潜心学习参考文献来获取更多信息。

4 不同的密度函数(伯努利或者多项式):我们已经尝试了高斯朴素贝叶斯,你也可以尝试下其他分布。实现一个不同的分布诸如多项分布、伯努利分布或者内核朴素贝叶斯,他们对于属性值的分布 和/或 与类值之间的关系有不同的假设。

本文分享自微信公众号 - 海天一树(gh_de7b45c40e8b)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-07-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 小朋友学Python(7):输入与输出

    一、print与input print "a =", a = input() print "b =" b = input() print "c =", c = ...

    海天一树
  • 小朋友学C++(19):函数模板

    在了解模板之前,咱们先来求一下两个 int型的和,两个float型的和,两个double型的和

    海天一树
  • ​信息学竞赛ACM评分机制

    算法竞赛常见的评分方式有两种:一种是中小学NOIP/CSP-JS所采用的OI机制;另一种是大学ACM-ICPC所采用的ACM机制。中小学采用OI机制的比赛比较多...

    海天一树
  • 今日 Paper | 多轨迹预测研究;3DMM 人脸模型;对抗网络判别器;交叉模态信息等

    论文名称:The Garden of Forking Paths: Towards Multi-Future Trajectory Predictio

    AI科技评论
  • 业界 | NovuMind异构智能核心技术引领智联网

    机器之心
  • MongoDB 基本操作

    切换数据库 use test 无需新建数据库,切换时若不存在则自动新建数据库。 查看数据库 $ show dbs 插入数据 db.表名.方法 不指明 id 则自...

    康怀帅
  • 介绍一下 information_schema 库

    今天给大家介绍一款 Mysql 中附属的数据库,就是 information_schema 数据库,为什么说是附属呢?是因为这个数据库是在安装 Mysql 的同...

    张俊红
  • python flask 代码覆盖报告

    1\ 代码覆盖报告 pip install coverage 2  manage.py

    用户5760343
  • 托管C++、C++/CLI、CLR

    拾点阳光
  • DeepMind给人工智能搞了一套IQ测试题

    量子位

扫码关注云+社区

领取腾讯云代金券

玩转腾讯云 有奖征文活动