资讯动态

朴素贝叶斯糖尿病检测:高斯概率密度与分类实现

发布时间:2026/10/19 7:33:16 来源:尧图企业网站定制
简介这份PDF文档面向模式识别、机器学习入门学习者与课程设计者围绕朴素贝叶斯分类器在医疗场景中的落地展开解决如何用统计学方法从生理指标中预测糖尿病发病风险的问题。资源包仅含1个PDF文件大小约771KB内容完整呈现从原理推导到代码实现的全部环节。文档以皮马印第安人糖尿病数据集为样本系统讲解贝叶斯定理分类原理、数据拆分与特征统计、高斯概率密度计算、条件概率累乘与类别判定并给出loadCsv、SplitDataset、summarizeByClass、calculateProbability等核心函数的实现思路最后通过准确率评估与图表可视化分析各参数与发病的关系。目前已有235人学习下载适合希望理解朴素贝叶斯完整流程、需要课程大作业参考或想掌握Python分类实战的读者可据此快速复现实验并迁移到其他二分类任务。1. 朴素贝叶斯做糖尿病检测为什么“假设特征独立”反而能跑出高准确率一个反直觉的事实是朴素贝叶斯分类器明明做了一个几乎不可能成立的假设——所有特征彼此独立——却常常在医学二分类任务上给出相当能打的准确率。糖尿病检测就是典型场景用年龄、BMI、血清化验指标等九项数值特征预测一个人五年内是否会发病。直觉上BMI 和血清指标显然相关年龄和代谢水平也纠缠不清强行“独立”应该错得离谱。但实际跑下来准确率往往能到七成以上而且训练速度快到几乎瞬间完成。这背后的机制值得拆开看它到底在算什么概率为什么“错得离谱的假设”没有把结果带偏以及一份完整的实现里从 CSV 载入到高斯概率密度再到准确率评估每一步的取舍在哪里。搞懂这套逻辑你再看其他分类器时会多一个“概率视角”的参照系。2. 贝叶斯定理在分类任务里到底怎么用从条件概率到后验决策2.1 先厘清我们要算的不是“得病概率”而是“给定指标下得病的概率”很多人第一次接触贝叶斯分类会误以为模型输出的是“这个人得糖尿病的概率”。严格说它算的是后验概率在已知这组特征值的前提下属于某个类别的概率。用公式写出来就是 P(类别 | 特征)。贝叶斯定理把它拆成三块P(类别) 是先验也就是训练集里发病和不发病各占多少P(特征 | 类别) 是似然表示“如果这个人真的发病了他出现这组指标的可能性有多大”分母 P(特征) 是证据对所有类别都一样。做分类决策时分母不变所以只需要比较分子——先验乘以似然。这就是为什么代码里从头到尾没有算过 P(特征)只算 P(类别) 和 P(特征 | 类别) 的乘积。这个拆解带来的直接好处是不需要一次性估计整个高维联合分布只需要对每个特征单独估计条件分布再乘起来。九维特征如果联合建模参数量会爆炸拆成九个一维问题每个只需要均值和方差两个参数。这是朴素贝叶斯在中小数据集上“快且稳”的根本原因。2.2 为什么选高斯分布连续数值特征的似然估计皮马印第安数据集的特征全是连续数值年龄、BMI、血清化验值单位不同但都是实数。对于连续变量P(特征 | 类别) 不能像离散特征那样查表必须假设一个概率分布形式。最常见的实现思路是假设每个特征在每个类别下服从高斯分布也就是正态分布。这个假设的合理性在于很多生理指标在人群中的分布本身就近似钟形曲线即使不完全正态用均值和方差去刻画“典型范围”也足够做区分。高斯概率密度函数长这样给定均值 μ 和标准差 σ某个值 x 出现的密度是 (1 / (σ√(2π))) * exp(-(x-μ)² / (2σ²))。代码里 calculateProbability() 做的就是这件事先算指数部分再乘前面的系数。注意这里用的是密度而不是概率密度值可以大于 1但乘起来比较大小没问题因为所有类别用的是同一套公式尺度一致。2.3 连乘与下溢为什么实际代码里很少直接乘概率理论上一个样本属于某类别的分数是“先验 × 所有特征似然的连乘”。但九项特征的概率密度乘起来数值会小到浮点数下溢直接比较可能全是 0。常见的工程处理有两种一是取对数把连乘变连加二是像这份实现一样直接连乘但接受精度损失因为特征数不多、密度值不会太小。这份代码用的是后者probabilities[classValue] 初始化为 1然后逐个乘 calculateProbability() 的返回值。如果特征维度上升到几十上百就必须切到对数域否则结果不可靠。这是一个典型的“小规模能跑、大规模翻车”的取舍点。3. 从 CSV 到预测结果一份朴素贝叶斯实现的完整链路3.1 数据载入与类型转换loadCsv() 里藏着的两个细节载入部分看起来简单但有两个容易忽略的点。第一open() 指定了 encodingutf-8因为文件路径里包含中文字符不指定编码在部分系统上会直接报错。第二读进来的每一行是字符串列表必须逐列转成 float否则后面算均值时 sum() 会变成字符串拼接。代码里用列表推导式 dataset[i] [float(x) for x in dataset[i]] 完成转换这一步不做后面所有统计计算全是错的。def loadCsv(): lines csv.reader(open(diabetes.data.csv, r, encodingutf-8)) dataset list(lines) for i in range(len(dataset)): dataset[i] [float(x) for x in dataset[i]] return dataset这里没有做缺失值处理。皮马数据集里有些特征的 0 值其实是缺失标记比如血糖、血压为 0 在生理上不可能。直接当数值用会拉低均值、放大方差影响高斯估计。更稳妥的做法是把这些 0 替换成该特征在训练集上的中位数或者直接剔除。这份实现选择了最简路径准确率会受一点影响但流程完整。3.2 拆分数据集为什么用随机抽取而不是切片splitDataset() 没有用 dataset[:trainSize] 这种切片而是用 random.randrange() 从副本里随机弹出样本。原因是数据集可能按类别有序排列直接切片会导致训练集和测试集的类别比例失衡。随机抽取能保证训练集里两类样本的分布接近原始比例。splitRatio 在主程序里设的是 0.67但正文描述里提到 90% 训练、10% 测试这两个值不一致。实际跑的时候0.67 意味着测试集更大评估结果波动也更大0.9 则训练更充分但测试样本少。这是一个需要根据数据量权衡的参数数据量小的时候测试集太小会让准确率估计方差很大。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]注意 copy.pop(index) 会修改副本所以循环结束时 copy 里剩下的就是测试集。这个写法比先打乱再切片更直观但每次弹出是 O(n) 操作数据量大时效率会下降。对于几百条记录的数据集完全够用。3.3 按类别汇总与高斯参数估计summarizeByClass() 的层次separateByClass() 先把数据按最后一列的类别值分成两个字典项每个项是一个列表里面是所有属于该类的样本。然后 summarize() 对每个类别做一次转置把同一列的所有值聚在一起算均值和标准差。这里用 zip(*dataset) 实现转置del summaries[-1] 去掉类别列本身因为类别列不需要算统计量。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标准差计算用的是样本标准差分母是 n-1 而不是 n。这个选择在统计上更常用因为训练集是总体的一个样本。如果分母用 n方差会被低估高斯分布会变窄对极端值的惩罚过重。代码里 variance sum([pow(x-avg,2) for x in numbers]) / float(len(numbers)-1) 明确用了 n-1这是一个正确的细节。3.4 预测与准确率predict() 和 getAccuracy() 的配合predict() 对单个样本计算属于每个类别的分数返回分数最高的类别。calculateClassProbabilities() 里 probabilities[classValue] 初始化为 1然后对每个特征乘上高斯密度。这里没有乘先验 P(类别)因为训练集里两类样本数量接近先验近似相等省略后对排序影响不大。如果类别严重不平衡比如发病样本只占 5%就必须把先验乘回去否则模型会倾向于预测多数类。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 bestLabelgetAccuracy() 把预测列表和测试集真实标签逐项对比正确数除以总数乘 100。这个指标在类别不平衡时会有误导性如果 90% 样本都是不发病全预测不发病也能拿 90% 准确率。更严谨的做法是同时看召回率和精确率但作为课程作业级别的实现准确率足够说明流程跑通。4. 线性回归与可视化为什么还要画每个特征和目标的散点图4.1 单特征线性回归score 的含义与局限可视化部分用 sklearn 的 LinearRegression 对每个特征单独建模画出散点图和拟合直线标题里带上 score。这个 score 是 R²表示该特征能解释目标变量多少方差。R² 接近 1 说明线性关系强接近 0 说明几乎没有线性关系。从结果看BMI 的 R² 相对较高性别几乎为 0这和正文里“发病概率和性别无关、和 BMI 关系很大”的结论一致。但要注意单特征 R² 低不代表该特征没用。糖尿病是典型的多因素疾病单个特征和目标的线性关系弱是正常的朴素贝叶斯正是通过组合多个弱特征来提升分类效果。把单特征 R² 当作特征重要性的唯一依据会漏掉交互效应。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) 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(score))reshape(-1, 1) 是因为 sklearn 要求二维输入而 df.loc[:, col].values 是一维的。这个转换不做会直接报错。subplot(5, 2, i1) 把十个特征排成五行两列figsize 设成 (12, 25) 保证每个子图有足够空间。4.2 图表能告诉你什么、不能告诉你什么散点图能直观看出特征和目标的整体趋势是正相关、负相关还是无关。但散点图不能告诉你特征之间的交互作用也不能告诉你某个特征在贝叶斯分类器里的实际贡献。要评估特征重要性更直接的方法是做消融实验去掉一个特征重新训练看准确率掉多少。或者看每个特征在高斯分布下两类均值的距离距离越大区分度越高。5. 避坑与排查朴素贝叶斯实现里最容易翻车的几个点5.1 准确率每次跑都不一样现象反复运行同一份代码准确率在几个百分点之间波动。原因splitDataset() 用了随机抽取每次训练集和测试集划分不同。解决固定随机种子 random.seed(42)或者做多次随机划分取平均准确率。单次划分的准确率不能代表模型真实性能。5.2 标准差为 0 导致除零错误现象某个特征在某个类别下所有值相同stdev 返回 0calculateProbability() 里除以 stdev 直接崩溃。原因训练集里该类别样本太少或者该特征本身是常量。解决在 stdev 上加一个极小值比如 stdev max(stdev, 1e-6)或者检查数据、剔除常量特征。5.3 特征量纲差异导致高斯密度尺度悬殊现象某个特征取值范围是 0 到 200另一个是 0 到 1算出来的密度值差好几个数量级连乘时小数值特征被淹没。原因高斯密度对尺度敏感σ 大的特征密度峰值低。解决训练前做标准化把每个特征减均值除标准差让所有特征在同一尺度上。这份实现没有做标准化准确率会受一定影响。5.4 把 0 值当真实测量值现象血糖、血压为 0 的样本参与训练拉低均值、拉大方差高斯分布变平区分度下降。原因皮马数据集里 0 是缺失标记不是真实值。解决载入后把生理上不可能的 0 替换成该特征在训练集上的中位数或者用 pandas 的 replace 批量处理。5.5 用准确率评估不平衡数据现象准确率看起来不错但模型几乎全预测为“不发病”。原因如果发病样本占比低多数类预测就能拿高准确率。解决同时输出混淆矩阵看召回率和精确率。对于糖尿病检测漏诊的代价比误诊大召回率比准确率更值得关注。6. 设计取舍为什么“简单假设”在真实任务里仍然值得用朴素贝叶斯的独立假设在糖尿病检测里显然不成立BMI 和血清指标相关年龄和代谢水平相关。但它的分类效果并不差原因在于分类任务只需要后验概率的排序正确不需要概率值本身精确。即使似然估计有偏差只要两个类别的分数差距方向对预测就是对的。这种“排序正确优先于数值精确”的特性让朴素贝叶斯在高维小样本场景下特别稳。另一个取舍是高斯假设。生理指标不一定严格正态但用均值和方差去刻画“典型范围”已经能捕捉大部分区分信息。换成核密度估计会更准但参数量上升、训练变慢对于几百条数据来说不划算。这份实现选择高斯是在准确率和实现复杂度之间取了平衡。还有一个容易被忽略的点这份代码没有做交叉验证。单次划分的准确率波动大固定种子只能保证可复现不能保证评估可靠。更稳妥的做法是 k 折交叉验证把数据分成 k 份轮流做测试集取平均准确率。代价是训练时间乘以 k但对于这个规模的数据集完全可接受。从那以后我每次做贝叶斯分类都会先检查三件事特征有没有标准化、缺失值有没有处理、评估有没有用交叉验证。这三步不做准确率数字再好看也不敢信。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑