ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

高斯朴素贝叶斯实战:皮马糖尿病数据集分类与线性回归可视化

高斯朴素贝叶斯实战:皮马糖尿病数据集分类与线性回归可视化 简介这份PDF文档面向模式识别、机器学习入门学习者与课程设计者围绕朴素贝叶斯分类器在医疗预测场景中的落地展开帮助读者理解如何用统计学方法完成糖尿病风险判别。资源包共1个PDF文件大小约771KB内容涵盖贝叶斯定理分类原理、Pima Indians数据集说明、特征提取与高斯概率计算、分类预测流程、准确率评估与可视化分析并附有完整Python代码实现与程序流程图。文档详细拆解了loadCsv()、SplitDataset()、separateByClass()、summarizeByClass()、calculateProbability()等核心函数的编写思路同时给出硬件与软件环境配置说明便于读者复现实验。目前已有234人学习浏览适合需要完成模式识别大作业、课程设计或希望掌握朴素贝叶斯实战流程的读者参考可快速获取从数据加载、训练测试拆分到结果图表展示的完整实现路径。1. 从一份课程设计说起442 条皮马数据怎么跑出可复现的糖尿病预测糖尿病早期筛查这件事真正落到代码层面往往不是深度学习那一套而是一个朴素贝叶斯分类器加一份皮马印第安人医学数据集。我手上这份《基于贝叶斯定理的糖尿病检测系统的设计与实现.pdf》就是围绕这条主线展开的用高斯朴素贝叶斯对 442 条观察记录做二分类判断五年内是否发病再用线性回归把每个生理指标和病情发展值的关系画出来。它适合两类人——正在做模式识别课程设计、需要一份能跑通全流程参考实现的学生以及想用最小依赖理解贝叶斯分类到底怎么算的从业者。整份资源的价值不在算法多新而在于它把载入、拆分、按类汇总、高斯概率密度、连乘、取最大、算准确率这条链路完整走了一遍每一步都有函数对应改参数和排错都有抓手。2. 高斯朴素贝叶斯的计算链路从 CSV 到条件概率2.1 为什么选高斯分布而不是离散计数朴素贝叶斯常见有两种实现路径一种是针对离散特征的频次计数另一种是针对连续特征的概率密度估计。皮马数据集里的属性——怀孕次数、血糖、血压、皮褶厚度、胰岛素、BMI、糖尿病 pedigree 函数、年龄——全是数值型而且单位不统一。如果硬做离散化分箱箱宽怎么定就成了玄学分细了每箱样本太少概率为 0分粗了信息损失大。所以这份实现选了高斯分布假设每个特征在每个类别下服从正态分布用训练集里该类样本的均值和标准差去描述这个分布预测时把测试样本的特征值代入概率密度函数求似然。这个选择的好处是参数极少每个特征每个类别只需要两个数均值、标准差442 条数据也扛得住。代价是它默认特征服从正态而像胰岛素这种明显右偏的指标其实不太符合这是后面准确率上不去的原因之一。理解这一点比记住公式更重要。2.2 数据载入与类型转换原始 CSV 读进来全是字符串必须先转 float否则后面算均值会直接报类型错误。载入函数长这样import csv def loadCsv(): # 路径按自己机器改注意 Windows 反斜杠转义或直接用正斜杠 lines csv.reader(open(diabetes.data.csv, r, encodingutf-8)) dataset list(lines) # 逐行把字符串转成浮点最后一位是类别标签 0/1 for i in range(len(dataset)): dataset[i] [float(x) for x in dataset[i]] return dataset逻辑说明csv.reader返回的是迭代器list()之后才能按下标访问。转换那一步用列表推导逐元素float()比循环 append 简洁。参数上唯一要动的是文件路径建议用相对路径并把 CSV 放在脚本同级目录避免绝对路径里带中文和空格导致open失败——原实现里路径含中文目录换机器基本必翻车。2.3 按比例拆分训练集与测试集拆分用的是随机不放回抽样保证训练集和测试集不重叠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)) # pop 出来即从 copy 移除 return [trainSet, copy]逻辑说明copy.pop(index)是关键抽走一条就少一条天然实现不放回。splitRatio传 0.67 表示 67% 训练、33% 测试原文档正文提到 90/10但主程序里实际写的是 0.67这个不一致后面避坑章节会专门讲。参数建议数据量只有 442 条时测试集别低于 30%否则准确率波动能到正负 5 个百分点看着像模型坏了其实是样本太少。2.4 按类别汇总并计算均值标准差预测需要的是「每个类别下每个特征的均值和标准差」所以先按类别把数据分开再逐特征统计import math def separateByClass(dataset): separated {} for vector in dataset: label vector[-1] if label not in separated: separated[label] [] separated[label].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): # zip(*dataset) 把每列拆出来逐列算均值和标准差 summaries [(mean(attr), stdev(attr)) for attr 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逻辑说明zip(*dataset)是这份代码里最值得记的一手它把「行列表」转置成「列列表」于是每个特征的所有取值聚在一起直接喂给mean和stdev。del summaries[-1]必须做否则会把类别标签也当成一个特征算进去预测时维度对不上。stdev分母用len-1是样本标准差的无偏估计样本量小时比总体标准差更稳。2.5 高斯概率密度与连乘求最大后验有了均值和标准差就能算某个特征值在该类别下的似然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逻辑说明calculateProbability就是高斯概率密度函数指数部分是exp(-(x-μ)²/2σ²)前面系数是1/(√(2π)·σ)。calculateClassProbabilities里初始值设 1 而不是 0因为要做连乘设 0 结果永远是 0。这里有个数值下溢的隐患特征多、概率都小于 1 时连乘结果会趋近 0浮点数直接变 0两个类别都变 0 就没法比大小。常见做法是取对数把连乘变连加这份实现没做属于可以改进的点。2.6 准确率评估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 x in range(len(testSet)): if testSet[x][-1] predictions[x]: correct 1 return (correct / float(len(testSet))) * 100.0逻辑说明getAccuracy拿预测标签和测试集最后一列真实标签逐个比对命中就加一最后除以测试集总数乘 100。这个指标就是最朴素的分类准确率没有考虑类别不平衡。皮马数据里阳性样本占比不算高所以一个全预测为 0 的傻瓜模型也能拿到六成多准确率看结果时心里要有这个基准线。3. 线性回归可视化把每个特征和病情发展值摊开看3.1 用 sklearn 自带数据集快速起图分类部分用的是 CSV可视化部分换成了sklearn.datasets.load_diabetes这是另一份 442 条的数据字段名一致但数值经过标准化。两条线并行不冲突一个练分类一个看相关性import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.datasets import load_diabetes from sklearn.linear_model import LinearRegression data_diabetes load_diabetes() data data_diabetes[data] target data_diabetes[target] feature_names data_diabetes[feature_names] df pd.DataFrame(data, columnsfeature_names)逻辑说明load_diabetes()返回字典三个 key 分别是data、target、feature_names。转成 DataFrame 是为了后面按列名取特征方便。注意这份数据的特征已经做过均值中心化和缩放所以数值都在零点几的量级和 CSV 那份原始量纲完全不同别把两边的系数直接对比。3.2 逐特征拟合直线并画散点plt.figure(figsize(12, 25)) for i, col in enumerate(df.columns): train_X df.loc[:, col].values.reshape(-1, 1) # 一维转二维 train_Y target linear_model LinearRegression() linear_model.fit(train_X, train_Y) score linear_model.score(train_X, train_Y) axes plt.subplot(5, 2, i 1) plt.scatter(train_X, train_Y, s8) k linear_model.coef_ b linear_model.intercept_ x np.linspace(train_X.min(), train_X.max(), 100) y k * x b plt.plot(x, y, cred) axes.set_title(col : str(round(score, 3))) plt.tight_layout() plt.show()逻辑说明reshape(-1, 1)是必须的LinearRegression要求输入二维直接传一维 Series 会报错。score返回的是 R²越接近 1 说明这个单特征对 target 的解释力越强。np.linspace生成 100 个等距点用来画拟合直线比只连两个端点平滑。从结果看BMI 对应的 R² 明显高于其他特征年龄有一点点性别基本是平的这和文档结论一致。参数建议figsize和subplot(5, 2, ...)要匹配10 个特征正好 5 行 2 列加特征就得改行数否则图会挤在一起或者报索引越界。4. 避坑与排查这份实现里最容易翻车的五处4.1 训练测试比例前后不一致现象正文写 90% 训练 10% 测试主程序里splitRatio 0.67跑出来的准确率和预期对不上。 原因文档描述和代码不同步属于课程设计里常见的遗留问题。 解决以代码为准或者自己统一成 0.8。改完记得重跑别拿旧结果写报告。4.2 概率连乘下溢导致预测全为同一类现象测试集所有样本都被预测成 0准确率卡在基准线上不动。 原因特征多、似然都小于 1连乘后浮点数下溢成 0两个类别概率都是 0比大小就退化成取第一个。 解决把calculateClassProbabilities里的连乘改成对数连加即累加math.log(prob)最后比对数大小结果等价且不会下溢。4.3 标准差为 0 触发除零现象某个特征在某个类别下所有取值相同stdev返回 0calculateProbability里除以stdev直接抛ZeroDivisionError。 原因样本量小或者某特征区分度太低时会出现。 解决在stdev里加保护返回一个极小值如1e-6或者在calculateProbability入口判断stdev 0时直接返回一个默认似然。4.4 CSV 路径含中文或反斜杠转义现象open报FileNotFoundError或UnicodeDecodeError。 原因Windows 路径里的\在字符串里是转义符中文目录在某些编码下读取异常。 解决路径前加r变成原始字符串或者统一用正斜杠/并把 CSV 放到脚本同级目录用相对路径。4.5 把类别标签当特征统计现象预测时维度对不上或者准确率异常低。 原因summarize里忘了del summaries[-1]类别标签被当成第 9 个特征算进了均值和标准差。 解决确认summarize在zip(*dataset)之后删掉了最后一列这是最容易被复制粘贴漏掉的一行。5. 进阶玩法把准确率从基准线往上抬一抬跑通只是起点这份实现默认的准确率大概在七成上下想再往上走有几个不动大结构就能试的方向。第一是特征标准化皮马数据里胰岛素和血糖的量纲差得远虽然高斯贝叶斯对量纲不敏感但线性回归那部分受影响明显统一做 z-score 之后 R² 会更可比。第二是处理零值皮马数据里血糖、血压、BMI 为 0 的记录其实是缺失值直接当真实值喂进去会拉偏均值和标准差常见做法是把 0 替换成该特征的中位数再训练。第三是换个评估方式别只看准确率。加一段混淆矩阵的代码看召回率和精确率尤其是阳性样本的召回——糖尿病筛查里漏诊的代价比误诊大得多。第四是交叉验证442 条数据单次随机拆分的结果方差很大用sklearn的cross_val_score跑 5 折取平均准确率比单次结果可信得多。from sklearn.model_selection import cross_val_score from sklearn.naive_bayes import GaussianNB import numpy as np # X 是特征矩阵y 是标签来自前面 loadCsv 后的拆分 model GaussianNB() scores cross_val_score(model, X, y, cv5, scoringaccuracy) print(5折准确率:, scores) print(平均:, np.mean(scores))逻辑说明cross_val_score自动做 5 折划分每折都训练一次评估一次返回 5 个准确率。GaussianNB就是 sklearn 版的高斯朴素贝叶斯和自己手写的逻辑一致但内部做了对数概率和方差平滑不会出现前面说的下溢和除零。拿它和自己手写的结果对比如果差距很大说明手写版某处有 bug这本身就是很好的验证手段。我自己的习惯是任何手写算法实现跑通之后都要拿 sklearn 对应实现做一次对拍两边结果接近才敢相信自己的代码。这份资源最大的价值也在这里——它给了你一条完整可改的链路而不是一个黑匣子。从那以后我每次做课程设计类的复现都强制先跑通原版、再对拍、最后才改参数省了太多返工的功夫。希望帮到你。本文还有配套的精品资源点击获取
返回列表