news 2026/10/31 2:24:09

手写朴素贝叶斯筛糖尿病:442条小样本高斯建模实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手写朴素贝叶斯筛糖尿病:442条小样本高斯建模实战

简介:本资源是一份面向机器学习初学者与模式识别课程学习者的实践型技术文档,聚焦于利用朴素贝叶斯分类器实现糖尿病早期风险预测。文档完整呈现了从理论原理(贝叶斯定理与高斯概率密度函数推导)、数据准备(基于442例Pima Indians Diabetes公开数据集)、特征处理(均值/方差/标准差计算与类别汇总)、Python代码实现(含loadCsv、SplitDataset、calculateProbability等6个核心函数)到模型评估(准确率计算与图表可视化)的全流程设计与实现细节。资源为单文件PDF,大小771KB,内容结构清晰,含系统流程图、关键代码截图、公式推导及结果分析,适合作为课程大作业参考、算法复现范本或贝叶斯分类入门实践材料。目前已有237人学习下载,涵盖数据预处理、条件概率建模、高斯朴素贝叶斯实现等关键技术点,可直接用于理解统计学习在医疗健康领域的落地应用。

1. 朴素贝叶斯真能筛出高风险糖尿病人群?——用442条皮马印第安人数据跑通完整检测链路

你信不信,不调 sklearn,不碰 TensorFlow,就靠import csv+math.exp()+ 手写高斯概率密度函数,也能从年龄、BMI、舒张压这些常规体检指标里,把未来五年可能发病的人筛出来?这不是玄学,是这篇 PDF 里实打实跑通的流程:它没用任何黑匣子模型,所有概率计算都摊开在代码里——均值怎么算、标准差怎么带入、条件概率怎么连乘、分母为什么能省掉……全在calculateProbability()和predict()两个函数里。它用的是真实临床场景下最常被忽略的那类数据:小样本(仅442例)、多维度(9项生理指标)、单位混杂(年龄是岁、BMI是kg/m²、血清值是μU/mL),却硬是靠朴素贝叶斯的“独立假设+高斯建模”扛住了噪声。适合谁?刚学完《模式识别》想交大作业的研究生、想快速验证某组体检指标预测价值的基层医生、或者手头只有Excel和Python环境但急需一个可解释性诊断辅助工具的开发者。它不追求SOTA准确率,但每一步推导都经得起追问:为什么用高斯分布?为什么训练集按90%切?为什么线性回归图要单特征画?答案不在论文里,在你删掉注释、改两行路径、跑通Accuracy: 76.3%那一刻的终端输出里。


2. 从CSV到高斯概率:手撕朴素贝叶斯的四层数据流

2.1 数据加载与类型强转:为什么float(x)不能少?

原文中loadCsv()函数看似简单,但藏着两个关键细节:

def loadCsv(): lines = csv.reader(open("D:\\研究\\diabetes.data.csv", "r", encoding="utf-8")) dataset = list(lines) for i in range(len(dataset)): dataset[i] = [float(x) for x in dataset[i]] # ← 强制转浮点! return dataset

提示:Pima Indians Diabetes 数据集原始 CSV 中所有字段都是纯数字字符串(如"6.0","148.0","72.0"),但 Python 的csv.reader默认读成字符串列表。若跳过float()转换,后续mean()计算会直接报TypeError: unsupported operand type(s) for +: 'int' and 'str'。更隐蔽的坑是:某些公开版本数据含空行或标题行,此处代码未做清洗,实际部署前必须加if len(row) == 9: dataset.append(...)校验列数。

这步不是“为了运行”,而是为后续所有统计量奠基——均值、方差、高斯公式里的x-mean,全依赖数值精度。我见过太多人卡在这一步,终端报错ValueError: could not convert string to float: '',翻半天才发现数据第一行是age,sex,bmi,...标题。

2.2 按类别拆分数据:separateByClass()的字典结构设计逻辑

朴素贝叶斯的核心是“按类别汇总统计量”。separateByClass()不是简单切片,而是构建一个{0: [[...], [...]], 1: [[...], [...]]}结构:

def separateByClass(dataset): separated = {} for i in range(len(dataset)): vector = dataset[i] classValue = vector[-1] # ← 最后一列是标签(0=未发病,1=发病) if classValue not in separated: separated[classValue] = [] separated[classValue].append(vector) return separated

这个设计直指贝叶斯公式的分子部分:P(x|C_i) = ∏ P(x_j|C_i)。separated[0]存所有健康样本,separated[1]存所有患者样本,后续summarizeByClass()才能分别对两类人群计算各自的均值/标准差。注意:原文代码中vector[-1]假设标签永远在最后一列,这是 Pima 数据集的约定,但如果你换用其他数据集(如标签在第一列),必须改这里,否则整个模型预测方向全反。

2.3 特征统计量生成:summarize()里的zip(*dataset)是什么魔法?

summarize()函数用一行zip(*dataset)完成“按列聚合”,这是 Python 处理表格数据的精髓:

def summarize(dataset): summaries = [(mean(attribute), stdev(attribute)) for attribute in zip(*dataset)] del summaries[-1] # ← 删掉最后一列(标签列)的统计量 return summaries

zip(*dataset)的作用是把行结构转为列结构。假设dataset = [[1,2,0], [3,4,1], [5,6,0]],则zip(*dataset)输出[(1,3,5), (2,4,6), (0,1,0)]—— 每个元组是一列数据。这样mean(attribute)就是对“年龄列”求均值,“BMI列”求均值……而del summaries[-1]精准剔除标签列,确保统计量只覆盖8个特征(原文说9项指标,实际Pima数据集是8个特征+1个标签)。参数说明:stdev()使用无偏估计(除以n-1),符合样本标准差定义,比用n更稳健。

2.4 高斯概率密度函数:calculateProbability()的数学落地

这才是贝叶斯分类器的“心脏”。原文公式F(x)=1/(σ√2π) * exp(-(x−μ)²/(2σ²))在代码中被严格还原:

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:当前样本在某个特征上的取值(如某人 BMI=32.0)
  • mean:该特征在某一类别(如“发病组”)中的均值(Pima数据中发病组BMI均值≈35.0)
  • stdev:该特征在该类别中的标准差(发病组BMI标准差≈7.5)
  • exponent:指数部分,决定曲线陡峭程度;stdev越小,exponent对x变化越敏感,分类边界越锐利

这个函数不返回概率(因高斯PDF积分不为1),而是返回“相对似然值”。正是这种相对性,让后续predict()中省略分母成为可能——我们只比大小,不求绝对值。


3. 训练-预测-评估闭环:从summarizeByClass()到getAccuracy()

3.1 类别概率合成:calculateClassProbabilities()的连乘陷阱

calculateClassProbabilities()把每个特征的高斯概率连乘,生成最终类别概率:

def calculateClassProbabilities(summaries, inputVector): probabilities = {} for classValue, classSummaries in summaries.items(): # 遍历每个类别(0和1) probabilities[classValue] = 1.0 for i in range(len(classSummaries)): # 遍历每个特征(0~7) mean, stdev = classSummaries[i] x = inputVector[i] probabilities[classValue] *= calculateProbability(x, mean, stdev) return probabilities

这里埋着一个经典坑:当某个特征x远离mean(如某人 BMI=15,而发病组均值=35),calculateProbability()返回极小值(如1e-100),连乘后probabilities[1]可能下溢为0.0,导致predict()永远选classValue=0。解决方案:改用对数概率(log-probability)避免下溢:

# 替换原函数中的连乘为累加对数 probabilities[classValue] = 0.0 ... probabilities[classValue] += math.log(calculateProbability(x, mean, stdev))

虽然原文没提,但这是实际部署必加的鲁棒性补丁。

3.2 预测决策:predict()中的“最大概率”本质是最大后验估计(MAP)

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

它没计算先验概率P(C_i)(即发病组/健康组的原始比例),而是默认P(C_0)=P(C_1)。这在Pima数据集中近似成立(发病比例≈35%),但如果用在某老年社区数据(发病率达60%),必须显式加入先验:probabilities[classValue] *= prior[classValue]。原文的“省略分母”在此处体现为:P(C_i|x) ∝ P(x|C_i) * P(C_i),我们只比P(x|C_i) * P(C_i)的大小。

3.3 准确率计算:getAccuracy()的边界校验必须做

getAccuracy()看似简单,但生产环境必须加校验:

def getAccuracy(testSet, predictions): if len(testSet) == 0: return 0.0 correct = 0 for i in range(len(testSet)): if testSet[i][-1] == predictions[i]: # ← 严格比对标签列 correct += 1 return (correct / float(len(testSet))) * 100.0

注意:原文testSet[x][-1]假设测试集格式与训练集完全一致(标签在末位)。若测试集是纯特征向量(无标签),此函数会报错。实际工程中,应分离X_test和y_test,传入getAccuracy(y_test, predictions)。

3.4 主流程控制:splitRatio = 0.67是经验还是缺陷?

主函数中splitRatio = 0.67(即67%训练,33%测试)与正文描述的“90%训练,10%测试”矛盾。这是典型的手动调试残留——作者先用0.67跑通,后在文档中写错比例。血泪经验:固定分割比例会导致结果波动大。正确做法是用sklearn.model_selection.StratifiedKFold做5折交叉验证,既保证每折中发病/未发病比例一致,又充分利用数据。哪怕不用sklearn,也该写个循环跑10次随机分割取平均准确率。


4. 避坑指南:我在复现时踩过的5个真实坑位

4.1 坑位1:路径中的中文和空格导致FileNotFoundError

  • 现象:运行loadCsv()报错FileNotFoundError: [Errno 2] No such file or directory: 'D:\\研究 生 课 程\\diabetes.data.csv'
  • 原因:原文路径D:\ 研 究 生 课 程 \ 模 式 识 别 \ 大 作 业 \ 于 杨\diabetes.data.csv含全角空格和中文,Windows命令行解析失败;且\在Python字符串中是转义符(如\n)
  • 解决:
    ① 将数据文件放在无中文、无空格路径(如D:/data/diabetes.csv);
    ② 路径用正斜杠/或双反斜杠\\:open("D:/data/diabetes.csv", "r");
    ③ 或用os.path.join()构建路径:os.path.join("D:", "data", "diabetes.csv")

4.2 坑位2:stdev()分母用n导致标准差偏小

  • 现象:计算出的标准差比numpy.std()小约3%,导致高斯概率曲线过宽,分类边界模糊
  • 原因:原文stdev()中variance = sum([...])/float(len(numbers)-1)正确用了n-1(样本标准差),但有人复制时误删-1,变成总体标准差
  • 解决:严格核对stdev()函数,确保是len(numbers)-1;验证方法:用[1,2,3,4,5]测试,正确标准差≈1.58

4.3 坑位3:splitDataset()随机分割未设种子,结果不可复现

  • 现象:每次运行Accuracy差异极大(65%~82%),无法判断模型是否真的有效
  • 原因:random.randrange()无种子,每次随机打乱顺序不同,训练集内容漂移
  • 解决:在splitDataset()前加random.seed(42)(或任意整数),确保结果可复现;更佳方案是用sklearn.model_selection.train_test_split(..., random_state=42)

4.4 坑位4:calculateProbability()输入stdev=0触发ZeroDivisionError

  • 现象:运行时报错ZeroDivisionError: float division by zero,定位到1 / (math.sqrt(2*math.pi) * stdev)
  • 原因:某特征在某一类别中所有值完全相同(如某血清指标在发病组全是120.0),导致stdev=0
  • 解决:在calculateProbability()开头加保护:
    if stdev == 0.0: return 1.0 if abs(x - mean) < 1e-6 else 0.0 # 完全相等则概率为1,否则为0

4.5 坑位5:线性回归部分load_diabetes()加载的是波士顿房价数据,非Pima数据集

  • 现象:运行线性回归代码,print(data_diabetes)显示feature_names是['age', 'sex', 'bmi', ...],但target是连续值(病情进展程度),而非二分类标签(0/1)
  • 原因:sklearn.datasets.load_diabetes()加载的是糖尿病病情进展数据集(回归任务),而Pima数据集是二分类(是否发病)。原文混淆了二者!
  • 解决:
    ① 删除from sklearn.datasets import load_diabetes相关代码;
    ② 线性回归部分应基于已加载的Pima数据(dataset)重写,取dataset[i][:-1]为X,dataset[i][-1]为y;
    ③ 或明确说明:此段为拓展分析,与贝叶斯分类器无关,仅展示特征与标签的线性关系趋势。

5. 可视化与特征归因:用图表回答“哪个指标最影响发病?”

5.1 单特征线性回归图:为什么必须画10张图?

原文线性回归代码循环遍历df.columns(10个特征),生成5×2子图。这不是炫技,而是朴素贝叶斯的“可解释性补刀”——贝叶斯给出的是概率,但医生需要知道:当BMI增加5个单位,发病概率上升多少?线性回归的斜率k就是量化答案:

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) k = linear_model.coef_[0] # ← 关键!斜率代表该特征每单位变化对target的影响 b = linear_model.intercept_ # 绘图...

参数解读表:

特征斜率k(示例值)解读临床意义
bmi320.5BMI每+1,病情进展值+320.5BMI是强正相关因子
sex-15.2性别编码(0=女,1=男),男性病情进展值低15.2性别影响微弱
s5(血清TC-89.3血清TC每+1,病情进展值-89.3TC升高反而降低进展?需结合医学知识

注意:Pima数据集的target是二分类(0/1),但load_diabetes()的target是连续值(病情进展程度)。若坚持用Pima数据做线性回归,必须将target设为dataset[i][-1](0或1),此时score会很低(因线性模型不适合分类),但k的符号仍指示相关性方向。

5.2 特征重要性排序:从高斯分布宽度看指标稳定性

贝叶斯模型本身不提供特征重要性,但我们能从summarizeByClass()输出的统计量反推。比较“发病组”和“健康组”同一特征的标准差:

特征健康组stdev发病组stdev差值解读
bmi5.27.8+2.6发病组BMI离散度更大,说明BMI对发病区分力强
age12.111.9-0.2年龄分布相似,区分力弱

操作步骤:

  1. 在summarizeByClass()后打印summaries[0]和summaries[1];
  2. 提取每列的stdev,计算abs(stdev_1 - stdev_0);
  3. 差值越大,该特征在两类中的分布差异越显著,越适合作为判别依据。

5.3 混淆矩阵可视化:比准确率更真实的诊断能力评估

原文只用Accuracy,但对疾病筛查,漏诊率(False Negative Rate)比准确率致命。补充混淆矩阵:

from collections import Counter def confusion_matrix(y_true, y_pred): tp = sum((a==1 and b==1) for a,b in zip(y_true, y_pred)) tn = sum((a==0 and b==0) for a,b in zip(y_true, y_pred)) fp = sum((a==0 and b==1) for a,b in zip(y_true, y_pred)) fn = sum((a==1 and b==0) for a,b in zip(y_true, y_pred)) print(f"TP:{tp}, TN:{tn}, FP:{fp}, FN:{fn}") print(f"Recall(查全率): {tp/(tp+fn):.3f}") # 漏诊率 = 1-Recall print(f"Precision(查准率): {tp/(tp+fp):.3f}") # 调用 y_true = [row[-1] for row in testSet] confusion_matrix(y_true, predictions)

在Pima数据上,典型结果是Recall≈0.65(即35%患者被漏诊),这比Accuracy≈76%更暴露模型短板——它更倾向预测“不发病”,对高危人群不够敏感。


6. 进阶技巧:把这份PDF代码升级成可交付的糖尿病风险评估工具

6.1 封装成命令行工具:让医生双击就能用

把核心逻辑封装为diabetes_predict.py,支持命令行输入:

python diabetes_predict.py --age 45 --bmi 32.5 --bp 80 --glucose 160

实现关键:

import argparse def main(): parser = argparse.ArgumentParser() parser.add_argument('--age', type=float, required=True) parser.add_argument('--bmi', type=float, required=True) parser.add_argument('--bp', type=float, required=True) parser.add_argument('--glucose', type=float, required=True) # ... 其他参数 args = parser.parse_args() # 构造输入向量(按Pima数据集顺序) input_vector = [args.age, 0, args.bmi, args.bp, 0, 0, 0, 0, args.glucose] # sex等缺失填0 prediction = predict(summaries, input_vector) risk_level = "高风险(建议复查)" if prediction == 1 else "低风险" print(f"预测结果:{risk_level},发病概率:{probabilities[1]:.2%}") if __name__ == '__main__': # 加载模型(summaries)一次 dataset = loadCsv() trainingSet, _ = splitDataset(dataset, 0.8) summaries = summarizeByClass(trainingSet) main()

好处:医生无需懂Python,运维只需部署一个脚本;输入参数名直白,避免CSV格式错误。

6.2 模型持久化:保存summaries避免每次重训

训练好的summaries(字典)可序列化为JSON,下次直接加载:

import json # 训练后保存 with open('bayes_model.json', 'w') as f: json.dump(summaries, f) # 预测前加载 with open('bayes_model.json', 'r') as f: summaries = json.load(f) # 注意:JSON不存tuple,需转回 (mean, stdev) for class_val in summaries: for i in range(len(summaries[class_val])): summaries[class_val][i] = tuple(summaries[class_val][i])

参数说明:summaries是轻量级(<1KB),比保存整个数据集高效百倍;JSON格式跨语言,未来可转Java/Go调用。

6.3 阈值调优:从“非0即1”到“风险分数”

朴素贝叶斯输出的是两类概率,原文硬切为0/1。但临床需要分级预警:

def predict_risk(inputVector, summaries, threshold=0.5): probabilities = calculateClassProbabilities(summaries, inputVector) prob_1 = probabilities[1] / (probabilities[0] + probabilities[1]) # 归一化 if prob_1 >= 0.7: return "高风险(立即就诊)", prob_1 elif prob_1 >= 0.4: return "中风险(1个月内复查)", prob_1 else: return "低风险", prob_1 # 调用 label, score = predict_risk([45,0,32.5,80,0,0,0,0,160], summaries) print(f"{label}({score:.0%})")

阈值选择依据:用测试集遍历threshold从0.1到0.9,画ROC曲线,选Youden指数最大点(灵敏度+特异度-1最大)。

6.4 特征工程增强:为什么“血糖/BMI”比单特征更强?

Pima数据中glucose(血糖)和bmi(肥胖)单独预测效果一般,但组合特征glucose/bmi相关性飙升。手动添加:

# 在 loadCsv() 后添加 for row in dataset: glucose = row[8] # Pima中glucose是第9列(索引8) bmi = row[5] # bmi是第6列(索引5) if bmi > 0: row.append(glucose / bmi) # 新增特征:血糖/体重比 # 确保所有行新增列

原理:肥胖者血糖代谢效率低,glucose/bmi比值更能反映胰岛素抵抗程度。这是领域知识驱动的特征工程,比盲目加多项式特征更可靠。

从那以后我每次接到医疗AI需求,第一件事不是调参,而是打开这份PDF,把calculateProbability()函数抄进新项目,然后对着Pima数据集的统计量一行行debug——因为真正的模型鲁棒性,不在框架的炫酷,而在math.exp()里那个-(x-mean)**2/(2*stdev**2)是否被你真正理解。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/31 2:23:05

Forest Pack 7 帮助文档拆解:从散布逻辑到渲染农场部署

简介&#xff1a;这份PDF是3ds Max森林树木植物散布插件Forest Pack 7的官方帮助文档&#xff0c;面向建筑可视化、游戏开发与影视特效领域的三维设计师&#xff0c;尤其适合需要在大场景中批量布置树木、植被、人群、岩石与地面覆盖物的中高级用户。文档系统覆盖安装与论坛访问…

作者头像 李华
网站建设 2026/10/31 2:22:54

《算法导论》英文原版教材入门指南:从插入排序到时间复杂度

简介&#xff1a;《算法导论&#xff08;英文原版教材&#xff09;》PDF 面向计算机专业学生、考研复习者及需要系统梳理算法知识体系的开发者&#xff0c;提供从基础到进阶的英文原版学习材料。包内共 1 个 PDF 文件&#xff0c;约 5.12MB&#xff0c;对应第三版英文教材&…

作者头像 李华
网站建设 2026/10/31 2:21:27

课程资源中Rational Rose安装避坑与许可证配置指南

简介&#xff1a;《rational rose安装教程》面向软件建模初学者、系统分析师及项目管理人员&#xff0c;围绕 IBM Rational Rose 的部署与首次使用&#xff0c;梳理从获取安装文件、接受许可协议、选择安装类型和设置安装路径&#xff0c;到完成安装、配置工作空间与启动建模环…

作者头像 李华
网站建设 2026/10/31 2:20:42

CentOS 7 安装 Oracle 19C 全攻略:系统准备、配置与避坑指南

简介&#xff1a;这份文档面向需要在 CentOS 7 或 Red Hat 7 上部署 Oracle 19C 的数据库运维人员与初学者&#xff0c;系统梳理了从环境准备到安装落地的完整流程&#xff0c;涵盖内存、硬盘与 Swap 分区检查&#xff0c;防火墙、图形桌面与 selinux 的关闭&#xff0c;静态 I…

作者头像 李华
网站建设 2026/10/31 2:20:09

大数据原理测试题高频考点:Hadoop、HDFS与HBase速查

简介&#xff1a;这份《大数据技术原理应用》测试题文档面向学习大数据基础课程、准备课堂测验或期末复习的学生与教师&#xff0c;围绕大数据概述、信息化浪潮、云计算与物联网、Hadoop 处理架构等核心章节组织单选与多选题&#xff0c;并附参考答案&#xff0c;便于自测与核对…

作者头像 李华
网站建设 2026/10/31 2:19:40

南京辰梵包装有限公司专业定制包装供应商实力与用户口碑

很多有包装定制需求的企业&#xff0c;在筹备采购的阶段&#xff0c;都会通过各类平台搜索相关问题&#xff0c;希望能找到合适的合作方。Q1&#xff1a;南京及周边区域&#xff0c;寻求靠谱包装公司应该从哪些维度判断?对有包装定制需求的企业来说&#xff0c;包装不仅是保护…

作者头像 李华